# 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 tag filter - **WHEN** an agent calls `kb_search` with `{"query": "email preferences", "tags": ["agent:mybot"]}` - **THEN** the MCP server SHALL include the tags in the filter and POST to the engine's search endpoint #### 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 - **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 and return the job ID #### Scenario: Add a note with tags - **WHEN** an agent calls `kb_addnote` with `{"text": "User prefers concise responses", "tags": ["agent:mybot", "feedback"]}` - **THEN** the MCP server SHALL submit the note with exactly those tags to the engine --- ### 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"]}` - **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`, 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 --- ### 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: Delete document tool The MCP server SHALL expose a `kb_delete` tool that permanently deletes a document from the knowledge base. The tool SHALL accept a `document_id` (required integer). Deletion SHALL remove the document, its chunks, embeddings, tags, and any stored file on disk. The tool SHALL return a confirmation response including the deleted document's ID and title. #### Scenario: Successful deletion - **WHEN** `kb_delete` is called with `document_id=42` - **THEN** the document, its chunks, embeddings, tag associations, and stored file SHALL be deleted - **AND** the response SHALL include `"status": "deleted"`, the `document_id`, and the document `title` #### Scenario: Document not found - **WHEN** `kb_delete` is called with a `document_id` that does not exist - **THEN** the tool SHALL return an error response indicating the document was not found --- ### Requirement: Tags-only document organisation The MCP server SHALL NOT maintain any collection abstraction. Documents SHALL be returned as-is from the engine with all tags visible. No tag stripping or collection field injection SHALL occur. Namespace isolation (e.g. separating agent memory from user documents) is achieved via tag conventions communicated through system prompts or tool descriptions. #### Scenario: Search results show all tags - **WHEN** `kb_search` is called and a result has tags `["agent:mybot", "collection:documents", "draft"]` - **THEN** all three tags SHALL be returned as-is — no stripping of `collection:*` tags #### Scenario: Add note with explicit tags only - **WHEN** `kb_addnote(text="hello", tags=["agent:mybot", "memory"])` is called - **THEN** the note SHALL be created with exactly those two tags — no default tags added