fixture-server.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. /**
  2. * Minimal MCP server over stdio for e2e testing of the dsh-mcp-client plugin.
  3. * Registers controlled tools with predictable behavior for asserting edge cases.
  4. *
  5. * Run: node fixture-server.ts
  6. */
  7. import { McpServer, type CallToolResult } from '@modelcontextprotocol/server'
  8. import { serveStdio } from '@modelcontextprotocol/server/stdio'
  9. import { z } from 'zod'
  10. function createFixtureServer(): McpServer {
  11. const server = new McpServer(
  12. { name: 'fixture-server', version: '1.0.0' },
  13. { capabilities: { tools: { listChanged: true } } },
  14. )
  15. server.registerTool('add', {
  16. title: 'Add Tool',
  17. description: 'Adds two numbers.',
  18. inputSchema: z.object({ a: z.number().describe('First number'), b: z.number().describe('Second number') }),
  19. }, async args => ({
  20. content: [{ type: 'text', text: String(args.a + args.b) }],
  21. }))
  22. server.registerTool('greet', {
  23. title: 'Greet Tool',
  24. description: 'Greets a person by name.',
  25. inputSchema: z.object({ name: z.string().describe('Name to greet') }),
  26. }, async args => ({
  27. content: [{ type: 'text', text: `Hello, ${args.name}!` }],
  28. }))
  29. server.registerTool('fail', {
  30. title: 'Fail Tool',
  31. description: 'Always returns an error.',
  32. inputSchema: z.object({}),
  33. }, async (): Promise<CallToolResult> => ({
  34. content: [{ type: 'text', text: 'Something went wrong' }],
  35. isError: true,
  36. }))
  37. server.registerTool('image', {
  38. title: 'Image Tool',
  39. description: 'Returns an image content block.',
  40. inputSchema: z.object({}),
  41. }, async (): Promise<CallToolResult> => ({
  42. content: [
  43. { type: 'text', text: 'Here is an image:' },
  44. { type: 'image', data: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC', mimeType: 'image/png' },
  45. { type: 'text', text: 'End of image.' },
  46. ],
  47. }))
  48. server.registerTool('crash', {
  49. title: 'Crash Tool',
  50. description: 'Replies, then exits the server process (crash-recovery test).',
  51. inputSchema: z.object({}),
  52. }, async (): Promise<CallToolResult> => {
  53. // Exit AFTER the response flushes so the caller observes a clean result
  54. // followed by a transport close, like a real post-reply crash.
  55. setTimeout(() => process.exit(7), 25)
  56. return { content: [{ type: 'text', text: 'crashing' }] }
  57. })
  58. // Dotted name: legal in MCP, illegal in the DeepSeek function-name contract.
  59. // Exercises the bridge's normalize-and-hash public-name path end to end.
  60. server.registerTool('admin.reset', {
  61. title: 'Admin Reset Tool',
  62. description: 'Tool with a dotted name (normalization test).',
  63. inputSchema: z.object({}),
  64. }, async (): Promise<CallToolResult> => ({
  65. content: [{ type: 'text', text: 'reset done' }],
  66. }))
  67. return server
  68. }
  69. serveStdio(createFixtureServer)