www

MCP serverEverything else

Read-only portfolio tools, resources, and prompts for Fmind.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use www

From the project's README

As published by fmind/www in README.md.

The portfolio website of Médéric Hurier (Fmind). It is a fully server-rendered Python application built with Litestar, Jinja, Tailwind CSS v4, and DaisyUI v5. Small vanilla JavaScript menu and calculator controllers provide progressive enhancement. There is no client framework, Node.js application project, database, cookie, or analytics tracker.

Highlights

  • Server-rendered by Litestar and strict, autoescaped Jinja templates packaged under src/www/templates/; Granian serves the ASGI application.
  • Fast: inlined critical CSS, Brotli/gzip page compression, content-hashed immutable static caching, self-hosted subset fonts, and content-visibility for below-the-fold sections. Static files keep their on-disk bytes so strong ETags, byte ranges, and HEAD responses agree.
  • Hardened: a strict, per-request-nonce CSP with no unsafe-inline or unsafe-eval scripts, the full security-header suite, MCP cross-origin protection, and a request-body cap on the public /mcp endpoint.
  • Article-native: strictly validated Markdown, responsive self-hosted media, server-side Pygments highlighting, full-text search and tag filtering at /articles/, social and JSON-LD metadata, related articles, Atom, raw Markdown at /articles/<slug>.md, and a canonical-only sitemap.
  • Sites: source-backed calculators under /sites/ use shareable GET assumptions, server-owned formulas, explicit cost boundaries, and progressive enhancement. The LLM hosting tool compares demand, batch/cache API pricing, capacity, measured latency, and cost per accepted task.
  • Agent-ready: a closed-world, read-only Model Context Protocol server at /mcp with tools, resources, prompts, server-card discovery, /api/profile, /llms.txt, /llms-full.txt, and connected JSON-LD graphs.
  • Privacy-first: no cookies, visitor identifiers, third-party scripts, runtime CDN dependencies, or client-side analytics. One aggregate record per HTML response can be routed from Cloud Logging to a 180-day partitioned BigQuery dataset without retaining an IP address, user-agent string, full referrer, session, or trace identifier.
  • Observable: optional OpenTelemetry export through OTEL_EXPORTER_OTLP_ENDPOINT, with trace_id and span_id correlation in structured logs.

Prerequisites

  • Python 3.14 (mise and the production image pin 3.14.7)
  • mise for the pinned Python, uv, browser, infrastructure, security, and formatting toolchain
  • Docker Engine with the Buildx plugin for the production image gate in mise run all

A fresh checkout needs network access for mise install. The first mise run all also installs Chromium, pulls the production base image, and refreshes vulnerability databases. Warm application tests remain local and need no cloud credentials.

Local Development

mise install        # install the pinned toolchain
mise run install    # sync the locked development environment and install Chromium
mise run watch      # run Granian with reload alongside the Tailwind watcher

The site is served at http://localhost:8080. Configuration is environment-driven through .env.example; with no environment set, it runs in development mode on port 8080. Use PORT=8081 mise run watch when the default port is occupied.

src/www/app.py is the composition root and exports www.app:app. src/www/__main__.py validates runtime configuration and starts Granian. Jinja templates are package data, while content/ and static/ remain explicit deploy-time trees. The production image copies both beside the locked virtual environment under /app.

Articles

Articles live in content/articles/<slug>.md, with images under static/img/articles/<slug>/. Each source begins with strict TOML frontmatter containing title, description, date, tags, slug, optional updated, external canonical, and syndicated, plus draft. Unknown keys or tags, invalid dates, duplicate slugs, and missing cover assets fail application construction. Production excludes drafts from pages and every discovery surface; development renders them with noindex for review.

Tags use the closed vocabulary in src/www/tags.py: Agent, Coding, LLM, RAG, MLOps, Cloud, Python, Project, Demo, and Guide. Each needs a matching [data-tag='…'] rule in assets/css/input.css and must tag at least one article. Declaration order is display order.

src/www/content.py parses and normalizes Markdown with markdown-it-py, shifts body headings beneath the page title, enhances body images, and folds a repeated image-alt paragraph into a caption. Pygments highlighting is centralized in src/www/highlighting.py. The first body image is preloaded; later figures load lazily. Standalone illustrations break out of the 896px text column up to the 1280px figure width, link to their full-resolution source, and never pan horizontally.

Sources are bounded by a roughly 2.4MP pixel budget. Each image ships only the responsive rungs it can support (<stem>-800.webp and <stem>-1280.webp). Generate and commit them with mise run build:images; the provenance lock binds exact source and target SHA-256 digests to the pinned Pillow/WebP encoder recipe, then rebuilds only changed, missing, tampered, or recipe-stale rungs. The canonical check runs check:images, which validates the complete archive without writing.

The validated article collection is the sole source for HTML, search, raw Markdown, Atom, sitemap, LLM text, JSON, and MCP publication surfaces. Markdown responses make rendered root-relative links absolute while preserving code examples and external URLs. Article changes follow the authorized article publication workflow; this repository owns the published body.

Branding

The light website palette follows fmind/theme: white canvas, light gray panels, charcoal text, and the logo blue #174EA6 for links, controls, and focus. assets/css/input.css owns interface colors; src/www/highlighting.py maps the same syntax roles to the existing token classes (blue keywords/functions, green strings, orange literals, purple types, gray comments, and red errors). Update both from the theme’s checks/palette.yaml and ptpython/fmind.py; the website builds independently of that checkout.

The full-resolution, losslessly optimized PNG masters are available at https://www.fmind.dev/logo.png and https://www.fmind.dev/banner.png. These stable URLs serve PNG bytes directly with cache revalidation and also retain the artwork's transparency for reuse.

Navigation uses a 96px lossless WebP logo for the 48px display, while favicons and home-screen icons use appropriately sized derivatives. Default social previews fit the complete banner onto a white 1200×630 progressive JPEG canvas; article previews retain their own covers. Regenerate these committed assets from static/logo.png and static/banner.png with mise run build:branding. Home-screen icons declare any because the logo's outer ring extends beyond the maskable safe area.

Conference Contact Page

Share https://www.fmind.dev/connect at conferences. The page puts LinkedIn first, followed by a downloadable contact card, email, and the portfolio. /connect.vcf derives its public name, role, email, website, and LinkedIn from the portfolio data at application startup. Visitors confirm saving the contact in their own app; some Android browsers require importing the downloaded .vcf from Contacts.

The page displays a QR code encoding the permanent /connect URL, so contact actions can change without replacing the code. The page and contact download work without JavaScript or third-party QR services.

Regenerate the displayed QR asset with the pinned Segno CLI; it is a build-time tool, with no application dependency:

uvx --from segno==1.6.6 segno --error M --border 4 --scale 8 --light white --output static/img/connect-qr.svg https://www.fmind.dev/connect

Decision Tools

Focused tools live under /sites/. src/www/data.py:SITE_PAGES drives metadata, the sitemap, LLM text, JSON, MCP discovery, and article relationships; src/www/app.py wires each page's route, template, and view builder explicitly. Calculation models and validation live under src/www/sites/; Jinja owns presentation; native GET forms keep scenarios linkable. Follow the repository's site skill when adding or revising one.

The first tool, /sites/llm-self-hosting/, compares the current top ten open-weight models from Artificial Analysis with editable GKE accelerator, memory, utilization, staffing, and managed-API assumptions. It includes L4 and RTX PRO 6000 nodes, separate on-demand and resource-CUD billing choices, and a compact hardware starting point based on memory fit and whole-machine cost. Commitments remain billed while idle. It is a planning calculator: ranks and list prices carry a visible snapshot date, and throughput remains a workload-specific input that must come from a pilot. The source snapshot records verified rates, scope, and recommendation rules.

Tasks

All tasks are defined in mise.toml and reused by Lefthook and CI:

TaskDescription
mise run installFrozen uv sync, Lefthook installation, and pinned Chromium installation
mise run watchGranian ASGI reload server and Tailwind watcher
mise run formatRuff Python imports/formatting, dprint, and OpenTofu formatting
mise run checkRuff, ty, metadata/lock, dependency, Dockerfile/IaC, and workflow checks
mise run check:imageBuild and scan the exact production OCI archive (Docker with Buildx required)
mise run check:image:deployedResolve and scan every serving Cloud Run revision with Trivy
mise run check:imagesValidate all image derivatives, hashes, modes, dimensions, and recipe without writing
mise run check:typosCheck article prose against the spelling floor
mise run check:linksCheck external content links (network-dependent; scheduled weekly in CI)
mise run check:tofuValidate and lint OpenTofu (network-dependent; runs in CI on infra/ changes)
mise run testRun pytest offline with branch coverage of at least 85%
mise run test:browserRun Chromium journeys on desktop and mobile in light mode
mise run test:imageSmoke-test the already-built production OCI archive through Docker
mise run test:lighthouse -- --base-url <origin> [--mode full|smoke|portfolio]Run the strict five-category Lighthouse matrix; never in all
mise run coverageShow the terminal coverage report
mise run buildCompile Tailwind CSS and build clean wheel and source distributions
mise run build:imagesReconcile changed, missing, tampered, or recipe-stale Pillow derivatives
mise run build:imageBuild the production OCI image archive at tmp/www-image.tar
mise run deploy <digest-ref>Roll Cloud Run to one repository-pinned image digest (manual and production-mutating)

mise run all runs sequentially: format, static checks, the package/CSS build, pytest, the exact OCI archive build and scan, its bounded runtime smoke test, one pinned Chromium installation, then browser journeys. The image smoke reuses the archive produced by check:image; it never builds. Docker Engine with Buildx must be running, and a cold run needs network access as described above.

Lighthouse remains an explicit, non-default audit because it needs a target origin. Full mode audits every sitemap page on desktop and mobile, then adds 48 stress audits; its count grows with the sitemap. Install Chromium first with mise run install:browser; if it is absent, the harness reports the equivalent playwright install chromium remediation. Use mise run test:lighthouse -- --base-url http://127.0.0.1:8080 --mode smoke for the four-audit local smoke matrix, use --mode portfolio for eight audits covering /, /connect, /articles/, and /sites/ without article or decision-page bodies, or omit --mode for full qualification. --plan validates tools without fetching the sitemap, so full-mode totals are unknown until execution. To exercise the browser suite against production, use BROWSER_BASE_URL=https://www.fmind.dev mise run test:browser. Reports and traces stay under tmp/. Local application checks need no cloud credentials.

Configuration and working-tree secret scans exclude generated tmp/, .venv/, and dist/ trees. Secret scans of Git history keep the default rules.

Operational commands live in scripts/deploy.py and scripts/image_smoke.py, outside the shipped application package. The image smoke uses the MCP SDK with bounded loopback HTTP requests and checks image identity, non-root execution, file permissions, HTTP, tools, resources, and prompts. mise keeps their public task names stable. Browser tooling keeps its Node dependencies in mise installations; there is no repository Node application or runtime Plotly dependency.

Deployment

The site runs on Google Cloud Run in project www-fmind-dev, region europe-west1, and is served at https://www.fmind.dev/. Existing GCP identifiers retain www-fmind-dev to avoid an unrelated production migration. OpenTofu under infra/ owns the Cloud Run shape, Artifact Registry, keyless GitHub Actions identity, alerts, and privacy-preserving analytics route.

  1. Continuous delivery — pull requests and pushes run .github/workflows/deploy.yml. Actions use immutable commit pins, updated through Dependabot. A main push builds the non-root OCI image with provenance and an SBOM, pushes it to Artifact Registry, scans and smoke-tests that exact digest, then deploys it through Workload Identity Federation restricted to main and the immutable GitHub repository and owner IDs.
  2. Infrastructuretofu -chdir=infra init && tofu -chdir=infra apply is always manual. OpenTofu owns CPU, memory, scaling, environment, probes, and IAM; CI owns only the image digest through lifecycle.ignore_changes.
  3. Runtime — Cloud Run injects PORT, OpenTofu sets ENVIRONMENT=production, and standard OTEL_EXPORTER_OTLP_* variables enable tracing. The container runs as UID/GID 10001 with a locked virtual environment, content/, and static/ under /app.
  4. Local image — run mise run build:image, load the archive with docker load --input tmp/www-image.tar, then run docker run -p 8080:8080 www:local.
  5. Manual rollout or rollbackmise run deploy europe-west1-docker.pkg.dev/www-fmind-dev/app/www-fmind-dev@sha256:<64-lowercase-hex> accepts exactly one digest from the production repository and changes only the service image. Tags, foreign repositories, and extra flags fail before gcloud; the task never runs from a hook or mise run all. Before a new rollout, qualify that exact digest with mise run check:image:digest <digest-ref> and mise run test:image <digest-ref>; rollback should select a previously qualified digest.

Reconcile reviewed infra/ changes before rolling out an application image. The Python runtime is qualified for the declared concurrency of 8, a 512 MiB service limit, scale-to-zero with at most 5 instances, and the startup probe's 6 attempts at 5-second intervals. CI and the manual deploy task change only the image; merging a branch does not apply service settings. After each image rollout, verify health, traffic, error logs, and browser journeys; keep the previous known-good digest available for rollback.

The weekly security workflow scans full Git history and source, then uses a separate read-only cloud identity to resolve and scan every image receiving traffic. It retains fixed and unfixed HIGH/CRITICAL advisory reports for 30 days and enforces the same fixable finding gate as deployment. SECURITY.md records the review owner, residual exposure, and next review. GitHub secret scanning, push protection, dependency alerts, and Dependabot security updates are enabled; main rejects deletion and force pushes while allowing ordinary owner pushes.

Analytics

Each HTML response emits one aggregate structured record: path, status, referrer host, UTM dimensions, and a bot flag. The schema keeps country empty because the public Cloud Run origin has no trusted geography header. A Cloud Logging sink routes records to a BigQuery dataset partitioned daily with a 180-day expiry. These aggregate analytics records retain no cookie, IP address, user-agent string, full referrer, session, trace identifier, or derived location. Cloud Run separately writes private operational request logs, which can contain IP addresses, user agents, and complete request URLs; the Cloud Logging _Default bucket retains those logs for 30 days. Query recipes live in the infra skill.

Connecting an AI Agent to /mcp

After deployment, add https://www.fmind.dev/mcp as a custom MCP connector. The server exposes the closed-world, read-only tools get_profile, list_experience, list_certifications, list_publications, search_articles, list_projects, and get_services; the portfolio://profile.json resource; and the assess_fit and brief_me prompts. The JSON profile advertises /api/profile/schema.json through a Link: rel="describedby" header; its JSON Schema derives from the same public serialization model. Server-card metadata, including the resource URI and MIME type, is available at /mcp/server-card and the well-known compatibility route. Browser calls are accepted only from the same origin; non-browser clients need no authentication.

The homepage, JSON profile, JSON-LD occupation skills, and LLM text derive expertise from the same EXPERTISE collection. Both headline lines and the six expertise descriptions are included in /llms.txt and /llms-full.txt. These are integration surfaces; they do not guarantee search indexing or AI citations.

Use the standard server/discover RPC for protocol negotiation and capabilities. The static server card is a compatibility summary of the registered primitives, not an official MCP schema or an A2A Agent Card.

The checked-in server.json describes io.github.fmind/portfolio in the official MCP Registry. Its version tracks the website release. After verifying that version is serving, publish with the official mcp-publisher CLI authenticated as the fmind GitHub account:

mcp-publisher validate server.json
mcp-publisher login github
mcp-publisher publish

Verify the published version through the official Registry API. Registry publication is an explicit owner action separate from website deployment.

Advanced
Delivery
portfolio MCP server → your ahel gateway (mcp.ahel.ai) → every connected AI client.
Catalog kind
mcp-server
Gateway key
io-github-fmind-portfolio
Source
github.com/fmind/www
Hosted endpoint
https://www.fmind.dev/mcp