Merge pull request #10 from modelcontextprotocol/e2e-testing

E2E testing
This commit is contained in:
Glen Maddern 2025-06-30 21:07:45 +10:00 • committed by GitHub
commit ace3220fca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 11726 additions and 17 deletions

50
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,50 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'
- name: Install dependencies - root
run: pnpm install
- name: Install dependencies - inspector example
run: pnpm install
working-directory: examples/inspector
# - name: Install dependencies - cf-agents example
# run: pnpm install
# working-directory: examples/servers/cf-agents
- name: Install dependencies - hono-mcp example
run: pnpm install
working-directory: examples/servers/hono-mcp
- name: Install dependencies - test
run: pnpm install && pnpm exec playwright install
working-directory: test
- name: Run tests
run: pnpm test:headless
working-directory: test

1
.gitignore vendored
View file

@ -5,4 +5,3 @@ dist
.aider*
**/.claude/settings.local.json
public

View file

@ -5,6 +5,12 @@
- `pnpm build`: Build the project
- `pnpm check`: Run prettier checks and TypeScript type checking
### Integration Tests (in /test directory)
- `cd test && pnpm test`: Run integration tests with visible browser
- `cd test && pnpm test:headless`: Run integration tests headlessly
- `cd test && pnpm test:watch`: Run integration tests in watch mode
- `cd test && pnpm test:ui`: Run integration tests with interactive UI
## Code Style Guidelines
### Imports

View file

@ -17,8 +17,7 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.6.2",
"tailwindcss": "^4.0.14",
"use-mcp": "link:../.."
"tailwindcss": "^4.0.14"
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.5.0",
@ -34,6 +33,7 @@
"prettier": "^3.5.3",
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
"use-mcp": "link:../..",
"vite": "^6.2.0",
"wrangler": "^4.19.1"
},

View file

@ -26,9 +26,6 @@ importers:
tailwindcss:
specifier: ^4.0.14
version: 4.1.6
use-mcp:
specifier: link:../..
version: link:../..
devDependencies:
'@cloudflare/vite-plugin':
specifier: ^1.5.0
@ -69,6 +66,9 @@ importers:
typescript-eslint:
specifier: ^8.24.1
version: 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.7.3)
use-mcp:
specifier: link:../..
version: link:../..
vite:
specifier: ^6.2.0
version: 6.3.5(jiti@2.4.2)(lightningcss@1.29.2)

View file

@ -10,7 +10,7 @@ function McpConnection({ serverUrl, onConnectionUpdate }: { serverUrl: string; o
debug: true,
autoRetry: false,
popupFeatures: 'width=500,height=600,resizable=yes,scrollbars=yes',
transportType: 'auto'
transportType: 'auto',
})
// Update parent component with connection data

View file

@ -5,4 +5,8 @@ import tailwindcss from '@tailwindcss/vite'
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
build: {
// @ts-ignore
minify: !process.env.NO_MINIFY,
},
})

33
examples/servers/hono-mcp/.gitignore vendored Normal file
View file

@ -0,0 +1,33 @@
# prod
dist/
# dev
.yarn/
!.yarn/releases
.vscode/*
!.vscode/launch.json
!.vscode/*.code-snippets
.idea/workspace.xml
.idea/usage.statistics.xml
.idea/shelf
# deps
node_modules/
.wrangler
# env
.env
.env.production
.dev.vars
# logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# misc
.DS_Store

View file

@ -0,0 +1,21 @@
```txt
npm install
npm run dev
```
```txt
npm run deploy
```
[For generating/synchronizing types based on your Worker configuration run](https://developers.cloudflare.com/workers/wrangler/commands/#types):
```txt
npm run cf-typegen
```
Pass the `CloudflareBindings` as generics when instantiation `Hono`:
```ts
// src/index.ts
const app = new Hono<{ Bindings: CloudflareBindings }>()
```

View file

@ -0,0 +1,17 @@
{
"name": "vengabus",
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy --minify",
"cf-typegen": "wrangler types --env-interface CloudflareBindings"
},
"dependencies": {
"hono": "^4.8.2"
},
"devDependencies": {
"@hono/mcp": "^0.1.0",
"@modelcontextprotocol/sdk": "^1.13.1",
"wrangler": "^4.21.0",
"zod": "^3.25.67"
}
}

1639
examples/servers/hono-mcp/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,63 @@
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StreamableHTTPTransport } from '@hono/mcp'
import { Hono } from 'hono'
import { z } from 'zod'
import { cors } from 'hono/cors'
const app = new Hono()
app.use(
'/*',
cors({
origin: '*',
allowHeaders: ['content-type', 'mcp-session-id', 'mcp-protocol-version'],
exposeHeaders: ['mcp-session-id'],
}),
)
// Your MCP server implementation
const mcpServer = new McpServer({
name: 'my-mcp-server',
version: '1.0.0',
})
mcpServer.registerTool(
'add',
{
title: 'Addition Tool',
description: 'Add two numbers',
inputSchema: { a: z.number(), b: z.number() },
},
async ({ a, b }) => ({
content: [{ type: 'text', text: String(a + b) }],
}),
)
mcpServer.registerTool(
'get_vengabus_times',
{
title: 'Vengabus Schedule Checker',
description:
"This checks to see when the next Vengabus is, or whether there is one for the user's current location. Please display all the information to the user in a tabulated form.",
inputSchema: {},
},
async () => {
await scheduler.wait(100 + Math.random() * 100)
return {
content: [
{
type: 'text',
text: `Next Vengabus: imminent. Current user's route: New York to San Francisco. Route name: "Intercity Disco".`,
},
],
}
},
)
app.post('/mcp', async (c) => {
const transport = new StreamableHTTPTransport()
await mcpServer.connect(transport)
return transport.handleRequest(c)
})
export default app

View file

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"skipLibCheck": true,
"lib": ["ESNext"],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx",
"types": ["./worker-configuration.d.ts"]
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,51 @@
/**
* For more details on how to configure Wrangler, refer to:
* https://developers.cloudflare.com/workers/wrangler/configuration/
*/
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "vengabus",
"main": "src/index.ts",
"compatibility_date": "2025-06-20",
"assets": {
"binding": "ASSETS",
"directory": "./public"
},
"observability": {
"enabled": true
}
/**
* Smart Placement
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
*/
// "placement": { "mode": "smart" },
/**
* Bindings
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
* databases, object storage, AI inference, real-time communication and more.
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
*/
/**
* Environment Variables
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
*/
// "vars": { "MY_VARIABLE": "production_value" },
/**
* Note: Use secrets to store sensitive data.
* https://developers.cloudflare.com/workers/configuration/secrets/
*/
/**
* Static Assets
* https://developers.cloudflare.com/workers/static-assets/binding/
*/
// "assets": { "directory": "./public/", "binding": "ASSETS" },
/**
* Service Bindings (communicate between multiple Workers)
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
*/
// "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
}

View file

@ -30,11 +30,11 @@
"deploy:site": "npx wrangler deploy"
},
"dependencies": {
"strict-url-sanitise": "^0.0.1",
"@modelcontextprotocol/sdk": "^1.13.1"
"strict-url-sanitise": "^0.0.1"
},
"devDependencies": {
"@axodotdev/oranda": "^0.6.5",
"@modelcontextprotocol/sdk": "^1.13.1",
"@types/react": "^19.0.12",
"prettier": "^3.5.3",
"react": "^19.0.0",

12
pnpm-lock.yaml generated
View file

@ -8,9 +8,6 @@ importers:
.:
dependencies:
'@modelcontextprotocol/sdk':
specifier: ^1.13.1
version: 1.13.2
strict-url-sanitise:
specifier: ^0.0.1
version: 0.0.1
@ -18,6 +15,9 @@ importers:
'@axodotdev/oranda':
specifier: ^0.6.5
version: 0.6.5
'@modelcontextprotocol/sdk':
specifier: ^1.13.1
version: 1.13.1
'@types/react':
specifier: ^19.0.12
version: 19.0.12
@ -535,8 +535,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.1':
resolution: {integrity: sha512-8q6+9aF0yA39/qWT/uaIj6zTpC+Qu07DnN/lb9mjoquCJsAh6l3HyYqc9O3t2j7GilseOQOQimLg7W3By6jqvg==}
engines: {node: '>=18'}
'@pkgjs/parseargs@0.11.0':
@ -1861,7 +1861,7 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
'@modelcontextprotocol/sdk@1.13.2':
'@modelcontextprotocol/sdk@1.13.1':
dependencies:
ajv: 6.12.6
content-type: 1.0.5

View file

@ -378,7 +378,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult {
// --- Orchestrate Connection Attempts ---
let finalStatus: 'success' | 'auth_redirect' | 'failed' | 'fallback' = 'failed' // Default to failed
console.log({transportType})
console.log({ transportType })
if (transportType === 'sse') {
// SSE only - skip HTTP entirely

111
test/README.md Normal file
View file

@ -0,0 +1,111 @@
# Integration Test Suite
This directory contains integration tests for the use-mcp project that test the complete flow:
1. Building the use-mcp library
2. Building the inspector example
3. Starting MCP server(s)
4. Testing browser connections to MCP servers
## Setup
Install test dependencies:
```bash
cd test
pnpm install
```
## Running Tests
Run all integration tests (shows browser window):
```bash
cd test
pnpm test
```
Run tests headlessly (no browser window):
```bash
cd test
pnpm test:headless
```
Run tests in watch mode (re-runs on file changes):
```bash
cd test
pnpm test:watch
```
Run tests with interactive UI:
```bash
cd test
pnpm test:ui
```
Debug hanging processes:
```bash
cd test
pnpm test:debug
```
## Test Architecture
- **Global Setup** (`setup/global-setup.ts`):
- Builds use-mcp library and inspector
- Starts hono-mcp server on port 9901
- Starts static file server for inspector dist files
- **Global Teardown** (`setup/global-teardown.ts`):
- Stops all servers and cleans up resources
- **Integration Tests** (`integration/mcp-connection.test.ts`):
- Uses Playwright to automate browser interactions
- Tests connection to MCP servers defined in `MCP_SERVERS` array
- Verifies successful connections and tool availability
- Logs debug information on failures
## Adding New MCP Servers
To test additional MCP servers, add them to the `MCP_SERVERS` array in the test file:
```typescript
const MCP_SERVERS = [
{
name: 'hono-mcp',
url: 'http://localhost:9901/mcp',
expectedTools: 1,
},
{
name: 'new-server',
url: 'http://localhost:9902/mcp',
expectedTools: 2,
},
]
```
Make sure to also update the global setup to start the new server process.
## Test Output
Successful connections will log:
- ✅ Connection success message
- 📋 List of available tools with count
Failed connections will log:
- ❌ Failure message
- 🐛 Debug log from the inspector interface
## Troubleshooting
If tests fail:
1. Check that all required dependencies are installed
2. Verify that ports 9901 and 8000+ are available
3. Look at the debug log output for MCP connection errors
4. Check browser console logs for JavaScript errors
5. Run tests with `--headed` flag to see browser interactions

Some files were not shown because too many files have changed in this diff Show more