Skip to content

Commit 2d76979

Browse files
authored
Merge pull request #67 from cloudflare/csparks/autorag-mcp
Add a cloudflare docs MCP server using autorag
2 parents a99a669 + 12d93e3 commit 2d76979

File tree

16 files changed

+6079
-66
lines changed

16 files changed

+6079
-66
lines changed

apps/docs-autorag/.dev.vars.example

Whitespace-only changes.

apps/docs-autorag/.eslintrc.cjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @type {import("eslint").Linter.Config} */
2+
module.exports = {
3+
root: true,
4+
extends: ['@repo/eslint-config/default.cjs'],
5+
}

apps/docs-autorag/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Model Context Protocol (MCP) Server + Cloudflare Documentation (via Autorag)
2+
3+
This is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) server that supports remote MCP connections. It connects to an autorag instance (in this case, Cloudflare docs)
4+
5+
To run this server, you'll need access to an autorag instance which has indexed the contents of cloudflare-docs: https://github.com/cloudflare/cloudflare-docs/
6+
7+
The Cloudflare account this worker is deployed on already has this Autorag instance setup and indexed.
8+
9+
## Running locally
10+
11+
```
12+
pnpm run start
13+
```
14+
15+
Then connect to the server via remote MCP at `http://localhost:8976/sse`
16+
17+
## Deploying
18+
19+
```
20+
pnpm run deploy --env [ENVIRONMENT]
21+
```

apps/docs-autorag/package.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "docs-autorag",
3+
"version": "0.0.1",
4+
"private": true,
5+
"scripts": {
6+
"check:lint": "run-eslint-workers",
7+
"check:types": "run-tsc",
8+
"deploy": "wrangler deploy",
9+
"dev": "wrangler dev",
10+
"start": "wrangler dev",
11+
"cf-typegen": "wrangler types",
12+
"test": "vitest run"
13+
},
14+
"dependencies": {
15+
"@cloudflare/workers-oauth-provider": "0.0.2",
16+
"@hono/zod-validator": "0.4.3",
17+
"@modelcontextprotocol/sdk": "1.9.0",
18+
"@repo/mcp-common": "workspace:*",
19+
"agents": "0.0.62",
20+
"cloudflare": "4.2.0",
21+
"hono": "4.7.6",
22+
"mime": "^4.0.6",
23+
"zod": "3.24.2"
24+
},
25+
"devDependencies": {
26+
"@cloudflare/vitest-pool-workers": "0.8.14",
27+
"@cloudflare/workers-types": "4.20250410.0",
28+
"prettier": "3.5.3",
29+
"typescript": "5.5.4",
30+
"vitest": "3.0.9",
31+
"wrangler": "4.10.0"
32+
}
33+
}

apps/docs-autorag/src/index.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2+
import { McpAgent } from 'agents/mcp'
3+
4+
import { registerDocsTools } from './tools/docs'
5+
6+
// The docs MCP server isn't stateful, so we don't have state/props
7+
export type Props = never
8+
9+
export type State = never
10+
11+
export class CloudflareDocumentationMCP extends McpAgent<Env, State, Props> {
12+
server = new McpServer({
13+
name: 'Remote MCP Server with Cloudflare Documentation',
14+
version: '1.0.0',
15+
})
16+
17+
constructor(
18+
public ctx: DurableObjectState,
19+
public env: Env
20+
) {
21+
super(ctx, env)
22+
}
23+
24+
async init() {
25+
registerDocsTools(this)
26+
}
27+
}
28+
29+
export default CloudflareDocumentationMCP.mount('/sse')

apps/docs-autorag/src/tools/docs.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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+
}

apps/docs-autorag/tsconfig.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "@repo/typescript-config/workers.json",
3+
"include": ["*/**.ts", "./vitest.config.ts"]
4+
}

apps/docs-autorag/vitest.config.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config'
2+
3+
export interface TestEnv extends Env {
4+
CLOUDFLARE_MOCK_ACCOUNT_ID: string
5+
CLOUDFLARE_MOCK_API_TOKEN: string
6+
}
7+
8+
export default defineWorkersConfig({
9+
test: {
10+
poolOptions: {
11+
workers: {
12+
wrangler: { configPath: `${__dirname}/wrangler.jsonc` },
13+
miniflare: {
14+
bindings: {
15+
CLOUDFLARE_MOCK_ACCOUNT_ID: 'mock-account-id',
16+
CLOUDFLARE_MOCK_API_TOKEN: 'mock-api-token',
17+
} satisfies Partial<TestEnv>,
18+
},
19+
},
20+
},
21+
},
22+
})

0 commit comments

Comments
 (0)