Install
$ agentstack add mcp-krezzoid-sncf-mcp ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
sncf-mcp
A Model Context Protocol server that gives AI agents structured access to the French railway network (SNCF) — journey planning, station lookup, departures, and disruptions — through the official SNCF / Navitia open-data API.
Built in Go on the official modelcontextprotocol/go-sdk.
> Why this exists. I've led engineering teams for a long time and wanted to stay close to the layer that actually makes AI agents useful: > their tooling. MCP is that layer. This project is a deliberately small, production-shaped server — the interesting part is not the API wrapper, it's > the engineering envelope around it (typed client, hermetic tests, CI, security posture, and the design decisions written down as ADRs).
What it does
The server exposes a small, focused set of tools rather than a thin mirror of every API endpoint:
find_station— resolve a free-text name ("Lyon Part Dieu") to station candidates.plan_journey— itineraries between two locations, with optional departure/arrival time.next_departures— upcoming departures from a station, with real-time delays.get_disruptions— active service disruptions, optionally scoped to a station.
Example
An agent disambiguates a station, then plans a journey. The output is the lean, context-friendly shape, not the raw Navitia payload (see [ADR-0005](docs/adr/0005-lean-output-contract.md)):
find_station({"query": "Lyon"}) →
{
"stations": [
{ "id": "stop_area:SNCF:87723197", "name": "Lyon Part-Dieu (Lyon)", "quality": 90 },
{ "id": "stop_area:SNCF:87722025", "name": "Lyon Perrache (Lyon)", "quality": 70 }
]
}
plan_journey({"from": "Paris Gare de Lyon", "to": "stop_area:SNCF:87723197"}) →
{
"journeys": [
{
"departure": "2026-06-20T14:00:00+02:00",
"arrival": "2026-06-20T15:56:00+02:00",
"duration_minutes": 116,
"transfers": 0,
"legs": [
{
"mode": "TGV INOUI", "train": "6607",
"from": "Paris Gare de Lyon", "to": "Lyon Part Dieu",
"departure": "2026-06-20T14:00:00+02:00", "arrival": "2026-06-20T15:56:00+02:00"
}
]
}
]
}
When a name can't be resolved, the tool returns a helpful message (not a raw error) steering the agent to rephrase or call find_station first.
Design decisions
The choices that distinguish this from a quick wrapper are documented as Architecture Decision Records in [docs/adr/](docs/adr/):
- [Station resolution via the
/placesendpoint, not a bundled CSV](docs/adr/0001-station-resolution-places-vs-csv.md) - [Deliberately not scraping ticket prices](docs/adr/0002-no-price-scraping.md)
- [The official Go SDK over
mark3labs/mcp-go](docs/adr/0003-official-go-sdk-vs-mark3labs.md) - [stdio as the default transport, HTTP as opt-in](docs/adr/0004-transport-stdio-vs-streamable-http.md)
- [A stable, lean output contract for tool results](docs/adr/0005-lean-output-contract.md)
- [Reactive retry with jittered backoff; no proactive rate limiter](docs/adr/0006-retry-and-rate-limiting.md)
A key piece of value is the transform layer (internal/transform): Navitia responses are large and deeply nested, which wastes an LLM's context window, so the server projects them down to a compact shape carrying only what an agent needs to reason about a trip.
The full build sequence and checkpoints live in [docs/PROJECT_PLAN.md](docs/PROJECT_PLAN.md).
Architecture
cmd/sncf-mcp/ entrypoint: config, logging, graceful shutdown
internal/navitia/ typed, context-aware API client (decoupled from MCP)
internal/transform/ projects heavy Navitia JSON into lean, LLM-friendly output
internal/tools/ MCP tool handlers (thin orchestration only)
internal/server/ wires tools into the SDK server and selects transport
testdata/ JSON fixtures for hermetic tests
docs/ Documentations and Architecture Decision Records
The navitia package has no MCP dependency, so it is unit-tested in isolation against an httptest server. No test touches the network.
Getting started
You need Go 1.26+ and a free SNCF API key ().
# Build and test from source
make build
make test
# Run over stdio
SNCF_API_KEY=your-key ./sncf-mcp
Or run it in a container (distroless, ~10 MB):
docker build -t sncf-mcp .
docker run --rm -i -e SNCF_API_KEY=your-key sncf-mcp
> Released binaries (via GoReleaser) and go install are served from a public > mirror repository; see [docs/RELEASING.md](docs/RELEASING.md).
Use with Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"sncf": {
"command": "/absolute/path/to/sncf-mcp",
"env": { "SNCF_API_KEY": "your-key" }
}
}
}
Security
- The API key is read only from the
SNCF_API_KEYenvironment variable and is never logged. It is sent as an HTTP Basic auth header (never in a URL), and API errors are regression-tested not to contain it. - Logs are structured (
slog) and go to stderr; stdout is reserved for the stdio transport, so nothing leaks into the protocol stream. - Tool inputs are validated: empty, unresolved, or malformed arguments return a clear message rather than a panic or a raw error.
- The container image is built
FROM scratch-style (distroless, nonroot, no shell) for a minimal attack surface. - CI runs
golangci-lint(includinggosec) andgovulncheckon every push.
Limitations
- No ticket prices. They are not part of the SNCF open-data API, and this server does not scrape them — see [ADR-0002](docs/adr/0002-no-price-scraping.md).
- Data covers TGV, TER, Transilien, and Intercités (theoretical + real-time).
- Times are local to Europe/Paris, rendered in RFC3339 with the correct offset (e.g.
…+02:00). - On 429/5xx the client retries with jittered backoff and honors
Retry-After([ADR-0006](docs/adr/0006-retry-and-rate-limiting.md)); it does not pre-emptively rate-limit, so please still respect the API's limits.
Status
All four tools — find_station, plan_journey, next_departures, and get_disruptions — are implemented, hardened (retry/backoff, input validation, timezone-correct output), and covered by hermetic and golden tests.
License
MIT — see [LICENSE](LICENSE).
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: krezzoid
- Source: krezzoid/sncf-mcp
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.