MCP server
Fallen-8 speaks to programs (the REST API) and to humans (F8 Studio). The MCP server is its surface for AI agents: a separate service that exposes Fallen-8 as typed Model Context Protocol tools any MCP client, Claude Code, Claude Desktop, IDE agents, other vendors’ agents, can discover and call.
It is a separate deployable: its own project (fallen-8-mcp), its own process and
container image. It never loads the engine; it bridges to a reachable Fallen-8 over the
existing REST API, so one MCP server can front a local scratch graph or a shared instance on
another host. A bug in the agent surface cannot take the database down.
Running it
Section titled “Running it”With the compose environment (the default). The MCP server comes up with the rest of the
environment on http://localhost:8090, anonymous and read-only, matching this
environment’s no-auth-in-the-way posture (the fallen8 service itself runs with no API key
here). Nothing extra to run:
npm run env:up# f8-mcp is on http://localhost:8090, fronting the fallen8 service, read-only.This local-dev posture is not for a serious deployment; see “Securing it” below.
Securing it for a real (off-box) setup. Everything is env-var configurable on the f8-mcp
service, so a serious setup locks it down without code changes: set an auth mode + credential,
and opt into only the tiers you need. Via the compose env, with the token exported rather
than inlined (nothing prints it back, and you need it again to register a client):
export F8_MCP_TOKEN=$(openssl rand -hex 32)# static bearer + writes enabled, still on the compose network:F8_MCP_AUTH_MODE=StaticToken F8_MCP_ENABLE_WRITE=true npm run env:upF8_MCP_ENABLE_ADMIN and F8_MCP_ENABLE_CODE open the other two tiers the same way (the full
list of compose variables is in Running Fallen-8).
Or run the image standalone against a Fallen-8 on another host, fully credentialed. No registry publishes it yet, so build it first (the Dockerfile’s context is the repo root):
docker build -f fallen-8-mcp/Dockerfile -t fallen-8-mcp .
docker run --rm -p 8090:8090 \ -e Fallen8Target__BaseUrl=https://graph.example:8443 \ -e Fallen8Target__ApiKey=$F8_KEY \ -e Mcp__Auth__Mode=StaticToken -e Mcp__Auth__StaticToken=$MCP_TOKEN \ -e Mcp__Security__BindAddress=0.0.0.0 -e Mcp__Security__AllowRemoteAccess=true \ fallen-8-mcpFor OAuth 2.1 instead of a static token, set Mcp__Auth__Mode=OAuth +
Mcp__Auth__Issuer/Mcp__Auth__Audience (see “Authentication”). The server fails closed:
a non-loopback bind refuses to start anonymously unless you explicitly opt in
(Mcp__Security__AcceptAnonymousRemote=true, which the demo compose sets and a real one must not).
It also refuses to start with Mode=StaticToken and no token, or Mode=OAuth and no audience,
instead of coming up and rejecting every call.
Local development over stdio (no network transport, loopback Fallen-8). The bridge targets
http://localhost:8080 unless told otherwise, so point it at whatever your Fallen-8 listens on:
a bare dotnet run of the API is on http://localhost:5000, the compose environment on :8080.
Fallen8Target__BaseUrl=http://localhost:5000 dotnet run --project fallen-8-mcp -- --stdioConnecting a client
Section titled “Connecting a client”Claude Code, over Streamable HTTP. The default dev server is anonymous:
claude mcp add --transport http fallen8 http://localhost:8090When you have secured it with a static bearer (above), add the header:
claude mcp add --transport http fallen8 http://localhost:8090 \ --header "Authorization: Bearer $F8_MCP_TOKEN"For stdio, point the client at dotnet run --project fallen-8-mcp -- --stdio (or the built
binary) and set Fallen8Target__BaseUrl in its environment.
The tools
Section titled “The tools”Eleven consolidated, capability-oriented tools cover the whole surface (not one per REST route),
so a client loads few schemas and each result stays compact. Nearly every tool takes an optional
namespace (a Fallen-8 hosts many isolated graphs; it defaults to default); f8_namespace
names its target directly instead, and f8_admin’s list_savegames/load are Fallen-8-level.
| Tier | Tool | What it does |
|---|---|---|
| read | f8_overview |
Discover a graph: omit namespace to list namespaces; set it for counts, index count, available algorithms/index plugins, and embedding/auth state. detail:"statistics" adds the full graph-shape snapshot (label/key cardinalities, degree distribution, and the index inventory itself). A namespace this process did not load is reported as not loaded, with no counts rather than zeros, and the summary names the fix (f8_admin op:'activate'). Start here. |
| read | f8_get |
Fetch a vertex/edge by id with optional neighbourhood (include) and property projection (fields). Compact by default, scalar values only, vectors omitted. |
| read | f8_search |
Find elements: mode = index | property (un-indexed, one named key) | properties (un-indexed contains scan across every property value) | fulltext | vector | semantic. kind and label restrict the hits (ignored by fulltext). Returns ids (+score); fields enriches with properties. Paginated (limit/cursor). |
| read | f8_paths |
Find paths between two vertices, unfiltered or by a registered stored query. Knobs: algorithm (free-form: BLS by default, DIJKSTRA, or any registered Path plugin the overview reports), maxDepth (default 7), maxResults. An empty result can also mean an internal traversal limit was hit, so it is not proof no path exists. |
| read | f8_analytics |
Run a whole-graph algorithm (PageRank, WCC, communities, centrality, triangle-count), or omit algorithm to list them. Optional per-run knobs: vertexLabel, edgePropertyId, direction, maxResults (default 25), maxIterations, and a numeric parameters map (e.g. {"DampingFactor": 0.85}). |
| read | f8_plugins |
The per-namespace plugin registry: list/get/invoke (a graph function by name); delete needs the write capability; register_algorithm/register_function (from C# source) need the code capability. An agent can run every registered plugin category: a graph function through invoke here, and a registered algorithm by naming it in the algorithm knob of f8_paths, f8_subgraph or f8_analytics. |
| read | f8_documents |
Unstructured ingestion: search (fused dense+lexical chunk retrieval; hits are vertex ids), list, get, binding (the index-binding state), entities (the deduplicated entity network); ingest_text/delete/bind need the write capability. Binary file upload stays REST-only (base64 through tool calls wastes tokens). |
| write | f8_mutate |
One transactional mutation: create_vertex, create_edge, create_vertices, create_edges (atomic batch creates), set_property, remove_property, remove_element, set_embedding. Property values are JSON-native. Success means the transaction applied. The batch creates return the assigned ids; the single creates do not (find them with f8_search), and set_property/remove_property/remove_element are no-ops for an absent id, so success does not prove the element existed. |
| write | f8_subgraph |
Define a subgraph from a stored template (or inline filters when the code capability is on). |
| write | f8_namespace |
Create, rename, or drop a namespace. |
| admin | f8_admin |
Durability & maintenance: save, list_savegames, load (by save-game id, optionally restoring a single restoreNamespace member), activate (load a namespace this process skipped at startup, see namespaces; it needs an explicit namespace, since activating default is always a no-op), trim, tabula_rasa. trim/tabula_rasa are fire-and-forget: they report “enqueued”, never “applied”. |
Tools carry MCP annotations so clients can surface the right confirmation UX. The five purely-read
tools (f8_overview, f8_get, f8_search, f8_paths, f8_analytics) are readOnlyHint;
f8_namespace (drop), f8_admin (load/trim/tabula_rasa) and also f8_plugins/f8_documents are
destructiveHint, the last two because they host write/code ops behind a per-op gate, so a client
may ask for confirmation even on their read ops. Annotations are hints: the real enforcement is
server-side tier gating.
Tiers and the code capability
Section titled “Tiers and the code capability”Tools are grouped by the same opt-in tiers Fallen-8 uses everywhere:
- read (on by default): discovery, fetch, search, paths, analytics, plus the read ops of the plugin registry and documents.
- write (
Mcp:Tools:EnableWrite): mutations, subgraph define, namespace lifecycle, and the write ops inside the read-tier tools (deleteonf8_plugins;ingest_text/delete/bindonf8_documents), which appear in those tools’oplist only once write is on. - admin (
Mcp:Tools:EnableAdmin): save/load/trim/tabula_rasa. - code (
Mcp:Tools:EnableCode): does not add tools; it widens existing ones with C# source: inline filter/cost fragments onf8_paths/f8_subgraph, and theregister_algorithm/register_functionops onf8_plugins(whole-type plugin source). Off by default so the MCP surface stays token-frugal and does not invite arbitrary C# from agents; the target Fallen-8 always accepts the equivalent (auth + the plugin gate permitting), so this is purely an MCP-side exposure choice.
A disabled tier’s tools are absent from the tool list and rejected if called anyway.
Authentication
Section titled “Authentication”Three modes (Mcp:Auth:Mode), additive:
- None: anonymous. Safe only on loopback / a private network. The server binds loopback
by default and refuses to start on a network-reachable bind while anonymous (unless you
explicitly set
Mcp:Security:AcceptAnonymousRemote=true). - StaticToken: a shared bearer token (
Authorization: Bearer …). Pragmatic for a network you mostly trust; the compose default is anonymous, so opt in withF8_MCP_AUTH_MODE=StaticToken+F8_MCP_TOKEN. - OAuth: a standards-track OAuth 2.1 resource server that validates JWT access tokens from
your authorization server (audience binding is mandatory), publishes
RFC 9728 protected-resource metadata at
/.well-known/oauth-protected-resource, and maps scopes to tiers fail-closed:f8:write/f8:admin/f8:codeeach need both the scope and the server-side tier flag.
The caller’s credential is never forwarded to Fallen-8: the MCP server holds one downstream credential (the Fallen-8 API key) and presents only that. TLS for the MCP endpoint itself is the deployment’s job (terminate at a proxy, or configure Kestrel certificates); the server warns if auth is on over a non-loopback cleartext bind.
Origin validation (DNS-rebinding protection) and a fixed-window request rate limiter guard the
HTTP transport. A request that sends no Origin header passes validation, deliberately, because
MCP clients are not browsers; /healthz and the protected-resource metadata path stay open. The
limiter is on by default (600 requests per 60 seconds) and
Mcp:Security:RateLimit:PermitPerWindow=0 turns it off.
Token economy
Section titled “Token economy”Agents pay for every tool schema and every byte returned, so the surface is deliberately
frugal: few flat schemas, JSON-native inputs (you never write .NET type names), compact
results (scalar property values by default, vector values omitted, long strings truncated),
and hard pagination caps. f8_overview answers “what is here and what can I do” in one call.
The bridge infers a property/literal’s type from the JSON value, string → System.String,
integer → System.Int32/64, real → System.Double, bool → System.Boolean, which covers the
common cases; typed comparisons against DateTime/decimal properties are not yet inferred
(embeddings are written via f8_mutate set_embedding, not as a typed property). Rich results
ride in the tool result’s structuredContent; clients must consume that field (the content
block is only a one-line summary).
When a call fails
Section titled “When a call fails”Every failure comes back the same way: an isError result whose single text line is
{status} {title}: {detail}, normalized from whatever shape Fallen-8 returned. A transient
failure the agent may retry with backoff (429/503-style) carries a (retryable) suffix. An
unknown tool name, or one whose tier is off, answers 404 Unknown or disabled tool with a hint
to enable the tier (and, under OAuth, hold its scope). Anything unexpected is flattened to a
generic 500 Internal error so nothing sensitive leaks, and the downstream Fallen-8 API key is
never echoed to the caller.
Security guidance
Section titled “Security guidance”Run agent-facing servers read-only unless you have a concrete need for writes. An agent can
be talked (via prompt injection) into destructive calls, so keep write/admin/code tiers off by
default, rely on the destructiveHint confirmation UX in your client, and treat the code
capability (which runs C# fragments as the Fallen-8 process) as a trusted, deliberate choice.
Configuration reference
Section titled “Configuration reference”| Setting | Meaning |
|---|---|
Mcp:Transport |
http (default) or stdio |
Mcp:Port |
HTTP port (default 8090) |
Mcp:Security:BindAddress |
Kestrel bind (loopback by default; 0.0.0.0 in a container) |
Mcp:Security:AllowRemoteAccess / AcceptAnonymousRemote |
accept remote callers / allow anonymous remote (fail-closed override) |
Mcp:Security:Origins |
allowed cross-origins (loopback allowed by default) |
Mcp:Security:RateLimit:PermitPerWindow / WindowSeconds |
fixed-window request throttle (default 600 requests per 60 seconds; 0 disables the limiter) |
Mcp:Tools:EnableWrite / EnableAdmin / EnableCode |
tier / capability opt-ins (all off by default) |
Mcp:Auth:Mode |
None | StaticToken | OAuth |
Mcp:Auth:StaticToken |
the shared bearer secret used when Mode=StaticToken (env / user-secrets only, never checked in) |
Mcp:Auth:Issuer / Audience |
the OAuth authorization server’s issuer + this server’s resource identifier (the token’s aud; mandatory under OAuth) |
Mcp:Auth:SigningKey |
lab-only: validate tokens against this symmetric HMAC key instead of discovering the issuer’s JWKS |
Mcp:Observability:Otlp:Endpoint, Mcp:Identity:* |
OTLP push + fleet identity; see Observability |
Fallen8Target:BaseUrl |
the Fallen-8 REST base URL this server bridges to (default http://localhost:8080) |
Fallen8Target:ApiKey / ApiKeyHeader |
the server’s single downstream credential (header default X-Api-Key) |
Fallen8Target:TlsInsecure |
lab-only: skip downstream TLS validation (loudly logged) |
For contributors: engine → REST → MCP
Section titled “For contributors: engine → REST → MCP”The MCP surface must not fall behind the database. When a capability grows in the engine and
gains a REST endpoint, it must also be surfaced to agents as an MCP tool, or be a conscious,
reasoned deferral. This is enforced by a test (McpRestCoverageTest): a new REST endpoint that
is neither bridged nor deferred fails the build.