Skip to content
Open
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
10 changes: 10 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ export type ServerInfo = { name: string; version: string }
*/
export const SERVER_INFO: ServerInfo = { name: 'cloudflare-api', version: '0.1.0' }

/**
* Concise, cross-product guidance advertised during MCP discovery and
* initialization. Keep each variant aligned with the tools exposed by its mode.
*/
export const CODEMODE_SERVER_INSTRUCTIONS =
'Use `docs` for product questions and `search` for exact API contracts before `execute`. Before changes, verify target IDs and current state; ask if the account, zone, or resource is ambiguous. Filter and paginate results.'

export const NON_CODEMODE_SERVER_INSTRUCTIONS =
'Use `docs` for product questions and endpoint schemas as exact API contracts. Before changes, verify target IDs and current state; ask if the account, zone, or resource is ambiguous. Filter and paginate large results.'

/**
* TypeScript declarations describing the `cloudflare` helper and `accountId`
* binding available to the `execute` tool's sandboxed code. Inlined into the
Expand Down
10 changes: 8 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ import { registerNonCodemodeTools } from './tools/non-codemode'
import { registerSearchTool } from './tools/search'
import { registerExecuteTool } from './tools/execute'
import { attachMetrics } from './metrics'
import { SERVER_INFO } from './constants'
import {
CODEMODE_SERVER_INSTRUCTIONS,
NON_CODEMODE_SERVER_INSTRUCTIONS,
SERVER_INFO
} from './constants'
import type { AuthProps } from './auth/types'

export async function createServer(props: AuthProps, codemode = true): Promise<McpServer> {
const server = new McpServer(SERVER_INFO)
const server = new McpServer(SERVER_INFO, {
instructions: codemode ? CODEMODE_SERVER_INSTRUCTIONS : NON_CODEMODE_SERVER_INSTRUCTIONS
})

if (!codemode) {
await registerNonCodemodeTools(server, props)
Expand Down
25 changes: 25 additions & 0 deletions tests/helpers/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { exports } from 'cloudflare:workers'

export const MCP_URL = 'https://mcp.cloudflare.com/mcp'
export const MCP_HOST = 'mcp.cloudflare.com'
export const LEGACY_MCP_VERSION = '2025-11-25'
export const MODERN_MCP_VERSION = '2026-07-28'

/** Result envelope of an MCP request over Streamable HTTP. */
Expand All @@ -10,6 +11,7 @@ export interface McpToolResult {
resultType?: string
supportedVersions?: string[]
serverInfo?: { name: string; version: string }
instructions?: string
content?: Array<{ type: string; text: string }>
isError?: boolean
tools?: Array<{
Expand All @@ -21,6 +23,29 @@ export interface McpToolResult {
error?: { code: number; message: string }
}

/** Build an MCP 2025 initialization request to the worker's `/mcp` endpoint. */
export function mcpInitializeRequest(token: string, id = 1): Request {
return new Request(MCP_URL, {
method: 'POST',
headers: {
Host: MCP_HOST,
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
Accept: 'application/json, text/event-stream'
},
body: JSON.stringify({
jsonrpc: '2.0',
id,
method: 'initialize',
params: {
protocolVersion: LEGACY_MCP_VERSION,
capabilities: {},
clientInfo: { name: 'cloudflare-mcp-tests', version: '1.0.0' }
}
})
})
}

/** Build a legacy JSON-RPC `tools/list` request to the worker's `/mcp` endpoint. */
export function mcpToolListRequest(token: string, id = 1): Request {
return new Request(MCP_URL, {
Expand Down
28 changes: 27 additions & 1 deletion tests/mcp-modern.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import {
mockIdentityProbe
} from './helpers/cloudflare-api'
import { clearKv } from './helpers/kv'
import { CODEMODE_SERVER_INSTRUCTIONS, NON_CODEMODE_SERVER_INSTRUCTIONS } from '../src/constants'
import {
MCP_HOST,
MCP_URL,
mcpInitializeRequest,
mcpToolListRequest,
modernMcpRequest,
parseMcpResult
Expand Down Expand Up @@ -55,11 +57,24 @@ describe('MCP 2026-07-28 stateless handler', () => {
result: {
resultType: 'complete',
supportedVersions: ['2026-07-28'],
serverInfo: { name: 'cloudflare-api', version: '0.1.0' }
serverInfo: { name: 'cloudflare-api', version: '0.1.0' },
instructions: CODEMODE_SERVER_INSTRUCTIONS
}
})
})

it('advertises mode-specific instructions for endpoint tools', async () => {
const response = await exports.default.fetch(
modernMcpRequest(API_TOKEN, 'server/discover', {}, { url: `${MCP_URL}?codemode=false` })
)
const body = await parseMcpResult(response)

expect(response.status).toBe(200)
expect(body.result?.instructions).toBe(NON_CODEMODE_SERVER_INSTRUCTIONS)
expect(body.result?.instructions).not.toContain('`search`')
expect(body.result?.instructions).not.toContain('`execute`')
})

it('serves modern tools/list with a complete result', async () => {
const response = await exports.default.fetch(modernMcpRequest(API_TOKEN, 'tools/list'))
const body = await parseMcpResult(response)
Expand Down Expand Up @@ -210,6 +225,17 @@ describe('MCP 2026-07-28 stateless handler', () => {
})
})

it('advertises the same instructions during MCP 2025 initialization', async () => {
const response = await exports.default.fetch(mcpInitializeRequest(API_TOKEN))
const body = await parseMcpResult(response)

expect(response.status).toBe(200)
expect(body.result).toMatchObject({
serverInfo: { name: 'cloudflare-api', version: '0.1.0' },
instructions: CODEMODE_SERVER_INSTRUCTIONS
})
})

it('retains stateless 2025 compatibility by default', async () => {
const response = await exports.default.fetch(mcpToolListRequest(API_TOKEN))
const body = await parseMcpResult(response)
Expand Down
5 changes: 4 additions & 1 deletion tests/non-codemode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { afterEach, describe, it, expect, vi } from 'vitest'
import { Client } from '@modelcontextprotocol/client'
import { InMemoryTransport, McpServer } from '@modelcontextprotocol/server'
import { createServer } from '../src/server'
import { CODEMODE_SERVER_INSTRUCTIONS } from '../src/constants'
import {
buildInputSchema,
buildNonCodemodeTools,
Expand Down Expand Up @@ -739,7 +740,9 @@ describe('createServer with codemode=false', () => {
const execute = (server as any)._registeredTools['execute']
const accountIdDescription = execute.inputSchema.shape.account_id.description

expect((server as any).server._instructions).toBeUndefined()
expect((server as any).server._instructions).toBe(CODEMODE_SERVER_INSTRUCTIONS)
expect((server as any).server._instructions).not.toContain('acct-1')
expect((server as any).server._instructions).not.toContain('Account 1')
expect(execute.description).toContain('Available accounts')
expect(execute.description).toContain('acct-1 (Account 1)')
expect(execute.description).toContain('acct-30 (Account 30)')
Expand Down