feat(mcpedia): Phase 2 — semantic + hybrid search, tRPC/Hono API
- @mcpedia/embeddings: OpenRouter provider (9router /v1, encoding_format float), chunkText + embedChunks; EMBED_DIM=2048 - document_chunks table (real[] embedding) — pgvector NOT available on shared imrnes Postgres, so cosine is computed in-app (KB-scale fine); pgvector deferred - indexer: chunk + embed + upsert per document - @mcpedia/search: semanticSearch (cosine) + hybridSearch (FTS+cosine RRF) - apps/api: Hono + tRPC v11 (6 procedures), serve on :4020 - MCP: semantic_search + hybrid_search tools (6 total) - web: keyword/hybrid toggle; .env.example + README + PHASES updated
This commit is contained in:
1 parent
ec3a2867d4
commit
9397303f01
32 files changed
+1120
-42
No files matched your search
+37
-2
@@ -1,8 +1,7 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { z } from "zod";
|
||||
import { listDocuments, getDocument, getRelated } from "@mcpedia/core";
|
||||
import { keywordSearch } from "@mcpedia/search";
|
||||
import { listDocuments, getDocument, getRelated, semanticSearch, hybridSearch, keywordSearch } from "@mcpedia/core";
|
||||
|
||||
export function createMcpServer(): McpServer {
|
||||
const server = new McpServer({
|
||||
@@ -84,6 +83,42 @@ export function createMcpServer(): McpServer {
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"semantic_search",
|
||||
{
|
||||
description:
|
||||
"Semantic (embedding) search across chunked document content. Best for conceptual/paraphrased queries that don't share exact keywords. Returns chunks ranked by cosine similarity.",
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe("Natural-language query"),
|
||||
limit: z.number().int().positive().max(50).optional(),
|
||||
}),
|
||||
},
|
||||
async ({ query, limit }) => {
|
||||
const hits = await semanticSearch(query, limit ?? 10);
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(hits, null, 2) }],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"hybrid_search",
|
||||
{
|
||||
description:
|
||||
"Hybrid search fusing full-text (Postgres FTS) and semantic (embedding) signals via Reciprocal Rank Fusion. Best general-purpose search.",
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe("Free-text or natural-language query"),
|
||||
limit: z.number().int().positive().max(50).optional(),
|
||||
}),
|
||||
},
|
||||
async ({ query, limit }) => {
|
||||
const hits = await hybridSearch(query, limit ?? 10);
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(hits, null, 2) }],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user