chore: fix prettier formatting and update example server dependencies

- Fix formatting issues in cf-agents, hono-mcp, and test files
- Update dependencies in example servers

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jem 2025-07-02 17:59:24 -07:00
commit e0743c35e4
9 changed files with 329 additions and 32 deletions

View file

@ -12,7 +12,7 @@
"type-check": "tsc --noEmit" "type-check": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.13.0", "@modelcontextprotocol/sdk": "^1.13.3",
"zod": "^3.25.67" "zod": "^3.25.67"
}, },
"devDependencies": { "devDependencies": {

View file

@ -9,8 +9,8 @@ importers:
.: .:
dependencies: dependencies:
'@modelcontextprotocol/sdk': '@modelcontextprotocol/sdk':
specifier: ^1.13.0 specifier: ^1.13.3
version: 1.13.2 version: 1.13.3
zod: zod:
specifier: ^3.25.67 specifier: ^3.25.67
version: 3.25.67 version: 3.25.67
@ -443,8 +443,8 @@ packages:
'@jridgewell/trace-mapping@0.3.9': '@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@modelcontextprotocol/sdk@1.13.2': '@modelcontextprotocol/sdk@1.13.3':
resolution: {integrity: sha512-Vx7qOcmoKkR3qhaQ9qf3GxiVKCEu+zfJddHv6x3dY/9P6+uIwJnmuAur5aB+4FDXf41rRrDnOEGkviX5oYZ67w==} resolution: {integrity: sha512-bGwA78F/U5G2jrnsdRkPY3IwIwZeWUEfb5o764b79lb0rJmMT76TLwKhdNZOWakOQtedYefwIR4emisEMvInKA==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@opentelemetry/api@1.9.0': '@opentelemetry/api@1.9.0':
@ -1314,13 +1314,14 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.2 '@jridgewell/sourcemap-codec': 1.5.2
'@modelcontextprotocol/sdk@1.13.2': '@modelcontextprotocol/sdk@1.13.3':
dependencies: dependencies:
ajv: 6.12.6 ajv: 6.12.6
content-type: 1.0.5 content-type: 1.0.5
cors: 2.8.5 cors: 2.8.5
cross-spawn: 7.0.6 cross-spawn: 7.0.6
eventsource: 3.0.7 eventsource: 3.0.7
eventsource-parser: 3.0.3
express: 5.1.0 express: 5.1.0
express-rate-limit: 7.5.1(express@5.1.0) express-rate-limit: 7.5.1(express@5.1.0)
pkce-challenge: 5.0.0 pkce-challenge: 5.0.0
@ -1349,7 +1350,7 @@ snapshots:
agents@0.0.100(@cloudflare/workers-types@4.20250628.0)(react@19.1.0): agents@0.0.100(@cloudflare/workers-types@4.20250628.0)(react@19.1.0):
dependencies: dependencies:
'@modelcontextprotocol/sdk': 1.13.2 '@modelcontextprotocol/sdk': 1.13.3
ai: 4.3.16(react@19.1.0)(zod@3.25.67) ai: 4.3.16(react@19.1.0)(zod@3.25.67)
cron-schedule: 5.0.4 cron-schedule: 5.0.4
nanoid: 5.1.5 nanoid: 5.1.5

View file

@ -1,7 +1,7 @@
import { McpAgent } from 'agents/mcp' import { McpAgent } from 'agents/mcp'
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { z } from 'zod' import { z } from 'zod'
import OAuthProvider, { OAuthHelpers } from '@cloudflare/workers-oauth-provider' import OAuthProvider, { type OAuthHelpers } from '@cloudflare/workers-oauth-provider'
import { Hono } from 'hono' import { Hono } from 'hono'
// Define our MCP agent with tools // Define our MCP agent with tools
@ -53,6 +53,95 @@ export class MyMCP extends McpAgent {
return { content: [{ type: 'text', text: String(result) }] } return { content: [{ type: 'text', text: String(result) }] }
}, },
) )
// Register sample resources using the agents framework API
this.server.resource('calc://history', 'Calculation History', async () => ({
contents: [
{
uri: 'calc://history',
mimeType: 'application/json',
text: JSON.stringify(
{
calculations: [
{ operation: 'add', a: 5, b: 3, result: 8, timestamp: '2024-01-01T10:00:00Z' },
{ operation: 'multiply', a: 4, b: 7, result: 28, timestamp: '2024-01-01T10:01:00Z' },
],
},
null,
2,
),
},
],
}))
this.server.resource('calc://settings', 'Calculator Settings', async () => ({
contents: [
{
uri: 'calc://settings',
mimeType: 'application/json',
text: JSON.stringify(
{
precision: 2,
allowNegative: true,
maxValue: 1000000,
},
null,
2,
),
},
],
}))
// Register a dynamic resource
this.server.resource('calc://stats', 'Calculation Statistics', async () => ({
contents: [
{
uri: 'calc://stats',
mimeType: 'application/json',
text: JSON.stringify(
{
totalCalculations: Math.floor(Math.random() * 100),
lastUpdated: new Date().toISOString(),
},
null,
2,
),
},
],
}))
// Register sample prompts using the agents framework API
this.server.prompt(
'math_problem',
'Generate a math problem',
{
difficulty: z.string(),
topic: z.string().optional(),
},
async ({ difficulty, topic }) => ({
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Generate a ${difficulty} math problem${topic ? ` about ${topic}` : ''}`,
},
},
],
}),
)
this.server.prompt('explain_calculation', 'Explain a calculation', { operation: z.string() }, async ({ operation }) => ({
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please explain how to perform ${operation} step by step with examples`,
},
},
],
}))
} }
} }

View file

@ -10,7 +10,7 @@
}, },
"devDependencies": { "devDependencies": {
"@hono/mcp": "^0.1.0", "@hono/mcp": "^0.1.0",
"@modelcontextprotocol/sdk": "^1.13.1", "@modelcontextprotocol/sdk": "^1.13.3",
"wrangler": "^4.21.0", "wrangler": "^4.21.0",
"zod": "^3.25.67" "zod": "^3.25.67"
} }

View file

@ -14,10 +14,10 @@ importers:
devDependencies: devDependencies:
'@hono/mcp': '@hono/mcp':
specifier: ^0.1.0 specifier: ^0.1.0
version: 0.1.0(@modelcontextprotocol/sdk@1.13.1)(hono@4.8.2) version: 0.1.0(@modelcontextprotocol/sdk@1.13.3)(hono@4.8.2)
'@modelcontextprotocol/sdk': '@modelcontextprotocol/sdk':
specifier: ^1.13.1 specifier: ^1.13.3
version: 1.13.1 version: 1.13.3
wrangler: wrangler:
specifier: ^4.21.0 specifier: ^4.21.0
version: 4.21.0 version: 4.21.0
@ -352,8 +352,8 @@ packages:
'@jridgewell/trace-mapping@0.3.9': '@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@modelcontextprotocol/sdk@1.13.1': '@modelcontextprotocol/sdk@1.13.3':
resolution: {integrity: sha512-8q6+9aF0yA39/qWT/uaIj6zTpC+Qu07DnN/lb9mjoquCJsAh6l3HyYqc9O3t2j7GilseOQOQimLg7W3By6jqvg==} resolution: {integrity: sha512-bGwA78F/U5G2jrnsdRkPY3IwIwZeWUEfb5o764b79lb0rJmMT76TLwKhdNZOWakOQtedYefwIR4emisEMvInKA==}
engines: {node: '>=18'} engines: {node: '>=18'}
accepts@2.0.0: accepts@2.0.0:
@ -972,9 +972,9 @@ snapshots:
'@fastify/busboy@2.1.1': {} '@fastify/busboy@2.1.1': {}
'@hono/mcp@0.1.0(@modelcontextprotocol/sdk@1.13.1)(hono@4.8.2)': '@hono/mcp@0.1.0(@modelcontextprotocol/sdk@1.13.3)(hono@4.8.2)':
dependencies: dependencies:
'@modelcontextprotocol/sdk': 1.13.1 '@modelcontextprotocol/sdk': 1.13.3
hono: 4.8.2 hono: 4.8.2
'@img/sharp-darwin-arm64@0.33.5': '@img/sharp-darwin-arm64@0.33.5':
@ -1061,13 +1061,14 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/sourcemap-codec': 1.5.0
'@modelcontextprotocol/sdk@1.13.1': '@modelcontextprotocol/sdk@1.13.3':
dependencies: dependencies:
ajv: 6.12.6 ajv: 6.12.6
content-type: 1.0.5 content-type: 1.0.5
cors: 2.8.5 cors: 2.8.5
cross-spawn: 7.0.6 cross-spawn: 7.0.6
eventsource: 3.0.7 eventsource: 3.0.7
eventsource-parser: 3.0.2
express: 5.1.0 express: 5.1.0
express-rate-limit: 7.5.1(express@5.1.0) express-rate-limit: 7.5.1(express@5.1.0)
pkce-challenge: 5.0.0 pkce-challenge: 5.0.0

View file

@ -1,4 +1,4 @@
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' import { McpServer, type ReadResourceTemplateCallback, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StreamableHTTPTransport } from '@hono/mcp' import { StreamableHTTPTransport } from '@hono/mcp'
import { Hono } from 'hono' import { Hono } from 'hono'
import { z } from 'zod' import { z } from 'zod'
@ -42,7 +42,8 @@ mcpServer.registerTool(
inputSchema: {}, inputSchema: {},
}, },
async () => { async () => {
await scheduler.wait(100 + Math.random() * 100) // Simulate some async work
await new Promise((resolve) => setTimeout(resolve, 100 + Math.random() * 100))
return { return {
content: [ content: [
{ {
@ -54,6 +55,139 @@ mcpServer.registerTool(
}, },
) )
// Register sample resources
mcpServer.registerResource(
'config',
'config://app',
{
title: 'Application Configuration',
description: 'Current application configuration settings',
mimeType: 'application/json',
},
async (uri: URL) => ({
contents: [
{
uri: uri.href,
mimeType: 'application/json',
text: JSON.stringify(
{
version: '1.0.0',
environment: 'test',
features: {
vengabus: true,
calculator: true,
},
},
null,
2,
),
},
],
}),
)
mcpServer.registerResource(
'readme',
'file://readme.md',
{
title: 'README',
description: 'Server documentation',
mimeType: 'text/markdown',
},
async (uri: URL) => ({
contents: [
{
uri: uri.href,
mimeType: 'text/markdown',
text: '# Test MCP Server\n\nThis is a test server for the use-mcp integration tests.\n\n## Features\n- Addition tool\n- Vengabus schedule checker\n- Sample resources\n- Sample prompts',
},
],
}),
)
// Register a resource template
mcpServer.registerResource(
'stats',
new ResourceTemplate('data://stats/{type}', { list: undefined }),
{
title: 'Statistics Data',
description: 'Get statistics for different types',
mimeType: 'application/json',
},
// @ts-expect-error - type is missing in the callback
async (uri: URL, { type }) => ({
contents: [
{
uri: uri.href,
mimeType: 'application/json',
text: JSON.stringify(
{
type: type,
count: Math.floor(Math.random() * 100),
lastUpdated: new Date().toISOString(),
},
null,
2,
),
},
],
}),
)
// Register sample prompts
mcpServer.registerPrompt(
'greeting',
{
title: 'Generate Greeting',
description: 'Generate a greeting message',
argsSchema: {
name: z.string().describe('Name of the person to greet'),
style: z.string().optional().describe('Style of greeting (formal/casual)'),
},
},
({ name, style }) => ({
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Generate a ${style || 'casual'} greeting for ${name}`,
},
},
{
role: 'assistant',
content: {
type: 'text',
text: style === 'formal' ? `Good day, ${name}. I hope this message finds you well.` : `Hey ${name}! How's it going?`,
},
},
],
}),
)
mcpServer.registerPrompt(
'code_review',
{
title: 'Code Review Template',
description: 'Template for code review requests',
argsSchema: {
language: z.string().describe('Programming language'),
focus: z.string().optional().describe('What to focus on (performance/security/style)'),
},
},
({ language, focus }) => ({
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please review the following ${language} code${focus ? ` with a focus on ${focus}` : ''}:`,
},
},
],
}),
)
app.post('/mcp', async (c) => { app.post('/mcp', async (c) => {
const transport = new StreamableHTTPTransport() const transport = new StreamableHTTPTransport()
await mcpServer.connect(transport) await mcpServer.connect(transport)

View file

@ -1,5 +1,5 @@
import { describe, test, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest' import { describe, test, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest'
import { chromium, Browser, Page } from 'playwright' import { chromium, type Browser, type Page } from 'playwright'
import { SERVER_CONFIGS } from './server-configs.js' import { SERVER_CONFIGS } from './server-configs.js'
import { getTestState, connectToMCPServer, cleanupProcess } from './test-utils.js' import { getTestState, connectToMCPServer, cleanupProcess } from './test-utils.js'
@ -82,9 +82,33 @@ describe('MCP Connection Integration Tests', () => {
console.log(` ${index + 1}. ${tool}`) console.log(` ${index + 1}. ${tool}`)
}) })
if (result.resources.length > 0) {
console.log(`📂 Available resources (${result.resources.length}):`)
result.resources.forEach((resource, index) => {
console.log(` ${index + 1}. ${resource}`)
})
}
if (result.prompts.length > 0) {
console.log(`💬 Available prompts (${result.prompts.length}):`)
result.prompts.forEach((prompt, index) => {
console.log(` ${index + 1}. ${prompt}`)
})
}
// Verify connection success // Verify connection success
expect(result.success).toBe(true) expect(result.success).toBe(true)
expect(result.tools.length).toBeGreaterThanOrEqual(serverConfig.expectedTools) expect(result.tools.length).toBeGreaterThanOrEqual(serverConfig.expectedTools)
// Verify resources if expected
if (serverConfig.expectedResources !== undefined) {
expect(result.resources.length).toBeGreaterThanOrEqual(serverConfig.expectedResources)
}
// Verify prompts if expected
if (serverConfig.expectedPrompts !== undefined) {
expect(result.prompts.length).toBeGreaterThanOrEqual(serverConfig.expectedPrompts)
}
} else { } else {
console.log(`❌ Failed to connect to ${serverConfig.name}`) console.log(`❌ Failed to connect to ${serverConfig.name}`)
if (result.debugLog) { if (result.debugLog) {

View file

@ -1,6 +1,6 @@
import { join, dirname } from 'path' import { join, dirname } from 'node:path'
import { fileURLToPath } from 'url' import { fileURLToPath } from 'node:url'
import { ServerConfig } from './test-utils.js' import type { ServerConfig } from './test-utils.js'
const __dirname = dirname(fileURLToPath(import.meta.url)) const __dirname = dirname(fileURLToPath(import.meta.url))
const rootDir = join(__dirname, '../..') const rootDir = join(__dirname, '../..')
@ -19,7 +19,9 @@ export const SERVER_CONFIGS: ServerConfig[] = [
transportTypes: ['auto', 'http'], transportTypes: ['auto', 'http'],
}, },
], ],
expectedTools: 1, expectedTools: 2,
expectedResources: 2, // 2 direct resources (templates are counted separately)
expectedPrompts: 2,
}, },
{ {
name: 'cf-agents', name: 'cf-agents',
@ -43,6 +45,8 @@ export const SERVER_CONFIGS: ServerConfig[] = [
transportTypes: ['auto', 'sse'], transportTypes: ['auto', 'sse'],
}, },
], ],
expectedTools: 1, expectedTools: 2,
expectedResources: 3, // 3 direct resources (no templates in cf-agents)
expectedPrompts: 2,
}, },
] ]

View file

@ -1,8 +1,8 @@
import { spawn, ChildProcess } from 'child_process' import { spawn, type ChildProcess } from 'node:child_process'
import { Page } from 'playwright' import type { Page } from 'playwright'
import { readFileSync } from 'fs' import { readFileSync } from 'node:fs'
import { join, dirname } from 'path' import { join, dirname } from 'node:path'
import { fileURLToPath } from 'url' import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url)) const __dirname = dirname(fileURLToPath(import.meta.url))
const testDir = join(__dirname, '..') const testDir = join(__dirname, '..')
@ -25,6 +25,8 @@ export interface ServerConfig {
portKey: keyof TestState portKey: keyof TestState
endpoints: ServerEndpoint[] endpoints: ServerEndpoint[]
expectedTools: number expectedTools: number
expectedResources?: number
expectedPrompts?: number
} }
/** /**
@ -132,7 +134,7 @@ export async function connectToMCPServer(
page: Page, page: Page,
serverUrl: string, serverUrl: string,
transportType: 'auto' | 'http' | 'sse' = 'auto', transportType: 'auto' | 'http' | 'sse' = 'auto',
): Promise<{ success: boolean; tools: string[]; debugLog: string }> { ): Promise<{ success: boolean; tools: string[]; resources: string[]; prompts: string[]; debugLog: string }> {
const state = getTestState() const state = getTestState()
if (!state.staticPort) { if (!state.staticPort) {
@ -197,7 +199,9 @@ export async function connectToMCPServer(
// Extract available tools // Extract available tools
const tools: string[] = [] const tools: string[] = []
try { try {
const toolCards = page.locator('.bg-white.rounded.border') // Look for the Available Tools section
const toolsSection = page.locator('h3:has-text("Available Tools")').locator('..')
const toolCards = toolsSection.locator('.bg-white.rounded.border')
const toolCount = await toolCards.count() const toolCount = await toolCards.count()
for (let i = 0; i < toolCount; i++) { for (let i = 0; i < toolCount; i++) {
@ -211,6 +215,44 @@ export async function connectToMCPServer(
console.warn('Could not extract tools list:', e) console.warn('Could not extract tools list:', e)
} }
// Extract available resources
const resources: string[] = []
try {
// Look for the Available Resources section
const resourcesSection = page.locator('h3:has-text("Available Resources")').locator('..')
const resourceCards = resourcesSection.locator('.bg-white.rounded.border')
const resourceCount = await resourceCards.count()
for (let i = 0; i < resourceCount; i++) {
const resourceNameElement = resourceCards.nth(i).locator('h4.font-medium.text-sm')
const resourceName = await resourceNameElement.textContent()
if (resourceName?.trim()) {
resources.push(resourceName.trim())
}
}
} catch (e) {
console.warn('Could not extract resources list:', e)
}
// Extract available prompts
const prompts: string[] = []
try {
// Look for the Available Prompts section
const promptsSection = page.locator('h3:has-text("Available Prompts")').locator('..')
const promptCards = promptsSection.locator('.bg-white.rounded.border')
const promptCount = await promptCards.count()
for (let i = 0; i < promptCount; i++) {
const promptNameElement = promptCards.nth(i).locator('h4.font-medium.text-sm')
const promptName = await promptNameElement.textContent()
if (promptName?.trim()) {
prompts.push(promptName.trim())
}
}
} catch (e) {
console.warn('Could not extract prompts list:', e)
}
// Extract debug log // Extract debug log
let debugLog = '' let debugLog = ''
try { try {
@ -225,6 +267,8 @@ export async function connectToMCPServer(
return { return {
success: isConnected, success: isConnected,
tools, tools,
resources,
prompts,
debugLog, debugLog,
} }
} }