diff --git a/packages/context/src/mcp/local-project-ports.test.ts b/packages/context/src/mcp/local-project-ports.test.ts index f5aa52c0..8692ab7d 100644 --- a/packages/context/src/mcp/local-project-ports.test.ts +++ b/packages/context/src/mcp/local-project-ports.test.ts @@ -520,6 +520,54 @@ describe('createLocalProjectMcpContextPorts', () => { }); }); + it('returns historic SQL usage frequency and snippet through semantic-layer list search', async () => { + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); + await project.fileStore.writeFile( + 'semantic-layer/warehouse/_schema/public.yaml', + `tables: + orders: + table: public.orders + usage: + narrative: Analysts inspect paid order lifecycle by customer segment. + frequencyTier: high + commonFilters: + - status + commonGroupBys: + - customer_segment + commonJoins: + - table: public.customers + on: + - customer_id + columns: + - name: order_id + type: string + - name: status + type: string +`, + 'ktx', + 'ktx@example.com', + 'Seed usage-backed manifest shard', + ); + + const ports = createLocalProjectMcpContextPorts(project); + await expect( + ports.semanticLayer?.listSources({ connectionId: 'warehouse', query: 'paid order lifecycle' }), + ).resolves.toEqual({ + sources: [ + expect.objectContaining({ + connectionId: 'warehouse', + connectionName: 'warehouse', + name: 'orders', + frequencyTier: 'high', + snippet: expect.stringContaining(''), + score: expect.any(Number), + matchReasons: expect.arrayContaining(['lexical']), + }), + ], + totalSources: 1, + }); + }); + it('uses configured local embeddings for semantic-layer search when available', async () => { const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); project.config.ingest.embeddings = { backend: 'none', dimensions: 2 }; diff --git a/packages/context/src/mcp/local-project-ports.ts b/packages/context/src/mcp/local-project-ports.ts index 331a14ae..41d1f916 100644 --- a/packages/context/src/mcp/local-project-ports.ts +++ b/packages/context/src/mcp/local-project-ports.ts @@ -479,6 +479,8 @@ export function createLocalProjectMcpContextPorts( columnCount: source.columnCount, measureCount: source.measureCount, joinCount: source.joinCount, + ...(hasSlSearchMetadata(source) && source.frequencyTier ? { frequencyTier: source.frequencyTier } : {}), + ...(hasSlSearchMetadata(source) && source.snippet ? { snippet: source.snippet } : {}), ...(hasSlSearchMetadata(source) ? { score: source.score } : {}), ...(hasSlSearchMetadata(source) && source.matchReasons ? { matchReasons: source.matchReasons } : {}), ...(hasSlSearchMetadata(source) && source.dictionaryMatches diff --git a/packages/context/src/mcp/types.ts b/packages/context/src/mcp/types.ts index 58f8e22b..f68444b2 100644 --- a/packages/context/src/mcp/types.ts +++ b/packages/context/src/mcp/types.ts @@ -1,4 +1,4 @@ -import type { IngestReportSnapshot, MemoryFlowReplayInput } from '../ingest/index.js'; +import type { IngestReportSnapshot, MemoryFlowReplayInput, TableUsageOutput } from '../ingest/index.js'; import type { MemoryCaptureService } from '../memory/index.js'; import type { KtxScanMode, KtxScanReport } from '../scan/index.js'; import type { @@ -131,6 +131,8 @@ export interface KtxSemanticLayerSourceSummary { columnCount: number; measureCount: number; joinCount: number; + frequencyTier?: TableUsageOutput['frequencyTier']; + snippet?: string; score?: number; matchReasons?: SlSearchMatchReason[]; dictionaryMatches?: SlDictionaryMatch[];