Architecture
Overview
@johannes.latzel/llm-chat-mcp bridges the @johannes.latzel/llm-chat tool
ecosystem with the Model Context Protocol. It takes Tool and
ToolPackage instances, converts their JSON Schema parameter definitions to
Zod schemas for validation, and exposes them over stdio or HTTP through the MCP
SDK.
Class hierarchy
BaseMcpServer (abstract)
├── StdioMcpServer: stdin/stdout transport
└── HttpMcpServer: Streamable HTTP transport (Express)
BaseMcpServer
- Stores the
serverInfometadata and aToolRegistryinventory of registered tools plus their enable/disable state registerTool(item)stores the item and registers its tools on the SDK'sMcpServersetDisabledTools(names)replaces the disabled-tool set; an empty list re-enables every tool.disableTool(name)andenableTool(name)toggle single toolsregisterDocumentregisters a single file as a static MCP resource;registerFolderregisters every matching file in a folder (recursively) as static resources. See Document ResourcescreateFreshMcpServer()creates a brand-newMcpServerwith all previously registered tools and documents replayed, used byHttpMcpServerto give each HTTP session its own isolated server instancestop()calls theonStop()lifecycle hook then closes the server- Subclasses implement
start()to connect their transport
StdioMcpServer
start()creates aStdioServerTransportand connects- Suitable for MCP clients that spawn a child process (e.g. VS Code, Claude Desktop)
HttpMcpServer
- Express-based server listening on a configurable port
- Provides a single route
POST /mcp(Streamable HTTP) - Multi-session architecture: each HTTP client gets its own
(StreamableHTTPServerTransport, McpServer)pair, keyed by theMcp-Session-Idheader - Session map access is protected by an
async-mutexMutexto prevent concurrent modification from overlapping requests (seeHttpMcpServerfor the full reference)
Request routing (/mcp)
| Method | Session ID header | Action |
|---|---|---|
| DELETE | present | Disconnect that session |
| DELETE | missing | 404 |
| POST | present | Route to existing session |
| POST | missing | Create a new session |
| GET | present | Route to existing session |
| GET | missing | 400 |
| Other | missing | 400 |
| Other | present | Route to existing session; the SDK rejects unsupported methods |
Lifecycle
constructor(info): initialises Express app and wires routesregisterTool(item): tools must be registered beforestart()(they are copied into each per-sessionMcpServerat creation time)start(): begins listening on the configured port- Client sends
POST /mcp(initialize) →handleCreateSessioncreates a transport, connects a freshMcpServer, stores the session, forwards the response - Subsequent requests include
Mcp-Session-Id→ routed viahandleExistingSession DELETE /mcp→handleDeleteremoves and closes the sessionstop()→onStop()snapshots and clears all sessions, closes each transport, then stops Express
Tool model and per-server views
BaseMcpServer keeps the tool inventory in a ToolRegistry, the model. It
holds every registered llm-chat Tool plus the disabled-tool policy and is the
single source of truth shared by the base server and every HTTP session. The
registry deliberately owns no handles.
SDK servers are the views: the base McpServer and each per-session
McpServer each own their own RegisteredTool handle set, created by
materializing the model's tools onto them. registerTool() registers onto the
base server and mirrors its handles; setDisabledTools() pushes the policy
onto the base server's handles and every active session's; new sessions replay
the model via createFreshMcpServer(). The SDK keeps its own private copy of
each server's handles, so BaseMcpServer shadows the base server's only to
push enable/disable state onto it.
Internal converters
toolSchemaToZod (schema-converter.ts)
Converts the JSON Schema output from Tool.toOpenAI().function.parameters into
a Zod object schema. Supports: string, number, integer, boolean,
array (with nested items), object (with nested properties). Recursively
handles required/optional fields and description annotations.
See src/lib/schema-converter.ts.
toolResultsToMcp (result-converter.ts)
Converts an array of llm-chat ToolResult into the MCP CallToolResult shape.
Maps each result to a { type: "text", text: string } content entry. Sets
isError: true when any result has a non-success status.
See src/lib/result-converter.ts.
Document resources
registerFileResourceOnServer (document-resource.ts)
Registers a single file as a static MCP resource at a file:// URI. The file is
read lazily on resources/read, served as text or base64 blob depending on the
MIME type inferred from the file extension.
registerFolderResourcesOnServer (document-resource.ts)
Recursively collects every file in a folder matching the configured extensions (default: all supported MIME types) and registers each as a static resource named by its path relative to the folder root. A folder with no matches registers nothing.
See src/mcp/document-resource.ts.
Public API surface
All public exports come from src/index.ts; the lib/ converters and the
McpSession and DocumentEntry types are internal. FileDocumentConfig and
FolderDocumentConfig are exported for typed registration.
Dependencies
| Package | Role |
|---|---|
@johannes.latzel/llm-chat |
Tool / ToolPackage framework |
@modelcontextprotocol/sdk |
MCP protocol, transports |
express |
HTTP server |
async-mutex |
Session map synchronisation |
zod |
Runtime schema validation |
See also: Servers, Converters, Document Resources, Quick Start