Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cold-teeth-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'docs-vectorize': minor
---

Moved Docs MCP server to be stateless for /mcp
66 changes: 43 additions & 23 deletions apps/docs-vectorize/src/docs-vectorize.app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { McpAgent } from 'agents/mcp'
import { createMcpHandler, McpAgent } from 'agents/mcp'

import { createApiHandler } from '@repo/mcp-common/src/api-handler'
import { getEnv } from '@repo/mcp-common/src/env'
import { registerPrompts } from '@repo/mcp-common/src/prompts/docs-vectorize.prompts'
import { initSentry } from '@repo/mcp-common/src/sentry'
Expand All @@ -11,12 +10,7 @@ import type { Env } from './docs-vectorize.context'

const env = getEnv<Env>()

// The docs MCP server isn't stateful, so we don't have state/props
export type Props = never

export type State = never

export class CloudflareDocumentationMCP extends McpAgent<Env, State, Props> {
export class CloudflareDocumentationMCP extends McpAgent<Env, never, never> {
_server: CloudflareMCPServer | undefined
set server(server: CloudflareMCPServer) {
this._server = server
Expand All @@ -25,7 +19,6 @@ export class CloudflareDocumentationMCP extends McpAgent<Env, State, Props> {
if (!this._server) {
throw new Error('Tried to access server before it was initialized')
}

return this._server
}

Expand All @@ -37,20 +30,47 @@ export class CloudflareDocumentationMCP extends McpAgent<Env, State, Props> {
}

async init() {
const sentry = initSentry(env, this.ctx)

this.server = new CloudflareMCPServer({
wae: env.MCP_METRICS,
serverInfo: {
name: env.MCP_SERVER_NAME,
version: env.MCP_SERVER_VERSION,
},
sentry,
})

registerDocsTools(this, this.env)
registerPrompts(this)
this.server = createMcpServer(env, this.ctx)
}
}

export default createApiHandler(CloudflareDocumentationMCP)
const sseHandler = CloudflareDocumentationMCP.serveSSE('/sse')

export default {
fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
const url = new URL(req.url)
if (url.pathname === '/sse' || url.pathname === '/sse/message') {
return sseHandler.fetch(req, env, ctx)
}
if (url.pathname === '/mcp') {
const server = createMcpServer(env, ctx, req)
const mcpHandler = createMcpHandler(server)
return mcpHandler(req, env, ctx)
}
return new Response('Not found', { status: 404 })
},
}

function createMcpServer(
env: Env,
ctx: {
waitUntil: ExecutionContext['waitUntil']
},
req?: Request
) {
const sentry = initSentry(env, ctx, req)

const server = new CloudflareMCPServer({
wae: env.MCP_METRICS,
serverInfo: {
name: env.MCP_SERVER_NAME,
version: env.MCP_SERVER_VERSION,
},
sentry,
})

registerDocsTools(server, env)
registerPrompts(server)

return server
}
1 change: 1 addition & 0 deletions apps/docs-vectorize/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"compatibility_date": "2025-03-10",
"compatibility_flags": ["nodejs_compat"],
"name": "mcp-cloudflare-docs-vectorize-dev",
"minify": true,
"migrations": [
{
"new_sqlite_classes": ["CloudflareDocumentationMCP"],
Expand Down
4 changes: 2 additions & 2 deletions apps/workers-bindings/src/bindings.app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ export class WorkersBindingsMCP extends McpAgent<Env, WorkersBindingsMCPState, P
registerHyperdriveTools(this)

// Add docs tools
registerDocsTools(this, this.env)
registerPrompts(this)
registerDocsTools(this.server, this.env)
registerPrompts(this.server)
}

async getActiveAccountId() {
Expand Down
4 changes: 2 additions & 2 deletions apps/workers-observability/src/workers-observability.app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ export class ObservabilityMCP extends McpAgent<Env, State, Props> {
registerObservabilityTools(this)

// Add docs tools
registerDocsTools(this, this.env)
registerPrompts(this)
registerDocsTools(this.server, this.env)
registerPrompts(this.server)
}

async getActiveAccountId() {
Expand Down
8 changes: 4 additions & 4 deletions packages/mcp-common/src/prompts/docs-vectorize.prompts.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { CloudflareMcpAgentNoAccount } from '../types/cloudflare-mcp-agent.types'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'

/**
* Registers developer-platform-related prompts with the MCP server
* @param agent The MCP server instance
* @param server The MCP server instance
*/
export function registerPrompts(agent: CloudflareMcpAgentNoAccount) {
agent.server.prompt(
export function registerPrompts(server: McpServer) {
server.prompt(
'workers-prompt-full',
'Detailed prompt for generating Cloudflare Workers code (and other developer platform products) from https://developers.cloudflare.com/workers/prompt.txt',
async () => ({
Expand Down
10 changes: 5 additions & 5 deletions packages/mcp-common/src/tools/docs-vectorize.tools.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from 'zod'

import type { CloudflareMcpAgentNoAccount } from '../types/cloudflare-mcp-agent.types'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'

interface RequiredEnv {
AI: Ai
Expand All @@ -12,10 +12,10 @@ const TOP_K = 10

/**
* Registers the docs search tool with the MCP server
* @param agent The MCP server instance
* @param server The MCP server instance
*/
export function registerDocsTools(agent: CloudflareMcpAgentNoAccount, env: RequiredEnv) {
agent.server.tool(
export function registerDocsTools(server: McpServer, env: RequiredEnv) {
server.tool(
'search_cloudflare_documentation',
`Search the Cloudflare documentation.

Expand Down Expand Up @@ -57,7 +57,7 @@ ${result.text}

// Note: this is a tool instead of a prompt because
// prompt support is much less common than tools.
agent.server.tool(
server.tool(
'migrate_pages_to_workers_guide',
`ALWAYS read this guide before migrating Pages projects to Workers.`,
{},
Expand Down