Implement smoke test for clients and servers (#76)
This commit adds `tests/smoke-test.sh`, which performs a smoke test of all clients and servers. The smoke test tests that the clients and servers run and that list tools is called. This commit also adds a GitHub Actions workflow to run the smoke test on all pull requests and pushes to main. For practicality, this commit modifies the TypeScript and Python clients to lazily initialize the Anthropic client, thus allowing tests to skip setting an API key. This change also provides a friendly error when users forget to set an API key. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Alex Akimov <alexeyakimov@gmail.com>
This commit is contained in:
parent
d5d455dcc3
commit
bd27d306c0
5 changed files with 1677 additions and 10 deletions
61
tests/helpers/mcp-test-client.ts
Normal file
61
tests/helpers/mcp-test-client.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Minimal MCP Test Client for testing servers
|
||||
* Connects to a server, initializes, and lists tools
|
||||
*/
|
||||
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||
|
||||
async function testServer(command: string, args: string[]) {
|
||||
console.error(`Testing server: ${command} ${args.join(" ")}`);
|
||||
|
||||
const transport = new StdioClientTransport({
|
||||
command,
|
||||
args,
|
||||
});
|
||||
|
||||
const client = new Client(
|
||||
{
|
||||
name: "mcp-test-client",
|
||||
version: "1.0.0",
|
||||
},
|
||||
{
|
||||
capabilities: {},
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
// Connect to server
|
||||
await client.connect(transport);
|
||||
console.error("✓ Connected to server");
|
||||
|
||||
// List tools
|
||||
const { tools } = await client.listTools();
|
||||
console.error(`✓ Listed ${tools.length} tools`);
|
||||
|
||||
// Success
|
||||
console.error("✓ Server test passed");
|
||||
await client.close();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error(`✗ Server test failed: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length < 1) {
|
||||
console.error("Usage: mcp-test-client <command> [args...]");
|
||||
console.error("Example: mcp-test-client node server.js");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const command = args[0];
|
||||
const commandArgs = args.slice(1);
|
||||
|
||||
testServer(command, commandArgs).catch((error) => {
|
||||
console.error(`Fatal error: ${error}`);
|
||||
process.exit(1);
|
||||
});
|
||||
49
tests/helpers/mock-mcp-server.ts
Normal file
49
tests/helpers/mock-mcp-server.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Mock MCP Server for testing clients
|
||||
* Verifies that clients call the tools/list method and returns an empty tool list
|
||||
*/
|
||||
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
const server = new McpServer(
|
||||
{
|
||||
name: "mock-test-server",
|
||||
version: "1.0.0",
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Track whether tools/list was called
|
||||
let toolsListCalled = false;
|
||||
|
||||
// Override the default tools/list handler to track calls
|
||||
server.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
toolsListCalled = true;
|
||||
return { tools: [] };
|
||||
});
|
||||
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error("Mock MCP Server running on stdio");
|
||||
}
|
||||
|
||||
// Verify that tools/list was called when the connection closes
|
||||
process.stdin.on("end", () => {
|
||||
if (!toolsListCalled) {
|
||||
console.error("Error: Client did not call tools/list");
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("Server error:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
1133
tests/helpers/package-lock.json
generated
Normal file
1133
tests/helpers/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
20
tests/helpers/package.json
Normal file
20
tests/helpers/package.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "mcp-test-helpers",
|
||||
"version": "1.0.0",
|
||||
"description": "Test helpers for MCP quickstart examples",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "rm -rf build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.22.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.13.4",
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
}
|
||||
21
tests/helpers/tsconfig.json
Normal file
21
tests/helpers/tsconfig.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"build"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in a new issue