- Python 97.6%
- Shell 1.7%
- Dockerfile 0.7%
| .forgejo/workflows | ||
| .gitignore | ||
| build.sh | ||
| CLAUDE.md | ||
| deploy.sh | ||
| Dockerfile | ||
| README.md | ||
| requirements.txt | ||
| server.py | ||
mcp-server — PeaceLink public MCP server
FastMCP server exposing public PeaceLink website data as MCP tools — starting with the
event calendar, with more site tools (e.g. search) expected to land here over time. Runs
next to the main website (mcp-server.peacelink.it), separate from the ai stack's
mcp-peacedata / mcp-guardian servers (those live on the pm83 cluster behind Keycloak
auth). This server has no authentication — it wraps data that's already public on the
website.
Data source
https://www.peacelink.it/js/events.php — same endpoint the website's own calendar widget
uses. This server does not re-implement caching or filtering: it calls that endpoint directly
and relies on its existing file-based cache (classes/cache.php, TTL from the cache_ttl ini
setting, default 6h) and its province/region ILIKE filtering (classes/events.php,
Events::EventsRange() — same province/region convention as classes/assos.php's org search).
It only reshapes the JSON into a form that's easier for an LLM to consume (plain-text
description, dropped iCal/export noise, grouped map links).
Events are almost entirely in Italy (PeaceLink is an Italian association); a small number are marked online/virtual with no physical location.
Tools
| Tool | Description |
|---|---|
get_upcoming_events(start?, end?, days?, province?, region?) |
Upcoming events, same params as events.php (start/end as YYYY-MM-DD, days default 28 capped at 90, province/region as Italian names, partial case-insensitive match) |
New tools land in this same app (@app.tool() in server.py) unless the surface grows
enough to warrant per-domain sub-apps like mcp-peacedata.
Local dev
pip install -r requirements.txt
EVENTS_URL=http://dev.peacelink.it:8070/js/events.php python server.py
# Server on :8003, health check: GET http://localhost:8003/health
Or via the docker compose stack in ../docker:
./build.sh
Env vars
| Var | Notes |
|---|---|
EVENTS_URL |
Base URL of the events JSON endpoint (default: https://www.peacelink.it/js/events.php) |
METRICS_PORT |
Port for the internal-only Prometheus /metrics server (default 9090) |
Discovery
Serves its own .well-known/mcp/server-card.json (unauthenticated) describing the MCP
endpoint (https://mcp-server.peacelink.it/mcp) and that no auth is required.
Metrics
/metrics is served on a separate port (METRICS_PORT, default 9090) via
prometheus_client.start_http_server(), not on the public app port (8003). This is an
internet-facing server, so the public port only ever exposes /health, the discovery
manifest, and /mcp — the k8s Service/Ingress forward port 8003 only, and Prometheus
scrapes port 9090 directly via the pod's cluster-internal IP (prometheus.io/scrape
pod annotations in pck3s/peacelink/mcp_server.yaml), so metrics are unreachable from
outside the cluster by construction, not by a path rule.
Request-count/duration labels also bucket unknown paths into "other" (see _KNOWN_PATHS
in server.py) so internet scanner noise (/.env, /.git/config, etc.) doesn't blow up
metric cardinality.
Which clients/models are using it
Every POST /mcp logs its raw User-Agent (kubectl logs/docker logs, grep for [mcp]) —
raw strings aren't put in a metric label since versions/random suffixes would blow up
cardinality the same way paths would. For a dashboard-friendly breakdown over time, the
mcp_client_requests_total{client=...} counter buckets the same header into a small fixed
set of known markers (_CLIENT_MARKERS in server.py) — claude, chatgpt, gemini,
perplexity, mcp-remote, or other. Add a marker there when a new client shows up in
the logs worth tracking on its own.
Deployment
Runs in the peacelink namespace on the pck3s cluster (not the ai namespace on pm83),
alongside the main website. Manifests live in pck3s/peacelink/mcp_server.yaml.
Forgejo Actions (.forgejo/workflows/ci.yml) builds and pushes docker.io/peacelink/mcp-server
(latest + short-SHA tags) on every push to main — same pattern as phpeace/pckui. Repo
secrets/vars needed on this Forgejo repo: DOCKER_HUB_USER, DOCKER_HUB_TOKEN, and
(optionally) MATTERMOST_WEBHOOK_URL.
Once CI has pushed a new image, roll it out:
./deploy.sh