summaryrefslogtreecommitdiff
path: root/mcp-servers/simple-mcp-server/.claude/commands/test.md
blob: eff8fe90070560bf69b3b20b005ba6874794289b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# Test MCP Server

Runs comprehensive tests for your MCP server including unit tests, integration tests, and protocol compliance validation.

## Usage

```
/test [type] [options]
```

## Options

- `type` - Test type: `unit`, `integration`, `all` (default: `all`)
- `--coverage` - Generate coverage report
- `--watch` - Run tests in watch mode
- `--inspector` - Launch MCP Inspector for manual testing

## Implementation

```typescript
import { exec } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs/promises';

const execAsync = promisify(exec);

async function runTests(
  type: 'unit' | 'integration' | 'all' = 'all',
  options: {
    coverage?: boolean;
    watch?: boolean;
    inspector?: boolean;
  } = {}
) {
  console.log('๐Ÿงช Running MCP Server Tests...');
  
  // Run linting first
  console.log('\n๐Ÿ” Running linter...');
  try {
    await execAsync('npm run lint');
    console.log('โœ… Linting passed');
  } catch (error) {
    console.error('โŒ Linting failed:', error.message);
    return;
  }
  
  // Run type checking
  console.log('\n๐Ÿ“ Type checking...');
  try {
    await execAsync('npm run typecheck');
    console.log('โœ… Type checking passed');
  } catch (error) {
    console.error('โŒ Type checking failed:', error.message);
    return;
  }
  
  // Run tests
  console.log(`\n๐Ÿงช Running ${type} tests...`);
  
  let testCommand = 'npx vitest';
  
  if (type === 'unit') {
    testCommand += ' tests/unit';
  } else if (type === 'integration') {
    testCommand += ' tests/integration';
  }
  
  if (options.coverage) {
    testCommand += ' --coverage';
  }
  
  if (options.watch) {
    testCommand += ' --watch';
  } else {
    testCommand += ' --run';
  }
  
  try {
    const { stdout } = await execAsync(testCommand);
    console.log(stdout);
    
    // Run protocol compliance check
    if (type === 'all' || type === 'integration') {
      console.log('\n๐Ÿ”Œ Checking MCP protocol compliance...');
      await checkProtocolCompliance();
    }
    
    // Generate test report
    if (options.coverage) {
      console.log('\n๐Ÿ“Š Coverage report generated:');
      console.log('  - HTML: coverage/index.html');
      console.log('  - JSON: coverage/coverage-final.json');
    }
    
    console.log('\nโœ… All tests passed!');
    
    // Launch inspector if requested
    if (options.inspector) {
      console.log('\n๐Ÿ” Launching MCP Inspector...');
      await launchInspector();
    }
  } catch (error) {
    console.error('\nโŒ Tests failed:', error.message);
    process.exit(1);
  }
}

async function checkProtocolCompliance() {
  const tests = [
    checkInitialization,
    checkToolsCapability,
    checkResourcesCapability,
    checkPromptsCapability,
    checkErrorHandling,
  ];
  
  for (const test of tests) {
    try {
      await test();
      console.log(`  โœ… ${test.name.replace('check', '')} compliance`);
    } catch (error) {
      console.log(`  โŒ ${test.name.replace('check', '')} compliance: ${error.message}`);
      throw error;
    }
  }
}

async function checkInitialization() {
  // Test that server properly handles initialization
  const { Server } = await import('@modelcontextprotocol/sdk/server/index.js');
  const { TestTransport } = await import('../tests/utils/test-transport.js');
  
  const server = new Server({
    name: 'test-server',
    version: '1.0.0',
  }, {
    capabilities: {
      tools: {},
      resources: {},
      prompts: {},
    },
  });
  
  const transport = new TestTransport();
  await server.connect(transport);
  
  const response = await transport.request({
    jsonrpc: '2.0',
    id: 1,
    method: 'initialize',
    params: {
      protocolVersion: '2024-11-05',
      capabilities: {},
      clientInfo: {
        name: 'test-client',
        version: '1.0.0',
      },
    },
  });
  
  if (!response.protocolVersion) {
    throw new Error('Server did not return protocol version');
  }
  
  await server.close();
}

async function checkToolsCapability() {
  // Verify tools capability is properly implemented
  const toolsExist = await fs.access('src/tools')
    .then(() => true)
    .catch(() => false);
  
  if (!toolsExist) {
    console.log('    (No tools implemented yet)');
    return;
  }
  
  // Check that tools are properly exported
  const { tools } = await import('../src/tools/index.js');
  if (!Array.isArray(tools)) {
    throw new Error('Tools must be exported as an array');
  }
}

async function checkResourcesCapability() {
  // Verify resources capability is properly implemented
  const resourcesExist = await fs.access('src/resources')
    .then(() => true)
    .catch(() => false);
  
  if (!resourcesExist) {
    console.log('    (No resources implemented yet)');
    return;
  }
  
  // Check that resources are properly exported
  const { resources } = await import('../src/resources/index.js');
  if (!Array.isArray(resources)) {
    throw new Error('Resources must be exported as an array');
  }
}

async function checkPromptsCapability() {
  // Verify prompts capability is properly implemented
  const promptsExist = await fs.access('src/prompts')
    .then(() => true)
    .catch(() => false);
  
  if (!promptsExist) {
    console.log('    (No prompts implemented yet)');
    return;
  }
  
  // Check that prompts are properly exported
  const { prompts } = await import('../src/prompts/index.js');
  if (!Array.isArray(prompts)) {
    throw new Error('Prompts must be exported as an array');
  }
}

async function checkErrorHandling() {
  // Test that server properly handles errors
  const { handleError } = await import('../src/utils/error-handler.js');
  
  // Test known error
  const knownError = new Error('Test error');
  knownError.code = 'TEST_ERROR';
  const response1 = handleError(knownError);
  if (!response1.error || response1.error.code !== 'TEST_ERROR') {
    throw new Error('Error handler does not preserve error codes');
  }
  
  // Test unknown error
  const unknownError = new Error('Unknown error');
  const response2 = handleError(unknownError);
  if (!response2.error || response2.error.code !== 'INTERNAL_ERROR') {
    throw new Error('Error handler does not handle unknown errors');
  }
}

async function launchInspector() {
  console.log('Starting MCP Inspector...');
  console.log('This will open an interactive testing UI in your browser.');
  console.log('Press Ctrl+C to stop the inspector.\n');
  
  const inspector = exec('npx @modelcontextprotocol/inspector');
  
  inspector.stdout.on('data', (data) => {
    console.log(data.toString());
  });
  
  inspector.stderr.on('data', (data) => {
    console.error(data.toString());
  });
  
  inspector.on('close', (code) => {
    console.log(`Inspector exited with code ${code}`);
  });
}
```