@arabold/docs-mcp-server 0.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/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DocumentManagementService,
4
+ FindVersionTool,
5
+ ListLibrariesTool,
6
+ PipelineManager,
7
+ ScrapeTool,
8
+ SearchTool
9
+ } from "./chunk-7CJZVHC7.js";
10
+
11
+ // src/cli.ts
12
+ import "dotenv/config";
13
+ import { Command } from "commander";
14
+ var formatOutput = (data) => JSON.stringify(data, null, 2);
15
+ async function main() {
16
+ let docService;
17
+ let pipelineManager;
18
+ try {
19
+ docService = new DocumentManagementService();
20
+ await docService.initialize();
21
+ pipelineManager = new PipelineManager(docService);
22
+ await pipelineManager.start();
23
+ const tools = {
24
+ listLibraries: new ListLibrariesTool(docService),
25
+ findVersion: new FindVersionTool(docService),
26
+ scrape: new ScrapeTool(docService, pipelineManager),
27
+ // Pass manager
28
+ search: new SearchTool(docService)
29
+ };
30
+ const program = new Command();
31
+ process.on("SIGINT", async () => {
32
+ if (pipelineManager) await pipelineManager.stop();
33
+ if (docService) await docService.shutdown();
34
+ process.exit(0);
35
+ });
36
+ program.name("docs-mcp").description("CLI for managing documentation vector store").version("1.0.0");
37
+ program.command("scrape <library> <url>").description("Scrape and index documentation from a URL").option("-v, --version <string>", "Version of the library (optional)").option("-p, --max-pages <number>", "Maximum pages to scrape", "100").option("-d, --max-depth <number>", "Maximum navigation depth", "3").option("-c, --max-concurrency <number>", "Maximum concurrent page requests", "3").option("--ignore-errors", "Ignore errors during scraping", true).action(async (library, url, options) => {
38
+ const result = await tools.scrape.execute({
39
+ url,
40
+ library,
41
+ version: options.version,
42
+ // Get version from options
43
+ options: {
44
+ maxPages: Number.parseInt(options.maxPages),
45
+ maxDepth: Number.parseInt(options.maxDepth),
46
+ maxConcurrency: Number.parseInt(options.maxConcurrency),
47
+ ignoreErrors: options.ignoreErrors
48
+ }
49
+ // CLI always waits for completion (default behavior)
50
+ });
51
+ if ("pagesScraped" in result) {
52
+ console.log(`\u2705 Successfully scraped ${result.pagesScraped} pages`);
53
+ } else {
54
+ console.log(`\u{1F680} Scraping job started with ID: ${result.jobId}`);
55
+ }
56
+ });
57
+ program.command("search <library> <query>").description(
58
+ "Search documents in a library. Version matching examples:\n - search react --version 18.0.0 'hooks' -> matches docs for React 18.0.0 or earlier versions\n - search react --version 18.0.0 'hooks' --exact-match -> only matches React 18.0.0\n - search typescript --version 5.x 'types' -> matches any TypeScript 5.x.x version\n - search typescript --version 5.2.x 'types' -> matches any TypeScript 5.2.x version"
59
+ ).option(
60
+ "-v, --version <string>",
61
+ // Add optional version flag
62
+ "Version of the library (optional, supports ranges)"
63
+ ).option("-l, --limit <number>", "Maximum number of results", "5").option(
64
+ "-e, --exact-match",
65
+ "Only use exact version match (e.g., '18.0.0' matches only 18.0.0, not 17.x.x) (default: false)",
66
+ false
67
+ ).action(async (library, query, options) => {
68
+ const result = await tools.search.execute({
69
+ library,
70
+ version: options.version,
71
+ // Get version from options
72
+ query,
73
+ limit: Number.parseInt(options.limit),
74
+ exactMatch: options.exactMatch
75
+ });
76
+ console.log(formatOutput(result.results));
77
+ });
78
+ program.command("list").description("List all available libraries and their versions").action(async () => {
79
+ const result = await tools.listLibraries.execute();
80
+ console.log(formatOutput(result.libraries));
81
+ });
82
+ program.command("find-version <library>").description("Find the best matching version for a library").option(
83
+ "-v, --version <string>",
84
+ // Add optional version flag
85
+ "Target version to match (optional, supports ranges)"
86
+ ).action(async (library, options) => {
87
+ const versionInfo = await tools.findVersion.execute({
88
+ library,
89
+ targetVersion: options.version
90
+ // Get version from options
91
+ });
92
+ if (!versionInfo) {
93
+ throw new Error("Failed to get version information");
94
+ }
95
+ console.log(versionInfo);
96
+ });
97
+ program.command("remove <library>").description("Remove documents for a specific library and version").option(
98
+ "-v, --version <string>",
99
+ "Version to remove (optional, removes unversioned if omitted)"
100
+ ).action(async (library, options) => {
101
+ if (!docService) {
102
+ throw new Error("Document service not initialized.");
103
+ }
104
+ const { version } = options;
105
+ try {
106
+ await docService.removeAllDocuments(library, version);
107
+ console.log(
108
+ `\u2705 Successfully removed documents for ${library}${version ? `@${version}` : " (unversioned)"}.`
109
+ );
110
+ } catch (error) {
111
+ console.error(
112
+ `\u274C Failed to remove documents for ${library}${version ? `@${version}` : " (unversioned)"}:`,
113
+ error instanceof Error ? error.message : String(error)
114
+ );
115
+ throw error;
116
+ }
117
+ });
118
+ await program.parseAsync();
119
+ } catch (error) {
120
+ console.error("Error:", error instanceof Error ? error.message : String(error));
121
+ if (pipelineManager) await pipelineManager.stop();
122
+ if (docService) await docService.shutdown();
123
+ process.exit(1);
124
+ }
125
+ if (pipelineManager) await pipelineManager.stop();
126
+ await docService.shutdown();
127
+ process.exit(0);
128
+ }
129
+ main().catch((error) => {
130
+ console.error("Fatal error:", error);
131
+ process.exit(1);
132
+ });
133
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport \"dotenv/config\";\nimport { Command } from \"commander\";\nimport { PipelineManager } from \"./pipeline/PipelineManager\"; // Import PipelineManager\nimport { DocumentManagementService } from \"./store/DocumentManagementService\";\nimport { FindVersionTool, ListLibrariesTool, ScrapeTool, SearchTool } from \"./tools\";\n\nconst formatOutput = (data: unknown) => JSON.stringify(data, null, 2);\n\nasync function main() {\n let docService: DocumentManagementService | undefined;\n let pipelineManager: PipelineManager | undefined;\n\n try {\n docService = new DocumentManagementService();\n await docService.initialize();\n\n // Instantiate PipelineManager for CLI use\n pipelineManager = new PipelineManager(docService); // Assign inside try\n // Start the manager for the CLI session\n await pipelineManager.start();\n\n const tools = {\n listLibraries: new ListLibrariesTool(docService),\n findVersion: new FindVersionTool(docService),\n scrape: new ScrapeTool(docService, pipelineManager), // Pass manager\n search: new SearchTool(docService),\n };\n\n const program = new Command();\n\n // Handle cleanup on SIGINT\n process.on(\"SIGINT\", async () => {\n if (pipelineManager) await pipelineManager.stop(); // Check before stopping\n if (docService) await docService.shutdown(); // Check before stopping\n process.exit(0);\n });\n\n program\n .name(\"docs-mcp\")\n .description(\"CLI for managing documentation vector store\")\n .version(\"1.0.0\");\n\n program\n .command(\"scrape <library> <url>\") // Remove <version> as positional\n .description(\"Scrape and index documentation from a URL\")\n .option(\"-v, --version <string>\", \"Version of the library (optional)\") // Add optional version flag\n .option(\"-p, --max-pages <number>\", \"Maximum pages to scrape\", \"100\")\n .option(\"-d, --max-depth <number>\", \"Maximum navigation depth\", \"3\")\n .option(\"-c, --max-concurrency <number>\", \"Maximum concurrent page requests\", \"3\")\n .option(\"--ignore-errors\", \"Ignore errors during scraping\", true)\n .action(async (library, url, options) => {\n // Update action parameters\n const result = await tools.scrape.execute({\n url,\n library,\n version: options.version, // Get version from options\n options: {\n maxPages: Number.parseInt(options.maxPages),\n maxDepth: Number.parseInt(options.maxDepth),\n maxConcurrency: Number.parseInt(options.maxConcurrency),\n ignoreErrors: options.ignoreErrors,\n },\n // CLI always waits for completion (default behavior)\n });\n // Type guard to satisfy TypeScript\n if (\"pagesScraped\" in result) {\n console.log(`✅ Successfully scraped ${result.pagesScraped} pages`);\n } else {\n // This branch should not be hit by the CLI\n console.log(`🚀 Scraping job started with ID: ${result.jobId}`);\n }\n });\n\n program\n .command(\"search <library> <query>\") // Remove <version> as positional\n .description(\n \"Search documents in a library. Version matching examples:\\n\" +\n \" - search react --version 18.0.0 'hooks' -> matches docs for React 18.0.0 or earlier versions\\n\" +\n \" - search react --version 18.0.0 'hooks' --exact-match -> only matches React 18.0.0\\n\" +\n \" - search typescript --version 5.x 'types' -> matches any TypeScript 5.x.x version\\n\" +\n \" - search typescript --version 5.2.x 'types' -> matches any TypeScript 5.2.x version\",\n )\n .option(\n \"-v, --version <string>\", // Add optional version flag\n \"Version of the library (optional, supports ranges)\",\n )\n .option(\"-l, --limit <number>\", \"Maximum number of results\", \"5\")\n .option(\n \"-e, --exact-match\",\n \"Only use exact version match (e.g., '18.0.0' matches only 18.0.0, not 17.x.x) (default: false)\",\n false,\n )\n .action(async (library, query, options) => {\n // Update action parameters\n const result = await tools.search.execute({\n library,\n version: options.version, // Get version from options\n query,\n limit: Number.parseInt(options.limit),\n exactMatch: options.exactMatch,\n });\n console.log(formatOutput(result.results));\n });\n\n program\n .command(\"list\")\n .description(\"List all available libraries and their versions\")\n .action(async () => {\n const result = await tools.listLibraries.execute();\n console.log(formatOutput(result.libraries));\n });\n\n program\n .command(\"find-version <library>\") // Remove [targetVersion] positional\n .description(\"Find the best matching version for a library\")\n .option(\n \"-v, --version <string>\", // Add optional version flag\n \"Target version to match (optional, supports ranges)\",\n )\n .action(async (library, options) => {\n // Update action parameters\n const versionInfo = await tools.findVersion.execute({\n library,\n targetVersion: options.version, // Get version from options\n });\n // findVersion.execute now returns a string, handle potential error messages within it\n if (!versionInfo) {\n // Should not happen with current tool logic, but good practice\n throw new Error(\"Failed to get version information\");\n }\n console.log(versionInfo); // Log the descriptive string from the tool\n });\n\n program\n .command(\"remove <library>\") // Library as positional argument\n .description(\"Remove documents for a specific library and version\")\n .option(\n \"-v, --version <string>\",\n \"Version to remove (optional, removes unversioned if omitted)\",\n )\n .action(async (library, options) => {\n // library is now the first arg\n if (!docService) {\n throw new Error(\"Document service not initialized.\");\n }\n const { version } = options; // Get version from options\n try {\n await docService.removeAllDocuments(library, version);\n console.log(\n `✅ Successfully removed documents for ${library}${version ? `@${version}` : \" (unversioned)\"}.`,\n );\n } catch (error) {\n console.error(\n `❌ Failed to remove documents for ${library}${version ? `@${version}` : \" (unversioned)\"}:`,\n error instanceof Error ? error.message : String(error),\n );\n // Re-throw to trigger the main catch block for shutdown\n throw error;\n }\n });\n\n await program.parseAsync();\n } catch (error) {\n console.error(\"Error:\", error instanceof Error ? error.message : String(error));\n if (pipelineManager) await pipelineManager.stop(); // Check before stopping\n if (docService) await docService.shutdown();\n process.exit(1);\n }\n\n // Clean shutdown after successful execution\n if (pipelineManager) await pipelineManager.stop(); // Check before stopping\n await docService.shutdown();\n process.exit(0);\n}\n\nmain().catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/server.js ADDED
@@ -0,0 +1,451 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CancelJobTool,
4
+ DocumentManagementService,
5
+ FindVersionTool,
6
+ GetJobInfoTool,
7
+ ListJobsTool,
8
+ ListLibrariesTool,
9
+ PipelineJobStatus,
10
+ PipelineManager,
11
+ RemoveTool,
12
+ ScrapeTool,
13
+ SearchTool,
14
+ VersionNotFoundError,
15
+ logger
16
+ } from "./chunk-7CJZVHC7.js";
17
+
18
+ // src/mcp/index.ts
19
+ import "dotenv/config";
20
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
21
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22
+ import { z } from "zod";
23
+
24
+ // src/mcp/utils.ts
25
+ function createResponse(text) {
26
+ return {
27
+ content: [
28
+ {
29
+ type: "text",
30
+ text
31
+ }
32
+ ],
33
+ isError: false
34
+ };
35
+ }
36
+ function createError(text) {
37
+ return {
38
+ content: [
39
+ {
40
+ type: "text",
41
+ text
42
+ }
43
+ ],
44
+ isError: true
45
+ };
46
+ }
47
+
48
+ // src/mcp/index.ts
49
+ async function startServer() {
50
+ logger.debug = () => {
51
+ };
52
+ logger.info = () => {
53
+ };
54
+ const docService = new DocumentManagementService();
55
+ try {
56
+ await docService.initialize();
57
+ const pipelineManager = new PipelineManager(docService);
58
+ await pipelineManager.start();
59
+ const tools = {
60
+ listLibraries: new ListLibrariesTool(docService),
61
+ findVersion: new FindVersionTool(docService),
62
+ // TODO: Update ScrapeTool constructor if needed to accept PipelineManager
63
+ // ScrapeTool currently uses docService.getPipelineManager() which doesn't exist.
64
+ // Pass both docService and pipelineManager to ScrapeTool constructor
65
+ scrape: new ScrapeTool(docService, pipelineManager),
66
+ search: new SearchTool(docService),
67
+ listJobs: new ListJobsTool(pipelineManager),
68
+ getJobInfo: new GetJobInfoTool(pipelineManager),
69
+ cancelJob: new CancelJobTool(pipelineManager),
70
+ remove: new RemoveTool(docService)
71
+ // Instantiate RemoveTool
72
+ };
73
+ const server = new McpServer(
74
+ {
75
+ name: "docs-mcp-server",
76
+ version: "0.1.0"
77
+ },
78
+ {
79
+ capabilities: {
80
+ tools: {},
81
+ prompts: {},
82
+ resources: {}
83
+ }
84
+ }
85
+ );
86
+ server.tool(
87
+ "scrape_docs",
88
+ "Scrape and index documentation from a URL",
89
+ {
90
+ url: z.string().url().describe("URL of the documentation to scrape"),
91
+ library: z.string().describe("Name of the library"),
92
+ version: z.string().optional().describe("Version of the library"),
93
+ maxPages: z.number().optional().default(100).describe("Maximum number of pages to scrape"),
94
+ maxDepth: z.number().optional().default(3).describe("Maximum navigation depth"),
95
+ subpagesOnly: z.boolean().optional().default(true).describe("Only scrape pages under the initial URL path")
96
+ },
97
+ // Remove context as it's not used without progress reporting
98
+ async ({ url, library, version, maxPages, maxDepth, subpagesOnly }) => {
99
+ try {
100
+ const result = await tools.scrape.execute({
101
+ url,
102
+ library,
103
+ version,
104
+ waitForCompletion: false,
105
+ // Don't wait for completion
106
+ // onProgress: undefined, // Explicitly undefined or omitted
107
+ options: {
108
+ maxPages,
109
+ maxDepth,
110
+ subpagesOnly
111
+ }
112
+ });
113
+ if ("jobId" in result) {
114
+ return createResponse(`\u{1F680} Scraping job started with ID: ${result.jobId}.`);
115
+ }
116
+ return createResponse(
117
+ `Scraping finished immediately (unexpectedly) with ${result.pagesScraped} pages.`
118
+ );
119
+ } catch (error) {
120
+ return createError(
121
+ `Failed to scrape documentation: ${error instanceof Error ? error.message : String(error)}`
122
+ );
123
+ }
124
+ }
125
+ );
126
+ server.tool(
127
+ "search_docs",
128
+ 'Search indexed documentation. Examples:\n- {library: "react", query: "how do hooks work"} -> matches latest version of React\n- {library: "react", version: "18.0.0", query: "how do hooks work"} -> matches React 18.0.0 or earlier\n- {library: "react", version: "18.0.0", query: "how do hooks work", exactMatch: true} -> only React 18.0.0\n- {library: "typescript", version: "5.x", query: "ReturnType example"} -> any TypeScript 5.x.x version\n- {library: "typescript", version: "5.2.x", query: "ReturnType example"} -> any TypeScript 5.2.x version',
129
+ {
130
+ library: z.string().describe("Name of the library"),
131
+ version: z.string().optional().describe(
132
+ "Version of the library (supports exact versions like '18.0.0' or X-Range patterns like '5.x', '5.2.x')"
133
+ ),
134
+ query: z.string().describe("Search query"),
135
+ limit: z.number().optional().default(5).describe("Maximum number of results"),
136
+ exactMatch: z.boolean().optional().default(false).describe("Only use exact version match")
137
+ },
138
+ async ({ library, version, query, limit, exactMatch }) => {
139
+ try {
140
+ const result = await tools.search.execute({
141
+ library,
142
+ version,
143
+ query,
144
+ limit,
145
+ exactMatch
146
+ });
147
+ const formattedResults = result.results.map(
148
+ (r, i) => `
149
+ ------------------------------------------------------------
150
+ Result ${i + 1}: ${r.url}
151
+
152
+ ${r.content}
153
+ `
154
+ );
155
+ return createResponse(
156
+ `Search results for '${query}' in ${library} v${version}:
157
+ ${formattedResults.join("")}`
158
+ );
159
+ } catch (error) {
160
+ if (error instanceof VersionNotFoundError) {
161
+ const indexedVersions = error.availableVersions.filter((v) => v.indexed).map((v) => v.version);
162
+ return createError(
163
+ indexedVersions.length > 0 ? `Version not found. Available indexed versions for ${library}: ${indexedVersions.join(", ")}` : `Version not found. No indexed versions available for ${library}.`
164
+ );
165
+ }
166
+ return createError(
167
+ `Failed to search documentation: ${error instanceof Error ? error.message : String(error)}`
168
+ );
169
+ }
170
+ }
171
+ );
172
+ server.tool("list_libraries", "List all indexed libraries", {}, async () => {
173
+ try {
174
+ const result = await tools.listLibraries.execute();
175
+ return createResponse(
176
+ `Indexed libraries:
177
+ ${result.libraries.map((lib) => `- ${lib.name}`).join("\n")}`
178
+ );
179
+ } catch (error) {
180
+ return createError(
181
+ `Failed to list libraries: ${error instanceof Error ? error.message : String(error)}`
182
+ );
183
+ }
184
+ });
185
+ server.tool(
186
+ "find_version",
187
+ "Find best matching version for a library",
188
+ {
189
+ library: z.string().describe("Name of the library"),
190
+ targetVersion: z.string().optional().describe(
191
+ "Target version to match (supports exact versions like '18.0.0' or X-Range patterns like '5.x', '5.2.x')"
192
+ )
193
+ },
194
+ async ({ library, targetVersion }) => {
195
+ try {
196
+ const version = await tools.findVersion.execute({
197
+ library,
198
+ targetVersion
199
+ });
200
+ if (!version) {
201
+ return createError("No matching version found");
202
+ }
203
+ return createResponse(`Found matching version: ${version}`);
204
+ } catch (error) {
205
+ return createError(
206
+ `Failed to find version: ${error instanceof Error ? error.message : String(error)}`
207
+ );
208
+ }
209
+ }
210
+ );
211
+ server.tool(
212
+ "list_jobs",
213
+ "List pipeline jobs, optionally filtering by status.",
214
+ {
215
+ status: z.nativeEnum(PipelineJobStatus).optional().describe("Optional status to filter jobs by.")
216
+ },
217
+ async ({ status }) => {
218
+ try {
219
+ const result = await tools.listJobs.execute({ status });
220
+ const formattedJobs = result.jobs.map(
221
+ (job) => `- ID: ${job.id}
222
+ Status: ${job.status}
223
+ Library: ${job.library}
224
+ Version: ${job.version}
225
+ Created: ${job.createdAt}${job.startedAt ? `
226
+ Started: ${job.startedAt}` : ""}${job.finishedAt ? `
227
+ Finished: ${job.finishedAt}` : ""}${job.error ? `
228
+ Error: ${job.error}` : ""}`
229
+ ).join("\n\n");
230
+ return createResponse(
231
+ result.jobs.length > 0 ? `Current Jobs:
232
+
233
+ ${formattedJobs}` : "No jobs found matching criteria."
234
+ );
235
+ } catch (error) {
236
+ return createError(
237
+ `Failed to list jobs: ${error instanceof Error ? error.message : String(error)}`
238
+ );
239
+ }
240
+ }
241
+ );
242
+ server.tool(
243
+ "get_job_info",
244
+ "Get the simplified info for a specific pipeline job.",
245
+ {
246
+ jobId: z.string().uuid().describe("The ID of the job to query.")
247
+ },
248
+ async ({ jobId }) => {
249
+ try {
250
+ const result = await tools.getJobInfo.execute({ jobId });
251
+ if (!result.job) {
252
+ return createError(`Job with ID ${jobId} not found.`);
253
+ }
254
+ const job = result.job;
255
+ const formattedJob = `- ID: ${job.id}
256
+ Status: ${job.status}
257
+ Library: ${job.library}@${job.version}
258
+ Created: ${job.createdAt}${job.startedAt ? `
259
+ Started: ${job.startedAt}` : ""}${job.finishedAt ? `
260
+ Finished: ${job.finishedAt}` : ""}${job.error ? `
261
+ Error: ${job.error}` : ""}`;
262
+ return createResponse(`Job Info:
263
+
264
+ ${formattedJob}`);
265
+ } catch (error) {
266
+ return createError(
267
+ `Failed to get job info for ${jobId}: ${error instanceof Error ? error.message : String(error)}`
268
+ );
269
+ }
270
+ }
271
+ );
272
+ server.tool(
273
+ "cancel_job",
274
+ "Attempt to cancel a queued or running pipeline job.",
275
+ {
276
+ jobId: z.string().uuid().describe("The ID of the job to cancel.")
277
+ },
278
+ async ({ jobId }) => {
279
+ try {
280
+ const result = await tools.cancelJob.execute({ jobId });
281
+ if (result.success) {
282
+ return createResponse(result.message);
283
+ }
284
+ return createError(result.message);
285
+ } catch (error) {
286
+ return createError(
287
+ `Failed to cancel job ${jobId}: ${error instanceof Error ? error.message : String(error)}`
288
+ );
289
+ }
290
+ }
291
+ );
292
+ server.tool(
293
+ "remove_docs",
294
+ "Remove indexed documentation for a library version.",
295
+ {
296
+ library: z.string().describe("Name of the library"),
297
+ version: z.string().optional().describe("Version of the library (optional, removes unversioned if omitted)")
298
+ },
299
+ async ({ library, version }) => {
300
+ try {
301
+ const result = await tools.remove.execute({ library, version });
302
+ return createResponse(result.message);
303
+ } catch (error) {
304
+ return createError(
305
+ `Failed to remove documents: ${error instanceof Error ? error.message : String(error)}`
306
+ );
307
+ }
308
+ }
309
+ );
310
+ server.prompt(
311
+ "docs",
312
+ "Search indexed documentation",
313
+ {
314
+ library: z.string().describe("Name of the library"),
315
+ version: z.string().optional().describe("Version of the library"),
316
+ query: z.string().describe("Search query")
317
+ },
318
+ async ({ library, version, query }) => {
319
+ return {
320
+ messages: [
321
+ {
322
+ role: "user",
323
+ content: {
324
+ type: "text",
325
+ text: `Please search ${library} ${version || ""} documentation for this query: ${query}`
326
+ }
327
+ }
328
+ ]
329
+ };
330
+ }
331
+ );
332
+ server.resource(
333
+ "libraries",
334
+ "docs://libraries",
335
+ {
336
+ description: "List all indexed libraries"
337
+ },
338
+ async (uri) => {
339
+ const result = await tools.listLibraries.execute();
340
+ return {
341
+ contents: result.libraries.map((lib) => ({
342
+ uri: new URL(lib.name, uri).href,
343
+ text: lib.name
344
+ }))
345
+ };
346
+ }
347
+ );
348
+ server.resource(
349
+ "versions",
350
+ new ResourceTemplate("docs://libraries/{library}/versions", {
351
+ list: void 0
352
+ }),
353
+ {
354
+ description: "List all indexed versions for a library"
355
+ },
356
+ async (uri, { library }) => {
357
+ const result = await tools.listLibraries.execute();
358
+ const lib = result.libraries.find((l) => l.name === library);
359
+ if (!lib) {
360
+ return { contents: [] };
361
+ }
362
+ return {
363
+ contents: lib.versions.map((v) => ({
364
+ uri: new URL(v.version, uri).href,
365
+ text: v.version
366
+ }))
367
+ };
368
+ }
369
+ );
370
+ server.resource(
371
+ "jobs",
372
+ "docs://jobs",
373
+ {
374
+ description: "List pipeline jobs, optionally filtering by status.",
375
+ mimeType: "application/json"
376
+ },
377
+ async (uri) => {
378
+ const statusParam = uri.searchParams.get("status");
379
+ let statusFilter;
380
+ if (statusParam) {
381
+ const validation = z.nativeEnum(PipelineJobStatus).safeParse(statusParam);
382
+ if (validation.success) {
383
+ statusFilter = validation.data;
384
+ } else {
385
+ logger.warn(`Invalid status parameter received: ${statusParam}`);
386
+ }
387
+ }
388
+ const result = await tools.listJobs.execute({ status: statusFilter });
389
+ return {
390
+ contents: [
391
+ {
392
+ uri: uri.href,
393
+ mimeType: "application/json",
394
+ text: JSON.stringify(result.jobs, null, 2)
395
+ // Stringify the simplified jobs array
396
+ }
397
+ ]
398
+ };
399
+ }
400
+ );
401
+ server.resource(
402
+ "job",
403
+ // A distinct name for this specific resource type
404
+ new ResourceTemplate("docs://jobs/{jobId}", { list: void 0 }),
405
+ {
406
+ description: "Get details for a specific pipeline job by ID.",
407
+ mimeType: "application/json"
408
+ },
409
+ async (uri, { jobId }) => {
410
+ if (typeof jobId !== "string" || jobId.length === 0) {
411
+ logger.warn(`Invalid jobId received in URI: ${jobId}`);
412
+ return { contents: [] };
413
+ }
414
+ const result = await tools.getJobInfo.execute({ jobId });
415
+ if (!result.job) {
416
+ return { contents: [] };
417
+ }
418
+ return {
419
+ contents: [
420
+ {
421
+ uri: uri.href,
422
+ mimeType: "application/json",
423
+ text: JSON.stringify(result.job, null, 2)
424
+ // Stringify the simplified job object
425
+ }
426
+ ]
427
+ };
428
+ }
429
+ );
430
+ const transport = new StdioServerTransport();
431
+ await server.connect(transport);
432
+ logger.info("Documentation MCP server running on stdio");
433
+ process.on("SIGINT", async () => {
434
+ await pipelineManager.stop();
435
+ await docService.shutdown();
436
+ await server.close();
437
+ process.exit(0);
438
+ });
439
+ } catch (error) {
440
+ await docService.shutdown();
441
+ logger.error(`\u274C Fatal Error: ${error}`);
442
+ process.exit(1);
443
+ }
444
+ }
445
+
446
+ // src/server.ts
447
+ startServer().catch((error) => {
448
+ logger.error(`\u274C Fatal Error: ${error}`);
449
+ process.exit(1);
450
+ });
451
+ //# sourceMappingURL=server.js.map