ktx/packages/context/src/mcp/server.ts

95 lines
3.2 KiB
TypeScript
Raw Normal View History

2026-05-10 23:12:26 +02:00
import { randomUUID } from 'node:crypto';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import type { MemoryAgentInput } from '../memory/index.js';
2026-05-10 23:51:24 +02:00
import { jsonErrorToolResult, jsonToolResult, registerKtxContextTools } from './context-tools.js';
import type { KtxMcpServerDeps, KtxMcpServerLike, MemoryCapturePort } from './types.js';
2026-05-10 23:12:26 +02:00
const memoryCaptureInputSchema = {
userMessage: z.string().min(1).describe('The user message that may contain durable knowledge.'),
assistantMessage: z.string().optional().describe('The assistant response that concluded the exchange.'),
connectionId: z.string().min(1).optional().describe('Optional connection id for semantic-layer capture.'),
};
const memoryCaptureStatusInputSchema = {
runId: z.string().min(1).describe('The memory capture run id returned by memory_capture.'),
};
function registerMemoryCaptureTools(deps: {
2026-05-10 23:51:24 +02:00
server: KtxMcpServerLike;
2026-05-10 23:12:26 +02:00
memoryCapture: MemoryCapturePort;
2026-05-10 23:51:24 +02:00
userContext: KtxMcpServerDeps['userContext'];
2026-05-10 23:12:26 +02:00
}): void {
deps.server.registerTool(
'memory_capture',
{
title: 'Memory Capture',
description:
'Capture durable knowledge and semantic-layer updates from the final user/assistant exchange. Returns a run id for polling.',
inputSchema: memoryCaptureInputSchema,
},
async (input) => {
const captureInput: MemoryAgentInput = {
userId: deps.userContext.userId,
chatId: `mcp-${randomUUID()}`,
userMessage: String(input.userMessage),
assistantMessage: typeof input.assistantMessage === 'string' ? input.assistantMessage : undefined,
connectionId: typeof input.connectionId === 'string' ? input.connectionId : undefined,
sourceType: 'external_ingest',
};
const result = await deps.memoryCapture.capture(captureInput);
return jsonToolResult(result);
},
);
deps.server.registerTool(
'memory_capture_status',
{
title: 'Memory Capture Status',
description: 'Read the current or final status for a memory capture run.',
inputSchema: memoryCaptureStatusInputSchema,
},
async (input) => {
const runId = String(input.runId);
const status = await deps.memoryCapture.status(runId);
return status ? jsonToolResult(status) : jsonErrorToolResult(`Memory capture run "${runId}" was not found.`);
},
);
}
2026-05-10 23:51:24 +02:00
export function createKtxMcpServer(deps: KtxMcpServerDeps): KtxMcpServerDeps['server'] {
2026-05-10 23:12:26 +02:00
if (deps.memoryCapture) {
registerMemoryCaptureTools({
server: deps.server,
memoryCapture: deps.memoryCapture,
userContext: deps.userContext,
});
}
if (deps.contextTools) {
2026-05-10 23:51:24 +02:00
registerKtxContextTools({
2026-05-10 23:12:26 +02:00
server: deps.server,
ports: deps.contextTools,
userContext: deps.userContext,
});
}
return deps.server;
}
2026-05-10 23:51:24 +02:00
export function createDefaultKtxMcpServer(
deps: Omit<KtxMcpServerDeps, 'server'> & { name?: string; version?: string },
2026-05-10 23:12:26 +02:00
): McpServer {
const server = new McpServer({
2026-05-10 23:51:24 +02:00
name: deps.name ?? 'ktx',
2026-05-10 23:12:26 +02:00
version: deps.version ?? '0.0.0-private',
});
2026-05-10 23:51:24 +02:00
createKtxMcpServer({
server: server as KtxMcpServerLike,
2026-05-10 23:12:26 +02:00
memoryCapture: deps.memoryCapture,
userContext: deps.userContext,
contextTools: deps.contextTools,
});
return server;
}