Skip to main content

MCP server

The API server includes a Model Context Protocol (MCP) server at the path /mcp. MCP is an open protocol through which AI assistants and other MCP clients discover the tools that a server offers and call those tools. This page describes the role of the MCP server, the transport, sessions and authentication, the three tools, the relation between MCP search and REST search, and the current limits. Integrators need this page to connect an AI assistant to Foundation4, and administrators need this page to issue API keys for MCP clients.

Role of the MCP server​

The MCP server lets an AI assistant find the pipelines that an API key can read and retrieve fragments from those pipelines. The assistant uses the fragments as context for an answer that the assistant's own language model generates. Foundation4 LLMs and agents take no part in an MCP exchange. The MCP server provides the retrieval step of retrieval-augmented generation (RAG) for an AI assistant outside Foundation4.

The MCP server runs inside the API server process. Each tool calls the same operation as a REST endpoint, reads the same data and requires the same permissions. MCP tools lists the input schema, the output and the errors of each tool.

Transport​

The MCP server uses the streamable HTTP transport of MCP. A client sends JSON-RPC messages to one URL, $FOUNDATION4_URL/mcp, without a trailing slash. The path /mcp/ returns HTTP 404.

MethodPurpose
POSTSends one JSON-RPC request, notification or response
GETOpens an event stream on which the server can send messages within a session
DELETEEnds a session

Every POST carries the header Content-Type: application/json and an Accept header that lists both application/json and text/event-stream. A request without these headers returns HTTP 406 or HTTP 415 with a plain-text body.

The server answers every JSON-RPC request with the content type text/event-stream, as server-sent events (SSE). One event holds the JSON-RPC response in the data field, and the server then closes the stream. Within a session, an event with an empty data field and a retry value of 3000 milliseconds precedes the response. A notification receives HTTP 202 with an empty body. Responses carry the x-request-id header described in API conventions.

Host names​

The MCP server accepts a request only when the Host header of the request names localhost, 127.0.0.1 or ::1, with any port. The MCP library applies this check by default to protect servers that run on a workstation against Domain Name System (DNS) rebinding. A request addressed to any other host name returns HTTP 403 with the plain-text body Forbidden: Host header is not allowed. The refused host names include the host name of the Foundation4 ingress or HTTPRoute and the name of the API server Service. The REST API has no such restriction.

An MCP client therefore reaches the MCP server through a port forward to the client's computer, at the URL http://localhost:8080/mcp. API and dashboard access describes the port forward.

Sessions​

The MCP server supports the MCP protocol revisions 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25 and 2026-07-28. The initialize request names the revision that the client uses. The server answers with the same revision when the server supports that revision, and with 2025-11-25 otherwise.

For the revisions up to 2025-11-25, the server keeps a session for each client:

  • Start. The response to initialize carries the session identifier in the Mcp-Session-Id header. Any other request without a session identifier returns HTTP 422 with the body Unexpected message, expect initialize request.
  • Use. Every later request carries the Mcp-Session-Id header. The client sends the notifications/initialized notification after initialize, as the protocol requires.
  • End. A session ends with a DELETE request, after 5 minutes without a message, or when the API server replica restarts. A request for an unknown or ended session returns HTTP 404 with the body Not Found: Session not found, and the client starts a new session with initialize.
  • Replica. A session exists only in the memory of the API server replica that created the session. With more than one API server replica, a request that reaches another replica returns HTTP 404, as for an ended session.

For the revision 2026-07-28, which removes sessions from the protocol, the server answers each request independently and returns no session identifier. Each request then carries the per-request protocol metadata and headers that the revision defines, including the client capabilities in _meta. MCP tools shows such a request.

Authentication​

The MCP server uses the API keys of the REST API. Every HTTP request to /mcp, including initialize, notifications, GET and DELETE, carries the x-api-key and x-api-key-secret headers described in Authenticate. A request without valid headers returns HTTP 401 with the JSON error body of the REST API, before the MCP server reads the request. Errors lists the authentication messages.

A session does not replace authentication. Each tool call runs with the permissions of the API key in the headers of the HTTP request that carries the call, and every request of a session can carry a different key.

Tools​

The MCP server offers three tools. Each tool corresponds to a REST operation and requires the permissions of that operation, described in Access control.

ToolResultREST equivalentRequired permissions
get_pipelinesThe pipelines that the API key can read, optionally filtered by a text in the name, 10 per call by defaultGET /pipelinesRead on each pipeline returned
get_pipeline_classificationsThe classifications of a pipeline and the inheritance pairsGET /pipelines/{id}/classificationsRead on the pipeline, and * in the allow-list
search_knowledgeFragments ranked by a similarity, maximal marginal relevance (MMR) or full-text searchPOST /pipelines/{id}/searchRead and execute on the pipeline

A key for an AI assistant that searches one pipeline holds read and execute permission (5) on that pipeline. A pipeline outside the key's permissions is missing from the listing, and the other two tools return the error Pipeline not found for that pipeline. With an allow-list other than *, get_pipeline_classifications returns an error.

Each tool is annotated as read-only and non-destructive. Each result carries the tool output twice: as structured content, and as the same JSON in a text block for MCP clients that read text only.

The listing returns five fields for each pipeline: pipeline_id, name, description, embedding_model_id and default_text_splitter_id. The embedding_model_id field holds the pipeline's vector embedding identifier, the value that the REST API returns as default_vector_embedding_id, and not an embedding model identifier. The listing reports neither the embedding models of the pipeline nor whether full-text search is enabled.

The search_knowledge tool runs the searches of POST /pipelines/{id}/search, described in Search and retrieval. The tool accepts a subset of the REST request:

AspectREST searchsearch_knowledge
PipelinePath parameterpipeline_id argument
Search typessimilarity, mmr, searchsimilarity, mmr, search
Parametersparams object of the search typeThe same params object, with the same defaults
Embedding modelparams.embedding, or the pipeline's default embedding modelembedding_model_id argument, or the pipeline's default embedding model. params.embedding is ignored.
ClassificationsString, array or object, with hierarchical or exact matchingArray of strings, with hierarchical matching. Names that the pipeline does not define are ignored.
Metadata filterfiltersNot available
Distance strategydistance_strategy, cosine by defaultAlways cosine
Query embeddingSimilarity and MMR searchEvery search type
Full-text search on a pipeline without full-text searchHTTP 501An internal error
hybrid and historyHTTP 501An internal error
ResultJSON array of fragmentsresults array of fragments with the same fields
ErrorsHTTP status and JSON error bodyJSON-RPC error, or a tool result marked as an error

The parameters k, fetch_k, lambda_mult and threshold have the meanings and defaults that Search and retrieval describes. When a call includes a params object, a parameter that the object omits takes the value 0, so the call sets k, and for MMR search also fetch_k, explicitly. Scores follow the score semantics of REST search: a distance for similarity and MMR search, and a rank for full-text search. Search requests describes the parameters and the classification forms of REST search.

The tool computes a query embedding before every search, including full-text search. A full-text search through MCP therefore requires the pipeline's embedding model to answer and adds the latency of one embedding call. A full-text-only pipeline, which has no embedding model, cannot be searched through MCP: every call returns the error Missing embedding model ID, and the pipeline is searched through the REST API instead.

Current limits​

The following limits apply in addition to the search differences above:

  • Retrieval only. The tools list pipelines, read classifications and search. No tool adds or expires documents, changes an object, runs an agent or queries an LLM. These operations use the REST API.
  • Tools only. The server declares the tools capability only. The server offers no MCP resources or prompts, so resources/list and prompts/list return empty lists, and the server sends no notifications.
  • Header authentication. The MCP server does not implement the OAuth-based authorization of the MCP specification and publishes no authorization metadata. An MCP client connects only when the client can send custom HTTP headers with every request.
  • Listing pages. get_pipelines returns no total count and no page information. A client reads further pages with the offset argument until a call returns fewer pipelines than limit.

Example exchange​

The following exchange starts a session, completes the initialization and runs a similarity search. The commands use the shell variables described in Authenticate, with FOUNDATION4_URL set to http://localhost:8080 through a port forward, and $PIPELINE_ID holding the identifier of the support-kb pipeline from First search.

The initialize request names the protocol revision and the client:

curl -i -X POST "$FOUNDATION4_URL/mcp" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-11-25", "capabilities": {}, "clientInfo": {"name": "curl", "version": "1.0"}}}'

The response has status 200. The following output is shortened to the status line, the session header and the events:

HTTP/1.1 200 OK
content-type: text/event-stream
mcp-session-id: f2b58b89-1909-471c-97f1-abeceeb3e88b

data:
id: 0
retry: 3000

data: {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","capabilities":{"tools":{}},"serverInfo":{"name":"rmcp","version":"3.1.4"},"instructions":"Foundation4.ai"}}

The result names the negotiated revision, the tools capability and the server information. Set a shell variable from the mcp-session-id header:

export MCP_SESSION_ID=<value of the mcp-session-id header>

The notifications/initialized notification completes the initialization:

curl -i -X POST "$FOUNDATION4_URL/mcp" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: $MCP_SESSION_ID" \
-H "MCP-Protocol-Version: 2025-11-25" \
-d '{"jsonrpc": "2.0", "method": "notifications/initialized"}'

The response has status 202 and an empty body. The tools/call request runs search_knowledge:

curl -X POST "$FOUNDATION4_URL/mcp" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: $MCP_SESSION_ID" \
-H "MCP-Protocol-Version: 2025-11-25" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "search_knowledge",
"arguments": {
"pipeline_id": "'"$PIPELINE_ID"'",
"query": "How do customers get their money back?",
"classifications": ["internal"],
"type": "similarity",
"params": {"k": 1}
}
}
}'

The response has status 200 and the following events:

data:
id: 0/0
retry: 3000

data: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"{\"results\":[{\"classification\":\"internal\",\"document_id\":\"01a0ed0d-df3f-7f80-9a1b-2c3d4e5f6a7b\",\"expired_at\":null,\"id\":\"01a0ed0d-e007-7c3d-8e4f-6a7b8c9d0e1f\",\"metadata\":{\"product\":\"billing\"},\"order_id\":0,\"page_content\":\"Refunds for annual plans are prorated. Support agents must record the refund reason in the ticket before approving a refund.\",\"score\":0.6444976753595035,\"version\":1790683504447128}]}"}],"structuredContent":{"results":[{"classification":"internal","document_id":"01a0ed0d-df3f-7f80-9a1b-2c3d4e5f6a7b","expired_at":null,"id":"01a0ed0d-e007-7c3d-8e4f-6a7b8c9d0e1f","metadata":{"product":"billing"},"order_id":0,"page_content":"Refunds for annual plans are prorated. Support agents must record the refund reason in the ticket before approving a refund.","score":0.6444976753595035,"version":1790683504447128}]},"isError":false}}
id: 1/0

The result object of the second event, formatted, shows the three parts of a tool result:

{
"content": [
{
"type": "text",
"text": "{\"results\":[{\"classification\":\"internal\",\"document_id\":\"01a0ed0d-df3f-7f80-9a1b-2c3d4e5f6a7b\",\"expired_at\":null,\"id\":\"01a0ed0d-e007-7c3d-8e4f-6a7b8c9d0e1f\",\"metadata\":{\"product\":\"billing\"},\"order_id\":0,\"page_content\":\"Refunds for annual plans are prorated. Support agents must record the refund reason in the ticket before approving a refund.\",\"score\":0.6444976753595035,\"version\":1790683504447128}]}"
}
],
"structuredContent": {
"results": [
{
"classification": "internal",
"document_id": "01a0ed0d-df3f-7f80-9a1b-2c3d4e5f6a7b",
"expired_at": null,
"id": "01a0ed0d-e007-7c3d-8e4f-6a7b8c9d0e1f",
"metadata": {"product": "billing"},
"order_id": 0,
"page_content": "Refunds for annual plans are prorated. Support agents must record the refund reason in the ticket before approving a refund.",
"score": 0.6444976753595035,
"version": 1790683504447128
}
]
},
"isError": false
}

The fragment fields are the fields of a REST search result, in alphabetical order. The score is the distance that REST search returns for the same query, shown at full precision; Search requests shows the same fragment rounded to 0.6445. A failed tool call returns a JSON-RPC error object with a code and a message instead of result, as described in MCP tools.