diff --git a/examples/servers/cf-agents/package.json b/examples/servers/cf-agents/package.json index 180b46c..17cf03c 100644 --- a/examples/servers/cf-agents/package.json +++ b/examples/servers/cf-agents/package.json @@ -12,7 +12,7 @@ "type-check": "tsc --noEmit" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.13.0", + "@modelcontextprotocol/sdk": "^1.13.3", "zod": "^3.25.67" }, "devDependencies": { diff --git a/examples/servers/cf-agents/pnpm-lock.yaml b/examples/servers/cf-agents/pnpm-lock.yaml index 7c3d7be..010e5d1 100644 --- a/examples/servers/cf-agents/pnpm-lock.yaml +++ b/examples/servers/cf-agents/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@modelcontextprotocol/sdk': - specifier: ^1.13.0 - version: 1.13.2 + specifier: ^1.13.3 + version: 1.13.3 zod: specifier: ^3.25.67 version: 3.25.67 @@ -443,8 +443,8 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@modelcontextprotocol/sdk@1.13.2': - resolution: {integrity: sha512-Vx7qOcmoKkR3qhaQ9qf3GxiVKCEu+zfJddHv6x3dY/9P6+uIwJnmuAur5aB+4FDXf41rRrDnOEGkviX5oYZ67w==} + '@modelcontextprotocol/sdk@1.13.3': + resolution: {integrity: sha512-bGwA78F/U5G2jrnsdRkPY3IwIwZeWUEfb5o764b79lb0rJmMT76TLwKhdNZOWakOQtedYefwIR4emisEMvInKA==} engines: {node: '>=18'} '@opentelemetry/api@1.9.0': @@ -1314,13 +1314,14 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.2 - '@modelcontextprotocol/sdk@1.13.2': + '@modelcontextprotocol/sdk@1.13.3': dependencies: ajv: 6.12.6 content-type: 1.0.5 cors: 2.8.5 cross-spawn: 7.0.6 eventsource: 3.0.7 + eventsource-parser: 3.0.3 express: 5.1.0 express-rate-limit: 7.5.1(express@5.1.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): dependencies: - '@modelcontextprotocol/sdk': 1.13.2 + '@modelcontextprotocol/sdk': 1.13.3 ai: 4.3.16(react@19.1.0)(zod@3.25.67) cron-schedule: 5.0.4 nanoid: 5.1.5 diff --git a/examples/servers/cf-agents/src/index.ts b/examples/servers/cf-agents/src/index.ts index 417e3b9..f474d0f 100644 --- a/examples/servers/cf-agents/src/index.ts +++ b/examples/servers/cf-agents/src/index.ts @@ -1,7 +1,7 @@ import { McpAgent } from 'agents/mcp' import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' 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' // Define our MCP agent with tools @@ -53,6 +53,95 @@ export class MyMCP extends McpAgent { 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`, + }, + }, + ], + })) } } diff --git a/examples/servers/hono-mcp/package.json b/examples/servers/hono-mcp/package.json index 7e7adb3..f4acdf7 100644 --- a/examples/servers/hono-mcp/package.json +++ b/examples/servers/hono-mcp/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "@hono/mcp": "^0.1.0", - "@modelcontextprotocol/sdk": "^1.13.1", + "@modelcontextprotocol/sdk": "^1.13.3", "wrangler": "^4.21.0", "zod": "^3.25.67" } diff --git a/examples/servers/hono-mcp/pnpm-lock.yaml b/examples/servers/hono-mcp/pnpm-lock.yaml index d03e141..7e74fb7 100644 --- a/examples/servers/hono-mcp/pnpm-lock.yaml +++ b/examples/servers/hono-mcp/pnpm-lock.yaml @@ -14,10 +14,10 @@ importers: devDependencies: '@hono/mcp': 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': - specifier: ^1.13.1 - version: 1.13.1 + specifier: ^1.13.3 + version: 1.13.3 wrangler: specifier: ^4.21.0 version: 4.21.0 @@ -352,8 +352,8 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@modelcontextprotocol/sdk@1.13.1': - resolution: {integrity: sha512-8q6+9aF0yA39/qWT/uaIj6zTpC+Qu07DnN/lb9mjoquCJsAh6l3HyYqc9O3t2j7GilseOQOQimLg7W3By6jqvg==} + '@modelcontextprotocol/sdk@1.13.3': + resolution: {integrity: sha512-bGwA78F/U5G2jrnsdRkPY3IwIwZeWUEfb5o764b79lb0rJmMT76TLwKhdNZOWakOQtedYefwIR4emisEMvInKA==} engines: {node: '>=18'} accepts@2.0.0: @@ -972,9 +972,9 @@ snapshots: '@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: - '@modelcontextprotocol/sdk': 1.13.1 + '@modelcontextprotocol/sdk': 1.13.3 hono: 4.8.2 '@img/sharp-darwin-arm64@0.33.5': @@ -1061,13 +1061,14 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@modelcontextprotocol/sdk@1.13.1': + '@modelcontextprotocol/sdk@1.13.3': dependencies: ajv: 6.12.6 content-type: 1.0.5 cors: 2.8.5 cross-spawn: 7.0.6 eventsource: 3.0.7 + eventsource-parser: 3.0.2 express: 5.1.0 express-rate-limit: 7.5.1(express@5.1.0) pkce-challenge: 5.0.0 diff --git a/examples/servers/hono-mcp/src/index.ts b/examples/servers/hono-mcp/src/index.ts index 5fc2067..da2ba96 100644 --- a/examples/servers/hono-mcp/src/index.ts +++ b/examples/servers/hono-mcp/src/index.ts @@ -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 { Hono } from 'hono' import { z } from 'zod' @@ -42,7 +42,8 @@ mcpServer.registerTool( inputSchema: {}, }, async () => { - await scheduler.wait(100 + Math.random() * 100) + // Simulate some async work + await new Promise((resolve) => setTimeout(resolve, 100 + Math.random() * 100)) return { 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) => { const transport = new StreamableHTTPTransport() await mcpServer.connect(transport) diff --git a/test/integration/mcp-connection.test.ts b/test/integration/mcp-connection.test.ts index a8c8a19..c60a544 100644 --- a/test/integration/mcp-connection.test.ts +++ b/test/integration/mcp-connection.test.ts @@ -1,5 +1,5 @@ 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 { getTestState, connectToMCPServer, cleanupProcess } from './test-utils.js' @@ -82,9 +82,33 @@ describe('MCP Connection Integration Tests', () => { 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 expect(result.success).toBe(true) 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 { console.log(`❌ Failed to connect to ${serverConfig.name}`) if (result.debugLog) { diff --git a/test/integration/server-configs.ts b/test/integration/server-configs.ts index 3b93389..47f1ad6 100644 --- a/test/integration/server-configs.ts +++ b/test/integration/server-configs.ts @@ -1,6 +1,6 @@ -import { join, dirname } from 'path' -import { fileURLToPath } from 'url' -import { ServerConfig } from './test-utils.js' +import { join, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import type { ServerConfig } from './test-utils.js' const __dirname = dirname(fileURLToPath(import.meta.url)) const rootDir = join(__dirname, '../..') @@ -19,7 +19,9 @@ export const SERVER_CONFIGS: ServerConfig[] = [ transportTypes: ['auto', 'http'], }, ], - expectedTools: 1, + expectedTools: 2, + expectedResources: 2, // 2 direct resources (templates are counted separately) + expectedPrompts: 2, }, { name: 'cf-agents', @@ -43,6 +45,8 @@ export const SERVER_CONFIGS: ServerConfig[] = [ transportTypes: ['auto', 'sse'], }, ], - expectedTools: 1, + expectedTools: 2, + expectedResources: 3, // 3 direct resources (no templates in cf-agents) + expectedPrompts: 2, }, ] diff --git a/test/integration/test-utils.ts b/test/integration/test-utils.ts index 066e835..95d5400 100644 --- a/test/integration/test-utils.ts +++ b/test/integration/test-utils.ts @@ -1,8 +1,8 @@ -import { spawn, ChildProcess } from 'child_process' -import { Page } from 'playwright' -import { readFileSync } from 'fs' -import { join, dirname } from 'path' -import { fileURLToPath } from 'url' +import { spawn, type ChildProcess } from 'node:child_process' +import type { Page } from 'playwright' +import { readFileSync } from 'node:fs' +import { join, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' const __dirname = dirname(fileURLToPath(import.meta.url)) const testDir = join(__dirname, '..') @@ -25,6 +25,8 @@ export interface ServerConfig { portKey: keyof TestState endpoints: ServerEndpoint[] expectedTools: number + expectedResources?: number + expectedPrompts?: number } /** @@ -132,7 +134,7 @@ export async function connectToMCPServer( page: Page, serverUrl: string, 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() if (!state.staticPort) { @@ -197,7 +199,9 @@ export async function connectToMCPServer( // Extract available tools const tools: string[] = [] 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() for (let i = 0; i < toolCount; i++) { @@ -211,6 +215,44 @@ export async function connectToMCPServer( 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 let debugLog = '' try { @@ -225,6 +267,8 @@ export async function connectToMCPServer( return { success: isConnected, tools, + resources, + prompts, debugLog, } }