Merge pull request #10 from modelcontextprotocol/e2e-testing
E2E testing
This commit is contained in:
parent
6a3130a075
commit
ace3220fca
28 changed files with 11726 additions and 17 deletions
50
.github/workflows/ci.yml
vendored
Normal file
50
.github/workflows/ci.yml
vendored
Normal 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
1
.gitignore
vendored
|
|
@ -5,4 +5,3 @@ dist
|
|||
.aider*
|
||||
|
||||
**/.claude/settings.local.json
|
||||
public
|
||||
|
|
|
|||
6
AGENT.md
6
AGENT.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
6
examples/inspector/pnpm-lock.yaml
generated
6
examples/inspector/pnpm-lock.yaml
generated
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
33
examples/servers/hono-mcp/.gitignore
vendored
Normal 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
|
||||
21
examples/servers/hono-mcp/README.md
Normal file
21
examples/servers/hono-mcp/README.md
Normal 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 }>()
|
||||
```
|
||||
17
examples/servers/hono-mcp/package.json
Normal file
17
examples/servers/hono-mcp/package.json
Normal 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
1639
examples/servers/hono-mcp/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
0
examples/servers/hono-mcp/public/.gitkeep
Normal file
0
examples/servers/hono-mcp/public/.gitkeep
Normal file
63
examples/servers/hono-mcp/src/index.ts
Normal file
63
examples/servers/hono-mcp/src/index.ts
Normal 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
|
||||
13
examples/servers/hono-mcp/tsconfig.json
Normal file
13
examples/servers/hono-mcp/tsconfig.json
Normal 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"]
|
||||
}
|
||||
}
|
||||
7946
examples/servers/hono-mcp/worker-configuration.d.ts
vendored
Normal file
7946
examples/servers/hono-mcp/worker-configuration.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load diff
51
examples/servers/hono-mcp/wrangler.jsonc
Normal file
51
examples/servers/hono-mcp/wrangler.jsonc
Normal 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" }]
|
||||
}
|
||||
|
|
@ -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
12
pnpm-lock.yaml
generated
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
111
test/README.md
Normal 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
Loading…
Reference in a new issue