New MCP server (mcp/) exposes kb operations as native MCP tools over
Streamable HTTP with Bearer token auth. Supports collections via tag
conventions, chunked file uploads, and agent-side search patterns.
Engine gains PATCH /api/v1/notes/{id} for in-place note updates with
transactional re-chunk/re-embed, and updated_at column on documents.
Go client adds updatenote command and Patch HTTP method.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
10 KiB
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:8000andKB_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_KEYis 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_KEYis 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_KEYis 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_KEYis 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_searchwith{"query": "pension revaluation", "top": 5} - THEN the MCP server SHALL POST to the engine's
/api/v1/searchendpoint and return the results with chunk text, scores, document metadata, and tags
Scenario: Search with collection filter
- WHEN an agent calls
kb_searchwith{"query": "email preferences", "collection": "memory"} - THEN the MCP server SHALL add
collection:memoryto the tags filter and POST to the engine's search endpoint
Scenario: Search with tags and collection
- WHEN an agent calls
kb_searchwith{"query": "feedback", "tags": ["email"], "collection": "memory"} - THEN the MCP server SHALL combine the explicit tags with
collection:memoryin 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 thetagsarray and add a separatecollectionfield, returning{"collection": "memory", "tags": ["feedback", "email"], ...}
Scenario: Search with mode override
- WHEN an agent calls
kb_searchwith{"query": "error log", "fts_only": true} - THEN the MCP server SHALL pass
fts_only: trueto 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_addnotewith{"text": "User prefers concise responses"} - THEN the MCP server SHALL submit the note to the engine's
POST /api/v1/jobsendpoint with the tagcollection:documentsand return the job ID
Scenario: Add a note to a specific collection
- WHEN an agent calls
kb_addnotewith{"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_addnotewith{"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_startwith{"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": "<uuid>"}
Scenario: Upload a chunk
- WHEN an agent calls
kb_upload_chunkwith{"upload_id": "<uuid>", "data": "<base64-encoded-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_chunkmultiple times with sequentialchunk_indexvalues for the sameupload_id - THEN the MCP server SHALL store each chunk and track the sequence
Scenario: Finish an upload
- WHEN an agent calls
kb_upload_finishwith{"upload_id": "<uuid>"} - 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/jobsendpoint with the tags fromkb_upload_start(includingcollection:<name>), and return the job ID
Scenario: Upload with invalid upload_id
- WHEN an agent calls
kb_upload_chunkorkb_upload_finishwith anupload_idthat 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_finishwithin 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_notewith{"document_id": 42, "text": "Updated preference: user prefers bullet points"} - THEN the MCP server SHALL send
PATCH /api/v1/notes/42to the engine and return the updated document
Scenario: Update a non-existent document
- WHEN an agent calls
kb_update_notewith adocument_idthat 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_notewith adocument_idthat 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_getwith{"document_id": 42} - THEN the MCP server SHALL fetch
GET /api/v1/documents/42and return the document details with chunks
Scenario: Get by source path
- WHEN an agent calls
kb_getwith{"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 separatecollectionfield
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_statuswith no parameters - THEN the MCP server SHALL fetch
GET /api/v1/statusand 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_jobswith no parameters - THEN the MCP server SHALL fetch
GET /api/v1/jobsand return the list of recent jobs
Scenario: Filter jobs by status
- WHEN an agent calls
kb_jobswith{"status": "failed"} - THEN the MCP server SHALL fetch
GET /api/v1/jobs?status=failedand 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_addnotewithout specifying a collection - THEN the MCP server SHALL apply the tag
collection:documents
Scenario: Explicit collection on addnote
- WHEN an agent calls
kb_addnotewith{"collection": "memory"} - THEN the MCP server SHALL apply the tag
collection:memory
Scenario: Exclusive collection enforcement
- WHEN a document already has the tag
collection:documentsand an operation changes its collection tomemory - THEN the MCP server SHALL first remove
collection:documentsvia the engine's tag API, then addcollection: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
collectionfield and excludecollection:*from thetagsarray