From 295d027739c94669227c9187f39c25597b6a859b Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Thu, 21 May 2026 01:58:53 +0200 Subject: [PATCH] refactor(mcp): resolve embedding provider in CLI factory, pass into context ports --- packages/cli/src/mcp-server-factory.ts | 12 ++++++++++ .../src/mcp/local-project-ports.test.ts | 24 +++++++++++-------- .../context/src/mcp/local-project-ports.ts | 10 +++----- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/packages/cli/src/mcp-server-factory.ts b/packages/cli/src/mcp-server-factory.ts index 5209f9b8..1e5ba50d 100644 --- a/packages/cli/src/mcp-server-factory.ts +++ b/packages/cli/src/mcp-server-factory.ts @@ -1,8 +1,10 @@ +import { KtxIngestEmbeddingPortAdapter } from '@ktx/context'; import { createDefaultKtxMcpServer, createLocalProjectMcpContextPorts } from '@ktx/context/mcp'; import { createLocalProjectMemoryIngest } from '@ktx/context/memory'; import type { KtxLocalProject } from '@ktx/context/project'; 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'; @@ -34,10 +36,20 @@ export async function createKtxMcpServerFactory(input: { installPolicy: 'auto', io, }); + const resolution = await resolveProjectEmbeddingProvider(input.project, { + mode: 'use-if-running', + cliVersion: input.cliVersion, + io, + }); + const embeddingService = + resolution.kind === 'configured' || resolution.kind === 'managed-running' || resolution.kind === 'managed-started' + ? new KtxIngestEmbeddingPortAdapter(resolution.provider) + : null; const contextTools = createLocalProjectMcpContextPorts(input.project, { semanticLayerCompute, queryExecutor, sqlAnalysis, + embeddingService, localScan: { createConnector: async (connectionId) => createKtxCliScanConnector(input.project, connectionId), }, diff --git a/packages/context/src/mcp/local-project-ports.test.ts b/packages/context/src/mcp/local-project-ports.test.ts index 119e901d..b9ca1fc0 100644 --- a/packages/context/src/mcp/local-project-ports.test.ts +++ b/packages/context/src/mcp/local-project-ports.test.ts @@ -174,7 +174,7 @@ describe('createLocalProjectMcpContextPorts', () => { driver: 'postgres', url: 'env:DATABASE_URL', }; - const ports = createLocalProjectMcpContextPorts(project); + const ports = createLocalProjectMcpContextPorts(project, { embeddingService: null }); expect(Object.keys(ports).sort()).toEqual([ 'connections', @@ -216,6 +216,7 @@ describe('createLocalProjectMcpContextPorts', () => { localScan: { createConnector, }, + embeddingService: null, }); expect(Object.keys(ports).sort()).toContain('sqlExecution'); @@ -269,6 +270,7 @@ describe('createLocalProjectMcpContextPorts', () => { localScan: { createConnector, }, + embeddingService: null, }); const result = await ports.sqlExecution?.execute( @@ -313,6 +315,7 @@ describe('createLocalProjectMcpContextPorts', () => { localScan: { createConnector: vi.fn(async () => connector), }, + embeddingService: null, }); await expect( @@ -332,7 +335,7 @@ describe('createLocalProjectMcpContextPorts', () => { url: 'env:DATABASE_URL', }; await seedScanReport(project.projectDir); - const ports = createLocalProjectMcpContextPorts(project); + const ports = createLocalProjectMcpContextPorts(project, { embeddingService: null }); await expect( ports.entityDetails?.read({ @@ -358,7 +361,7 @@ describe('createLocalProjectMcpContextPorts', () => { driver: 'postgres', url: 'env:DATABASE_URL', }; - const ports = createLocalProjectMcpContextPorts(project); + const ports = createLocalProjectMcpContextPorts(project, { embeddingService: null }); await expect( ports.entityDetails?.read({ @@ -411,7 +414,7 @@ describe('createLocalProjectMcpContextPorts', () => { 'Seed dictionary profile', ); - const ports = createLocalProjectMcpContextPorts(project); + const ports = createLocalProjectMcpContextPorts(project, { embeddingService: null }); await expect(ports.dictionarySearch?.search({ values: ['paid'] })).resolves.toMatchObject({ searched: [{ connectionId: 'warehouse', status: 'ready' }], @@ -432,7 +435,7 @@ describe('createLocalProjectMcpContextPorts', () => { url: 'env:DATABASE_URL', }; - const ports = createLocalProjectMcpContextPorts(project); + const ports = createLocalProjectMcpContextPorts(project, { embeddingService: null }); await expect(ports.dictionarySearch?.search({ values: ['paid'] })).resolves.toEqual({ searched: [ @@ -601,7 +604,7 @@ describe('createLocalProjectMcpContextPorts', () => { 'seed scan report', ); - const ports = createLocalProjectMcpContextPorts(project); + const ports = createLocalProjectMcpContextPorts(project, { embeddingService: null }); const results = await ports.discover?.search({ query: 'paid orders', connectionId: 'warehouse', limit: 10 }); expect(results).toEqual( @@ -635,7 +638,7 @@ describe('createLocalProjectMcpContextPorts', () => { 'ktx@example.com', 'Seed wiki', ); - const ports = createLocalProjectMcpContextPorts(project); + const ports = createLocalProjectMcpContextPorts(project, { embeddingService: null }); await expect(ports.knowledge?.read({ userId: 'local-user', key: 'revenue' })).resolves.toMatchObject({ key: 'revenue', @@ -680,7 +683,7 @@ describe('createLocalProjectMcpContextPorts', () => { '', ].join('\n'), }); - const ports = createLocalProjectMcpContextPorts(project); + const ports = createLocalProjectMcpContextPorts(project, { embeddingService: null }); await expect( ports.semanticLayer?.readSource({ connectionId: 'warehouse', sourceName: 'orders' }), @@ -692,7 +695,7 @@ describe('createLocalProjectMcpContextPorts', () => { it('rejects path traversal keys before touching the project directory', async () => { const project = await initKtxProject({ projectDir: tempDir }); - const ports = createLocalProjectMcpContextPorts(project); + const ports = createLocalProjectMcpContextPorts(project, { embeddingService: null }); await expect( ports.knowledge?.read({ @@ -746,7 +749,7 @@ describe('createLocalProjectMcpContextPorts', () => { })), generateSources: vi.fn(), }; - const ports = createLocalProjectMcpContextPorts(project, { semanticLayerCompute }); + const ports = createLocalProjectMcpContextPorts(project, { semanticLayerCompute, embeddingService: null }); await expect( ports.semanticLayer?.query({ @@ -817,6 +820,7 @@ describe('createLocalProjectMcpContextPorts', () => { const ports = createLocalProjectMcpContextPorts(project, { semanticLayerCompute: compute, queryExecutor, + embeddingService: null, }); const result = await ports.semanticLayer?.query({ diff --git a/packages/context/src/mcp/local-project-ports.ts b/packages/context/src/mcp/local-project-ports.ts index 073b042d..bf40dc80 100644 --- a/packages/context/src/mcp/local-project-ports.ts +++ b/packages/context/src/mcp/local-project-ports.ts @@ -1,7 +1,6 @@ import { type KtxSqlQueryExecutorPort, localConnectionInfoFromConfig } from '../connections/index.js'; import type { KtxEmbeddingPort } from '../core/index.js'; import type { KtxSemanticLayerComputePort } from '../daemon/index.js'; -import { createLocalKtxEmbeddingProviderFromConfig, KtxIngestEmbeddingPortAdapter } from '../llm/index.js'; import type { KtxLocalProject } from '../project/index.js'; import { createKtxEntityDetailsService, type KtxScanConnector, type LocalScanMcpOptions } from '../scan/index.js'; import { createKtxDiscoverDataService } from '../search/index.js'; @@ -15,7 +14,7 @@ interface CreateLocalProjectMcpContextPortsOptions { queryExecutor?: KtxSqlQueryExecutorPort; sqlAnalysis?: SqlAnalysisPort; localScan?: LocalScanMcpOptions; - embeddingService?: KtxEmbeddingPort | null; + embeddingService: KtxEmbeddingPort | null; } function dialectForDriver(driver: string | undefined): string { @@ -133,12 +132,9 @@ async function executeValidatedReadOnlySql( export function createLocalProjectMcpContextPorts( project: KtxLocalProject, - options: CreateLocalProjectMcpContextPortsOptions = {}, + options: CreateLocalProjectMcpContextPortsOptions, ): KtxMcpContextPorts { - const configuredEmbeddingProvider = createLocalKtxEmbeddingProviderFromConfig(project.config.ingest.embeddings); - const embeddingService = - options.embeddingService ?? - (configuredEmbeddingProvider ? new KtxIngestEmbeddingPortAdapter(configuredEmbeddingProvider) : null); + const embeddingService = options.embeddingService; const ports: KtxMcpContextPorts = { connections: { async list() {