# MCP Server ## Purpose The MCP server provides a Model Context Protocol interface to the kb engine, exposing knowledge base operations as native MCP tools over Streamable HTTP transport. It runs as a separate Docker container alongside the engine, translating MCP tool calls into engine HTTP API calls. ## Requirements ### Requirement: MCP server transport and deployment The MCP server SHALL expose tools via Streamable HTTP transport. It SHALL run as a Docker container, configured to connect to the kb engine's HTTP API. It SHALL read `KB_ENGINE_URL` and `KB_API_KEY` from environment variables to connect to the engine. #### Scenario: MCP server starts and connects to engine - **WHEN** the MCP server container starts with `KB_ENGINE_URL=http://engine:8000` and `KB_API_KEY=secret` - **THEN** it SHALL begin accepting MCP connections over Streamable HTTP and use the configured URL and API key for all engine API calls #### Scenario: Engine unreachable at startup - **WHEN** the MCP server starts but cannot reach the engine at `KB_ENGINE_URL` - **THEN** it SHALL start and accept connections, but tool calls SHALL return errors indicating the engine is unreachable #### Scenario: Docker Compose deployment - **WHEN** the MCP server is deployed via Docker Compose alongside the engine - **THEN** it SHALL connect to the engine via the Docker network using the service name (e.g. `http://engine:8000`) --- ### Requirement: MCP server authentication The MCP server SHALL require Bearer token authentication from calling agents via the `KB_MCP_API_KEY` environment variable. This is independent of the engine's `KB_API_KEY`. #### Scenario: Valid MCP API key - **WHEN** `KB_MCP_API_KEY` is set and a calling agent provides a matching Bearer token - **THEN** the MCP server SHALL process the request normally #### Scenario: Missing MCP API key when required - **WHEN** `KB_MCP_API_KEY` is set and a calling agent connects without a Bearer token - **THEN** the MCP server SHALL reject the connection with an authentication error #### Scenario: Invalid MCP API key - **WHEN** `KB_MCP_API_KEY` is set and a calling agent provides a non-matching Bearer token - **THEN** the MCP server SHALL reject the connection with an authentication error #### Scenario: MCP auth disabled - **WHEN** `KB_MCP_API_KEY` is not set - **THEN** the MCP server SHALL accept all connections without authentication --- ### Requirement: Search tool The MCP server SHALL expose a `kb_search` tool that queries the knowledge base via the engine's search API. #### Scenario: Basic search - **WHEN** an agent calls `kb_search` with `{"query": "pension revaluation", "top": 5}` - **THEN** the MCP server SHALL POST to the engine's `/api/v1/search` endpoint and return the results with chunk text, scores, document metadata, and tags #### Scenario: Search with collection filter - **WHEN** an agent calls `kb_search` with `{"query": "email preferences", "collection": "memory"}` - **THEN** the MCP server SHALL add `collection:memory` to the tags filter and POST to the engine's search endpoint #### Scenario: Search with tags and collection - **WHEN** an agent calls `kb_search` with `{"query": "feedback", "tags": ["email"], "collection": "memory"}` - **THEN** the MCP server SHALL combine the explicit tags with `collection:memory` in the tag filter #### Scenario: Search results strip collection tags - **WHEN** the engine returns search results containing tags `["collection:memory", "feedback", "email"]` - **THEN** the MCP server SHALL strip `collection:*` tags from the `tags` array and add a separate `collection` field, returning `{"collection": "memory", "tags": ["feedback", "email"], ...}` #### Scenario: Search with mode override - **WHEN** an agent calls `kb_search` with `{"query": "error log", "fts_only": true}` - **THEN** the MCP server SHALL pass `fts_only: true` to the engine search endpoint --- ### Requirement: Add note tool The MCP server SHALL expose a `kb_addnote` tool that submits a text note to the engine for ingestion. #### Scenario: Add a note with default collection - **WHEN** an agent calls `kb_addnote` with `{"text": "User prefers concise responses"}` - **THEN** the MCP server SHALL submit the note to the engine's `POST /api/v1/jobs` endpoint with the tag `collection:documents` and return the job ID #### Scenario: Add a note to a specific collection - **WHEN** an agent calls `kb_addnote` with `{"text": "User prefers concise responses", "collection": "memory", "tags": ["feedback"]}` - **THEN** the MCP server SHALL submit the note with tags `["collection:memory", "feedback"]` to the engine #### Scenario: Add a note to a collection replaces existing collection tag - **WHEN** an agent calls `kb_addnote` with `{"text": "some note", "collection": "memory"}` and the note is ingested - **THEN** the resulting document SHALL have exactly one `collection:*` tag: `collection:memory` --- ### Requirement: Chunked file upload tools The MCP server SHALL expose a three-step chunked file upload pattern for transferring files from remote agents to the engine. #### Scenario: Start an upload - **WHEN** an agent calls `kb_upload_start` with `{"filename": "report.pdf", "total_size": 5242880, "tags": ["insurance"], "collection": "documents"}` - **THEN** the MCP server SHALL create a staging entry, generate a UUID `upload_id`, and return `{"upload_id": ""}` #### Scenario: Upload a chunk - **WHEN** an agent calls `kb_upload_chunk` with `{"upload_id": "", "data": "", "chunk_index": 0}` - **THEN** the MCP server SHALL decode the base64 data and write it to the staging area for the given upload #### Scenario: Upload multiple chunks in sequence - **WHEN** an agent calls `kb_upload_chunk` multiple times with sequential `chunk_index` values for the same `upload_id` - **THEN** the MCP server SHALL store each chunk and track the sequence #### Scenario: Finish an upload - **WHEN** an agent calls `kb_upload_finish` with `{"upload_id": ""}` - **THEN** the MCP server SHALL reassemble the chunks in order, forward the complete file as a multipart upload to the engine's `POST /api/v1/jobs` endpoint with the tags from `kb_upload_start` (including `collection:`), and return the job ID #### Scenario: Upload with invalid upload_id - **WHEN** an agent calls `kb_upload_chunk` or `kb_upload_finish` with an `upload_id` that does not exist - **THEN** the MCP server SHALL return an error indicating the upload ID is not found #### Scenario: Abandoned upload cleanup - **WHEN** an agent starts an upload but does not call `kb_upload_finish` within 10 minutes - **THEN** the MCP server SHALL clean up the staged chunks and remove the upload tracking entry #### Scenario: MCP server restart during upload - **WHEN** the MCP server container restarts while an upload is in progress - **THEN** the in-progress upload SHALL be lost and the agent SHALL need to restart from `kb_upload_start` --- ### Requirement: Update note tool The MCP server SHALL expose a `kb_update_note` tool that updates an existing note in place via the engine's note mutation endpoint. #### Scenario: Update an existing note - **WHEN** an agent calls `kb_update_note` with `{"document_id": 42, "text": "Updated preference: user prefers bullet points"}` - **THEN** the MCP server SHALL send `PATCH /api/v1/notes/42` to the engine and return the updated document #### Scenario: Update a non-existent document - **WHEN** an agent calls `kb_update_note` with a `document_id` that does not exist - **THEN** the MCP server SHALL return an error indicating the document was not found #### Scenario: Update a non-note document - **WHEN** an agent calls `kb_update_note` with a `document_id` that refers to a PDF - **THEN** the MCP server SHALL return an error indicating that only notes can be updated --- ### Requirement: Get document tool The MCP server SHALL expose a `kb_get` tool that retrieves document details from the engine. #### Scenario: Get by document ID - **WHEN** an agent calls `kb_get` with `{"document_id": 42}` - **THEN** the MCP server SHALL fetch `GET /api/v1/documents/42` and return the document details with chunks #### Scenario: Get by source path - **WHEN** an agent calls `kb_get` with `{"source_path": "memory/feedback_testing.md"}` - **THEN** the MCP server SHALL query the engine's documents endpoint filtered by source path and return matching documents #### Scenario: Get results strip collection tags - **WHEN** the engine returns document details with tags including `collection:memory` - **THEN** the MCP server SHALL strip `collection:*` from tags and present a separate `collection` field --- ### Requirement: Status tool The MCP server SHALL expose a `kb_status` tool that returns engine health and statistics. #### Scenario: Get engine status - **WHEN** an agent calls `kb_status` with no parameters - **THEN** the MCP server SHALL fetch `GET /api/v1/status` and return engine version, model info, device info, document counts, and queue state --- ### Requirement: Jobs tool The MCP server SHALL expose a `kb_jobs` tool that returns ingestion job status. #### Scenario: List recent jobs - **WHEN** an agent calls `kb_jobs` with no parameters - **THEN** the MCP server SHALL fetch `GET /api/v1/jobs` and return the list of recent jobs #### Scenario: Filter jobs by status - **WHEN** an agent calls `kb_jobs` with `{"status": "failed"}` - **THEN** the MCP server SHALL fetch `GET /api/v1/jobs?status=failed` and return matching jobs --- ### Requirement: Collection management via tags The MCP server SHALL manage collections using tag conventions. The MCP server SHALL enforce exclusive collection membership — a document SHALL belong to exactly one collection. #### Scenario: Default collection on addnote - **WHEN** an agent calls `kb_addnote` without specifying a collection - **THEN** the MCP server SHALL apply the tag `collection:documents` #### Scenario: Explicit collection on addnote - **WHEN** an agent calls `kb_addnote` with `{"collection": "memory"}` - **THEN** the MCP server SHALL apply the tag `collection:memory` #### Scenario: Exclusive collection enforcement - **WHEN** a document already has the tag `collection:documents` and an operation changes its collection to `memory` - **THEN** the MCP server SHALL first remove `collection:documents` via the engine's tag API, then add `collection:memory` #### Scenario: Collection field in search results - **WHEN** search results include documents with `collection:*` tags - **THEN** the MCP server SHALL present the collection as a top-level `collection` field and exclude `collection:*` from the `tags` array