ktx/packages/cli/src/mcp-server-factory.ts
Andrey Avtomonov 96fb94c89a fix(cli): defer managed Python runtime install to first semantic-layer call
createManagedPythonSemanticLayerComputePort is now synchronous and resolves
the managed runtime lazily on the first query/validate/generate call, caching
the port on success. An MCP session that only searches never installs the
Python runtime at boot, avoiding the eager-install crash loop on uv-less hosts.
2026-06-18 14:02:24 +02:00

83 lines
3 KiB
TypeScript

import { KtxIngestEmbeddingPortAdapter } from './context/llm/embedding-port.js';
import { createDefaultKtxMcpServer } from './context/mcp/server.js';
import { createLocalProjectMcpContextPorts } from './context/mcp/local-project-ports.js';
import { createLocalProjectMemoryIngest } from './context/memory/local-memory.js';
import type { KtxLocalProject } from './context/project/project.js';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { KtxCliIo } from './cli-runtime.js';
import { resolveProjectEmbeddingProvider } from './embedding-resolution.js';
import { createKtxCliIngestQueryExecutor } from './ingest-query-executor.js';
import { createKtxCliScanConnector } from './local-scan-connectors.js';
import { createManagedPythonSemanticLayerComputePort } from './managed-python-command.js';
import { resolveSqlAnalysisPort } from './managed-python-http.js';
function noopMcpIo(): KtxCliIo {
return {
stdout: { write() {} },
stderr: { write() {} },
};
}
export async function createKtxMcpServerFactory(input: {
project: KtxLocalProject;
projectDir: string;
cliVersion: string;
io?: KtxCliIo;
}): Promise<() => McpServer> {
const io = input.io ?? noopMcpIo();
const queryExecutor = createKtxCliIngestQueryExecutor(input.project);
const semanticLayerCompute = createManagedPythonSemanticLayerComputePort({
cliVersion: input.cliVersion,
installPolicy: 'auto',
io,
});
const sqlAnalysis = resolveSqlAnalysisPort({
cliVersion: input.cliVersion,
projectDir: input.projectDir,
installPolicy: 'auto',
io,
});
const resolution = await resolveProjectEmbeddingProvider(input.project, {
mode: 'use-if-running',
cliVersion: input.cliVersion,
io,
});
const embeddingProvider =
resolution.kind === 'configured' || resolution.kind === 'managed-running' || resolution.kind === 'managed-started'
? resolution.provider
: null;
const embeddingService = embeddingProvider ? new KtxIngestEmbeddingPortAdapter(embeddingProvider) : null;
const contextTools = createLocalProjectMcpContextPorts(input.project, {
semanticLayerCompute,
queryExecutor,
sqlAnalysis,
embeddingService,
localScan: {
createConnector: async (connectionId) => createKtxCliScanConnector(input.project, connectionId),
},
});
let memoryIngest: ReturnType<typeof createLocalProjectMemoryIngest> | undefined;
try {
memoryIngest = createLocalProjectMemoryIngest(input.project, {
semanticLayerCompute,
queryExecutor,
embeddingProvider,
});
} catch (error) {
io.stderr.write(`ktx MCP memory_ingest disabled: ${error instanceof Error ? error.message : String(error)}\n`);
}
return () =>
createDefaultKtxMcpServer({
name: 'ktx',
version: input.cliVersion,
userContext: { userId: 'local' },
projectDir: input.projectDir,
io,
contextTools: {
...contextTools,
...(memoryIngest ? { memoryIngest } : {}),
},
});
}