|
| 1 | +import { type EmbeddedResource } from '@modelcontextprotocol/sdk/types.js' |
| 2 | +import mime from 'mime' |
| 3 | +import { z } from 'zod' |
| 4 | + |
| 5 | +import type { CloudflareDocumentationMCP } from '../index' |
| 6 | + |
| 7 | +/** |
| 8 | + * Registers the docs search tool with the MCP server |
| 9 | + * @param agent The MCP server instance |
| 10 | + */ |
| 11 | +export function registerDocsTools(agent: CloudflareDocumentationMCP) { |
| 12 | + // Register the worker logs analysis tool by worker name |
| 13 | + agent.server.tool( |
| 14 | + 'search_cloudflare_documentation', |
| 15 | + `Search the Cloudflare documentation. |
| 16 | + |
| 17 | + You should use this tool when: |
| 18 | + - A user asks questions about Cloudflare products (Workers, Developer Platform, Zero Trust, CDN, etc) |
| 19 | + - A user requests information about a Cloudflare feature |
| 20 | + - You are unsure of how to use some Cloudflare functionality |
| 21 | + - You are writing Cloudflare Workers code and need to look up Workers-specific documentation |
| 22 | +
|
| 23 | + This tool returns a number of results from a vector database. These are embedded as resources in the response and are plaintext documents in a variety of formats. |
| 24 | + `, |
| 25 | + { |
| 26 | + // partially pulled from autorag query optimization example |
| 27 | + query: z.string().describe(`Search query. The query should: |
| 28 | +1. Identify the core concepts and intent |
| 29 | +2. Add relevant synonyms and related terms |
| 30 | +3. Remove irrelevant filler words |
| 31 | +4. Structure the query to emphasize key terms |
| 32 | +5. Include technical or domain-specific terminology if applicable`), |
| 33 | + scoreThreshold: z |
| 34 | + .number() |
| 35 | + .min(0) |
| 36 | + .max(1) |
| 37 | + .optional() |
| 38 | + .describe('A score threshold (0-1) for which matches should be included.'), |
| 39 | + maxNumResults: z |
| 40 | + .number() |
| 41 | + .default(10) |
| 42 | + .optional() |
| 43 | + .describe('The maximum number of results to return.'), |
| 44 | + }, |
| 45 | + async (params) => { |
| 46 | + // we don't need "rewrite query" OR aiSearch because an LLM writes the query and formats the output for us. |
| 47 | + const result = await agent.env.AI.autorag(agent.env.AUTORAG_NAME).search({ |
| 48 | + query: params.query, |
| 49 | + ranking_options: params.scoreThreshold |
| 50 | + ? { |
| 51 | + score_threshold: params.scoreThreshold, |
| 52 | + } |
| 53 | + : undefined, |
| 54 | + max_num_results: params.maxNumResults, |
| 55 | + }) |
| 56 | + |
| 57 | + const resources: EmbeddedResource[] = result.data.map((result) => { |
| 58 | + const content = result.content.reduce((acc, contentPart) => { |
| 59 | + return acc + contentPart.text |
| 60 | + }, '') |
| 61 | + return { |
| 62 | + type: 'resource', |
| 63 | + resource: { |
| 64 | + uri: `docs://${result.filename}`, |
| 65 | + mimeType: mime.getType(result.filename) ?? 'text/plain', |
| 66 | + text: content, |
| 67 | + }, |
| 68 | + } |
| 69 | + }) |
| 70 | + |
| 71 | + return { |
| 72 | + content: resources, |
| 73 | + } |
| 74 | + } |
| 75 | + ) |
| 76 | +} |
0 commit comments