@matrajs/mcp 1.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/index.cjs ADDED
@@ -0,0 +1,240 @@
1
+ 'use strict';
2
+
3
+ // src/index.ts
4
+ var PARSE_ERROR = -32700;
5
+ var INVALID_REQUEST = -32600;
6
+ var METHOD_NOT_FOUND = -32601;
7
+ var INVALID_PARAMS = -32602;
8
+ var RpcError = class extends Error {
9
+ constructor(code, message) {
10
+ super(message);
11
+ this.code = code;
12
+ }
13
+ code;
14
+ };
15
+ var PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
16
+ var LATEST = PROTOCOL_VERSIONS[0];
17
+ var tokens = (text) => text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((word) => word.length > 1);
18
+ function searchDocs(docs, query, limit = 5) {
19
+ const terms = tokens(query);
20
+ if (!terms.length) return [];
21
+ const hits = [];
22
+ for (const doc of docs) {
23
+ const title = doc.title.toLowerCase();
24
+ const body = doc.text.toLowerCase();
25
+ let score = 0;
26
+ let missing = false;
27
+ for (const term of terms) {
28
+ const inTitle = title.includes(term) ? 3 : 0;
29
+ let inBody = 0;
30
+ let at = body.indexOf(term);
31
+ while (at !== -1 && inBody < 20) {
32
+ inBody++;
33
+ at = body.indexOf(term, at + term.length);
34
+ }
35
+ if (!inTitle && !inBody) {
36
+ missing = true;
37
+ break;
38
+ }
39
+ score += inTitle + inBody;
40
+ }
41
+ if (missing) continue;
42
+ hits.push({ doc, score, snippet: snippetFor(doc.text, terms[0]) });
43
+ }
44
+ hits.sort((a, b) => b.score - a.score || a.doc.title.localeCompare(b.doc.title));
45
+ return hits.slice(0, Math.max(1, Math.min(limit, 20)));
46
+ }
47
+ function snippetFor(text, term) {
48
+ const lower = text.toLowerCase();
49
+ const at = lower.indexOf(term);
50
+ if (at === -1) return text.slice(0, 200).replace(/\s+/g, " ").trim();
51
+ const start = Math.max(0, at - 120);
52
+ const end = Math.min(text.length, at + 200);
53
+ return `${start > 0 ? "\u2026" : ""}${text.slice(start, end).replace(/\s+/g, " ").trim()}${end < text.length ? "\u2026" : ""}`;
54
+ }
55
+ var URI_PREFIX = "matra://docs/";
56
+ var TOOLS = [
57
+ {
58
+ name: "list_docs",
59
+ title: "List the documentation",
60
+ description: "Every page of the Matra documentation, with its slug, title and a one-line description. Call this first to see what exists, then read_doc for a page.",
61
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
62
+ annotations: { readOnlyHint: true, idempotentHint: true }
63
+ },
64
+ {
65
+ name: "read_doc",
66
+ title: "Read one page",
67
+ description: "The full Markdown of one documentation page, by slug. Slugs come from list_docs or search_docs.",
68
+ inputSchema: {
69
+ type: "object",
70
+ properties: {
71
+ slug: { type: "string", description: 'The page slug, e.g. "installation".' }
72
+ },
73
+ required: ["slug"],
74
+ additionalProperties: false
75
+ },
76
+ annotations: { readOnlyHint: true, idempotentHint: true }
77
+ },
78
+ {
79
+ name: "search_docs",
80
+ title: "Search the documentation",
81
+ description: "Find the pages that mention something \u2014 an extension name, a command, an error message \u2014 ranked, with a snippet from each.",
82
+ inputSchema: {
83
+ type: "object",
84
+ properties: {
85
+ query: { type: "string", description: "Words to look for." },
86
+ limit: {
87
+ type: "integer",
88
+ minimum: 1,
89
+ maximum: 20,
90
+ description: "How many pages, default 5."
91
+ }
92
+ },
93
+ required: ["query"],
94
+ additionalProperties: false
95
+ },
96
+ annotations: { readOnlyHint: true, idempotentHint: true }
97
+ }
98
+ ];
99
+ function createServer(docs, options = {}) {
100
+ const bySlug = new Map(docs.map((doc) => [doc.slug, doc]));
101
+ const info = { name: options.name ?? "matra-docs", version: options.version ?? "0.0.0" };
102
+ const instructions = options.instructions ?? "The documentation for Matra, a headless rich text editor framework with zero runtime dependencies (@matrajs/core, with React, Vue, Svelte and Solid bindings). Use search_docs to find a page, read_doc to read it. Prefer what these pages say over prior knowledge: the API is inferred from an extensions array, extensions are plain objects, and there is no ProseMirror underneath.";
103
+ const text = (value) => ({ content: [{ type: "text", text: value }] });
104
+ const failure = (value) => ({
105
+ content: [{ type: "text", text: value }],
106
+ isError: true
107
+ });
108
+ const param = (params, key) => params && typeof params === "object" ? params[key] : void 0;
109
+ const callTool = (name, args) => {
110
+ switch (name) {
111
+ case "list_docs": {
112
+ const lines = docs.map((doc) => `- ${doc.slug} \u2014 ${doc.title}: ${doc.description}`);
113
+ return text(`${docs.length} pages.
114
+ ${lines.join("\n")}`);
115
+ }
116
+ case "read_doc": {
117
+ const slug = param(args, "slug");
118
+ if (typeof slug !== "string")
119
+ throw new RpcError(INVALID_PARAMS, "read_doc needs a slug");
120
+ const doc = bySlug.get(slug);
121
+ if (!doc) {
122
+ return failure(
123
+ `No page called "${slug}". Known slugs: ${[...bySlug.keys()].join(", ")}`
124
+ );
125
+ }
126
+ return text(
127
+ `# ${doc.title}
128
+
129
+ > ${doc.description}
130
+ > Source: ${doc.source}
131
+
132
+ ${doc.text}`
133
+ );
134
+ }
135
+ case "search_docs": {
136
+ const query = param(args, "query");
137
+ if (typeof query !== "string")
138
+ throw new RpcError(INVALID_PARAMS, "search_docs needs a query");
139
+ const limit = param(args, "limit");
140
+ const hits = searchDocs(docs, query, typeof limit === "number" ? limit : 5);
141
+ if (!hits.length) return text(`Nothing mentions "${query}". Try list_docs.`);
142
+ return text(
143
+ hits.map((hit) => `## ${hit.doc.title} (slug: ${hit.doc.slug})
144
+ ${hit.snippet}`).join("\n\n")
145
+ );
146
+ }
147
+ default:
148
+ throw new RpcError(INVALID_PARAMS, `Unknown tool "${String(name)}"`);
149
+ }
150
+ };
151
+ const dispatch = (method, params) => {
152
+ switch (method) {
153
+ case "initialize": {
154
+ const asked = param(params, "protocolVersion");
155
+ const protocolVersion = typeof asked === "string" && PROTOCOL_VERSIONS.includes(asked) ? asked : LATEST;
156
+ return {
157
+ protocolVersion,
158
+ capabilities: { tools: { listChanged: false }, resources: { listChanged: false } },
159
+ serverInfo: { ...info, title: "Matra docs" },
160
+ instructions
161
+ };
162
+ }
163
+ case "ping":
164
+ return {};
165
+ case "tools/list":
166
+ return { tools: TOOLS };
167
+ case "tools/call":
168
+ return callTool(param(params, "name"), param(params, "arguments"));
169
+ case "resources/list":
170
+ return {
171
+ resources: docs.map((doc) => ({
172
+ uri: `${URI_PREFIX}${doc.slug}`,
173
+ name: doc.slug,
174
+ title: doc.title,
175
+ description: doc.description,
176
+ mimeType: "text/markdown"
177
+ }))
178
+ };
179
+ case "resources/templates/list":
180
+ return { resourceTemplates: [] };
181
+ case "resources/read": {
182
+ const uri = param(params, "uri");
183
+ if (typeof uri !== "string" || !uri.startsWith(URI_PREFIX)) {
184
+ throw new RpcError(INVALID_PARAMS, `Not a matra:// documentation URI: ${String(uri)}`);
185
+ }
186
+ const doc = bySlug.get(uri.slice(URI_PREFIX.length));
187
+ if (!doc) throw new RpcError(INVALID_PARAMS, `No page at ${uri}`);
188
+ return { contents: [{ uri, mimeType: "text/markdown", text: doc.text }] };
189
+ }
190
+ case "prompts/list":
191
+ return { prompts: [] };
192
+ case "logging/setLevel":
193
+ return {};
194
+ default:
195
+ throw new RpcError(METHOD_NOT_FOUND, `Method not found: ${method}`);
196
+ }
197
+ };
198
+ const handle = (message) => {
199
+ const request = message;
200
+ const id = request && typeof request === "object" && "id" in request ? request.id ?? null : null;
201
+ if (!request || typeof request !== "object" || request.jsonrpc !== "2.0" || typeof request.method !== "string") {
202
+ return {
203
+ jsonrpc: "2.0",
204
+ id,
205
+ error: { code: INVALID_REQUEST, message: "Not a JSON-RPC 2.0 request" }
206
+ };
207
+ }
208
+ const isNotification = !("id" in request) || request.id === void 0;
209
+ if (request.method.startsWith("notifications/")) return null;
210
+ try {
211
+ const result = dispatch(request.method, request.params);
212
+ return isNotification ? null : { jsonrpc: "2.0", id, result };
213
+ } catch (error) {
214
+ if (isNotification) return null;
215
+ const code = error instanceof RpcError ? error.code : -32603;
216
+ const message2 = error instanceof Error ? error.message : String(error);
217
+ return { jsonrpc: "2.0", id, error: { code, message: message2 } };
218
+ }
219
+ };
220
+ const handleRaw = (json) => {
221
+ let parsed;
222
+ try {
223
+ parsed = JSON.parse(json);
224
+ } catch {
225
+ return { jsonrpc: "2.0", id: null, error: { code: PARSE_ERROR, message: "Parse error" } };
226
+ }
227
+ if (Array.isArray(parsed)) {
228
+ const replies = parsed.map(handle).filter((reply) => reply !== null);
229
+ return replies.length ? replies : null;
230
+ }
231
+ return handle(parsed);
232
+ };
233
+ return { handle, handleRaw, docs };
234
+ }
235
+
236
+ exports.PROTOCOL_VERSIONS = PROTOCOL_VERSIONS;
237
+ exports.createServer = createServer;
238
+ exports.searchDocs = searchDocs;
239
+ //# sourceMappingURL=index.cjs.map
240
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["message"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * The Matra documentation, served over the Model Context Protocol.\n *\n * MCP is JSON-RPC 2.0 with a handshake and a small vocabulary — tools,\n * resources, prompts — that Claude, Cursor, Codex and the rest all speak. This\n * module is the protocol half: it takes one message and returns one reply,\n * and knows nothing about where either came from. The transports — stdio and\n * HTTP — are in `cli.ts`, and a test drives this without either.\n *\n * Written against the spec directly, with no SDK, because every other package\n * here has zero runtime dependencies and the one that exists to be installed\n * with `npx` should not be the exception.\n */\n\n/** One page of documentation. */\nexport interface Doc {\n /** URL-safe, unique: `installation`, `engine`, `changelog`. */\n slug: string\n title: string\n description: string\n /** Where it came from — a path in the repository, or a page on the site. */\n source: string\n /** Markdown. */\n text: string\n}\n\nexport interface ServerOptions {\n name?: string\n version?: string\n /** What a client is told about this server when it connects. */\n instructions?: string\n}\n\n// --- JSON-RPC ---------------------------------------------------------------\n\nexport type JsonRpcId = string | number | null\n\nexport interface JsonRpcRequest {\n jsonrpc: '2.0'\n id?: JsonRpcId\n method: string\n params?: Record<string, unknown>\n}\n\nexport interface JsonRpcResponse {\n jsonrpc: '2.0'\n id: JsonRpcId\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\nconst PARSE_ERROR = -32700\nconst INVALID_REQUEST = -32600\nconst METHOD_NOT_FOUND = -32601\nconst INVALID_PARAMS = -32602\n\nclass RpcError extends Error {\n constructor(\n readonly code: number,\n message: string,\n ) {\n super(message)\n }\n}\n\n/** Protocol revisions this server can speak, newest first. */\nexport const PROTOCOL_VERSIONS = ['2025-06-18', '2025-03-26', '2024-11-05'] as const\n\nconst LATEST = PROTOCOL_VERSIONS[0]\n\n// --- searching ----------------------------------------------------------------\n\nconst tokens = (text: string): string[] =>\n text\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter((word) => word.length > 1)\n\ninterface Hit {\n doc: Doc\n score: number\n snippet: string\n}\n\n/**\n * Rank pages for a query.\n *\n * Term frequency, with the title worth more than the body and every term\n * required to appear somewhere. Small enough to read, good enough for twenty\n * pages of documentation, and it needs no index built in advance.\n */\nexport function searchDocs(docs: readonly Doc[], query: string, limit = 5): Hit[] {\n const terms = tokens(query)\n if (!terms.length) return []\n const hits: Hit[] = []\n for (const doc of docs) {\n const title = doc.title.toLowerCase()\n const body = doc.text.toLowerCase()\n let score = 0\n let missing = false\n for (const term of terms) {\n const inTitle = title.includes(term) ? 3 : 0\n let inBody = 0\n let at = body.indexOf(term)\n while (at !== -1 && inBody < 20) {\n inBody++\n at = body.indexOf(term, at + term.length)\n }\n if (!inTitle && !inBody) {\n missing = true\n break\n }\n score += inTitle + inBody\n }\n if (missing) continue\n hits.push({ doc, score, snippet: snippetFor(doc.text, terms[0] as string) })\n }\n hits.sort((a, b) => b.score - a.score || a.doc.title.localeCompare(b.doc.title))\n return hits.slice(0, Math.max(1, Math.min(limit, 20)))\n}\n\n/** A line or two around the first place a term appears. */\nfunction snippetFor(text: string, term: string): string {\n const lower = text.toLowerCase()\n const at = lower.indexOf(term)\n if (at === -1) return text.slice(0, 200).replace(/\\s+/g, ' ').trim()\n const start = Math.max(0, at - 120)\n const end = Math.min(text.length, at + 200)\n return `${start > 0 ? '…' : ''}${text.slice(start, end).replace(/\\s+/g, ' ').trim()}${end < text.length ? '…' : ''}`\n}\n\n// --- the server --------------------------------------------------------------\n\nconst URI_PREFIX = 'matra://docs/'\n\nconst TOOLS = [\n {\n name: 'list_docs',\n title: 'List the documentation',\n description:\n 'Every page of the Matra documentation, with its slug, title and a one-line description. Call this first to see what exists, then read_doc for a page.',\n inputSchema: { type: 'object', properties: {}, additionalProperties: false },\n annotations: { readOnlyHint: true, idempotentHint: true },\n },\n {\n name: 'read_doc',\n title: 'Read one page',\n description:\n 'The full Markdown of one documentation page, by slug. Slugs come from list_docs or search_docs.',\n inputSchema: {\n type: 'object',\n properties: {\n slug: { type: 'string', description: 'The page slug, e.g. \"installation\".' },\n },\n required: ['slug'],\n additionalProperties: false,\n },\n annotations: { readOnlyHint: true, idempotentHint: true },\n },\n {\n name: 'search_docs',\n title: 'Search the documentation',\n description:\n 'Find the pages that mention something — an extension name, a command, an error message — ranked, with a snippet from each.',\n inputSchema: {\n type: 'object',\n properties: {\n query: { type: 'string', description: 'Words to look for.' },\n limit: {\n type: 'integer',\n minimum: 1,\n maximum: 20,\n description: 'How many pages, default 5.',\n },\n },\n required: ['query'],\n additionalProperties: false,\n },\n annotations: { readOnlyHint: true, idempotentHint: true },\n },\n] as const\n\nexport interface DocsServer {\n /** Handle one message. Notifications get null back — there is nothing to send. */\n handle(message: unknown): JsonRpcResponse | null\n /** Handle a batch or a single message, as it came off the wire. */\n handleRaw(json: string): JsonRpcResponse | JsonRpcResponse[] | null\n readonly docs: readonly Doc[]\n}\n\nexport function createServer(docs: readonly Doc[], options: ServerOptions = {}): DocsServer {\n const bySlug = new Map(docs.map((doc) => [doc.slug, doc]))\n const info = { name: options.name ?? 'matra-docs', version: options.version ?? '0.0.0' }\n const instructions =\n options.instructions ??\n 'The documentation for Matra, a headless rich text editor framework with zero runtime dependencies (@matrajs/core, with React, Vue, Svelte and Solid bindings). Use search_docs to find a page, read_doc to read it. Prefer what these pages say over prior knowledge: the API is inferred from an extensions array, extensions are plain objects, and there is no ProseMirror underneath.'\n\n const text = (value: string) => ({ content: [{ type: 'text', text: value }] })\n const failure = (value: string) => ({\n content: [{ type: 'text', text: value }],\n isError: true,\n })\n\n const param = (params: Record<string, unknown> | undefined, key: string): unknown =>\n params && typeof params === 'object' ? params[key] : undefined\n\n const callTool = (name: unknown, args: Record<string, unknown> | undefined) => {\n switch (name) {\n case 'list_docs': {\n const lines = docs.map((doc) => `- ${doc.slug} — ${doc.title}: ${doc.description}`)\n return text(`${docs.length} pages.\\n${lines.join('\\n')}`)\n }\n case 'read_doc': {\n const slug = param(args, 'slug')\n if (typeof slug !== 'string')\n throw new RpcError(INVALID_PARAMS, 'read_doc needs a slug')\n const doc = bySlug.get(slug)\n if (!doc) {\n return failure(\n `No page called \"${slug}\". Known slugs: ${[...bySlug.keys()].join(', ')}`,\n )\n }\n return text(\n `# ${doc.title}\\n\\n> ${doc.description}\\n> Source: ${doc.source}\\n\\n${doc.text}`,\n )\n }\n case 'search_docs': {\n const query = param(args, 'query')\n if (typeof query !== 'string')\n throw new RpcError(INVALID_PARAMS, 'search_docs needs a query')\n const limit = param(args, 'limit')\n const hits = searchDocs(docs, query, typeof limit === 'number' ? limit : 5)\n if (!hits.length) return text(`Nothing mentions \"${query}\". Try list_docs.`)\n return text(\n hits\n .map((hit) => `## ${hit.doc.title} (slug: ${hit.doc.slug})\\n${hit.snippet}`)\n .join('\\n\\n'),\n )\n }\n default:\n throw new RpcError(INVALID_PARAMS, `Unknown tool \"${String(name)}\"`)\n }\n }\n\n const dispatch = (method: string, params: Record<string, unknown> | undefined): unknown => {\n switch (method) {\n case 'initialize': {\n const asked = param(params, 'protocolVersion')\n const protocolVersion =\n typeof asked === 'string' && (PROTOCOL_VERSIONS as readonly string[]).includes(asked)\n ? asked\n : LATEST\n return {\n protocolVersion,\n capabilities: { tools: { listChanged: false }, resources: { listChanged: false } },\n serverInfo: { ...info, title: 'Matra docs' },\n instructions,\n }\n }\n case 'ping':\n return {}\n case 'tools/list':\n return { tools: TOOLS }\n case 'tools/call':\n return callTool(param(params, 'name'), param(params, 'arguments') as never)\n case 'resources/list':\n return {\n resources: docs.map((doc) => ({\n uri: `${URI_PREFIX}${doc.slug}`,\n name: doc.slug,\n title: doc.title,\n description: doc.description,\n mimeType: 'text/markdown',\n })),\n }\n case 'resources/templates/list':\n return { resourceTemplates: [] }\n case 'resources/read': {\n const uri = param(params, 'uri')\n if (typeof uri !== 'string' || !uri.startsWith(URI_PREFIX)) {\n throw new RpcError(INVALID_PARAMS, `Not a matra:// documentation URI: ${String(uri)}`)\n }\n const doc = bySlug.get(uri.slice(URI_PREFIX.length))\n if (!doc) throw new RpcError(INVALID_PARAMS, `No page at ${uri}`)\n return { contents: [{ uri, mimeType: 'text/markdown', text: doc.text }] }\n }\n case 'prompts/list':\n return { prompts: [] }\n case 'logging/setLevel':\n return {}\n default:\n throw new RpcError(METHOD_NOT_FOUND, `Method not found: ${method}`)\n }\n }\n\n const handle = (message: unknown): JsonRpcResponse | null => {\n const request = message as Partial<JsonRpcRequest> | null\n const id: JsonRpcId =\n request && typeof request === 'object' && 'id' in request ? (request.id ?? null) : null\n if (\n !request ||\n typeof request !== 'object' ||\n request.jsonrpc !== '2.0' ||\n typeof request.method !== 'string'\n ) {\n return {\n jsonrpc: '2.0',\n id,\n error: { code: INVALID_REQUEST, message: 'Not a JSON-RPC 2.0 request' },\n }\n }\n // A notification carries no id and gets no reply.\n const isNotification = !('id' in request) || request.id === undefined\n if (request.method.startsWith('notifications/')) return null\n try {\n const result = dispatch(request.method, request.params)\n return isNotification ? null : { jsonrpc: '2.0', id, result }\n } catch (error) {\n if (isNotification) return null\n const code = error instanceof RpcError ? error.code : -32603\n const message = error instanceof Error ? error.message : String(error)\n return { jsonrpc: '2.0', id, error: { code, message } }\n }\n }\n\n const handleRaw = (json: string): JsonRpcResponse | JsonRpcResponse[] | null => {\n let parsed: unknown\n try {\n parsed = JSON.parse(json)\n } catch {\n return { jsonrpc: '2.0', id: null, error: { code: PARSE_ERROR, message: 'Parse error' } }\n }\n if (Array.isArray(parsed)) {\n const replies = parsed\n .map(handle)\n .filter((reply): reply is JsonRpcResponse => reply !== null)\n return replies.length ? replies : null\n }\n return handle(parsed)\n }\n\n return { handle, handleRaw, docs }\n}\n"]}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The Matra documentation, served over the Model Context Protocol.
3
+ *
4
+ * MCP is JSON-RPC 2.0 with a handshake and a small vocabulary — tools,
5
+ * resources, prompts — that Claude, Cursor, Codex and the rest all speak. This
6
+ * module is the protocol half: it takes one message and returns one reply,
7
+ * and knows nothing about where either came from. The transports — stdio and
8
+ * HTTP — are in `cli.ts`, and a test drives this without either.
9
+ *
10
+ * Written against the spec directly, with no SDK, because every other package
11
+ * here has zero runtime dependencies and the one that exists to be installed
12
+ * with `npx` should not be the exception.
13
+ */
14
+ /** One page of documentation. */
15
+ interface Doc {
16
+ /** URL-safe, unique: `installation`, `engine`, `changelog`. */
17
+ slug: string;
18
+ title: string;
19
+ description: string;
20
+ /** Where it came from — a path in the repository, or a page on the site. */
21
+ source: string;
22
+ /** Markdown. */
23
+ text: string;
24
+ }
25
+ interface ServerOptions {
26
+ name?: string;
27
+ version?: string;
28
+ /** What a client is told about this server when it connects. */
29
+ instructions?: string;
30
+ }
31
+ type JsonRpcId = string | number | null;
32
+ interface JsonRpcRequest {
33
+ jsonrpc: '2.0';
34
+ id?: JsonRpcId;
35
+ method: string;
36
+ params?: Record<string, unknown>;
37
+ }
38
+ interface JsonRpcResponse {
39
+ jsonrpc: '2.0';
40
+ id: JsonRpcId;
41
+ result?: unknown;
42
+ error?: {
43
+ code: number;
44
+ message: string;
45
+ data?: unknown;
46
+ };
47
+ }
48
+ /** Protocol revisions this server can speak, newest first. */
49
+ declare const PROTOCOL_VERSIONS: readonly ["2025-06-18", "2025-03-26", "2024-11-05"];
50
+ interface Hit {
51
+ doc: Doc;
52
+ score: number;
53
+ snippet: string;
54
+ }
55
+ /**
56
+ * Rank pages for a query.
57
+ *
58
+ * Term frequency, with the title worth more than the body and every term
59
+ * required to appear somewhere. Small enough to read, good enough for twenty
60
+ * pages of documentation, and it needs no index built in advance.
61
+ */
62
+ declare function searchDocs(docs: readonly Doc[], query: string, limit?: number): Hit[];
63
+ interface DocsServer {
64
+ /** Handle one message. Notifications get null back — there is nothing to send. */
65
+ handle(message: unknown): JsonRpcResponse | null;
66
+ /** Handle a batch or a single message, as it came off the wire. */
67
+ handleRaw(json: string): JsonRpcResponse | JsonRpcResponse[] | null;
68
+ readonly docs: readonly Doc[];
69
+ }
70
+ declare function createServer(docs: readonly Doc[], options?: ServerOptions): DocsServer;
71
+
72
+ export { type Doc, type DocsServer, type JsonRpcId, type JsonRpcRequest, type JsonRpcResponse, PROTOCOL_VERSIONS, type ServerOptions, createServer, searchDocs };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The Matra documentation, served over the Model Context Protocol.
3
+ *
4
+ * MCP is JSON-RPC 2.0 with a handshake and a small vocabulary — tools,
5
+ * resources, prompts — that Claude, Cursor, Codex and the rest all speak. This
6
+ * module is the protocol half: it takes one message and returns one reply,
7
+ * and knows nothing about where either came from. The transports — stdio and
8
+ * HTTP — are in `cli.ts`, and a test drives this without either.
9
+ *
10
+ * Written against the spec directly, with no SDK, because every other package
11
+ * here has zero runtime dependencies and the one that exists to be installed
12
+ * with `npx` should not be the exception.
13
+ */
14
+ /** One page of documentation. */
15
+ interface Doc {
16
+ /** URL-safe, unique: `installation`, `engine`, `changelog`. */
17
+ slug: string;
18
+ title: string;
19
+ description: string;
20
+ /** Where it came from — a path in the repository, or a page on the site. */
21
+ source: string;
22
+ /** Markdown. */
23
+ text: string;
24
+ }
25
+ interface ServerOptions {
26
+ name?: string;
27
+ version?: string;
28
+ /** What a client is told about this server when it connects. */
29
+ instructions?: string;
30
+ }
31
+ type JsonRpcId = string | number | null;
32
+ interface JsonRpcRequest {
33
+ jsonrpc: '2.0';
34
+ id?: JsonRpcId;
35
+ method: string;
36
+ params?: Record<string, unknown>;
37
+ }
38
+ interface JsonRpcResponse {
39
+ jsonrpc: '2.0';
40
+ id: JsonRpcId;
41
+ result?: unknown;
42
+ error?: {
43
+ code: number;
44
+ message: string;
45
+ data?: unknown;
46
+ };
47
+ }
48
+ /** Protocol revisions this server can speak, newest first. */
49
+ declare const PROTOCOL_VERSIONS: readonly ["2025-06-18", "2025-03-26", "2024-11-05"];
50
+ interface Hit {
51
+ doc: Doc;
52
+ score: number;
53
+ snippet: string;
54
+ }
55
+ /**
56
+ * Rank pages for a query.
57
+ *
58
+ * Term frequency, with the title worth more than the body and every term
59
+ * required to appear somewhere. Small enough to read, good enough for twenty
60
+ * pages of documentation, and it needs no index built in advance.
61
+ */
62
+ declare function searchDocs(docs: readonly Doc[], query: string, limit?: number): Hit[];
63
+ interface DocsServer {
64
+ /** Handle one message. Notifications get null back — there is nothing to send. */
65
+ handle(message: unknown): JsonRpcResponse | null;
66
+ /** Handle a batch or a single message, as it came off the wire. */
67
+ handleRaw(json: string): JsonRpcResponse | JsonRpcResponse[] | null;
68
+ readonly docs: readonly Doc[];
69
+ }
70
+ declare function createServer(docs: readonly Doc[], options?: ServerOptions): DocsServer;
71
+
72
+ export { type Doc, type DocsServer, type JsonRpcId, type JsonRpcRequest, type JsonRpcResponse, PROTOCOL_VERSIONS, type ServerOptions, createServer, searchDocs };
package/dist/index.js ADDED
@@ -0,0 +1,236 @@
1
+ // src/index.ts
2
+ var PARSE_ERROR = -32700;
3
+ var INVALID_REQUEST = -32600;
4
+ var METHOD_NOT_FOUND = -32601;
5
+ var INVALID_PARAMS = -32602;
6
+ var RpcError = class extends Error {
7
+ constructor(code, message) {
8
+ super(message);
9
+ this.code = code;
10
+ }
11
+ code;
12
+ };
13
+ var PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
14
+ var LATEST = PROTOCOL_VERSIONS[0];
15
+ var tokens = (text) => text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((word) => word.length > 1);
16
+ function searchDocs(docs, query, limit = 5) {
17
+ const terms = tokens(query);
18
+ if (!terms.length) return [];
19
+ const hits = [];
20
+ for (const doc of docs) {
21
+ const title = doc.title.toLowerCase();
22
+ const body = doc.text.toLowerCase();
23
+ let score = 0;
24
+ let missing = false;
25
+ for (const term of terms) {
26
+ const inTitle = title.includes(term) ? 3 : 0;
27
+ let inBody = 0;
28
+ let at = body.indexOf(term);
29
+ while (at !== -1 && inBody < 20) {
30
+ inBody++;
31
+ at = body.indexOf(term, at + term.length);
32
+ }
33
+ if (!inTitle && !inBody) {
34
+ missing = true;
35
+ break;
36
+ }
37
+ score += inTitle + inBody;
38
+ }
39
+ if (missing) continue;
40
+ hits.push({ doc, score, snippet: snippetFor(doc.text, terms[0]) });
41
+ }
42
+ hits.sort((a, b) => b.score - a.score || a.doc.title.localeCompare(b.doc.title));
43
+ return hits.slice(0, Math.max(1, Math.min(limit, 20)));
44
+ }
45
+ function snippetFor(text, term) {
46
+ const lower = text.toLowerCase();
47
+ const at = lower.indexOf(term);
48
+ if (at === -1) return text.slice(0, 200).replace(/\s+/g, " ").trim();
49
+ const start = Math.max(0, at - 120);
50
+ const end = Math.min(text.length, at + 200);
51
+ return `${start > 0 ? "\u2026" : ""}${text.slice(start, end).replace(/\s+/g, " ").trim()}${end < text.length ? "\u2026" : ""}`;
52
+ }
53
+ var URI_PREFIX = "matra://docs/";
54
+ var TOOLS = [
55
+ {
56
+ name: "list_docs",
57
+ title: "List the documentation",
58
+ description: "Every page of the Matra documentation, with its slug, title and a one-line description. Call this first to see what exists, then read_doc for a page.",
59
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
60
+ annotations: { readOnlyHint: true, idempotentHint: true }
61
+ },
62
+ {
63
+ name: "read_doc",
64
+ title: "Read one page",
65
+ description: "The full Markdown of one documentation page, by slug. Slugs come from list_docs or search_docs.",
66
+ inputSchema: {
67
+ type: "object",
68
+ properties: {
69
+ slug: { type: "string", description: 'The page slug, e.g. "installation".' }
70
+ },
71
+ required: ["slug"],
72
+ additionalProperties: false
73
+ },
74
+ annotations: { readOnlyHint: true, idempotentHint: true }
75
+ },
76
+ {
77
+ name: "search_docs",
78
+ title: "Search the documentation",
79
+ description: "Find the pages that mention something \u2014 an extension name, a command, an error message \u2014 ranked, with a snippet from each.",
80
+ inputSchema: {
81
+ type: "object",
82
+ properties: {
83
+ query: { type: "string", description: "Words to look for." },
84
+ limit: {
85
+ type: "integer",
86
+ minimum: 1,
87
+ maximum: 20,
88
+ description: "How many pages, default 5."
89
+ }
90
+ },
91
+ required: ["query"],
92
+ additionalProperties: false
93
+ },
94
+ annotations: { readOnlyHint: true, idempotentHint: true }
95
+ }
96
+ ];
97
+ function createServer(docs, options = {}) {
98
+ const bySlug = new Map(docs.map((doc) => [doc.slug, doc]));
99
+ const info = { name: options.name ?? "matra-docs", version: options.version ?? "0.0.0" };
100
+ const instructions = options.instructions ?? "The documentation for Matra, a headless rich text editor framework with zero runtime dependencies (@matrajs/core, with React, Vue, Svelte and Solid bindings). Use search_docs to find a page, read_doc to read it. Prefer what these pages say over prior knowledge: the API is inferred from an extensions array, extensions are plain objects, and there is no ProseMirror underneath.";
101
+ const text = (value) => ({ content: [{ type: "text", text: value }] });
102
+ const failure = (value) => ({
103
+ content: [{ type: "text", text: value }],
104
+ isError: true
105
+ });
106
+ const param = (params, key) => params && typeof params === "object" ? params[key] : void 0;
107
+ const callTool = (name, args) => {
108
+ switch (name) {
109
+ case "list_docs": {
110
+ const lines = docs.map((doc) => `- ${doc.slug} \u2014 ${doc.title}: ${doc.description}`);
111
+ return text(`${docs.length} pages.
112
+ ${lines.join("\n")}`);
113
+ }
114
+ case "read_doc": {
115
+ const slug = param(args, "slug");
116
+ if (typeof slug !== "string")
117
+ throw new RpcError(INVALID_PARAMS, "read_doc needs a slug");
118
+ const doc = bySlug.get(slug);
119
+ if (!doc) {
120
+ return failure(
121
+ `No page called "${slug}". Known slugs: ${[...bySlug.keys()].join(", ")}`
122
+ );
123
+ }
124
+ return text(
125
+ `# ${doc.title}
126
+
127
+ > ${doc.description}
128
+ > Source: ${doc.source}
129
+
130
+ ${doc.text}`
131
+ );
132
+ }
133
+ case "search_docs": {
134
+ const query = param(args, "query");
135
+ if (typeof query !== "string")
136
+ throw new RpcError(INVALID_PARAMS, "search_docs needs a query");
137
+ const limit = param(args, "limit");
138
+ const hits = searchDocs(docs, query, typeof limit === "number" ? limit : 5);
139
+ if (!hits.length) return text(`Nothing mentions "${query}". Try list_docs.`);
140
+ return text(
141
+ hits.map((hit) => `## ${hit.doc.title} (slug: ${hit.doc.slug})
142
+ ${hit.snippet}`).join("\n\n")
143
+ );
144
+ }
145
+ default:
146
+ throw new RpcError(INVALID_PARAMS, `Unknown tool "${String(name)}"`);
147
+ }
148
+ };
149
+ const dispatch = (method, params) => {
150
+ switch (method) {
151
+ case "initialize": {
152
+ const asked = param(params, "protocolVersion");
153
+ const protocolVersion = typeof asked === "string" && PROTOCOL_VERSIONS.includes(asked) ? asked : LATEST;
154
+ return {
155
+ protocolVersion,
156
+ capabilities: { tools: { listChanged: false }, resources: { listChanged: false } },
157
+ serverInfo: { ...info, title: "Matra docs" },
158
+ instructions
159
+ };
160
+ }
161
+ case "ping":
162
+ return {};
163
+ case "tools/list":
164
+ return { tools: TOOLS };
165
+ case "tools/call":
166
+ return callTool(param(params, "name"), param(params, "arguments"));
167
+ case "resources/list":
168
+ return {
169
+ resources: docs.map((doc) => ({
170
+ uri: `${URI_PREFIX}${doc.slug}`,
171
+ name: doc.slug,
172
+ title: doc.title,
173
+ description: doc.description,
174
+ mimeType: "text/markdown"
175
+ }))
176
+ };
177
+ case "resources/templates/list":
178
+ return { resourceTemplates: [] };
179
+ case "resources/read": {
180
+ const uri = param(params, "uri");
181
+ if (typeof uri !== "string" || !uri.startsWith(URI_PREFIX)) {
182
+ throw new RpcError(INVALID_PARAMS, `Not a matra:// documentation URI: ${String(uri)}`);
183
+ }
184
+ const doc = bySlug.get(uri.slice(URI_PREFIX.length));
185
+ if (!doc) throw new RpcError(INVALID_PARAMS, `No page at ${uri}`);
186
+ return { contents: [{ uri, mimeType: "text/markdown", text: doc.text }] };
187
+ }
188
+ case "prompts/list":
189
+ return { prompts: [] };
190
+ case "logging/setLevel":
191
+ return {};
192
+ default:
193
+ throw new RpcError(METHOD_NOT_FOUND, `Method not found: ${method}`);
194
+ }
195
+ };
196
+ const handle = (message) => {
197
+ const request = message;
198
+ const id = request && typeof request === "object" && "id" in request ? request.id ?? null : null;
199
+ if (!request || typeof request !== "object" || request.jsonrpc !== "2.0" || typeof request.method !== "string") {
200
+ return {
201
+ jsonrpc: "2.0",
202
+ id,
203
+ error: { code: INVALID_REQUEST, message: "Not a JSON-RPC 2.0 request" }
204
+ };
205
+ }
206
+ const isNotification = !("id" in request) || request.id === void 0;
207
+ if (request.method.startsWith("notifications/")) return null;
208
+ try {
209
+ const result = dispatch(request.method, request.params);
210
+ return isNotification ? null : { jsonrpc: "2.0", id, result };
211
+ } catch (error) {
212
+ if (isNotification) return null;
213
+ const code = error instanceof RpcError ? error.code : -32603;
214
+ const message2 = error instanceof Error ? error.message : String(error);
215
+ return { jsonrpc: "2.0", id, error: { code, message: message2 } };
216
+ }
217
+ };
218
+ const handleRaw = (json) => {
219
+ let parsed;
220
+ try {
221
+ parsed = JSON.parse(json);
222
+ } catch {
223
+ return { jsonrpc: "2.0", id: null, error: { code: PARSE_ERROR, message: "Parse error" } };
224
+ }
225
+ if (Array.isArray(parsed)) {
226
+ const replies = parsed.map(handle).filter((reply) => reply !== null);
227
+ return replies.length ? replies : null;
228
+ }
229
+ return handle(parsed);
230
+ };
231
+ return { handle, handleRaw, docs };
232
+ }
233
+
234
+ export { PROTOCOL_VERSIONS, createServer, searchDocs };
235
+ //# sourceMappingURL=index.js.map
236
+ //# sourceMappingURL=index.js.map