@tesseron/docs-mcp 0.2.0 → 2.0.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/dist/docs-index.json +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/search.ts","../src/server.ts","../src/snapshot.ts"],"names":["MiniSearch","McpServer","z","ResourceTemplate","dirname","fileURLToPath","resolve","existsSync","readFileSync"],"mappings":"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","file":"index.cjs","sourcesContent":["import MiniSearch from 'minisearch';\nimport type { DocEntry } from './content/types.js';\n\nexport type SearchHit = {\n slug: string;\n title: string;\n description: string;\n section: string;\n score: number;\n snippet: string;\n};\n\nconst SNIPPET_RADIUS = 120;\nconst SNIPPET_MAX = SNIPPET_RADIUS * 2;\n\nexport class DocsIndex {\n private readonly mini: MiniSearch<DocEntry>;\n private readonly bySlug: Map<string, DocEntry>;\n\n constructor(docs: DocEntry[]) {\n this.bySlug = new Map(docs.map((d) => [d.slug, d]));\n this.mini = new MiniSearch<DocEntry>({\n idField: 'slug',\n fields: ['title', 'description', 'bodyText'],\n storeFields: ['slug', 'title', 'description', 'section'],\n searchOptions: {\n boost: { title: 3, description: 2 },\n fuzzy: 0.15,\n prefix: true,\n },\n });\n this.mini.addAll(docs);\n }\n\n search(query: string, limit = 8): SearchHit[] {\n const raw = this.mini.search(query);\n const out: SearchHit[] = [];\n for (const r of raw.slice(0, Math.max(1, Math.min(limit, 20)))) {\n const slug = String(r['slug']);\n const entry = this.bySlug.get(slug);\n if (!entry) continue;\n out.push({\n slug,\n title: String(r['title']),\n description: String(r['description'] ?? ''),\n section: String(r['section'] ?? ''),\n score: typeof r['score'] === 'number' ? r['score'] : 0,\n snippet: makeSnippet(entry, query),\n });\n }\n return out;\n }\n}\n\n/**\n * Extract a ~240-char snippet centred on the best match for `query` within\n * `entry.bodyText`. Falls back to `description` when no body match is found.\n */\nexport function makeSnippet(entry: DocEntry, query: string): string {\n const text = entry.bodyText;\n if (!text) return entry.description;\n\n const terms = query\n .toLowerCase()\n .split(/\\s+/)\n .filter((t) => t.length > 1);\n if (terms.length === 0) return truncate(text, SNIPPET_MAX);\n\n const lower = text.toLowerCase();\n let bestIdx = -1;\n for (const t of terms) {\n const idx = lower.indexOf(t);\n if (idx !== -1 && (bestIdx === -1 || idx < bestIdx)) bestIdx = idx;\n }\n if (bestIdx === -1) return entry.description || truncate(text, SNIPPET_MAX);\n\n const start = Math.max(0, bestIdx - SNIPPET_RADIUS);\n const end = Math.min(text.length, bestIdx + SNIPPET_RADIUS);\n let slice = text.slice(start, end).replace(/\\s+/g, ' ').trim();\n if (start > 0) slice = `…${slice}`;\n if (end < text.length) slice = `${slice}…`;\n return slice;\n}\n\nfunction truncate(s: string, n: number): string {\n const single = s.replace(/\\s+/g, ' ').trim();\n return single.length <= n ? single : `${single.slice(0, n - 1)}…`;\n}\n","import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport type { DocEntry, Snapshot } from './content/types.js';\nimport { DocsIndex } from './search.js';\n\nconst RESOURCE_SCHEME = 'tesseron-docs';\n\nexport type ServerOptions = {\n /** Snapshot produced by `scripts/build-snapshot.ts`. */\n snapshot: Snapshot;\n /** Optional override of the reported server name (for test isolation). */\n name?: string;\n /** Optional override of the reported server version. */\n version?: string;\n};\n\n/**\n * Build an `McpServer` wired up with `list_docs`, `search_docs`, `read_doc`\n * tools and `tesseron-docs://<slug>` resources.\n */\nexport function createDocsMcpServer(options: ServerOptions): McpServer {\n const { snapshot } = options;\n const bySlug = new Map<string, DocEntry>(snapshot.docs.map((d) => [d.slug, d]));\n const index = new DocsIndex(snapshot.docs);\n\n const server = new McpServer({\n name: options.name ?? 'tesseron-docs',\n version: options.version ?? snapshot.version,\n });\n\n server.registerTool(\n 'list_docs',\n {\n description:\n 'List every Tesseron documentation page with title, slug, section, short description, and related slugs. Cheap, call before searching when you want the full catalogue.',\n inputSchema: {},\n },\n () => {\n const docs = snapshot.docs.map((d) => ({\n slug: d.slug,\n title: d.title,\n section: d.section,\n description: d.description,\n related: d.related,\n }));\n return {\n content: [{ type: 'text', text: JSON.stringify({ count: docs.length, docs }, null, 2) }],\n };\n },\n );\n\n server.registerTool(\n 'search_docs',\n {\n description:\n 'Full-text search across Tesseron docs (BM25, title- and description-weighted). Returns ranked hits with short snippets. Call `read_doc(slug)` on promising hits for full content.',\n inputSchema: {\n query: z\n .string()\n .min(1)\n .describe('Free-form query. Supports multiple terms; fuzzy + prefix matching are on.'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(20)\n .optional()\n .describe('Maximum hits to return. Default 8, hard cap 20.'),\n },\n },\n ({ query, limit }) => {\n const hits = index.search(query, limit ?? 8);\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({ query, count: hits.length, hits }, null, 2),\n },\n ],\n };\n },\n );\n\n server.registerTool(\n 'read_doc',\n {\n description:\n 'Return the full markdown body of a Tesseron docs page plus its structured frontmatter (title, description, section, related). Slug format: `<section>/<basename>` without extension (e.g. `protocol/handshake`).',\n inputSchema: {\n slug: z\n .string()\n .min(1)\n .describe('Page slug. Use `list_docs` or `search_docs` to discover valid slugs.'),\n },\n },\n ({ slug }) => {\n const entry = bySlug.get(slug);\n if (!entry) {\n return {\n isError: true,\n content: [\n {\n type: 'text',\n text: `No doc found for slug \"${slug}\". Call list_docs to see valid slugs.`,\n },\n ],\n };\n }\n const payload = {\n slug: entry.slug,\n title: entry.title,\n description: entry.description,\n section: entry.section,\n related: entry.related,\n body: entry.bodyRaw,\n };\n return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };\n },\n );\n\n server.registerResource(\n 'tesseron-doc',\n new ResourceTemplate(`${RESOURCE_SCHEME}://{+slug}`, {\n list: () => ({\n resources: snapshot.docs.map((d) => ({\n uri: `${RESOURCE_SCHEME}://${d.slug}`,\n name: d.title,\n description: d.description,\n mimeType: 'text/markdown',\n })),\n }),\n }),\n {\n title: 'Tesseron documentation page',\n description: 'Full markdown body of a single Tesseron docs page, keyed by slug.',\n },\n (uri, variables) => {\n const slug = Array.isArray(variables['slug'])\n ? variables['slug'][0]\n : (variables['slug'] as string | undefined);\n if (!slug) {\n throw new Error(`Missing slug in resource URI ${uri.href}`);\n }\n const entry = bySlug.get(slug);\n if (!entry) {\n throw new Error(`No doc found for slug \"${slug}\".`);\n }\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'text/markdown',\n text: entry.bodyRaw,\n },\n ],\n };\n },\n );\n\n return server;\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { DocEntry, Snapshot } from './content/types.js';\n\nconst SNAPSHOT_FILENAME = 'docs-index.json';\nconst ENV_SNAPSHOT_PATH = 'TESSERON_DOCS_SNAPSHOT';\n\nfunction thisModuleDir(): string {\n if (typeof __dirname !== 'undefined') return __dirname;\n return dirname(fileURLToPath(import.meta.url));\n}\n\n/**\n * Locate the bundled snapshot JSON. Lookup order:\n * 1. `TESSERON_DOCS_SNAPSHOT` environment variable, if set.\n * 2. Explicit `override` argument.\n * 3. Alongside the compiled bin (production / `npx` use).\n * 4. `../dist/docs-index.json` (when running via `tsx src/cli.ts` in dev).\n */\nexport function resolveSnapshotPath(override?: string): string {\n const envPath = process.env[ENV_SNAPSHOT_PATH];\n if (envPath) return envPath;\n if (override) return override;\n\n const here = thisModuleDir();\n const bundled = resolve(here, SNAPSHOT_FILENAME);\n if (existsSync(bundled)) return bundled;\n\n const devFallback = resolve(here, '..', 'dist', SNAPSHOT_FILENAME);\n return devFallback;\n}\n\nexport function loadSnapshot(override?: string): Snapshot {\n const path = resolveSnapshotPath(override);\n if (!existsSync(path)) {\n throw new Error(\n `[tesseron-docs-mcp] snapshot not found at ${path}. ` +\n 'Run \"pnpm --filter @tesseron/docs-mcp build\" to generate it, ' +\n `or set ${ENV_SNAPSHOT_PATH}=/abs/path/to/docs-index.json.`,\n );\n }\n const raw = readFileSync(path, 'utf8');\n const parsed = JSON.parse(raw) as Snapshot;\n if (!Array.isArray(parsed.docs)) {\n throw new Error(`[tesseron-docs-mcp] invalid snapshot at ${path}: missing \"docs\" array.`);\n }\n return parsed;\n}\n\n/** Index docs by slug for O(1) lookup in `read_doc`. */\nexport function indexBySlug(docs: DocEntry[]): Map<string, DocEntry> {\n const map = new Map<string, DocEntry>();\n for (const d of docs) map.set(d.slug, d);\n return map;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/search.ts","../src/server.ts","../src/snapshot.ts"],"names":["MiniSearch","McpServer","z","ResourceTemplate","dirname","fileURLToPath","resolve","existsSync","readFileSync"],"mappings":"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","file":"index.cjs","sourcesContent":["import MiniSearch from 'minisearch';\nimport type { DocEntry } from './content/types.js';\n\nexport type SearchHit = {\n slug: string;\n title: string;\n description: string;\n section: string;\n score: number;\n snippet: string;\n};\n\nconst SNIPPET_RADIUS = 120;\nconst SNIPPET_MAX = SNIPPET_RADIUS * 2;\n\nexport class DocsIndex {\n private readonly mini: MiniSearch<DocEntry>;\n private readonly bySlug: Map<string, DocEntry>;\n\n constructor(docs: DocEntry[]) {\n this.bySlug = new Map(docs.map((d) => [d.slug, d]));\n this.mini = new MiniSearch<DocEntry>({\n idField: 'slug',\n fields: ['title', 'description', 'bodyText'],\n storeFields: ['slug', 'title', 'description', 'section'],\n searchOptions: {\n boost: { title: 3, description: 2 },\n fuzzy: 0.15,\n prefix: true,\n },\n });\n this.mini.addAll(docs);\n }\n\n search(query: string, limit = 8): SearchHit[] {\n const raw = this.mini.search(query);\n const out: SearchHit[] = [];\n for (const r of raw.slice(0, Math.max(1, Math.min(limit, 20)))) {\n const slug = String(r['slug']);\n const entry = this.bySlug.get(slug);\n if (!entry) continue;\n out.push({\n slug,\n title: String(r['title']),\n description: String(r['description'] ?? ''),\n section: String(r['section'] ?? ''),\n score: typeof r['score'] === 'number' ? r['score'] : 0,\n snippet: makeSnippet(entry, query),\n });\n }\n return out;\n }\n}\n\n/**\n * Extract a ~240-char snippet centred on the best match for `query` within\n * `entry.bodyText`. Falls back to `description` when no body match is found.\n */\nexport function makeSnippet(entry: DocEntry, query: string): string {\n const text = entry.bodyText;\n if (!text) return entry.description;\n\n const terms = query\n .toLowerCase()\n .split(/\\s+/)\n .filter((t) => t.length > 1);\n if (terms.length === 0) return truncate(text, SNIPPET_MAX);\n\n const lower = text.toLowerCase();\n let bestIdx = -1;\n for (const t of terms) {\n const idx = lower.indexOf(t);\n if (idx !== -1 && (bestIdx === -1 || idx < bestIdx)) bestIdx = idx;\n }\n if (bestIdx === -1) return entry.description || truncate(text, SNIPPET_MAX);\n\n const start = Math.max(0, bestIdx - SNIPPET_RADIUS);\n const end = Math.min(text.length, bestIdx + SNIPPET_RADIUS);\n let slice = text.slice(start, end).replace(/\\s+/g, ' ').trim();\n if (start > 0) slice = `…${slice}`;\n if (end < text.length) slice = `${slice}…`;\n return slice;\n}\n\nfunction truncate(s: string, n: number): string {\n const single = s.replace(/\\s+/g, ' ').trim();\n return single.length <= n ? single : `${single.slice(0, n - 1)}…`;\n}\n","import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport type { DocEntry, Snapshot } from './content/types.js';\nimport { DocsIndex } from './search.js';\n\nconst RESOURCE_SCHEME = 'tesseron-docs';\n\nexport type ServerOptions = {\n /** Snapshot produced by `scripts/build-snapshot.ts`. */\n snapshot: Snapshot;\n /** Optional override of the reported server name (for test isolation). */\n name?: string;\n /** Optional override of the reported server version. */\n version?: string;\n};\n\n/**\n * Build an `McpServer` wired up with `list_docs`, `search_docs`, `read_doc`\n * tools and `tesseron-docs://<slug>` resources.\n */\nexport function createDocsMcpServer(options: ServerOptions): McpServer {\n const { snapshot } = options;\n const bySlug = new Map<string, DocEntry>(snapshot.docs.map((d) => [d.slug, d]));\n const index = new DocsIndex(snapshot.docs);\n\n const server = new McpServer({\n name: options.name ?? 'tesseron-docs',\n version: options.version ?? snapshot.version,\n });\n\n server.registerTool(\n 'list_docs',\n {\n description:\n 'List every Tesseron documentation page with title, slug, section, short description, and related slugs. Cheap, call before searching when you want the full catalogue.',\n inputSchema: {},\n },\n () => {\n const docs = snapshot.docs.map((d) => ({\n slug: d.slug,\n title: d.title,\n section: d.section,\n description: d.description,\n related: d.related,\n }));\n return {\n content: [{ type: 'text', text: JSON.stringify({ count: docs.length, docs }, null, 2) }],\n };\n },\n );\n\n server.registerTool(\n 'search_docs',\n {\n description:\n 'Full-text search across Tesseron docs (BM25, title- and description-weighted). Returns ranked hits with short snippets. Call `read_doc(slug)` on promising hits for full content.',\n inputSchema: {\n query: z\n .string()\n .min(1)\n .describe('Free-form query. Supports multiple terms; fuzzy + prefix matching are on.'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(20)\n .optional()\n .describe('Maximum hits to return. Default 8, hard cap 20.'),\n },\n },\n ({ query, limit }) => {\n const hits = index.search(query, limit ?? 8);\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({ query, count: hits.length, hits }, null, 2),\n },\n ],\n };\n },\n );\n\n server.registerTool(\n 'read_doc',\n {\n description:\n 'Return the full markdown body of a Tesseron docs page plus its structured frontmatter (title, description, section, related). Slug format: `<section>/<basename>` without extension (e.g. `protocol/handshake`).',\n inputSchema: {\n slug: z\n .string()\n .min(1)\n .describe('Page slug. Use `list_docs` or `search_docs` to discover valid slugs.'),\n },\n },\n ({ slug }) => {\n const entry = bySlug.get(slug);\n if (!entry) {\n return {\n isError: true,\n content: [\n {\n type: 'text',\n text: `No doc found for slug \"${slug}\". Call list_docs to see valid slugs.`,\n },\n ],\n };\n }\n const payload = {\n slug: entry.slug,\n title: entry.title,\n description: entry.description,\n section: entry.section,\n related: entry.related,\n body: entry.bodyRaw,\n };\n return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };\n },\n );\n\n server.registerResource(\n 'tesseron-doc',\n new ResourceTemplate(`${RESOURCE_SCHEME}://{+slug}`, {\n list: () => ({\n resources: snapshot.docs.map((d) => ({\n uri: `${RESOURCE_SCHEME}://${d.slug}`,\n name: d.title,\n description: d.description,\n mimeType: 'text/markdown',\n })),\n }),\n }),\n {\n title: 'Tesseron documentation page',\n description: 'Full markdown body of a single Tesseron docs page, keyed by slug.',\n },\n (uri, variables) => {\n const slug = Array.isArray(variables['slug'])\n ? variables['slug'][0]\n : (variables['slug'] as string | undefined);\n if (!slug) {\n throw new Error(`Missing slug in resource URI ${uri.href}`);\n }\n const entry = bySlug.get(slug);\n if (!entry) {\n throw new Error(`No doc found for slug \"${slug}\".`);\n }\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'text/markdown',\n text: entry.bodyRaw,\n },\n ],\n };\n },\n );\n\n return server;\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { DocEntry, Snapshot } from './content/types.js';\n\nconst SNAPSHOT_FILENAME = 'docs-index.json';\nconst ENV_SNAPSHOT_PATH = 'TESSERON_DOCS_SNAPSHOT';\n\nfunction thisModuleDir(): string {\n if (typeof __dirname !== 'undefined') return __dirname;\n return dirname(fileURLToPath(import.meta.url));\n}\n\n/**\n * Locate the bundled snapshot JSON. Lookup order:\n * 1. `TESSERON_DOCS_SNAPSHOT` environment variable, if set.\n * 2. Explicit `override` argument.\n * 3. Alongside the compiled bin (production / `npx` use).\n * 4. `../dist/docs-index.json` (when running via `tsx src/cli.ts` in dev).\n */\nexport function resolveSnapshotPath(override?: string): string {\n const envPath = process.env[ENV_SNAPSHOT_PATH];\n if (envPath) return envPath;\n if (override) return override;\n\n const here = thisModuleDir();\n const bundled = resolve(here, SNAPSHOT_FILENAME);\n if (existsSync(bundled)) return bundled;\n\n const devFallback = resolve(here, '..', 'dist', SNAPSHOT_FILENAME);\n return devFallback;\n}\n\nexport function loadSnapshot(override?: string): Snapshot {\n const path = resolveSnapshotPath(override);\n if (!existsSync(path)) {\n throw new Error(\n `[tesseron-docs-mcp] snapshot not found at ${path}. Run \"pnpm --filter @tesseron/docs-mcp build\" to generate it, or set ${ENV_SNAPSHOT_PATH}=/abs/path/to/docs-index.json.`,\n );\n }\n const raw = readFileSync(path, 'utf8');\n const parsed = JSON.parse(raw) as Snapshot;\n if (!Array.isArray(parsed.docs)) {\n throw new Error(`[tesseron-docs-mcp] invalid snapshot at ${path}: missing \"docs\" array.`);\n }\n return parsed;\n}\n\n/** Index docs by slug for O(1) lookup in `read_doc`. */\nexport function indexBySlug(docs: DocEntry[]): Map<string, DocEntry> {\n const map = new Map<string, DocEntry>();\n for (const d of docs) map.set(d.slug, d);\n return map;\n}\n"]}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/search.ts","../src/server.ts","../src/snapshot.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import MiniSearch from 'minisearch';\nimport type { DocEntry } from './content/types.js';\n\nexport type SearchHit = {\n slug: string;\n title: string;\n description: string;\n section: string;\n score: number;\n snippet: string;\n};\n\nconst SNIPPET_RADIUS = 120;\nconst SNIPPET_MAX = SNIPPET_RADIUS * 2;\n\nexport class DocsIndex {\n private readonly mini: MiniSearch<DocEntry>;\n private readonly bySlug: Map<string, DocEntry>;\n\n constructor(docs: DocEntry[]) {\n this.bySlug = new Map(docs.map((d) => [d.slug, d]));\n this.mini = new MiniSearch<DocEntry>({\n idField: 'slug',\n fields: ['title', 'description', 'bodyText'],\n storeFields: ['slug', 'title', 'description', 'section'],\n searchOptions: {\n boost: { title: 3, description: 2 },\n fuzzy: 0.15,\n prefix: true,\n },\n });\n this.mini.addAll(docs);\n }\n\n search(query: string, limit = 8): SearchHit[] {\n const raw = this.mini.search(query);\n const out: SearchHit[] = [];\n for (const r of raw.slice(0, Math.max(1, Math.min(limit, 20)))) {\n const slug = String(r['slug']);\n const entry = this.bySlug.get(slug);\n if (!entry) continue;\n out.push({\n slug,\n title: String(r['title']),\n description: String(r['description'] ?? ''),\n section: String(r['section'] ?? ''),\n score: typeof r['score'] === 'number' ? r['score'] : 0,\n snippet: makeSnippet(entry, query),\n });\n }\n return out;\n }\n}\n\n/**\n * Extract a ~240-char snippet centred on the best match for `query` within\n * `entry.bodyText`. Falls back to `description` when no body match is found.\n */\nexport function makeSnippet(entry: DocEntry, query: string): string {\n const text = entry.bodyText;\n if (!text) return entry.description;\n\n const terms = query\n .toLowerCase()\n .split(/\\s+/)\n .filter((t) => t.length > 1);\n if (terms.length === 0) return truncate(text, SNIPPET_MAX);\n\n const lower = text.toLowerCase();\n let bestIdx = -1;\n for (const t of terms) {\n const idx = lower.indexOf(t);\n if (idx !== -1 && (bestIdx === -1 || idx < bestIdx)) bestIdx = idx;\n }\n if (bestIdx === -1) return entry.description || truncate(text, SNIPPET_MAX);\n\n const start = Math.max(0, bestIdx - SNIPPET_RADIUS);\n const end = Math.min(text.length, bestIdx + SNIPPET_RADIUS);\n let slice = text.slice(start, end).replace(/\\s+/g, ' ').trim();\n if (start > 0) slice = `…${slice}`;\n if (end < text.length) slice = `${slice}…`;\n return slice;\n}\n\nfunction truncate(s: string, n: number): string {\n const single = s.replace(/\\s+/g, ' ').trim();\n return single.length <= n ? single : `${single.slice(0, n - 1)}…`;\n}\n","import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport type { DocEntry, Snapshot } from './content/types.js';\nimport { DocsIndex } from './search.js';\n\nconst RESOURCE_SCHEME = 'tesseron-docs';\n\nexport type ServerOptions = {\n /** Snapshot produced by `scripts/build-snapshot.ts`. */\n snapshot: Snapshot;\n /** Optional override of the reported server name (for test isolation). */\n name?: string;\n /** Optional override of the reported server version. */\n version?: string;\n};\n\n/**\n * Build an `McpServer` wired up with `list_docs`, `search_docs`, `read_doc`\n * tools and `tesseron-docs://<slug>` resources.\n */\nexport function createDocsMcpServer(options: ServerOptions): McpServer {\n const { snapshot } = options;\n const bySlug = new Map<string, DocEntry>(snapshot.docs.map((d) => [d.slug, d]));\n const index = new DocsIndex(snapshot.docs);\n\n const server = new McpServer({\n name: options.name ?? 'tesseron-docs',\n version: options.version ?? snapshot.version,\n });\n\n server.registerTool(\n 'list_docs',\n {\n description:\n 'List every Tesseron documentation page with title, slug, section, short description, and related slugs. Cheap, call before searching when you want the full catalogue.',\n inputSchema: {},\n },\n () => {\n const docs = snapshot.docs.map((d) => ({\n slug: d.slug,\n title: d.title,\n section: d.section,\n description: d.description,\n related: d.related,\n }));\n return {\n content: [{ type: 'text', text: JSON.stringify({ count: docs.length, docs }, null, 2) }],\n };\n },\n );\n\n server.registerTool(\n 'search_docs',\n {\n description:\n 'Full-text search across Tesseron docs (BM25, title- and description-weighted). Returns ranked hits with short snippets. Call `read_doc(slug)` on promising hits for full content.',\n inputSchema: {\n query: z\n .string()\n .min(1)\n .describe('Free-form query. Supports multiple terms; fuzzy + prefix matching are on.'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(20)\n .optional()\n .describe('Maximum hits to return. Default 8, hard cap 20.'),\n },\n },\n ({ query, limit }) => {\n const hits = index.search(query, limit ?? 8);\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({ query, count: hits.length, hits }, null, 2),\n },\n ],\n };\n },\n );\n\n server.registerTool(\n 'read_doc',\n {\n description:\n 'Return the full markdown body of a Tesseron docs page plus its structured frontmatter (title, description, section, related). Slug format: `<section>/<basename>` without extension (e.g. `protocol/handshake`).',\n inputSchema: {\n slug: z\n .string()\n .min(1)\n .describe('Page slug. Use `list_docs` or `search_docs` to discover valid slugs.'),\n },\n },\n ({ slug }) => {\n const entry = bySlug.get(slug);\n if (!entry) {\n return {\n isError: true,\n content: [\n {\n type: 'text',\n text: `No doc found for slug \"${slug}\". Call list_docs to see valid slugs.`,\n },\n ],\n };\n }\n const payload = {\n slug: entry.slug,\n title: entry.title,\n description: entry.description,\n section: entry.section,\n related: entry.related,\n body: entry.bodyRaw,\n };\n return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };\n },\n );\n\n server.registerResource(\n 'tesseron-doc',\n new ResourceTemplate(`${RESOURCE_SCHEME}://{+slug}`, {\n list: () => ({\n resources: snapshot.docs.map((d) => ({\n uri: `${RESOURCE_SCHEME}://${d.slug}`,\n name: d.title,\n description: d.description,\n mimeType: 'text/markdown',\n })),\n }),\n }),\n {\n title: 'Tesseron documentation page',\n description: 'Full markdown body of a single Tesseron docs page, keyed by slug.',\n },\n (uri, variables) => {\n const slug = Array.isArray(variables['slug'])\n ? variables['slug'][0]\n : (variables['slug'] as string | undefined);\n if (!slug) {\n throw new Error(`Missing slug in resource URI ${uri.href}`);\n }\n const entry = bySlug.get(slug);\n if (!entry) {\n throw new Error(`No doc found for slug \"${slug}\".`);\n }\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'text/markdown',\n text: entry.bodyRaw,\n },\n ],\n };\n },\n );\n\n return server;\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { DocEntry, Snapshot } from './content/types.js';\n\nconst SNAPSHOT_FILENAME = 'docs-index.json';\nconst ENV_SNAPSHOT_PATH = 'TESSERON_DOCS_SNAPSHOT';\n\nfunction thisModuleDir(): string {\n if (typeof __dirname !== 'undefined') return __dirname;\n return dirname(fileURLToPath(import.meta.url));\n}\n\n/**\n * Locate the bundled snapshot JSON. Lookup order:\n * 1. `TESSERON_DOCS_SNAPSHOT` environment variable, if set.\n * 2. Explicit `override` argument.\n * 3. Alongside the compiled bin (production / `npx` use).\n * 4. `../dist/docs-index.json` (when running via `tsx src/cli.ts` in dev).\n */\nexport function resolveSnapshotPath(override?: string): string {\n const envPath = process.env[ENV_SNAPSHOT_PATH];\n if (envPath) return envPath;\n if (override) return override;\n\n const here = thisModuleDir();\n const bundled = resolve(here, SNAPSHOT_FILENAME);\n if (existsSync(bundled)) return bundled;\n\n const devFallback = resolve(here, '..', 'dist', SNAPSHOT_FILENAME);\n return devFallback;\n}\n\nexport function loadSnapshot(override?: string): Snapshot {\n const path = resolveSnapshotPath(override);\n if (!existsSync(path)) {\n throw new Error(\n `[tesseron-docs-mcp] snapshot not found at ${path}. ` +\n 'Run \"pnpm --filter @tesseron/docs-mcp build\" to generate it, ' +\n `or set ${ENV_SNAPSHOT_PATH}=/abs/path/to/docs-index.json.`,\n );\n }\n const raw = readFileSync(path, 'utf8');\n const parsed = JSON.parse(raw) as Snapshot;\n if (!Array.isArray(parsed.docs)) {\n throw new Error(`[tesseron-docs-mcp] invalid snapshot at ${path}: missing \"docs\" array.`);\n }\n return parsed;\n}\n\n/** Index docs by slug for O(1) lookup in `read_doc`. */\nexport function indexBySlug(docs: DocEntry[]): Map<string, DocEntry> {\n const map = new Map<string, DocEntry>();\n for (const d of docs) map.set(d.slug, d);\n return map;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/search.ts","../src/server.ts","../src/snapshot.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import MiniSearch from 'minisearch';\nimport type { DocEntry } from './content/types.js';\n\nexport type SearchHit = {\n slug: string;\n title: string;\n description: string;\n section: string;\n score: number;\n snippet: string;\n};\n\nconst SNIPPET_RADIUS = 120;\nconst SNIPPET_MAX = SNIPPET_RADIUS * 2;\n\nexport class DocsIndex {\n private readonly mini: MiniSearch<DocEntry>;\n private readonly bySlug: Map<string, DocEntry>;\n\n constructor(docs: DocEntry[]) {\n this.bySlug = new Map(docs.map((d) => [d.slug, d]));\n this.mini = new MiniSearch<DocEntry>({\n idField: 'slug',\n fields: ['title', 'description', 'bodyText'],\n storeFields: ['slug', 'title', 'description', 'section'],\n searchOptions: {\n boost: { title: 3, description: 2 },\n fuzzy: 0.15,\n prefix: true,\n },\n });\n this.mini.addAll(docs);\n }\n\n search(query: string, limit = 8): SearchHit[] {\n const raw = this.mini.search(query);\n const out: SearchHit[] = [];\n for (const r of raw.slice(0, Math.max(1, Math.min(limit, 20)))) {\n const slug = String(r['slug']);\n const entry = this.bySlug.get(slug);\n if (!entry) continue;\n out.push({\n slug,\n title: String(r['title']),\n description: String(r['description'] ?? ''),\n section: String(r['section'] ?? ''),\n score: typeof r['score'] === 'number' ? r['score'] : 0,\n snippet: makeSnippet(entry, query),\n });\n }\n return out;\n }\n}\n\n/**\n * Extract a ~240-char snippet centred on the best match for `query` within\n * `entry.bodyText`. Falls back to `description` when no body match is found.\n */\nexport function makeSnippet(entry: DocEntry, query: string): string {\n const text = entry.bodyText;\n if (!text) return entry.description;\n\n const terms = query\n .toLowerCase()\n .split(/\\s+/)\n .filter((t) => t.length > 1);\n if (terms.length === 0) return truncate(text, SNIPPET_MAX);\n\n const lower = text.toLowerCase();\n let bestIdx = -1;\n for (const t of terms) {\n const idx = lower.indexOf(t);\n if (idx !== -1 && (bestIdx === -1 || idx < bestIdx)) bestIdx = idx;\n }\n if (bestIdx === -1) return entry.description || truncate(text, SNIPPET_MAX);\n\n const start = Math.max(0, bestIdx - SNIPPET_RADIUS);\n const end = Math.min(text.length, bestIdx + SNIPPET_RADIUS);\n let slice = text.slice(start, end).replace(/\\s+/g, ' ').trim();\n if (start > 0) slice = `…${slice}`;\n if (end < text.length) slice = `${slice}…`;\n return slice;\n}\n\nfunction truncate(s: string, n: number): string {\n const single = s.replace(/\\s+/g, ' ').trim();\n return single.length <= n ? single : `${single.slice(0, n - 1)}…`;\n}\n","import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport type { DocEntry, Snapshot } from './content/types.js';\nimport { DocsIndex } from './search.js';\n\nconst RESOURCE_SCHEME = 'tesseron-docs';\n\nexport type ServerOptions = {\n /** Snapshot produced by `scripts/build-snapshot.ts`. */\n snapshot: Snapshot;\n /** Optional override of the reported server name (for test isolation). */\n name?: string;\n /** Optional override of the reported server version. */\n version?: string;\n};\n\n/**\n * Build an `McpServer` wired up with `list_docs`, `search_docs`, `read_doc`\n * tools and `tesseron-docs://<slug>` resources.\n */\nexport function createDocsMcpServer(options: ServerOptions): McpServer {\n const { snapshot } = options;\n const bySlug = new Map<string, DocEntry>(snapshot.docs.map((d) => [d.slug, d]));\n const index = new DocsIndex(snapshot.docs);\n\n const server = new McpServer({\n name: options.name ?? 'tesseron-docs',\n version: options.version ?? snapshot.version,\n });\n\n server.registerTool(\n 'list_docs',\n {\n description:\n 'List every Tesseron documentation page with title, slug, section, short description, and related slugs. Cheap, call before searching when you want the full catalogue.',\n inputSchema: {},\n },\n () => {\n const docs = snapshot.docs.map((d) => ({\n slug: d.slug,\n title: d.title,\n section: d.section,\n description: d.description,\n related: d.related,\n }));\n return {\n content: [{ type: 'text', text: JSON.stringify({ count: docs.length, docs }, null, 2) }],\n };\n },\n );\n\n server.registerTool(\n 'search_docs',\n {\n description:\n 'Full-text search across Tesseron docs (BM25, title- and description-weighted). Returns ranked hits with short snippets. Call `read_doc(slug)` on promising hits for full content.',\n inputSchema: {\n query: z\n .string()\n .min(1)\n .describe('Free-form query. Supports multiple terms; fuzzy + prefix matching are on.'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(20)\n .optional()\n .describe('Maximum hits to return. Default 8, hard cap 20.'),\n },\n },\n ({ query, limit }) => {\n const hits = index.search(query, limit ?? 8);\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({ query, count: hits.length, hits }, null, 2),\n },\n ],\n };\n },\n );\n\n server.registerTool(\n 'read_doc',\n {\n description:\n 'Return the full markdown body of a Tesseron docs page plus its structured frontmatter (title, description, section, related). Slug format: `<section>/<basename>` without extension (e.g. `protocol/handshake`).',\n inputSchema: {\n slug: z\n .string()\n .min(1)\n .describe('Page slug. Use `list_docs` or `search_docs` to discover valid slugs.'),\n },\n },\n ({ slug }) => {\n const entry = bySlug.get(slug);\n if (!entry) {\n return {\n isError: true,\n content: [\n {\n type: 'text',\n text: `No doc found for slug \"${slug}\". Call list_docs to see valid slugs.`,\n },\n ],\n };\n }\n const payload = {\n slug: entry.slug,\n title: entry.title,\n description: entry.description,\n section: entry.section,\n related: entry.related,\n body: entry.bodyRaw,\n };\n return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };\n },\n );\n\n server.registerResource(\n 'tesseron-doc',\n new ResourceTemplate(`${RESOURCE_SCHEME}://{+slug}`, {\n list: () => ({\n resources: snapshot.docs.map((d) => ({\n uri: `${RESOURCE_SCHEME}://${d.slug}`,\n name: d.title,\n description: d.description,\n mimeType: 'text/markdown',\n })),\n }),\n }),\n {\n title: 'Tesseron documentation page',\n description: 'Full markdown body of a single Tesseron docs page, keyed by slug.',\n },\n (uri, variables) => {\n const slug = Array.isArray(variables['slug'])\n ? variables['slug'][0]\n : (variables['slug'] as string | undefined);\n if (!slug) {\n throw new Error(`Missing slug in resource URI ${uri.href}`);\n }\n const entry = bySlug.get(slug);\n if (!entry) {\n throw new Error(`No doc found for slug \"${slug}\".`);\n }\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'text/markdown',\n text: entry.bodyRaw,\n },\n ],\n };\n },\n );\n\n return server;\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { DocEntry, Snapshot } from './content/types.js';\n\nconst SNAPSHOT_FILENAME = 'docs-index.json';\nconst ENV_SNAPSHOT_PATH = 'TESSERON_DOCS_SNAPSHOT';\n\nfunction thisModuleDir(): string {\n if (typeof __dirname !== 'undefined') return __dirname;\n return dirname(fileURLToPath(import.meta.url));\n}\n\n/**\n * Locate the bundled snapshot JSON. Lookup order:\n * 1. `TESSERON_DOCS_SNAPSHOT` environment variable, if set.\n * 2. Explicit `override` argument.\n * 3. Alongside the compiled bin (production / `npx` use).\n * 4. `../dist/docs-index.json` (when running via `tsx src/cli.ts` in dev).\n */\nexport function resolveSnapshotPath(override?: string): string {\n const envPath = process.env[ENV_SNAPSHOT_PATH];\n if (envPath) return envPath;\n if (override) return override;\n\n const here = thisModuleDir();\n const bundled = resolve(here, SNAPSHOT_FILENAME);\n if (existsSync(bundled)) return bundled;\n\n const devFallback = resolve(here, '..', 'dist', SNAPSHOT_FILENAME);\n return devFallback;\n}\n\nexport function loadSnapshot(override?: string): Snapshot {\n const path = resolveSnapshotPath(override);\n if (!existsSync(path)) {\n throw new Error(\n `[tesseron-docs-mcp] snapshot not found at ${path}. Run \"pnpm --filter @tesseron/docs-mcp build\" to generate it, or set ${ENV_SNAPSHOT_PATH}=/abs/path/to/docs-index.json.`,\n );\n }\n const raw = readFileSync(path, 'utf8');\n const parsed = JSON.parse(raw) as Snapshot;\n if (!Array.isArray(parsed.docs)) {\n throw new Error(`[tesseron-docs-mcp] invalid snapshot at ${path}: missing \"docs\" array.`);\n }\n return parsed;\n}\n\n/** Index docs by slug for O(1) lookup in `read_doc`. */\nexport function indexBySlug(docs: DocEntry[]): Map<string, DocEntry> {\n const map = new Map<string, DocEntry>();\n for (const d of docs) map.set(d.slug, d);\n return map;\n}\n"]}
|
package/package.json
CHANGED