@gresmcp/mcp 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -8
- package/dist/cli.js +683 -116
- package/dist/cli.js.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/mcp.js.map +1 -1
- package/package.json +32 -1
package/dist/mcp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mcp.ts","../src/db.ts","../src/config.ts","../src/search.ts","../src/ollama.ts","../src/version.ts"],"sourcesContent":["import { FastMCP, UserError } from \"fastmcp\";\r\nimport { z } from \"zod\";\r\nimport {\r\n initDb,\r\n closePool,\r\n listKs,\r\n countEntries,\r\n type KnowledgeSource,\r\n} from \"./db.js\";\r\nimport {\r\n search,\r\n getEntry,\r\n listEntries,\r\n listSources,\r\n type SearchMode,\r\n} from \"./search.js\";\r\nimport { VERSION } from \"./version.js\";\r\n\r\nfunction parseArgs(argv: string[]): {\r\n ksNames: string[];\r\n all: boolean;\r\n transport: \"stdio\" | \"http\";\r\n port: number;\r\n} {\r\n const ksNames: string[] = [];\r\n let all = false;\r\n let transport: \"stdio\" | \"http\" = \"stdio\";\r\n let port = 8080;\r\n for (let i = 0; i < argv.length; i++) {\r\n const arg = argv[i]!;\r\n if (arg === \"--all\") {\r\n all = true;\r\n } else if (arg === \"--transport\" || arg === \"-t\") {\r\n const value = argv[++i];\r\n if (value !== \"stdio\" && value !== \"http\") {\r\n throw new Error(`invalid --transport value '${value}', expected 'stdio' or 'http'`);\r\n }\r\n transport = value;\r\n } else if (arg === \"--port\" || arg === \"-p\") {\r\n port = parseInt(argv[++i] ?? \"\", 10);\r\n if (!Number.isFinite(port) || port <= 0) throw new Error(`invalid --port value`);\r\n } else if (arg === \"--help\" || arg === \"-h\") {\r\n console.error(`usage: mcp [ks1,ks2,... | --all] [--transport stdio|http] [--port N]`);\r\n process.exit(0);\r\n } else if (arg.startsWith(\"-\")) {\r\n throw new Error(`unknown option '${arg}'`);\r\n } else {\r\n ksNames.push(...arg.split(\",\").map((s) => s.trim()).filter(Boolean));\r\n }\r\n }\r\n if (!all && ksNames.length === 0) {\r\n const env = process.env.GRESMCP_KS;\r\n if (env) ksNames.push(...env.split(\",\").map((s) => s.trim()).filter(Boolean));\r\n }\r\n return { ksNames, all, transport, port };\r\n}\r\n\r\nasync function main(): Promise<void> {\r\n const { ksNames, all, transport, port } = parseArgs(process.argv.slice(2));\r\n await initDb();\r\n\r\n const available = await listKs();\r\n let scoped: KnowledgeSource[];\r\n if (all) {\r\n scoped = available;\r\n } else {\r\n const wanted = new Set(ksNames);\r\n const missing = ksNames.filter((n) => !available.some((k) => k.name === n));\r\n if (missing.length > 0) {\r\n throw new Error(\r\n `unknown knowledge source(s): ${missing.join(\", \")}. Available: ${available.map((k) => k.name).join(\", \") || \"(none)\"}`,\r\n );\r\n }\r\n scoped = available.filter((k) => wanted.has(k.name));\r\n }\r\n if (scoped.length === 0) {\r\n throw new Error(\"no knowledge sources configured; pass ks names as arguments or use --all\");\r\n }\r\n\r\n const descriptions = scoped\r\n .map((k) => `- ${k.name}: ${k.description || \"(no description)\"} [model=${k.embedding_model}, dim=${k.embedding_dim}]`)\r\n .join(\"\\n\");\r\n\r\n const server = new FastMCP({\r\n name: \"gresmcp\",\r\n version: VERSION,\r\n instructions:\r\n `This server exposes read-only access to Postgres-backed knowledge sources (RAG: pgvector + tsvector hybrid search).\\n` +\r\n `Available knowledge sources:\\n${descriptions}\\n` +\r\n `Use 'search' with hybrid mode for best results, 'get_entry' to read a full chunk, 'list_sources' to see what documents exist.`,\r\n });\r\n\r\n const ksRef = z\r\n .string()\r\n .optional()\r\n .describe(\"Knowledge source name; optional when the server has exactly one\");\r\n\r\n function resolveKs(name?: string): KnowledgeSource {\r\n if (!name) {\r\n if (scoped.length === 1) return scoped[0]!;\r\n throw new UserError(\r\n `Multiple knowledge sources configured (${scoped.map((k) => k.name).join(\", \")}); specify one with the 'ks' parameter`,\r\n );\r\n }\r\n const ks = scoped.find((k) => k.name === name);\r\n if (!ks) {\r\n throw new UserError(\r\n `Knowledge source '${name}' is not configured on this server; configured: ${scoped.map((k) => k.name).join(\", \")}`,\r\n );\r\n }\r\n return ks;\r\n }\r\n\r\n function filterKs(names?: string[]): KnowledgeSource[] {\r\n if (!names || names.length === 0) return scoped;\r\n const out: KnowledgeSource[] = [];\r\n for (const name of names) {\r\n const ks = scoped.find((k) => k.name === name);\r\n if (!ks) {\r\n throw new UserError(\r\n `Knowledge source '${name}' is not configured on this server; configured: ${scoped.map((k) => k.name).join(\", \")}`,\r\n );\r\n }\r\n out.push(ks);\r\n }\r\n return out;\r\n }\r\n\r\n server.addTool({\r\n name: \"list_ks\",\r\n description: \"List the knowledge sources served by this server, with entry counts\",\r\n parameters: z.object({}),\r\n annotations: { readOnlyHint: true },\r\n execute: async () => {\r\n const out = [];\r\n for (const ks of scoped) {\r\n out.push({\r\n name: ks.name,\r\n description: ks.description,\r\n entries: await countEntries(ks),\r\n embedding_model: ks.embedding_model,\r\n embedding_dim: ks.embedding_dim,\r\n });\r\n }\r\n return JSON.stringify(out, null, 2);\r\n },\r\n });\r\n\r\n server.addTool({\r\n name: \"search\",\r\n description:\r\n \"Hybrid semantic + keyword search across knowledge sources (pgvector cosine similarity fused with tsvector full-text via reciprocal rank fusion). Returns ranked chunks with snippets and scores.\",\r\n parameters: z.object({\r\n query: z.string().describe(\"Natural language search query\"),\r\n ks: z.array(z.string()).optional().describe(\"Restrict search to these knowledge source names (default: all configured)\"),\r\n mode: z.enum([\"hybrid\", \"vector\", \"keyword\"]).default(\"hybrid\").describe(\"hybrid = vector+keyword fusion, vector = semantic only, keyword = full-text only\"),\r\n limit: z.number().int().min(1).max(50).default(8).describe(\"Max results\"),\r\n tags: z.array(z.string()).optional().describe(\"Filter entries by tags (any-of match)\"),\r\n }),\r\n annotations: { readOnlyHint: true },\r\n execute: async (args) => {\r\n const hits = await search(filterKs(args.ks), {\r\n query: args.query,\r\n mode: args.mode as SearchMode,\r\n limit: args.limit,\r\n tags: args.tags,\r\n });\r\n if (hits.length === 0) return \"No results found.\";\r\n return hits\r\n .map(\r\n (h, i) =>\r\n `${i + 1}. [${h.ks}] id=${h.id} score=${h.score.toFixed(4)} (${h.source}#chunk${h.chunk_index})\\n title: ${h.title}\\n ${h.snippet.replace(/\\s+/g, \" \").trim()}`,\r\n )\r\n .join(\"\\n\\n\");\r\n },\r\n });\r\n\r\n server.addTool({\r\n name: \"get_entry\",\r\n description: \"Fetch a single knowledge entry (chunk) by id, including full content and metadata\",\r\n parameters: z.object({\r\n ks: ksRef,\r\n id: z.number().int().describe(\"Entry id as returned by search or list_entries\"),\r\n }),\r\n annotations: { readOnlyHint: true },\r\n execute: async (args) => {\r\n const entry = await getEntry(resolveKs(args.ks), args.id);\r\n if (!entry) throw new UserError(`Entry ${args.id} not found`);\r\n return JSON.stringify(\r\n {\r\n id: Number(entry.id),\r\n source: entry.source,\r\n title: entry.title,\r\n chunk_index: entry.chunk_index,\r\n content: entry.content,\r\n metadata: entry.metadata,\r\n },\r\n null,\r\n 2,\r\n );\r\n },\r\n });\r\n\r\n server.addTool({\r\n name: \"list_entries\",\r\n description: \"List entries of a knowledge source (id, source, title, chunk index, size)\",\r\n parameters: z.object({\r\n ks: ksRef,\r\n source: z.string().optional().describe(\"Filter by source (e.g. a file path)\"),\r\n limit: z.number().int().min(1).max(200).default(20),\r\n offset: z.number().int().min(0).default(0),\r\n }),\r\n annotations: { readOnlyHint: true },\r\n execute: async (args) => {\r\n const rows = await listEntries(resolveKs(args.ks), {\r\n source: args.source,\r\n limit: args.limit,\r\n offset: args.offset,\r\n });\r\n return JSON.stringify(rows, null, 2);\r\n },\r\n });\r\n\r\n server.addTool({\r\n name: \"list_sources\",\r\n description: \"List ingested documents/sources (file paths or manual source names) with chunk counts\",\r\n parameters: z.object({\r\n ks: ksRef,\r\n }),\r\n annotations: { readOnlyHint: true },\r\n execute: async (args) => {\r\n const ks = args.ks ? [resolveKs(args.ks)] : scoped;\r\n return JSON.stringify(await listSources(ks), null, 2);\r\n },\r\n });\r\n\r\n process.on(\"SIGINT\", () => void closePool().finally(() => process.exit(0)));\r\n process.on(\"SIGTERM\", () => void closePool().finally(() => process.exit(0)));\r\n\r\n if (transport === \"http\") {\r\n await server.start({\r\n transportType: \"httpStream\",\r\n httpStream: { port },\r\n });\r\n console.error(`gresmcp MCP server listening on http://localhost:${port}/mcp (ks: ${scoped.map((k) => k.name).join(\", \")})`);\r\n } else {\r\n await server.start({ transportType: \"stdio\" });\r\n }\r\n}\r\n\r\nmain().catch((err) => {\r\n console.error(`gresmcp: ${err instanceof Error ? err.message : err}`);\r\n void closePool().finally(() => process.exit(1));\r\n});\r\n","import { Pool } from \"pg\";\r\nimport { toSql } from \"pgvector\";\r\nimport { createHash } from \"node:crypto\";\r\nimport { databaseUrl, DEFAULT_OLLAMA_URL } from \"./config.js\";\r\n\r\nlet pool: Pool | undefined;\r\n\r\nexport function getPool(): Pool {\r\n if (!pool) {\r\n pool = new Pool({ connectionString: databaseUrl(), max: 5 });\r\n }\r\n return pool;\r\n}\r\n\r\nexport async function closePool(): Promise<void> {\r\n if (pool) {\r\n await pool.end();\r\n pool = undefined;\r\n }\r\n}\r\n\r\nexport interface KnowledgeSource {\r\n id: string;\r\n name: string;\r\n description: string;\r\n embedding_model: string;\r\n embedding_dim: number;\r\n ollama_url: string;\r\n created_at: Date;\r\n updated_at: Date;\r\n}\r\n\r\nexport interface EntryRow {\r\n id: string | number;\r\n source: string;\r\n title: string;\r\n chunk_index: number;\r\n content: string;\r\n content_hash: string;\r\n metadata: Record<string, unknown>;\r\n created_at: Date;\r\n}\r\n\r\nexport function entryTableName(ks: Pick<KnowledgeSource, \"id\">): string {\r\n const name = `entry_${ks.id.replace(/-/g, \"\")}`;\r\n assertTableName(name);\r\n return name;\r\n}\r\n\r\nexport function assertTableName(name: string): void {\r\n if (!/^entry_[0-9a-f]{32}$/.test(name)) {\r\n throw new Error(`Refusing to use unsafe table name: ${name}`);\r\n }\r\n}\r\n\r\nexport function contentHash(content: string): string {\r\n return createHash(\"sha256\").update(content).digest(\"hex\");\r\n}\r\n\r\nexport async function initDb(): Promise<void> {\r\n const p = getPool();\r\n await p.query(`CREATE EXTENSION IF NOT EXISTS vector`);\r\n await p.query(`\r\n CREATE TABLE IF NOT EXISTS ks (\r\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\r\n name text NOT NULL UNIQUE,\r\n description text NOT NULL DEFAULT '',\r\n embedding_model text NOT NULL,\r\n embedding_dim integer NOT NULL,\r\n ollama_url text NOT NULL DEFAULT '${DEFAULT_OLLAMA_URL}',\r\n created_at timestamptz NOT NULL DEFAULT now(),\r\n updated_at timestamptz NOT NULL DEFAULT now()\r\n )\r\n `);\r\n}\r\n\r\nexport async function createKs(input: {\r\n name: string;\r\n description?: string;\r\n model: string;\r\n dim: number;\r\n url: string;\r\n}): Promise<KnowledgeSource> {\r\n const res = await getPool().query(\r\n `INSERT INTO ks (name, description, embedding_model, embedding_dim, ollama_url)\r\n VALUES ($1, $2, $3, $4, $5)\r\n RETURNING *`,\r\n [input.name, input.description ?? \"\", input.model, input.dim, input.url],\r\n );\r\n const ks = res.rows[0] as KnowledgeSource;\r\n await ensureEntryTable(ks);\r\n return ks;\r\n}\r\n\r\nexport async function listKs(): Promise<KnowledgeSource[]> {\r\n const res = await getPool().query(`SELECT * FROM ks ORDER BY name`);\r\n return res.rows as KnowledgeSource[];\r\n}\r\n\r\nexport async function getKs(name: string): Promise<KnowledgeSource | undefined> {\r\n const res = await getPool().query(`SELECT * FROM ks WHERE name = $1`, [name]);\r\n return res.rows[0] as KnowledgeSource | undefined;\r\n}\r\n\r\nexport async function updateKs(\r\n id: string,\r\n fields: { name?: string; description?: string; ollama_url?: string },\r\n): Promise<KnowledgeSource> {\r\n const sets: string[] = [];\r\n const params: unknown[] = [];\r\n if (fields.name !== undefined) {\r\n params.push(fields.name);\r\n sets.push(`name = $${params.length}`);\r\n }\r\n if (fields.description !== undefined) {\r\n params.push(fields.description);\r\n sets.push(`description = $${params.length}`);\r\n }\r\n if (fields.ollama_url !== undefined) {\r\n params.push(fields.ollama_url);\r\n sets.push(`ollama_url = $${params.length}`);\r\n }\r\n if (sets.length === 0) {\r\n const res = await getPool().query(`SELECT * FROM ks WHERE id = $1`, [id]);\r\n return res.rows[0] as KnowledgeSource;\r\n }\r\n sets.push(`updated_at = now()`);\r\n params.push(id);\r\n const res = await getPool().query(\r\n `UPDATE ks SET ${sets.join(\", \")} WHERE id = $${params.length} RETURNING *`,\r\n params,\r\n );\r\n return res.rows[0] as KnowledgeSource;\r\n}\r\n\r\nexport async function deleteKs(ks: KnowledgeSource): Promise<void> {\r\n const tbl = entryTableName(ks);\r\n await getPool().query(`DROP TABLE IF EXISTS ${tbl}`);\r\n await getPool().query(`DELETE FROM ks WHERE id = $1`, [ks.id]);\r\n}\r\n\r\nexport async function ensureEntryTable(ks: KnowledgeSource): Promise<void> {\r\n const tbl = entryTableName(ks);\r\n const p = getPool();\r\n await p.query(`\r\n CREATE TABLE IF NOT EXISTS ${tbl} (\r\n id bigserial PRIMARY KEY,\r\n source text NOT NULL,\r\n title text NOT NULL DEFAULT '',\r\n chunk_index integer NOT NULL DEFAULT 0,\r\n content text NOT NULL,\r\n content_hash text NOT NULL,\r\n metadata jsonb NOT NULL DEFAULT '{}',\r\n tsv tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED,\r\n embedding vector(${ks.embedding_dim}) NOT NULL,\r\n created_at timestamptz NOT NULL DEFAULT now()\r\n )\r\n `);\r\n await p.query(`CREATE INDEX IF NOT EXISTS ${tbl}_tsv_idx ON ${tbl} USING gin (tsv)`);\r\n await p.query(`CREATE INDEX IF NOT EXISTS ${tbl}_emb_idx ON ${tbl} USING hnsw (embedding vector_cosine_ops)`);\r\n await p.query(`CREATE INDEX IF NOT EXISTS ${tbl}_source_idx ON ${tbl} (source)`);\r\n}\r\n\r\nexport interface ChunkInsert {\r\n source: string;\r\n title: string;\r\n chunk_index: number;\r\n content: string;\r\n metadata: Record<string, unknown>;\r\n embedding: number[];\r\n}\r\n\r\nexport async function insertChunks(ks: KnowledgeSource, rows: ChunkInsert[]): Promise<number> {\r\n if (rows.length === 0) return 0;\r\n const tbl = entryTableName(ks);\r\n const p = getPool();\r\n const BATCH = 25;\r\n let inserted = 0;\r\n for (let i = 0; i < rows.length; i += BATCH) {\r\n const batch = rows.slice(i, i + BATCH);\r\n const values: string[] = [];\r\n const params: unknown[] = [];\r\n batch.forEach((r, j) => {\r\n const b = j * 7;\r\n values.push(`($${b + 1}, $${b + 2}, $${b + 3}, $${b + 4}, $${b + 5}, $${b + 6}, $${b + 7}::vector)`);\r\n params.push(r.source, r.title, r.chunk_index, r.content, contentHash(r.content), JSON.stringify(r.metadata), toSql(r.embedding));\r\n });\r\n const res = await p.query(\r\n `INSERT INTO ${tbl} (source, title, chunk_index, content, content_hash, metadata, embedding)\r\n VALUES ${values.join(\", \")}`,\r\n params,\r\n );\r\n inserted += res.rowCount ?? 0;\r\n }\r\n return inserted;\r\n}\r\n\r\nexport async function deleteBySource(ks: KnowledgeSource, source: string): Promise<number> {\r\n const tbl = entryTableName(ks);\r\n const res = await getPool().query(`DELETE FROM ${tbl} WHERE source = $1`, [source]);\r\n return res.rowCount ?? 0;\r\n}\r\n\r\nexport async function sourceChunkHashes(\r\n ks: KnowledgeSource,\r\n source: string,\r\n): Promise<Map<number, string>> {\r\n const tbl = entryTableName(ks);\r\n const res = await getPool().query(\r\n `SELECT chunk_index, content_hash FROM ${tbl} WHERE source = $1`,\r\n [source],\r\n );\r\n const map = new Map<number, string>();\r\n for (const row of res.rows) {\r\n map.set(Number(row.chunk_index), String(row.content_hash));\r\n }\r\n return map;\r\n}\r\n\r\nexport async function countEntries(ks: KnowledgeSource): Promise<number> {\r\n const tbl = entryTableName(ks);\r\n const res = await getPool().query(`SELECT count(*)::int AS n FROM ${tbl}`);\r\n return Number(res.rows[0]?.n ?? 0);\r\n}\r\n","export const DEFAULT_DATABASE_URL = \"postgresql://gres:gres@localhost/gres\";\r\nexport const DEFAULT_OLLAMA_URL = \"http://localhost:11434\";\r\n\r\nexport function databaseUrl(): string {\r\n return process.env.GRESMCP_DATABASE_URL || DEFAULT_DATABASE_URL;\r\n}\r\n\r\nexport function normalizeUrl(url: string): string {\r\n return url.replace(/\\/+$/, \"\");\r\n}\r\n\r\nexport function ollamaUrl(override?: string): string {\r\n const raw = override || process.env.GRESMCP_OLLAMA_URL || DEFAULT_OLLAMA_URL;\r\n return normalizeUrl(raw);\r\n}\r\n","import { toSql } from \"pgvector\";\r\nimport { getPool, entryTableName, type EntryRow, type KnowledgeSource } from \"./db.js\";\r\nimport { embed } from \"./ollama.js\";\r\nimport { normalizeUrl } from \"./config.js\";\r\n\r\nexport type SearchMode = \"hybrid\" | \"vector\" | \"keyword\";\r\n\r\nexport interface SearchHit {\r\n ks: string;\r\n id: number;\r\n source: string;\r\n title: string;\r\n chunk_index: number;\r\n score: number;\r\n snippet: string;\r\n metadata: Record<string, unknown>;\r\n}\r\n\r\nconst RRF_K = 60;\r\nconst HEADLINE_OPTS = \"MaxWords=35, MinWords=15, ShortWord=0, MaxFragments=1, StartSel=[, StopSel=]\";\r\n\r\ninterface SqlParts {\r\n sql: string;\r\n params: unknown[];\r\n}\r\n\r\nfunction tagClause(params: unknown[], tags?: string[]): string {\r\n if (!tags || tags.length === 0) return \"\";\r\n params.push(tags);\r\n return ` AND metadata->'tags' ?| $${params.length}::text[]`;\r\n}\r\n\r\nfunction buildSearchSql(\r\n tbl: string,\r\n mode: SearchMode,\r\n limit: number,\r\n tags?: string[],\r\n): SqlParts {\r\n const params: unknown[] = [];\r\n if (mode === \"vector\") params.push(\"__VECTOR__\");\r\n else if (mode === \"keyword\") params.push(\"__QUERY__\");\r\n else params.push(\"__VECTOR__\", \"__QUERY__\");\r\n const tag = tagClause(params, tags);\r\n\r\n if (mode === \"vector\") {\r\n const sql = `\r\n SELECT id, source, title, chunk_index, metadata,\r\n 1 - (embedding <=> $1::vector) AS score,\r\n left(regexp_replace(content, '\\\\s+', ' ', 'g'), 320) AS snippet\r\n FROM ${tbl}\r\n WHERE true${tag}\r\n ORDER BY embedding <=> $1::vector\r\n LIMIT ${limit}`;\r\n return { sql, params };\r\n }\r\n\r\n if (mode === \"keyword\") {\r\n const sql = `\r\n WITH q AS (SELECT websearch_to_tsquery('english', $1) AS query)\r\n SELECT e.id, e.source, e.title, e.chunk_index, e.metadata,\r\n ts_rank_cd(e.tsv, q.query) AS score,\r\n ts_headline('english', e.content, q.query, '${HEADLINE_OPTS}') AS snippet\r\n FROM ${tbl} e CROSS JOIN q\r\n WHERE e.tsv @@ q.query${tag}\r\n ORDER BY score DESC, e.id\r\n LIMIT ${limit}`;\r\n return { sql, params };\r\n }\r\n\r\n const inner = Math.min(Math.max(limit * 4, 20), 100);\r\n const sql = `\r\n WITH v AS (\r\n SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> $1::vector) AS r\r\n FROM ${tbl}\r\n WHERE true${tag}\r\n ORDER BY embedding <=> $1::vector\r\n LIMIT ${inner}\r\n ),\r\n k AS (\r\n SELECT id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(tsv, q.query) DESC) AS r\r\n FROM ${tbl} CROSS JOIN (SELECT websearch_to_tsquery('english', $2) AS query) q\r\n WHERE tsv @@ q.query${tag}\r\n ORDER BY ts_rank_cd(tsv, q.query) DESC\r\n LIMIT ${inner}\r\n ),\r\n s AS (\r\n SELECT COALESCE(v.id, k.id) AS id,\r\n COALESCE(1.0 / (${RRF_K} + v.r), 0) + COALESCE(1.0 / (${RRF_K} + k.r), 0) AS score,\r\n (v.r IS NOT NULL) AS vhit,\r\n (k.r IS NOT NULL) AS khit\r\n FROM v FULL JOIN k ON k.id = v.id\r\n )\r\n SELECT e.id, e.source, e.title, e.chunk_index, e.metadata, s.score,\r\n CASE WHEN s.khit\r\n THEN ts_headline('english', e.content, websearch_to_tsquery('english', $2), '${HEADLINE_OPTS}')\r\n ELSE left(regexp_replace(e.content, '\\\\s+', ' ', 'g'), 320)\r\n END AS snippet\r\n FROM s\r\n JOIN ${tbl} e ON e.id = s.id\r\n ORDER BY s.score DESC, e.id\r\n LIMIT ${limit}`;\r\n return { sql, params };\r\n}\r\n\r\nasync function queryEmbedding(ks: KnowledgeSource, query: string, cache: Map<string, number[]>): Promise<number[]> {\r\n const key = `${normalizeUrl(ks.ollama_url)}::${ks.embedding_model}::${query}`;\r\n const cached = cache.get(key);\r\n if (cached) return cached;\r\n const embs = await embed(ks.ollama_url, ks.embedding_model, [query], 30_000);\r\n const emb = embs[0];\r\n if (!emb) {\r\n throw new Error(`Ollama returned an empty embedding for query on ks '${ks.name}'`);\r\n }\r\n cache.set(key, emb);\r\n return emb;\r\n}\r\n\r\nexport interface SearchArgs {\r\n query: string;\r\n mode?: SearchMode;\r\n limit?: number;\r\n tags?: string[];\r\n ksFilter?: string[];\r\n}\r\n\r\nexport async function search(ksList: KnowledgeSource[], args: SearchArgs): Promise<SearchHit[]> {\r\n const mode = args.mode ?? \"hybrid\";\r\n const limit = Math.min(Math.max(args.limit ?? 8, 1), 50);\r\n let targets = ksList;\r\n if (args.ksFilter && args.ksFilter.length > 0) {\r\n const wanted = new Set(args.ksFilter);\r\n targets = ksList.filter((k) => wanted.has(k.name));\r\n }\r\n if (targets.length === 0) return [];\r\n\r\n const cache = new Map<string, number[]>();\r\n const all: SearchHit[] = [];\r\n for (const ks of targets) {\r\n const tbl = entryTableName(ks);\r\n const { sql, params } = buildSearchSql(tbl, mode, limit, args.tags);\r\n const resolved: unknown[] = [];\r\n for (const p of params) {\r\n if (p === \"__VECTOR__\") {\r\n const emb = await queryEmbedding(ks, args.query, cache);\r\n if (emb.length !== ks.embedding_dim) {\r\n throw new Error(\r\n `Embedding dimension mismatch for ks '${ks.name}': expected ${ks.embedding_dim}, got ${emb.length}`,\r\n );\r\n }\r\n resolved.push(toSql(emb));\r\n } else if (p === \"__QUERY__\") {\r\n resolved.push(args.query);\r\n } else {\r\n resolved.push(p);\r\n }\r\n }\r\n const res = await getPool().query(sql, resolved);\r\n for (const row of res.rows) {\r\n all.push({\r\n ks: ks.name,\r\n id: Number(row.id),\r\n source: String(row.source),\r\n title: String(row.title ?? \"\"),\r\n chunk_index: Number(row.chunk_index ?? 0),\r\n score: Number(row.score ?? 0),\r\n snippet: String(row.snippet ?? \"\"),\r\n metadata: (row.metadata ?? {}) as Record<string, unknown>,\r\n });\r\n }\r\n }\r\n all.sort((a, b) => b.score - a.score || a.ks.localeCompare(b.ks));\r\n return all.slice(0, limit);\r\n}\r\n\r\nexport async function getEntry(ks: KnowledgeSource, id: number): Promise<EntryRow | undefined> {\r\n const tbl = entryTableName(ks);\r\n const res = await getPool().query(\r\n `SELECT id, source, title, chunk_index, content, content_hash, metadata, created_at\r\n FROM ${tbl} WHERE id = $1`,\r\n [id],\r\n );\r\n return res.rows[0] as EntryRow | undefined;\r\n}\r\n\r\nexport interface EntryListItem {\r\n id: number;\r\n source: string;\r\n title: string;\r\n chunk_index: number;\r\n chars: number;\r\n created_at: Date;\r\n}\r\n\r\nexport async function listEntries(\r\n ks: KnowledgeSource,\r\n opts: { source?: string; limit?: number; offset?: number } = {},\r\n): Promise<EntryListItem[]> {\r\n const tbl = entryTableName(ks);\r\n const limit = Math.min(Math.max(opts.limit ?? 20, 1), 200);\r\n const offset = Math.max(opts.offset ?? 0, 0);\r\n const params: unknown[] = [];\r\n let where = \"\";\r\n if (opts.source) {\r\n params.push(opts.source);\r\n where = `WHERE source = $${params.length}`;\r\n }\r\n params.push(limit, offset);\r\n const res = await getPool().query(\r\n `SELECT id, source, title, chunk_index, length(content) AS chars, created_at\r\n FROM ${tbl} ${where}\r\n ORDER BY source, chunk_index, id\r\n LIMIT $${params.length - 1} OFFSET $${params.length}`,\r\n params,\r\n );\r\n return res.rows.map((r) => ({\r\n id: Number(r.id),\r\n source: String(r.source),\r\n title: String(r.title ?? \"\"),\r\n chunk_index: Number(r.chunk_index),\r\n chars: Number(r.chars),\r\n created_at: r.created_at as Date,\r\n }));\r\n}\r\n\r\nexport interface SourceInfo {\r\n ks: string;\r\n source: string;\r\n title: string;\r\n chunks: number;\r\n chars: number;\r\n}\r\n\r\nexport async function listSources(ksList: KnowledgeSource[]): Promise<SourceInfo[]> {\r\n const out: SourceInfo[] = [];\r\n for (const ks of ksList) {\r\n const tbl = entryTableName(ks);\r\n const res = await getPool().query(\r\n `SELECT source, max(title) AS title, count(*)::int AS chunks, sum(length(content))::bigint AS chars\r\n FROM ${tbl}\r\n GROUP BY source\r\n ORDER BY source`,\r\n );\r\n for (const r of res.rows) {\r\n out.push({\r\n ks: ks.name,\r\n source: String(r.source),\r\n title: String(r.title ?? \"\"),\r\n chunks: Number(r.chunks),\r\n chars: Number(r.chars),\r\n });\r\n }\r\n }\r\n return out;\r\n}\r\n","import { normalizeUrl } from \"./config.js\";\r\n\r\nexport class OllamaError extends Error {\r\n constructor(\r\n message: string,\r\n public readonly status?: number,\r\n ) {\r\n super(message);\r\n this.name = \"OllamaError\";\r\n }\r\n}\r\n\r\nexport async function embed(\r\n baseUrl: string,\r\n model: string,\r\n inputs: string[],\r\n timeoutMs = 120_000,\r\n): Promise<number[][]> {\r\n const url = `${normalizeUrl(baseUrl)}/api/embed`;\r\n let res: Response;\r\n try {\r\n res = await fetch(url, {\r\n method: \"POST\",\r\n headers: { \"content-type\": \"application/json\" },\r\n body: JSON.stringify({ model, input: inputs }),\r\n signal: AbortSignal.timeout(timeoutMs),\r\n });\r\n } catch (err) {\r\n throw new OllamaError(\r\n `Cannot reach Ollama at ${normalizeUrl(baseUrl)} (${err instanceof Error ? err.message : err})`,\r\n );\r\n }\r\n if (!res.ok) {\r\n const body = await res.text().catch(() => \"\");\r\n throw new OllamaError(`Ollama returned ${res.status} for ${url}: ${body.slice(0, 300)}`, res.status);\r\n }\r\n const data = (await res.json()) as { embeddings?: number[][]; error?: string };\r\n if (data.error) {\r\n throw new OllamaError(`Ollama error: ${data.error}`);\r\n }\r\n if (!Array.isArray(data.embeddings) || data.embeddings.length !== inputs.length) {\r\n throw new OllamaError(`Unexpected response shape from ${url}`);\r\n }\r\n return data.embeddings;\r\n}\r\n\r\nexport async function embedBatch(\r\n baseUrl: string,\r\n model: string,\r\n inputs: string[],\r\n batchSize = 32,\r\n onProgress?: (done: number, total: number) => void,\r\n): Promise<number[][]> {\r\n const out: number[][] = [];\r\n let done = 0;\r\n for (let i = 0; i < inputs.length; i += batchSize) {\r\n const batch = inputs.slice(i, i + batchSize);\r\n const embs = await embed(baseUrl, model, batch);\r\n out.push(...embs);\r\n done += batch.length;\r\n onProgress?.(done, inputs.length);\r\n }\r\n return out;\r\n}\r\n\r\nexport async function probeModel(\r\n baseUrl: string,\r\n model: string,\r\n): Promise<{ dim: number }> {\r\n const embs = await embed(baseUrl, model, [\"gresmcp dimension probe\"], 30_000);\r\n const first = embs[0];\r\n if (!first || first.length === 0) {\r\n throw new OllamaError(`Ollama returned an empty embedding for model ${model}`);\r\n }\r\n return { dim: first.length };\r\n}\r\n","export const VERSION = \"1.0.0\";\r\n"],"mappings":"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","names":["toSql","sql","toSql"]}
|
|
1
|
+
{"version":3,"sources":["../src/mcp.ts","../src/db.ts","../src/config.ts","../src/search.ts","../src/ollama.ts","../src/version.ts"],"sourcesContent":["import { FastMCP, UserError } from \"fastmcp\";\r\nimport { z } from \"zod\";\r\nimport {\r\n initDb,\r\n closePool,\r\n listKs,\r\n countEntries,\r\n type KnowledgeSource,\r\n} from \"./db.js\";\r\nimport {\r\n search,\r\n getEntry,\r\n listEntries,\r\n listSources,\r\n type SearchMode,\r\n} from \"./search.js\";\r\nimport { VERSION } from \"./version.js\";\r\n\r\nfunction parseArgs(argv: string[]): {\r\n ksNames: string[];\r\n all: boolean;\r\n transport: \"stdio\" | \"http\";\r\n port: number;\r\n} {\r\n const ksNames: string[] = [];\r\n let all = false;\r\n let transport: \"stdio\" | \"http\" = \"stdio\";\r\n let port = 8080;\r\n for (let i = 0; i < argv.length; i++) {\r\n const arg = argv[i]!;\r\n if (arg === \"--all\") {\r\n all = true;\r\n } else if (arg === \"--transport\" || arg === \"-t\") {\r\n const value = argv[++i];\r\n if (value !== \"stdio\" && value !== \"http\") {\r\n throw new Error(`invalid --transport value '${value}', expected 'stdio' or 'http'`);\r\n }\r\n transport = value;\r\n } else if (arg === \"--port\" || arg === \"-p\") {\r\n port = parseInt(argv[++i] ?? \"\", 10);\r\n if (!Number.isFinite(port) || port <= 0) throw new Error(`invalid --port value`);\r\n } else if (arg === \"--help\" || arg === \"-h\") {\r\n console.error(`usage: mcp [ks1,ks2,... | --all] [--transport stdio|http] [--port N]`);\r\n process.exit(0);\r\n } else if (arg.startsWith(\"-\")) {\r\n throw new Error(`unknown option '${arg}'`);\r\n } else {\r\n ksNames.push(...arg.split(\",\").map((s) => s.trim()).filter(Boolean));\r\n }\r\n }\r\n if (!all && ksNames.length === 0) {\r\n const env = process.env.GRESMCP_KS;\r\n if (env) ksNames.push(...env.split(\",\").map((s) => s.trim()).filter(Boolean));\r\n }\r\n return { ksNames, all, transport, port };\r\n}\r\n\r\nasync function main(): Promise<void> {\r\n const { ksNames, all, transport, port } = parseArgs(process.argv.slice(2));\r\n await initDb();\r\n\r\n const available = await listKs();\r\n let scoped: KnowledgeSource[];\r\n if (all) {\r\n scoped = available;\r\n } else {\r\n const wanted = new Set(ksNames);\r\n const missing = ksNames.filter((n) => !available.some((k) => k.name === n));\r\n if (missing.length > 0) {\r\n throw new Error(\r\n `unknown knowledge source(s): ${missing.join(\", \")}. Available: ${available.map((k) => k.name).join(\", \") || \"(none)\"}`,\r\n );\r\n }\r\n scoped = available.filter((k) => wanted.has(k.name));\r\n }\r\n if (scoped.length === 0) {\r\n throw new Error(\"no knowledge sources configured; pass ks names as arguments or use --all\");\r\n }\r\n\r\n const descriptions = scoped\r\n .map((k) => `- ${k.name}: ${k.description || \"(no description)\"} [model=${k.embedding_model}, dim=${k.embedding_dim}]`)\r\n .join(\"\\n\");\r\n\r\n const server = new FastMCP({\r\n name: \"gresmcp\",\r\n version: VERSION,\r\n instructions:\r\n `This server exposes read-only access to Postgres-backed knowledge sources (RAG: pgvector + tsvector hybrid search).\\n` +\r\n `Available knowledge sources:\\n${descriptions}\\n` +\r\n `Use 'search' with hybrid mode for best results, 'get_entry' to read a full chunk, 'list_sources' to see what documents exist.`,\r\n });\r\n\r\n const ksRef = z\r\n .string()\r\n .optional()\r\n .describe(\"Knowledge source name; optional when the server has exactly one\");\r\n\r\n function resolveKs(name?: string): KnowledgeSource {\r\n if (!name) {\r\n if (scoped.length === 1) return scoped[0]!;\r\n throw new UserError(\r\n `Multiple knowledge sources configured (${scoped.map((k) => k.name).join(\", \")}); specify one with the 'ks' parameter`,\r\n );\r\n }\r\n const ks = scoped.find((k) => k.name === name);\r\n if (!ks) {\r\n throw new UserError(\r\n `Knowledge source '${name}' is not configured on this server; configured: ${scoped.map((k) => k.name).join(\", \")}`,\r\n );\r\n }\r\n return ks;\r\n }\r\n\r\n function filterKs(names?: string[]): KnowledgeSource[] {\r\n if (!names || names.length === 0) return scoped;\r\n const out: KnowledgeSource[] = [];\r\n for (const name of names) {\r\n const ks = scoped.find((k) => k.name === name);\r\n if (!ks) {\r\n throw new UserError(\r\n `Knowledge source '${name}' is not configured on this server; configured: ${scoped.map((k) => k.name).join(\", \")}`,\r\n );\r\n }\r\n out.push(ks);\r\n }\r\n return out;\r\n }\r\n\r\n server.addTool({\r\n name: \"list_ks\",\r\n description: \"List the knowledge sources served by this server, with entry counts\",\r\n parameters: z.object({}),\r\n annotations: { readOnlyHint: true },\r\n execute: async () => {\r\n const out = [];\r\n for (const ks of scoped) {\r\n out.push({\r\n name: ks.name,\r\n description: ks.description,\r\n entries: await countEntries(ks),\r\n embedding_model: ks.embedding_model,\r\n embedding_dim: ks.embedding_dim,\r\n });\r\n }\r\n return JSON.stringify(out, null, 2);\r\n },\r\n });\r\n\r\n server.addTool({\r\n name: \"search\",\r\n description:\r\n \"Hybrid semantic + keyword search across knowledge sources (pgvector cosine similarity fused with tsvector full-text via reciprocal rank fusion). Returns ranked chunks with snippets and scores.\",\r\n parameters: z.object({\r\n query: z.string().describe(\"Natural language search query\"),\r\n ks: z.array(z.string()).optional().describe(\"Restrict search to these knowledge source names (default: all configured)\"),\r\n mode: z.enum([\"hybrid\", \"vector\", \"keyword\"]).default(\"hybrid\").describe(\"hybrid = vector+keyword fusion, vector = semantic only, keyword = full-text only\"),\r\n limit: z.number().int().min(1).max(50).default(8).describe(\"Max results\"),\r\n tags: z.array(z.string()).optional().describe(\"Filter entries by tags (any-of match)\"),\r\n }),\r\n annotations: { readOnlyHint: true },\r\n execute: async (args) => {\r\n const hits = await search(filterKs(args.ks), {\r\n query: args.query,\r\n mode: args.mode as SearchMode,\r\n limit: args.limit,\r\n tags: args.tags,\r\n });\r\n if (hits.length === 0) return \"No results found.\";\r\n return hits\r\n .map(\r\n (h, i) =>\r\n `${i + 1}. [${h.ks}] id=${h.id} score=${h.score.toFixed(4)} (${h.source}#chunk${h.chunk_index})\\n title: ${h.title}\\n ${h.snippet.replace(/\\s+/g, \" \").trim()}`,\r\n )\r\n .join(\"\\n\\n\");\r\n },\r\n });\r\n\r\n server.addTool({\r\n name: \"get_entry\",\r\n description: \"Fetch a single knowledge entry (chunk) by id, including full content and metadata\",\r\n parameters: z.object({\r\n ks: ksRef,\r\n id: z.number().int().describe(\"Entry id as returned by search or list_entries\"),\r\n }),\r\n annotations: { readOnlyHint: true },\r\n execute: async (args) => {\r\n const entry = await getEntry(resolveKs(args.ks), args.id);\r\n if (!entry) throw new UserError(`Entry ${args.id} not found`);\r\n return JSON.stringify(\r\n {\r\n id: Number(entry.id),\r\n source: entry.source,\r\n title: entry.title,\r\n chunk_index: entry.chunk_index,\r\n content: entry.content,\r\n metadata: entry.metadata,\r\n },\r\n null,\r\n 2,\r\n );\r\n },\r\n });\r\n\r\n server.addTool({\r\n name: \"list_entries\",\r\n description: \"List entries of a knowledge source (id, source, title, chunk index, size)\",\r\n parameters: z.object({\r\n ks: ksRef,\r\n source: z.string().optional().describe(\"Filter by source (e.g. a file path)\"),\r\n limit: z.number().int().min(1).max(200).default(20),\r\n offset: z.number().int().min(0).default(0),\r\n }),\r\n annotations: { readOnlyHint: true },\r\n execute: async (args) => {\r\n const rows = await listEntries(resolveKs(args.ks), {\r\n source: args.source,\r\n limit: args.limit,\r\n offset: args.offset,\r\n });\r\n return JSON.stringify(rows, null, 2);\r\n },\r\n });\r\n\r\n server.addTool({\r\n name: \"list_sources\",\r\n description: \"List ingested documents/sources (file paths or manual source names) with chunk counts\",\r\n parameters: z.object({\r\n ks: ksRef,\r\n }),\r\n annotations: { readOnlyHint: true },\r\n execute: async (args) => {\r\n const ks = args.ks ? [resolveKs(args.ks)] : scoped;\r\n return JSON.stringify(await listSources(ks), null, 2);\r\n },\r\n });\r\n\r\n process.on(\"SIGINT\", () => void closePool().finally(() => process.exit(0)));\r\n process.on(\"SIGTERM\", () => void closePool().finally(() => process.exit(0)));\r\n\r\n if (transport === \"http\") {\r\n await server.start({\r\n transportType: \"httpStream\",\r\n httpStream: { port },\r\n });\r\n console.error(`gresmcp MCP server listening on http://localhost:${port}/mcp (ks: ${scoped.map((k) => k.name).join(\", \")})`);\r\n } else {\r\n await server.start({ transportType: \"stdio\" });\r\n }\r\n}\r\n\r\nmain().catch((err) => {\r\n console.error(`gresmcp: ${err instanceof Error ? err.message : err}`);\r\n void closePool().finally(() => process.exit(1));\r\n});\r\n","import { Pool } from \"pg\";\r\nimport { toSql } from \"pgvector\";\r\nimport { createHash } from \"node:crypto\";\r\nimport { databaseUrl, DEFAULT_OLLAMA_URL } from \"./config.js\";\r\n\r\nlet pool: Pool | undefined;\r\n\r\nexport function getPool(): Pool {\r\n if (!pool) {\r\n pool = new Pool({ connectionString: databaseUrl(), max: 5 });\r\n }\r\n return pool;\r\n}\r\n\r\nexport async function closePool(): Promise<void> {\r\n if (pool) {\r\n await pool.end();\r\n pool = undefined;\r\n }\r\n}\r\n\r\nexport interface KnowledgeSource {\r\n id: string;\r\n name: string;\r\n description: string;\r\n embedding_model: string;\r\n embedding_dim: number;\r\n ollama_url: string;\r\n created_at: Date;\r\n updated_at: Date;\r\n}\r\n\r\nexport interface EntryRow {\r\n id: string | number;\r\n source: string;\r\n title: string;\r\n chunk_index: number;\r\n content: string;\r\n content_hash: string;\r\n metadata: Record<string, unknown>;\r\n created_at: Date;\r\n}\r\n\r\nexport function entryTableName(ks: Pick<KnowledgeSource, \"id\">): string {\r\n const name = `entry_${ks.id.replace(/-/g, \"\")}`;\r\n assertTableName(name);\r\n return name;\r\n}\r\n\r\nexport function assertTableName(name: string): void {\r\n if (!/^entry_[0-9a-f]{32}$/.test(name)) {\r\n throw new Error(`Refusing to use unsafe table name: ${name}`);\r\n }\r\n}\r\n\r\nexport function contentHash(content: string): string {\r\n return createHash(\"sha256\").update(content).digest(\"hex\");\r\n}\r\n\r\nexport async function initDb(): Promise<void> {\r\n const p = getPool();\r\n await p.query(`CREATE EXTENSION IF NOT EXISTS vector`);\r\n await p.query(`\r\n CREATE TABLE IF NOT EXISTS ks (\r\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\r\n name text NOT NULL UNIQUE,\r\n description text NOT NULL DEFAULT '',\r\n embedding_model text NOT NULL,\r\n embedding_dim integer NOT NULL,\r\n ollama_url text NOT NULL DEFAULT '${DEFAULT_OLLAMA_URL}',\r\n created_at timestamptz NOT NULL DEFAULT now(),\r\n updated_at timestamptz NOT NULL DEFAULT now()\r\n )\r\n `);\r\n}\r\n\r\nexport async function createKs(input: {\r\n name: string;\r\n description?: string;\r\n model: string;\r\n dim: number;\r\n url: string;\r\n}): Promise<KnowledgeSource> {\r\n const res = await getPool().query(\r\n `INSERT INTO ks (name, description, embedding_model, embedding_dim, ollama_url)\r\n VALUES ($1, $2, $3, $4, $5)\r\n RETURNING *`,\r\n [input.name, input.description ?? \"\", input.model, input.dim, input.url],\r\n );\r\n const ks = res.rows[0] as KnowledgeSource;\r\n await ensureEntryTable(ks);\r\n return ks;\r\n}\r\n\r\nexport async function listKs(): Promise<KnowledgeSource[]> {\r\n const res = await getPool().query(`SELECT * FROM ks ORDER BY name`);\r\n return res.rows as KnowledgeSource[];\r\n}\r\n\r\nexport async function getKs(name: string): Promise<KnowledgeSource | undefined> {\r\n const res = await getPool().query(`SELECT * FROM ks WHERE name = $1`, [name]);\r\n return res.rows[0] as KnowledgeSource | undefined;\r\n}\r\n\r\nexport async function updateKs(\r\n id: string,\r\n fields: { name?: string; description?: string; ollama_url?: string },\r\n): Promise<KnowledgeSource> {\r\n const sets: string[] = [];\r\n const params: unknown[] = [];\r\n if (fields.name !== undefined) {\r\n params.push(fields.name);\r\n sets.push(`name = $${params.length}`);\r\n }\r\n if (fields.description !== undefined) {\r\n params.push(fields.description);\r\n sets.push(`description = $${params.length}`);\r\n }\r\n if (fields.ollama_url !== undefined) {\r\n params.push(fields.ollama_url);\r\n sets.push(`ollama_url = $${params.length}`);\r\n }\r\n if (sets.length === 0) {\r\n const res = await getPool().query(`SELECT * FROM ks WHERE id = $1`, [id]);\r\n return res.rows[0] as KnowledgeSource;\r\n }\r\n sets.push(`updated_at = now()`);\r\n params.push(id);\r\n const res = await getPool().query(\r\n `UPDATE ks SET ${sets.join(\", \")} WHERE id = $${params.length} RETURNING *`,\r\n params,\r\n );\r\n return res.rows[0] as KnowledgeSource;\r\n}\r\n\r\nexport async function deleteKs(ks: KnowledgeSource): Promise<void> {\r\n const tbl = entryTableName(ks);\r\n await getPool().query(`DROP TABLE IF EXISTS ${tbl}`);\r\n await getPool().query(`DELETE FROM ks WHERE id = $1`, [ks.id]);\r\n}\r\n\r\nexport async function ensureEntryTable(ks: KnowledgeSource): Promise<void> {\r\n const tbl = entryTableName(ks);\r\n const p = getPool();\r\n await p.query(`\r\n CREATE TABLE IF NOT EXISTS ${tbl} (\r\n id bigserial PRIMARY KEY,\r\n source text NOT NULL,\r\n title text NOT NULL DEFAULT '',\r\n chunk_index integer NOT NULL DEFAULT 0,\r\n content text NOT NULL,\r\n content_hash text NOT NULL,\r\n metadata jsonb NOT NULL DEFAULT '{}',\r\n tsv tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED,\r\n embedding vector(${ks.embedding_dim}) NOT NULL,\r\n created_at timestamptz NOT NULL DEFAULT now()\r\n )\r\n `);\r\n await p.query(`CREATE INDEX IF NOT EXISTS ${tbl}_tsv_idx ON ${tbl} USING gin (tsv)`);\r\n await p.query(`CREATE INDEX IF NOT EXISTS ${tbl}_emb_idx ON ${tbl} USING hnsw (embedding vector_cosine_ops)`);\r\n await p.query(`CREATE INDEX IF NOT EXISTS ${tbl}_source_idx ON ${tbl} (source)`);\r\n}\r\n\r\nexport interface ChunkInsert {\r\n source: string;\r\n title: string;\r\n chunk_index: number;\r\n content: string;\r\n metadata: Record<string, unknown>;\r\n embedding: number[];\r\n}\r\n\r\nexport async function insertChunks(ks: KnowledgeSource, rows: ChunkInsert[]): Promise<number> {\r\n if (rows.length === 0) return 0;\r\n const tbl = entryTableName(ks);\r\n const p = getPool();\r\n const BATCH = 25;\r\n let inserted = 0;\r\n for (let i = 0; i < rows.length; i += BATCH) {\r\n const batch = rows.slice(i, i + BATCH);\r\n const values: string[] = [];\r\n const params: unknown[] = [];\r\n batch.forEach((r, j) => {\r\n const b = j * 7;\r\n values.push(`($${b + 1}, $${b + 2}, $${b + 3}, $${b + 4}, $${b + 5}, $${b + 6}, $${b + 7}::vector)`);\r\n params.push(r.source, r.title, r.chunk_index, r.content, contentHash(r.content), JSON.stringify(r.metadata), toSql(r.embedding));\r\n });\r\n const res = await p.query(\r\n `INSERT INTO ${tbl} (source, title, chunk_index, content, content_hash, metadata, embedding)\r\n VALUES ${values.join(\", \")}`,\r\n params,\r\n );\r\n inserted += res.rowCount ?? 0;\r\n }\r\n return inserted;\r\n}\r\n\r\nexport async function deleteBySource(ks: KnowledgeSource, source: string): Promise<number> {\r\n const tbl = entryTableName(ks);\r\n const res = await getPool().query(`DELETE FROM ${tbl} WHERE source = $1`, [source]);\r\n return res.rowCount ?? 0;\r\n}\r\n\r\nexport async function sourceChunkHashes(\r\n ks: KnowledgeSource,\r\n source: string,\r\n): Promise<Map<number, string>> {\r\n const tbl = entryTableName(ks);\r\n const res = await getPool().query(\r\n `SELECT chunk_index, content_hash FROM ${tbl} WHERE source = $1`,\r\n [source],\r\n );\r\n const map = new Map<number, string>();\r\n for (const row of res.rows) {\r\n map.set(Number(row.chunk_index), String(row.content_hash));\r\n }\r\n return map;\r\n}\r\n\r\nexport async function countEntries(ks: KnowledgeSource): Promise<number> {\r\n const tbl = entryTableName(ks);\r\n const res = await getPool().query(`SELECT count(*)::int AS n FROM ${tbl}`);\r\n return Number(res.rows[0]?.n ?? 0);\r\n}\r\n\r\nexport async function dbClockTimestamp(): Promise<Date> {\r\n const res = await getPool().query(`SELECT clock_timestamp() AS ts`);\r\n return res.rows[0]!.ts as Date;\r\n}\r\n\r\nexport interface KsStats {\r\n ks: KnowledgeSource;\r\n entries: number;\r\n sources: number;\r\n chars: number;\r\n avg_chunk_chars: number | null;\r\n min_chunk_chars: number | null;\r\n max_chunk_chars: number | null;\r\n last_fed_at: Date | null;\r\n}\r\n\r\nexport async function ksStats(ks: KnowledgeSource, since?: Date): Promise<KsStats> {\r\n const tbl = entryTableName(ks);\r\n const params: unknown[] = [];\r\n let where = \"\";\r\n if (since) {\r\n params.push(since);\r\n where = ` WHERE created_at >= $1`;\r\n }\r\n const res = await getPool().query(\r\n `SELECT count(*)::int AS entries,\r\n count(DISTINCT source)::int AS sources,\r\n COALESCE(sum(length(content)), 0)::bigint AS chars,\r\n round(avg(length(content)))::int AS avg_chunk_chars,\r\n min(length(content))::int AS min_chunk_chars,\r\n max(length(content))::int AS max_chunk_chars,\r\n max(created_at) AS last_fed_at\r\n FROM ${tbl}${where}`,\r\n params,\r\n );\r\n const r = res.rows[0] as Record<string, unknown>;\r\n return {\r\n ks,\r\n entries: Number(r.entries ?? 0),\r\n sources: Number(r.sources ?? 0),\r\n chars: Number(r.chars ?? 0),\r\n avg_chunk_chars: r.avg_chunk_chars === null ? null : Number(r.avg_chunk_chars),\r\n min_chunk_chars: r.min_chunk_chars === null ? null : Number(r.min_chunk_chars),\r\n max_chunk_chars: r.max_chunk_chars === null ? null : Number(r.max_chunk_chars),\r\n last_fed_at: r.last_fed_at === null ? null : (r.last_fed_at as Date),\r\n };\r\n}\r\n","export const DEFAULT_DATABASE_URL = \"postgresql://gres:gres@localhost/gres\";\r\nexport const DEFAULT_OLLAMA_URL = \"http://localhost:11434\";\r\n\r\nexport function databaseUrl(): string {\r\n return process.env.GRESMCP_DATABASE_URL || DEFAULT_DATABASE_URL;\r\n}\r\n\r\nexport function normalizeUrl(url: string): string {\r\n return url.replace(/\\/+$/, \"\");\r\n}\r\n\r\nexport function ollamaUrl(override?: string): string {\r\n const raw = override || process.env.GRESMCP_OLLAMA_URL || DEFAULT_OLLAMA_URL;\r\n return normalizeUrl(raw);\r\n}\r\n","import { toSql } from \"pgvector\";\r\nimport { getPool, entryTableName, type EntryRow, type KnowledgeSource } from \"./db.js\";\r\nimport { embed } from \"./ollama.js\";\r\nimport { normalizeUrl } from \"./config.js\";\r\n\r\nexport type SearchMode = \"hybrid\" | \"vector\" | \"keyword\";\r\n\r\nexport interface SearchHit {\r\n ks: string;\r\n id: number;\r\n source: string;\r\n title: string;\r\n chunk_index: number;\r\n score: number;\r\n snippet: string;\r\n metadata: Record<string, unknown>;\r\n}\r\n\r\nconst RRF_K = 60;\r\nconst HEADLINE_OPTS = \"MaxWords=35, MinWords=15, ShortWord=0, MaxFragments=1, StartSel=[, StopSel=]\";\r\n\r\ninterface SqlParts {\r\n sql: string;\r\n params: unknown[];\r\n}\r\n\r\nfunction tagClause(params: unknown[], tags?: string[]): string {\r\n if (!tags || tags.length === 0) return \"\";\r\n params.push(tags);\r\n return ` AND metadata->'tags' ?| $${params.length}::text[]`;\r\n}\r\n\r\nfunction buildSearchSql(\r\n tbl: string,\r\n mode: SearchMode,\r\n limit: number,\r\n tags?: string[],\r\n): SqlParts {\r\n const params: unknown[] = [];\r\n if (mode === \"vector\") params.push(\"__VECTOR__\");\r\n else if (mode === \"keyword\") params.push(\"__QUERY__\");\r\n else params.push(\"__VECTOR__\", \"__QUERY__\");\r\n const tag = tagClause(params, tags);\r\n\r\n if (mode === \"vector\") {\r\n const sql = `\r\n SELECT id, source, title, chunk_index, metadata,\r\n 1 - (embedding <=> $1::vector) AS score,\r\n left(regexp_replace(content, '\\\\s+', ' ', 'g'), 320) AS snippet\r\n FROM ${tbl}\r\n WHERE true${tag}\r\n ORDER BY embedding <=> $1::vector\r\n LIMIT ${limit}`;\r\n return { sql, params };\r\n }\r\n\r\n if (mode === \"keyword\") {\r\n const sql = `\r\n WITH q AS (SELECT websearch_to_tsquery('english', $1) AS query)\r\n SELECT e.id, e.source, e.title, e.chunk_index, e.metadata,\r\n ts_rank_cd(e.tsv, q.query) AS score,\r\n ts_headline('english', e.content, q.query, '${HEADLINE_OPTS}') AS snippet\r\n FROM ${tbl} e CROSS JOIN q\r\n WHERE e.tsv @@ q.query${tag}\r\n ORDER BY score DESC, e.id\r\n LIMIT ${limit}`;\r\n return { sql, params };\r\n }\r\n\r\n const inner = Math.min(Math.max(limit * 4, 20), 100);\r\n const sql = `\r\n WITH v AS (\r\n SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> $1::vector) AS r\r\n FROM ${tbl}\r\n WHERE true${tag}\r\n ORDER BY embedding <=> $1::vector\r\n LIMIT ${inner}\r\n ),\r\n k AS (\r\n SELECT id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(tsv, q.query) DESC) AS r\r\n FROM ${tbl} CROSS JOIN (SELECT websearch_to_tsquery('english', $2) AS query) q\r\n WHERE tsv @@ q.query${tag}\r\n ORDER BY ts_rank_cd(tsv, q.query) DESC\r\n LIMIT ${inner}\r\n ),\r\n s AS (\r\n SELECT COALESCE(v.id, k.id) AS id,\r\n COALESCE(1.0 / (${RRF_K} + v.r), 0) + COALESCE(1.0 / (${RRF_K} + k.r), 0) AS score,\r\n (v.r IS NOT NULL) AS vhit,\r\n (k.r IS NOT NULL) AS khit\r\n FROM v FULL JOIN k ON k.id = v.id\r\n )\r\n SELECT e.id, e.source, e.title, e.chunk_index, e.metadata, s.score,\r\n CASE WHEN s.khit\r\n THEN ts_headline('english', e.content, websearch_to_tsquery('english', $2), '${HEADLINE_OPTS}')\r\n ELSE left(regexp_replace(e.content, '\\\\s+', ' ', 'g'), 320)\r\n END AS snippet\r\n FROM s\r\n JOIN ${tbl} e ON e.id = s.id\r\n ORDER BY s.score DESC, e.id\r\n LIMIT ${limit}`;\r\n return { sql, params };\r\n}\r\n\r\nasync function queryEmbedding(ks: KnowledgeSource, query: string, cache: Map<string, number[]>): Promise<number[]> {\r\n const key = `${normalizeUrl(ks.ollama_url)}::${ks.embedding_model}::${query}`;\r\n const cached = cache.get(key);\r\n if (cached) return cached;\r\n const embs = await embed(ks.ollama_url, ks.embedding_model, [query], 30_000);\r\n const emb = embs[0];\r\n if (!emb) {\r\n throw new Error(`Ollama returned an empty embedding for query on ks '${ks.name}'`);\r\n }\r\n cache.set(key, emb);\r\n return emb;\r\n}\r\n\r\nexport interface SearchArgs {\r\n query: string;\r\n mode?: SearchMode;\r\n limit?: number;\r\n tags?: string[];\r\n ksFilter?: string[];\r\n}\r\n\r\nexport async function search(ksList: KnowledgeSource[], args: SearchArgs): Promise<SearchHit[]> {\r\n const mode = args.mode ?? \"hybrid\";\r\n const limit = Math.min(Math.max(args.limit ?? 8, 1), 50);\r\n let targets = ksList;\r\n if (args.ksFilter && args.ksFilter.length > 0) {\r\n const wanted = new Set(args.ksFilter);\r\n targets = ksList.filter((k) => wanted.has(k.name));\r\n }\r\n if (targets.length === 0) return [];\r\n\r\n const cache = new Map<string, number[]>();\r\n const all: SearchHit[] = [];\r\n for (const ks of targets) {\r\n const tbl = entryTableName(ks);\r\n const { sql, params } = buildSearchSql(tbl, mode, limit, args.tags);\r\n const resolved: unknown[] = [];\r\n for (const p of params) {\r\n if (p === \"__VECTOR__\") {\r\n const emb = await queryEmbedding(ks, args.query, cache);\r\n if (emb.length !== ks.embedding_dim) {\r\n throw new Error(\r\n `Embedding dimension mismatch for ks '${ks.name}': expected ${ks.embedding_dim}, got ${emb.length}`,\r\n );\r\n }\r\n resolved.push(toSql(emb));\r\n } else if (p === \"__QUERY__\") {\r\n resolved.push(args.query);\r\n } else {\r\n resolved.push(p);\r\n }\r\n }\r\n const res = await getPool().query(sql, resolved);\r\n for (const row of res.rows) {\r\n all.push({\r\n ks: ks.name,\r\n id: Number(row.id),\r\n source: String(row.source),\r\n title: String(row.title ?? \"\"),\r\n chunk_index: Number(row.chunk_index ?? 0),\r\n score: Number(row.score ?? 0),\r\n snippet: String(row.snippet ?? \"\"),\r\n metadata: (row.metadata ?? {}) as Record<string, unknown>,\r\n });\r\n }\r\n }\r\n all.sort((a, b) => b.score - a.score || a.ks.localeCompare(b.ks));\r\n return all.slice(0, limit);\r\n}\r\n\r\nexport async function getEntry(ks: KnowledgeSource, id: number): Promise<EntryRow | undefined> {\r\n const tbl = entryTableName(ks);\r\n const res = await getPool().query(\r\n `SELECT id, source, title, chunk_index, content, content_hash, metadata, created_at\r\n FROM ${tbl} WHERE id = $1`,\r\n [id],\r\n );\r\n return res.rows[0] as EntryRow | undefined;\r\n}\r\n\r\nexport interface EntryListItem {\r\n id: number;\r\n source: string;\r\n title: string;\r\n chunk_index: number;\r\n chars: number;\r\n created_at: Date;\r\n}\r\n\r\nexport async function listEntries(\r\n ks: KnowledgeSource,\r\n opts: { source?: string; limit?: number; offset?: number } = {},\r\n): Promise<EntryListItem[]> {\r\n const tbl = entryTableName(ks);\r\n const limit = Math.min(Math.max(opts.limit ?? 20, 1), 200);\r\n const offset = Math.max(opts.offset ?? 0, 0);\r\n const params: unknown[] = [];\r\n let where = \"\";\r\n if (opts.source) {\r\n params.push(opts.source);\r\n where = `WHERE source = $${params.length}`;\r\n }\r\n params.push(limit, offset);\r\n const res = await getPool().query(\r\n `SELECT id, source, title, chunk_index, length(content) AS chars, created_at\r\n FROM ${tbl} ${where}\r\n ORDER BY source, chunk_index, id\r\n LIMIT $${params.length - 1} OFFSET $${params.length}`,\r\n params,\r\n );\r\n return res.rows.map((r) => ({\r\n id: Number(r.id),\r\n source: String(r.source),\r\n title: String(r.title ?? \"\"),\r\n chunk_index: Number(r.chunk_index),\r\n chars: Number(r.chars),\r\n created_at: r.created_at as Date,\r\n }));\r\n}\r\n\r\nexport interface SourceInfo {\r\n ks: string;\r\n source: string;\r\n title: string;\r\n chunks: number;\r\n chars: number;\r\n}\r\n\r\nexport async function listSources(ksList: KnowledgeSource[]): Promise<SourceInfo[]> {\r\n const out: SourceInfo[] = [];\r\n for (const ks of ksList) {\r\n const tbl = entryTableName(ks);\r\n const res = await getPool().query(\r\n `SELECT source, max(title) AS title, count(*)::int AS chunks, sum(length(content))::bigint AS chars\r\n FROM ${tbl}\r\n GROUP BY source\r\n ORDER BY source`,\r\n );\r\n for (const r of res.rows) {\r\n out.push({\r\n ks: ks.name,\r\n source: String(r.source),\r\n title: String(r.title ?? \"\"),\r\n chunks: Number(r.chunks),\r\n chars: Number(r.chars),\r\n });\r\n }\r\n }\r\n return out;\r\n}\r\n","import { normalizeUrl } from \"./config.js\";\r\n\r\nexport class OllamaError extends Error {\r\n constructor(\r\n message: string,\r\n public readonly status?: number,\r\n ) {\r\n super(message);\r\n this.name = \"OllamaError\";\r\n }\r\n}\r\n\r\nexport async function embed(\r\n baseUrl: string,\r\n model: string,\r\n inputs: string[],\r\n timeoutMs = 120_000,\r\n): Promise<number[][]> {\r\n const url = `${normalizeUrl(baseUrl)}/api/embed`;\r\n let res: Response;\r\n try {\r\n res = await fetch(url, {\r\n method: \"POST\",\r\n headers: { \"content-type\": \"application/json\" },\r\n body: JSON.stringify({ model, input: inputs }),\r\n signal: AbortSignal.timeout(timeoutMs),\r\n });\r\n } catch (err) {\r\n throw new OllamaError(\r\n `Cannot reach Ollama at ${normalizeUrl(baseUrl)} (${err instanceof Error ? err.message : err})`,\r\n );\r\n }\r\n if (!res.ok) {\r\n const body = await res.text().catch(() => \"\");\r\n throw new OllamaError(`Ollama returned ${res.status} for ${url}: ${body.slice(0, 300)}`, res.status);\r\n }\r\n const data = (await res.json()) as { embeddings?: number[][]; error?: string };\r\n if (data.error) {\r\n throw new OllamaError(`Ollama error: ${data.error}`);\r\n }\r\n if (!Array.isArray(data.embeddings) || data.embeddings.length !== inputs.length) {\r\n throw new OllamaError(`Unexpected response shape from ${url}`);\r\n }\r\n return data.embeddings;\r\n}\r\n\r\nexport async function embedBatch(\r\n baseUrl: string,\r\n model: string,\r\n inputs: string[],\r\n batchSize = 32,\r\n onProgress?: (done: number, total: number) => void,\r\n): Promise<number[][]> {\r\n const out: number[][] = [];\r\n let done = 0;\r\n for (let i = 0; i < inputs.length; i += batchSize) {\r\n const batch = inputs.slice(i, i + batchSize);\r\n const embs = await embed(baseUrl, model, batch);\r\n out.push(...embs);\r\n done += batch.length;\r\n onProgress?.(done, inputs.length);\r\n }\r\n return out;\r\n}\r\n\r\nexport async function probeModel(\r\n baseUrl: string,\r\n model: string,\r\n): Promise<{ dim: number }> {\r\n const embs = await embed(baseUrl, model, [\"gresmcp dimension probe\"], 30_000);\r\n const first = embs[0];\r\n if (!first || first.length === 0) {\r\n throw new OllamaError(`Ollama returned an empty embedding for model ${model}`);\r\n }\r\n return { dim: first.length };\r\n}\r\n","export const VERSION = \"1.1.0\";\r\n"],"mappings":"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","names":["toSql","sql","toSql"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gresmcp/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "From raw docs to refined answers — Gresy fires your documents into solid Postgres knowledge, entirely local, your MCP.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"model-context-protocol",
|
|
8
|
+
"rag",
|
|
9
|
+
"knowledge-base",
|
|
10
|
+
"semantic-search",
|
|
11
|
+
"hybrid-search",
|
|
12
|
+
"vector-search",
|
|
13
|
+
"full-text-search",
|
|
14
|
+
"embeddings",
|
|
15
|
+
"pgvector",
|
|
16
|
+
"postgres",
|
|
17
|
+
"postgresql",
|
|
18
|
+
"ollama",
|
|
19
|
+
"ai-tools",
|
|
20
|
+
"cli"
|
|
21
|
+
],
|
|
5
22
|
"license": "MIT",
|
|
6
23
|
"type": "module",
|
|
7
24
|
"repository": {
|
|
@@ -34,7 +51,9 @@
|
|
|
34
51
|
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
35
52
|
},
|
|
36
53
|
"dependencies": {
|
|
54
|
+
"@mozilla/readability": "^0.5.0",
|
|
37
55
|
"commander": "^14.0.0",
|
|
56
|
+
"crawlee": "^3.18.1",
|
|
38
57
|
"fastmcp": "^4.20.3",
|
|
39
58
|
"jsdom": "^26.1.0",
|
|
40
59
|
"pg": "^8.16.0",
|
|
@@ -43,6 +62,18 @@
|
|
|
43
62
|
"turndown-plugin-gfm": "^1.0.2",
|
|
44
63
|
"zod": "^3.25.76"
|
|
45
64
|
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"playwright": ">=1.0.0",
|
|
67
|
+
"puppeteer": ">=20.0.0"
|
|
68
|
+
},
|
|
69
|
+
"peerDependenciesMeta": {
|
|
70
|
+
"playwright": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"puppeteer": {
|
|
74
|
+
"optional": true
|
|
75
|
+
}
|
|
76
|
+
},
|
|
46
77
|
"devDependencies": {
|
|
47
78
|
"@types/jsdom": "^21.1.7",
|
|
48
79
|
"@types/node": "^24.0.0",
|