mcp-server-diff 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.github/dependabot.yml +21 -0
  2. package/.github/workflows/ci.yml +51 -0
  3. package/.github/workflows/publish.yml +36 -0
  4. package/.github/workflows/release.yml +51 -0
  5. package/.prettierignore +3 -0
  6. package/.prettierrc +8 -0
  7. package/CONTRIBUTING.md +81 -0
  8. package/LICENSE +21 -0
  9. package/README.md +526 -0
  10. package/action.yml +250 -0
  11. package/dist/__tests__/fixtures/http-server.d.ts +7 -0
  12. package/dist/__tests__/fixtures/stdio-server.d.ts +7 -0
  13. package/dist/cli/__tests__/fixtures/http-server.d.ts +7 -0
  14. package/dist/cli/__tests__/fixtures/stdio-server.d.ts +7 -0
  15. package/dist/cli/cli.d.ts +7 -0
  16. package/dist/cli/diff.d.ts +44 -0
  17. package/dist/cli/git.d.ts +37 -0
  18. package/dist/cli/index.d.ts +7 -0
  19. package/dist/cli/index.js +57182 -0
  20. package/dist/cli/licenses.txt +466 -0
  21. package/dist/cli/logger.d.ts +46 -0
  22. package/dist/cli/package.json +3 -0
  23. package/dist/cli/probe.d.ts +35 -0
  24. package/dist/cli/reporter.d.ts +20 -0
  25. package/dist/cli/runner.d.ts +30 -0
  26. package/dist/cli/types.d.ts +134 -0
  27. package/dist/cli.d.ts +7 -0
  28. package/dist/diff.d.ts +44 -0
  29. package/dist/git.d.ts +37 -0
  30. package/dist/index.d.ts +7 -0
  31. package/dist/index.js +58032 -0
  32. package/dist/licenses.txt +466 -0
  33. package/dist/logger.d.ts +46 -0
  34. package/dist/package.json +3 -0
  35. package/dist/probe.d.ts +35 -0
  36. package/dist/reporter.d.ts +20 -0
  37. package/dist/runner.d.ts +30 -0
  38. package/dist/types.d.ts +134 -0
  39. package/eslint.config.mjs +47 -0
  40. package/jest.config.mjs +26 -0
  41. package/package.json +64 -0
  42. package/src/__tests__/fixtures/http-server.ts +103 -0
  43. package/src/__tests__/fixtures/stdio-server.ts +158 -0
  44. package/src/__tests__/integration.test.ts +306 -0
  45. package/src/__tests__/runner.test.ts +430 -0
  46. package/src/cli.ts +421 -0
  47. package/src/diff.ts +252 -0
  48. package/src/git.ts +262 -0
  49. package/src/index.ts +284 -0
  50. package/src/logger.ts +93 -0
  51. package/src/probe.ts +327 -0
  52. package/src/reporter.ts +214 -0
  53. package/src/runner.ts +902 -0
  54. package/src/types.ts +155 -0
  55. package/tsconfig.json +30 -0
package/README.md ADDED
@@ -0,0 +1,526 @@
1
+ # MCP Server Diff
2
+
3
+ [![GitHub Marketplace](https://img.shields.io/badge/Marketplace-MCP%20Server%20Diff-blue?logo=github)](https://github.com/marketplace/actions/mcp-server-diff)
4
+ [![GitHub release](https://img.shields.io/github/v/release/SamMorrowDrums/mcp-server-diff)](https://github.com/SamMorrowDrums/mcp-server-diff/releases)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ A GitHub Action for diffing [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server **public interfaces** between versions. This action compares the current branch against a baseline to surface any changes to your server's exposed tools, resources, prompts, and capabilities—helping you document API evolution and catch unintended modifications.
8
+
9
+ ## Overview
10
+
11
+ MCP servers expose a **public interface** to AI assistants: tools (with their input schemas), resources, prompts, and server capabilities. As your server evolves, changes to this interface are worth tracking. This action automates public interface comparison by:
12
+
13
+ 1. Building your MCP server from both the current branch and a baseline (merge-base, tag, or specified ref)
14
+ 2. Querying both versions for their complete public interface (tools, resources, prompts, capabilities)
15
+ 3. Generating a diff report showing exactly what changed
16
+ 4. Surfacing results directly in GitHub's Job Summary
17
+
18
+ This is **not** about testing internal logic or correctness—it's about visibility into what your server _advertises_ to clients.
19
+
20
+ ## Quick Start
21
+
22
+ Create `.github/workflows/mcp-diff.yml` in your repository:
23
+
24
+ ```yaml
25
+ name: MCP Server Diff
26
+
27
+ on:
28
+ pull_request:
29
+ branches: [main]
30
+ push:
31
+ branches: [main]
32
+ tags: ['v*']
33
+
34
+ permissions:
35
+ contents: read
36
+
37
+ jobs:
38
+ mcp-diff:
39
+ runs-on: ubuntu-latest
40
+ steps:
41
+ - uses: actions/checkout@v4
42
+ with:
43
+ fetch-depth: 0
44
+
45
+ - uses: SamMorrowDrums/mcp-server-diff@v2
46
+ with:
47
+ setup_node: true
48
+ install_command: npm ci
49
+ build_command: npm run build
50
+ start_command: node dist/stdio.js
51
+ ```
52
+
53
+ ## Language Examples
54
+
55
+ ### Node.js / TypeScript
56
+
57
+ ```yaml
58
+ - uses: SamMorrowDrums/mcp-server-diff@v2
59
+ with:
60
+ setup_node: true
61
+ node_version: '22'
62
+ install_command: npm ci
63
+ build_command: npm run build
64
+ start_command: node dist/stdio.js
65
+ ```
66
+
67
+ ### Python
68
+
69
+ ```yaml
70
+ - uses: SamMorrowDrums/mcp-server-diff@v2
71
+ with:
72
+ setup_python: true
73
+ python_version: '3.12'
74
+ install_command: pip install -e .
75
+ start_command: python -m my_mcp_server
76
+ ```
77
+
78
+ ### Go
79
+
80
+ ```yaml
81
+ - uses: SamMorrowDrums/mcp-server-diff@v2
82
+ with:
83
+ setup_go: true
84
+ install_command: go mod download
85
+ build_command: go build -o bin/server ./cmd/stdio
86
+ start_command: ./bin/server
87
+ ```
88
+
89
+ ### Rust
90
+
91
+ ```yaml
92
+ - uses: SamMorrowDrums/mcp-server-diff@v2
93
+ with:
94
+ setup_rust: true
95
+ install_command: cargo fetch
96
+ build_command: cargo build --release
97
+ start_command: ./target/release/my-mcp-server
98
+ ```
99
+
100
+ ### C# / .NET
101
+
102
+ ```yaml
103
+ - uses: SamMorrowDrums/mcp-server-diff@v2
104
+ with:
105
+ setup_dotnet: true
106
+ dotnet_version: '9.0.x'
107
+ install_command: dotnet restore
108
+ build_command: dotnet build -c Release
109
+ start_command: dotnet run --no-build -c Release
110
+ ```
111
+
112
+ ### Custom Setup
113
+
114
+ If you need more control over environment setup (caching, specific registries, etc.), do your own setup before calling the action:
115
+
116
+ ```yaml
117
+ steps:
118
+ - uses: actions/checkout@v4
119
+ with:
120
+ fetch-depth: 0
121
+
122
+ - uses: actions/setup-node@v4
123
+ with:
124
+ node-version: '22'
125
+ cache: 'npm'
126
+ registry-url: 'https://npm.pkg.github.com'
127
+
128
+ - uses: SamMorrowDrums/mcp-server-diff@v2
129
+ with:
130
+ install_command: npm ci
131
+ build_command: npm run build
132
+ start_command: node dist/stdio.js
133
+ ```
134
+
135
+ ## Testing Multiple Transports
136
+
137
+ Test both stdio and HTTP transports in a single run using the `configurations` input:
138
+
139
+ ```yaml
140
+ - uses: SamMorrowDrums/mcp-server-diff@v2
141
+ with:
142
+ setup_node: true
143
+ install_command: npm ci
144
+ build_command: npm run build
145
+ configurations: |
146
+ [
147
+ {
148
+ "name": "stdio",
149
+ "transport": "stdio",
150
+ "start_command": "node dist/stdio.js"
151
+ },
152
+ {
153
+ "name": "streamable-http",
154
+ "transport": "streamable-http",
155
+ "start_command": "node dist/http.js",
156
+ "server_url": "http://localhost:3000/mcp"
157
+ }
158
+ ]
159
+ ```
160
+
161
+ ## Inputs Reference
162
+
163
+ ### Language Setup (Optional)
164
+
165
+ | Input | Description | Default |
166
+ |-------|-------------|---------|
167
+ | `setup_node` | Set up Node.js environment | `false` |
168
+ | `node_version` | Node.js version | `20` |
169
+ | `setup_python` | Set up Python environment | `false` |
170
+ | `python_version` | Python version | `3.11` |
171
+ | `setup_go` | Set up Go environment | `false` |
172
+ | `go_version` | Go version (reads from go.mod if empty) | `""` |
173
+ | `setup_rust` | Set up Rust environment | `false` |
174
+ | `rust_toolchain` | Rust toolchain | `stable` |
175
+ | `setup_dotnet` | Set up .NET environment | `false` |
176
+ | `dotnet_version` | .NET version | `8.0.x` |
177
+
178
+ ### Required Inputs
179
+
180
+ | Input | Description |
181
+ |-------|-------------|
182
+ | `install_command` | Command to install dependencies (e.g., `npm ci`, `pip install -e .`, `go mod download`) |
183
+
184
+ ### Server Configuration
185
+
186
+ | Input | Description | Default |
187
+ |-------|-------------|---------|
188
+ | `build_command` | Command to build the server. Optional for interpreted languages. | `""` |
189
+ | `start_command` | Command to start the server for stdio transport | `""` |
190
+ | `transport` | Transport type: `stdio` or `streamable-http` | `stdio` |
191
+ | `server_url` | Server URL for HTTP transport (e.g., `http://localhost:3000/mcp`) | `""` |
192
+ | `configurations` | JSON array of test configurations for testing multiple transports | `""` |
193
+ | `server_timeout` | Timeout in seconds to wait for server response | `10` |
194
+ | `env_vars` | Environment variables as newline-separated `KEY=VALUE` pairs | `""` |
195
+
196
+ Either `start_command` (for stdio) or `server_url` (for HTTP) must be provided, unless using `configurations`.
197
+
198
+ ### Comparison Configuration
199
+
200
+ | Input | Description | Default |
201
+ |-------|-------------|---------|
202
+ | `compare_ref` | Git ref to compare against. Auto-detects merge-base on PRs or previous tag on tag pushes if not specified. | `""` |
203
+ | `fail_on_diff` | Fail the action if API changes are detected. Useful for release validation workflows. | `false` |
204
+ | `fail_on_error` | Fail the action if probe errors occur (connection failures, etc.) | `true` |
205
+
206
+ ### Configuration Object Schema
207
+
208
+ When using `configurations`, each object supports:
209
+
210
+ | Field | Description | Required |
211
+ |-------|-------------|----------|
212
+ | `name` | Identifier for this configuration (appears in report) | Yes |
213
+ | `transport` | `stdio` or `streamable-http` | No (default: `stdio`) |
214
+ | `start_command` | Server start command (stdio: spawns process, HTTP: starts server in background) | Yes for stdio, optional for HTTP |
215
+ | `server_url` | URL for HTTP transport | Required for `streamable-http` |
216
+ | `startup_wait_ms` | Milliseconds to wait for HTTP server to start (when using `start_command`) | No (default: 2000) |
217
+ | `pre_test_command` | Command to run before probing (alternative to `start_command` for HTTP) | No |
218
+ | `pre_test_wait_ms` | Milliseconds to wait after `pre_test_command` | No |
219
+ | `post_test_command` | Command to run after probing (cleanup, used with `pre_test_command`) | No |
220
+ | `headers` | HTTP headers for this configuration | No |
221
+ | `env_vars` | Additional environment variables | No |
222
+ | `custom_messages` | Config-specific custom messages | No |
223
+
224
+ ## How It Works
225
+
226
+ ### Execution Flow
227
+
228
+ 1. **Baseline Detection**: Determines the comparison ref:
229
+ - For pull requests: merge-base with target branch
230
+ - For tag pushes: previous tag (e.g., `v1.1.0` compares against `v1.0.0`)
231
+ - Explicit: uses `compare_ref` if provided
232
+ 2. **Build Baseline**: Creates a git worktree at the baseline ref and builds the server
233
+ 3. **Build Current**: Builds the server from the current branch
234
+ 4. **Conformance Testing**: Sends MCP protocol requests to both servers:
235
+ - `initialize` - Server capabilities and metadata
236
+ - `tools/list` - Available tools and their schemas
237
+ - `resources/list` - Available resources
238
+ - `prompts/list` - Available prompts
239
+ 5. **Report Generation**: Produces a Markdown report with diffs, uploaded as an artifact and displayed in Job Summary
240
+
241
+ ### What Gets Compared
242
+
243
+ The action queries the **public interface** of both server versions and compares the responses:
244
+
245
+ | Method | What It Reveals |
246
+ |--------|----------------|
247
+ | `initialize` | Server name, version, capabilities |
248
+ | `tools/list` | Available tools and their JSON schemas |
249
+ | `resources/list` | Exposed resources |
250
+ | `prompts/list` | Available prompts |
251
+
252
+ Differences appear as unified diffs in the report. Common changes include:
253
+
254
+ - New tools, resources, or prompts added
255
+ - Schema changes (new parameters, updated descriptions)
256
+ - Capability changes (new features enabled)
257
+ - Version string updates
258
+
259
+ ## Transport Support
260
+
261
+ ### stdio Transport
262
+
263
+ The default transport communicates with your server via stdin/stdout using JSON-RPC. For stdio, each configuration spawns a fresh server process:
264
+
265
+ ```yaml
266
+ - uses: SamMorrowDrums/mcp-server-diff@v2
267
+ with:
268
+ setup_node: true
269
+ install_command: npm ci
270
+ build_command: npm run build
271
+ start_command: node dist/stdio.js
272
+ ```
273
+
274
+ ### Streamable HTTP Transport
275
+
276
+ For HTTP servers, you typically want to **start the server once** and test multiple configurations against it. Use `start_command` at the configuration level—the action spawns the server, waits for startup, probes it, then terminates it after that configuration completes:
277
+
278
+ ```yaml
279
+ configurations: |
280
+ [{
281
+ "name": "http-server",
282
+ "transport": "streamable-http",
283
+ "start_command": "node dist/http.js",
284
+ "server_url": "http://localhost:3000/mcp",
285
+ "startup_wait_ms": 2000
286
+ }]
287
+ ```
288
+
289
+ **Per-configuration server lifecycle**: If your use case requires a fresh server instance per configuration (e.g., testing different flags or environment variables), include `start_command` in each configuration—each will get its own server process started and stopped.
290
+
291
+ **Shared server for multiple configurations**: If you want one HTTP server to handle multiple test configurations, use `pre_test_command`/`post_test_command` on the first/last configuration, or start the server in a prior workflow step:
292
+
293
+ ```yaml
294
+ configurations: |
295
+ [
296
+ {
297
+ "name": "config-a",
298
+ "transport": "streamable-http",
299
+ "server_url": "http://localhost:3000/mcp",
300
+ "pre_test_command": "node dist/http.js &",
301
+ "pre_test_wait_ms": 2000
302
+ },
303
+ {
304
+ "name": "config-b",
305
+ "transport": "streamable-http",
306
+ "server_url": "http://localhost:3000/mcp"
307
+ },
308
+ {
309
+ "name": "config-c",
310
+ "transport": "streamable-http",
311
+ "server_url": "http://localhost:3000/mcp",
312
+ "post_test_command": "pkill -f 'node dist/http.js' || true"
313
+ }
314
+ ]
315
+ ```
316
+
317
+ **Pre-deployed servers**: For already-running servers (staging, production), omit lifecycle commands entirely:
318
+
319
+ ```yaml
320
+ - uses: SamMorrowDrums/mcp-server-diff@v2
321
+ with:
322
+ install_command: 'true'
323
+ transport: streamable-http
324
+ server_url: https://mcp.example.com/api
325
+ ```
326
+
327
+ ## Version Comparison Strategies
328
+
329
+ ### Pull Requests
330
+
331
+ On pull requests, the action automatically compares against the merge-base with the target branch. This shows exactly what changes the PR introduces.
332
+
333
+ ### Tag Releases
334
+
335
+ When triggered by a tag push matching `v*`, the action finds the previous tag and compares against it:
336
+
337
+ ```yaml
338
+ on:
339
+ push:
340
+ tags: ['v*']
341
+
342
+ # v1.2.0 will automatically compare against v1.1.0
343
+ ```
344
+
345
+ ### Explicit Baseline
346
+
347
+ Specify any git ref to compare against:
348
+
349
+ ```yaml
350
+ - uses: SamMorrowDrums/mcp-server-diff@v2
351
+ with:
352
+ setup_node: true
353
+ install_command: npm ci
354
+ build_command: npm run build
355
+ start_command: node dist/stdio.js
356
+ compare_ref: v1.0.0
357
+ ```
358
+
359
+ ### Failing on Changes (Release Validation)
360
+
361
+ For release workflows where you want to ensure no API changes, use `fail_on_diff`:
362
+
363
+ ```yaml
364
+ - uses: SamMorrowDrums/mcp-server-diff@v2
365
+ with:
366
+ setup_node: true
367
+ install_command: npm ci
368
+ build_command: npm run build
369
+ start_command: node dist/stdio.js
370
+ compare_ref: v1.0.0
371
+ fail_on_diff: true # Action fails if any API changes are detected
372
+ ```
373
+
374
+ ## Artifacts and Reports
375
+
376
+ The action produces:
377
+
378
+ 1. **Job Summary**: Inline Markdown report in the GitHub Actions UI showing test results and diffs
379
+ 2. **Artifact**: `mcp-diff-report` artifact containing `MCP_DIFF_REPORT.md` for download or further processing
380
+
381
+ ## Example Output
382
+
383
+ ### No Changes Detected
384
+
385
+ When the MCP server's public interface hasn't changed between branches:
386
+
387
+ ```
388
+ 📊 Comparison:
389
+ Current: HEAD
390
+ Compare: abc1234 (v1.0.0)
391
+
392
+ 🧪 Running diff...
393
+
394
+ 📊 Phase 3: Comparing results...
395
+ 📋 Configuration stdio: ✅ No changes
396
+
397
+ ✅ No API Changes - All configurations match the baseline.
398
+ ```
399
+
400
+ ### Changes Detected
401
+
402
+ When changes are detected, the action shows a semantic diff with clear paths to each change:
403
+
404
+ ```
405
+ 📋 Configuration stdio: 3 change(s) found
406
+ ```
407
+
408
+ The generated report shows exactly what changed using path notation:
409
+
410
+ ```diff
411
+ --- base/tools.json
412
+ +++ branch/tools.json
413
+
414
+ + tools[new_tool]: {"name": "new_tool", "description": "A newly added tool", ...}
415
+ - tools[old_tool].inputSchema.properties.name.description: "Old description"
416
+ + tools[old_tool].inputSchema.properties.name.description: "Updated description"
417
+ - tools[calculator].inputSchema.properties.precision.type: "string"
418
+ + tools[calculator].inputSchema.properties.precision.type: "number"
419
+ ```
420
+
421
+ ```diff
422
+ --- base/resources.json
423
+ +++ branch/resources.json
424
+
425
+ + resources[config://settings]: {"uri": "config://settings", "name": "Settings", ...}
426
+ ```
427
+
428
+ Each line shows:
429
+ - `+` for additions (new tools, resources, or changed values)
430
+ - `-` for removals (deleted items or previous values)
431
+ - Full path to the change: `tools[tool_name].inputSchema.properties.param.type`
432
+
433
+ This makes it easy to see exactly what changed without wading through entire JSON dumps
434
+
435
+ ## Recommended Workflow
436
+
437
+ ```yaml
438
+ name: MCP Server Diff
439
+
440
+ on:
441
+ workflow_dispatch:
442
+ pull_request:
443
+ branches: [main]
444
+ push:
445
+ branches: [main]
446
+ tags: ['v*']
447
+
448
+ permissions:
449
+ contents: read
450
+
451
+ jobs:
452
+ mcp-diff:
453
+ runs-on: ubuntu-latest
454
+ steps:
455
+ - uses: actions/checkout@v4
456
+ with:
457
+ fetch-depth: 0
458
+
459
+ - uses: SamMorrowDrums/mcp-server-diff@v2
460
+ with:
461
+ setup_node: true
462
+ install_command: npm ci
463
+ build_command: npm run build
464
+ configurations: |
465
+ [
466
+ {
467
+ "name": "stdio",
468
+ "transport": "stdio",
469
+ "start_command": "node dist/stdio.js"
470
+ },
471
+ {
472
+ "name": "streamable-http",
473
+ "transport": "streamable-http",
474
+ "start_command": "node dist/http.js",
475
+ "server_url": "http://localhost:3000/mcp"
476
+ }
477
+ ]
478
+ ```
479
+
480
+ ## Troubleshooting
481
+
482
+ ### Server fails to start
483
+
484
+ - Check that `start_command` works locally
485
+ - Increase `server_timeout` for slow-starting servers
486
+ - Verify all dependencies are installed by `install_command`
487
+
488
+ ### Missing baseline
489
+
490
+ - Ensure `fetch-depth: 0` in your checkout step
491
+ - For new repositories, the first run may fail (no baseline exists)
492
+
493
+ ### HTTP transport connection refused
494
+
495
+ - Verify `server_url` matches your server's listen address
496
+ - Ensure the server binds to `0.0.0.0` or `127.0.0.1`, not just `localhost` on some systems
497
+ - Check firewall or container networking if running in Docker
498
+
499
+ ## License
500
+
501
+ MIT License. See [LICENSE](LICENSE) for details.
502
+
503
+ ## Contributing
504
+
505
+ Contributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
506
+
507
+ ## Related Resources
508
+
509
+ - [Model Context Protocol Specification](https://modelcontextprotocol.io/)
510
+ - [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
511
+ - [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)
512
+ - [MCP Go SDK](https://github.com/modelcontextprotocol/go-sdk)
513
+
514
+ ### Example Configurations
515
+
516
+ Working examples of this action in various languages:
517
+
518
+ | Language | Repository | Workflow |
519
+ |----------|------------|----------|
520
+ | TypeScript | [mcp-typescript-starter](https://github.com/SamMorrowDrums/mcp-typescript-starter) | [mcp-diff.yml](https://github.com/SamMorrowDrums/mcp-typescript-starter/blob/main/.github/workflows/mcp-diff.yml) |
521
+ | Python | [mcp-python-starter](https://github.com/SamMorrowDrums/mcp-python-starter) | [mcp-diff.yml](https://github.com/SamMorrowDrums/mcp-python-starter/blob/main/.github/workflows/mcp-diff.yml) |
522
+ | Go | [mcp-go-starter](https://github.com/SamMorrowDrums/mcp-go-starter) | [mcp-diff.yml](https://github.com/SamMorrowDrums/mcp-go-starter/blob/main/.github/workflows/mcp-diff.yml) |
523
+ | Rust | [mcp-rust-starter](https://github.com/SamMorrowDrums/mcp-rust-starter) | [mcp-diff.yml](https://github.com/SamMorrowDrums/mcp-rust-starter/blob/main/.github/workflows/mcp-diff.yml) |
524
+ | C# | [mcp-csharp-starter](https://github.com/SamMorrowDrums/mcp-csharp-starter) | [mcp-diff.yml](https://github.com/SamMorrowDrums/mcp-csharp-starter/blob/main/.github/workflows/mcp-diff.yml) |
525
+
526
+ For a production example, see [github-mcp-server](https://github.com/github/github-mcp-server).