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/action.yml ADDED
@@ -0,0 +1,250 @@
1
+ name: 'MCP Server Diff'
2
+ description: 'Diff MCP server public interfaces between versions'
3
+ author: 'Sam Morrow'
4
+ branding:
5
+ icon: 'check-circle'
6
+ color: 'green'
7
+
8
+ inputs:
9
+ # Language setup (optional convenience)
10
+ setup_node:
11
+ description: 'Set up Node.js environment'
12
+ required: false
13
+ default: 'false'
14
+ node_version:
15
+ description: 'Node.js version (default: 20)'
16
+ required: false
17
+ default: '20'
18
+ setup_python:
19
+ description: 'Set up Python environment'
20
+ required: false
21
+ default: 'false'
22
+ python_version:
23
+ description: 'Python version (default: 3.11)'
24
+ required: false
25
+ default: '3.11'
26
+ setup_go:
27
+ description: 'Set up Go environment'
28
+ required: false
29
+ default: 'false'
30
+ go_version:
31
+ description: 'Go version (default: read from go.mod)'
32
+ required: false
33
+ default: ''
34
+ setup_rust:
35
+ description: 'Set up Rust environment'
36
+ required: false
37
+ default: 'false'
38
+ rust_toolchain:
39
+ description: 'Rust toolchain (default: stable)'
40
+ required: false
41
+ default: 'stable'
42
+ setup_dotnet:
43
+ description: 'Set up .NET environment'
44
+ required: false
45
+ default: 'false'
46
+ dotnet_version:
47
+ description: '.NET version (default: 8.0.x)'
48
+ required: false
49
+ default: '8.0.x'
50
+
51
+ # Build configuration
52
+ install_command:
53
+ description: 'Command to install dependencies (optional if no install step needed)'
54
+ required: false
55
+ default: ''
56
+ build_command:
57
+ description: 'Command to build the MCP server (optional for interpreted languages)'
58
+ required: false
59
+ default: ''
60
+ start_command:
61
+ description: 'Command to start the MCP server (for stdio transport, or to start HTTP server)'
62
+ required: false
63
+ default: ''
64
+
65
+ # Transport configuration
66
+ transport:
67
+ description: 'Transport type: stdio or streamable-http'
68
+ required: false
69
+ default: 'stdio'
70
+ server_url:
71
+ description: 'Server URL for HTTP transport (e.g., http://localhost:3000/mcp)'
72
+ required: false
73
+ default: ''
74
+ configurations:
75
+ description: |
76
+ JSON array of test configurations for multiple transports/scenarios.
77
+ Each object supports: name, transport, start_command, args, server_url, headers, env_vars, custom_messages.
78
+
79
+ For HTTP transport, use start_command to have the action manage server lifecycle:
80
+ - start_command: Command to start the server (action spawns, probes, then kills it)
81
+ - startup_wait_ms: Milliseconds to wait for server startup (default: 2000)
82
+
83
+ Or use pre/post commands for manual control:
84
+ - pre_test_command: Command to run before probing (e.g., start server in background)
85
+ - pre_test_wait_ms: Milliseconds to wait after pre_test_command
86
+ - post_test_command: Command to run after probing (e.g., kill server process)
87
+ required: false
88
+ default: ''
89
+ custom_messages:
90
+ description: 'JSON array of custom JSON-RPC messages to send. Each object: id (number), name (string), message (JSON-RPC object). Applied to all configurations unless overridden per-config.'
91
+ required: false
92
+ default: ''
93
+ headers:
94
+ description: 'HTTP headers to send with streamable-http requests. JSON object or newline-separated "Header: value" pairs. Applied to all HTTP configurations unless overridden per-config.'
95
+ required: false
96
+ default: ''
97
+
98
+ # Test configuration
99
+ compare_ref:
100
+ description: 'Git ref to compare against (auto-detects merge-base or previous tag if not set)'
101
+ required: false
102
+ default: ''
103
+ fail_on_error:
104
+ description: 'Fail the action if probe errors occur (not just API differences)'
105
+ required: false
106
+ default: 'true'
107
+ fail_on_diff:
108
+ description: 'Fail the action if API differences are detected (useful for release validation)'
109
+ required: false
110
+ default: 'false'
111
+ env_vars:
112
+ description: 'Environment variables (newline-separated KEY=VALUE pairs)'
113
+ required: false
114
+ default: ''
115
+ server_timeout:
116
+ description: 'Timeout in seconds to wait for server response'
117
+ required: false
118
+ default: '10'
119
+
120
+ # Shared HTTP server (starts once, tests all HTTP configurations against it)
121
+ http_start_command:
122
+ description: |
123
+ Command to start a shared HTTP server for all HTTP transport configurations.
124
+ When set, the server starts once before all HTTP tests and stops after they complete.
125
+ This is more efficient than starting/stopping per-configuration.
126
+ Per-config start_command is ignored when this is set.
127
+ required: false
128
+ default: ''
129
+ http_startup_wait_ms:
130
+ description: 'Milliseconds to wait for shared HTTP server to start (default: 2000)'
131
+ required: false
132
+ default: '2000'
133
+
134
+ outputs:
135
+ status:
136
+ description: 'Test status (passed, differences, or error)'
137
+ value: ${{ steps.diff.outputs.status }}
138
+ report_path:
139
+ description: 'Path to the diff report'
140
+ value: 'mcp-diff-report/MCP_DIFF_REPORT.md'
141
+
142
+ runs:
143
+ using: 'composite'
144
+ steps:
145
+ # Optional language setup
146
+ - name: Set up Node.js
147
+ if: inputs.setup_node == 'true'
148
+ uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
149
+ with:
150
+ node-version: ${{ inputs.node_version }}
151
+
152
+ - name: Set up Python
153
+ if: inputs.setup_python == 'true'
154
+ uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
155
+ with:
156
+ python-version: ${{ inputs.python_version }}
157
+
158
+ - name: Set up Go
159
+ if: inputs.setup_go == 'true'
160
+ uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
161
+ with:
162
+ go-version: ${{ inputs.go_version || null }}
163
+ go-version-file: ${{ inputs.go_version == '' && 'go.mod' || null }}
164
+
165
+ - name: Set up Rust
166
+ if: inputs.setup_rust == 'true'
167
+ uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
168
+ with:
169
+ toolchain: ${{ inputs.rust_toolchain }}
170
+
171
+ - name: Set up .NET
172
+ if: inputs.setup_dotnet == 'true'
173
+ uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
174
+ with:
175
+ dotnet-version: ${{ inputs.dotnet_version }}
176
+
177
+ - name: Set environment variables
178
+ if: inputs.env_vars != ''
179
+ shell: bash
180
+ run: |
181
+ echo "${{ inputs.env_vars }}" >> $GITHUB_ENV
182
+
183
+ - name: Run MCP server diff
184
+ id: diff
185
+ shell: bash
186
+ env:
187
+ INPUT_INSTALL_COMMAND: ${{ inputs.install_command }}
188
+ INPUT_BUILD_COMMAND: ${{ inputs.build_command }}
189
+ INPUT_START_COMMAND: ${{ inputs.start_command }}
190
+ INPUT_SERVER_TIMEOUT: ${{ inputs.server_timeout }}
191
+ INPUT_COMPARE_REF: ${{ inputs.compare_ref }}
192
+ INPUT_FAIL_ON_ERROR: ${{ inputs.fail_on_error }}
193
+ INPUT_TRANSPORT: ${{ inputs.transport }}
194
+ INPUT_SERVER_URL: ${{ inputs.server_url }}
195
+ INPUT_CONFIGURATIONS: ${{ inputs.configurations }}
196
+ INPUT_CUSTOM_MESSAGES: ${{ inputs.custom_messages }}
197
+ INPUT_HEADERS: ${{ inputs.headers }}
198
+ INPUT_ENV_VARS: ${{ inputs.env_vars }}
199
+ INPUT_HTTP_START_COMMAND: ${{ inputs.http_start_command }}
200
+ INPUT_HTTP_STARTUP_WAIT_MS: ${{ inputs.http_startup_wait_ms }}
201
+ INPUT_SETUP_NODE: ${{ inputs.setup_node }}
202
+ INPUT_NODE_VERSION: ${{ inputs.node_version }}
203
+ INPUT_SETUP_PYTHON: ${{ inputs.setup_python }}
204
+ INPUT_PYTHON_VERSION: ${{ inputs.python_version }}
205
+ INPUT_SETUP_GO: ${{ inputs.setup_go }}
206
+ INPUT_GO_VERSION: ${{ inputs.go_version }}
207
+ INPUT_SETUP_RUST: ${{ inputs.setup_rust }}
208
+ INPUT_RUST_TOOLCHAIN: ${{ inputs.rust_toolchain }}
209
+ INPUT_SETUP_DOTNET: ${{ inputs.setup_dotnet }}
210
+ INPUT_DOTNET_VERSION: ${{ inputs.dotnet_version }}
211
+ run: |
212
+ node "${{ github.action_path }}/dist/index.js"
213
+
214
+ - name: Generate Job Summary
215
+ shell: bash
216
+ run: |
217
+ echo "# MCP Server Diff Report" >> $GITHUB_STEP_SUMMARY
218
+ echo "" >> $GITHUB_STEP_SUMMARY
219
+ if [ -n "${{ inputs.compare_ref }}" ]; then
220
+ echo "Comparing against: \`${{ inputs.compare_ref }}\`" >> $GITHUB_STEP_SUMMARY
221
+ elif [[ "${{ github.ref }}" == refs/tags/* ]]; then
222
+ echo "Comparing tag against previous tag (auto-detected)" >> $GITHUB_STEP_SUMMARY
223
+ else
224
+ echo "Comparing against merge-base with \`origin/main\`" >> $GITHUB_STEP_SUMMARY
225
+ fi
226
+ echo "" >> $GITHUB_STEP_SUMMARY
227
+
228
+ if [ -f mcp-diff-report/MCP_DIFF_REPORT.md ]; then
229
+ tail -n +5 mcp-diff-report/MCP_DIFF_REPORT.md >> $GITHUB_STEP_SUMMARY
230
+ else
231
+ echo "Report file not found - check the workflow logs for errors" >> $GITHUB_STEP_SUMMARY
232
+ fi
233
+
234
+ echo "" >> $GITHUB_STEP_SUMMARY
235
+ echo "---" >> $GITHUB_STEP_SUMMARY
236
+ echo "" >> $GITHUB_STEP_SUMMARY
237
+
238
+ if [ "${{ steps.diff.outputs.status }}" = "passed" ]; then
239
+ echo "**No API changes detected** between the branches." >> $GITHUB_STEP_SUMMARY
240
+ else
241
+ echo "**API changes detected** — review above to confirm they are expected." >> $GITHUB_STEP_SUMMARY
242
+ fi
243
+
244
+ - name: Upload diff report
245
+ if: always()
246
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
247
+ with:
248
+ name: mcp-diff-report
249
+ path: mcp-diff-report/
250
+ if-no-files-found: ignore
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Minimal MCP Server for integration testing (streamable-http transport)
4
+ *
5
+ * Run with: npx tsx http-server.ts <port>
6
+ */
7
+ export {};
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Minimal MCP Server for integration testing (stdio transport)
4
+ *
5
+ * This server exposes tools, prompts, and resources for testing the probe functionality.
6
+ */
7
+ export {};
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Minimal MCP Server for integration testing (streamable-http transport)
4
+ *
5
+ * Run with: npx tsx http-server.ts <port>
6
+ */
7
+ export {};
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Minimal MCP Server for integration testing (stdio transport)
4
+ *
5
+ * This server exposes tools, prompts, and resources for testing the probe functionality.
6
+ */
7
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * MCP Server Diff - CLI Entry Point
3
+ *
4
+ * Standalone CLI for diffing MCP server public interfaces.
5
+ * Can compare any two servers or multiple servers against a base.
6
+ */
7
+ export {};
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Core diffing logic for MCP servers
3
+ *
4
+ * Pure functions for comparing probe results - no I/O side effects.
5
+ */
6
+ import type { ProbeResult, PrimitiveCounts } from "./types.js";
7
+ export interface DiffResult {
8
+ endpoint: string;
9
+ diff: string;
10
+ }
11
+ export interface ComparisonResult {
12
+ baseName: string;
13
+ targetName: string;
14
+ hasDifferences: boolean;
15
+ diffs: DiffResult[];
16
+ baseCounts: PrimitiveCounts;
17
+ targetCounts: PrimitiveCounts;
18
+ baseError?: string;
19
+ targetError?: string;
20
+ }
21
+ /**
22
+ * Extract primitive counts from a probe result
23
+ */
24
+ export declare function extractCounts(result: ProbeResult): PrimitiveCounts;
25
+ /**
26
+ * Compare two probe results and return structured diff results
27
+ */
28
+ export declare function compareProbeResults(baseResult: ProbeResult, targetResult: ProbeResult): DiffResult[];
29
+ /**
30
+ * Generate semantic JSON diff
31
+ */
32
+ export declare function generateJsonDiff(name: string, base: string, target: string): string | null;
33
+ /**
34
+ * Recursively find differences between two JSON objects
35
+ */
36
+ export declare function findJsonDifferences(base: unknown, target: unknown, path: string): string[];
37
+ /**
38
+ * Format a value for display in diff output
39
+ */
40
+ export declare function formatValue(value: unknown): string;
41
+ /**
42
+ * Convert DiffResult array to Map for backward compatibility
43
+ */
44
+ export declare function diffsToMap(diffs: DiffResult[]): Map<string, string>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Git utilities for MCP server diff
3
+ */
4
+ export interface GitInfo {
5
+ currentBranch: string;
6
+ compareRef: string;
7
+ }
8
+ /**
9
+ * Get current branch name
10
+ */
11
+ export declare function getCurrentBranch(): Promise<string>;
12
+ /**
13
+ * Determine what ref to compare against
14
+ * Priority: 1) Explicit compare_ref, 2) Auto-detect previous tag, 3) Merge-base with main
15
+ */
16
+ export declare function determineCompareRef(explicitRef?: string, githubRef?: string): Promise<string>;
17
+ /**
18
+ * Create a worktree for the compare ref
19
+ */
20
+ export declare function createWorktree(ref: string, path: string): Promise<boolean>;
21
+ /**
22
+ * Remove a worktree
23
+ */
24
+ export declare function removeWorktree(path: string): Promise<void>;
25
+ /**
26
+ * Checkout a ref (fallback if worktree fails)
27
+ */
28
+ export declare function checkout(ref: string): Promise<void>;
29
+ /**
30
+ * Checkout previous branch/ref
31
+ */
32
+ export declare function checkoutPrevious(): Promise<void>;
33
+ /**
34
+ * Get a display-friendly name for a ref.
35
+ * Returns branch/tag name if available, otherwise the short SHA.
36
+ */
37
+ export declare function getRefDisplayName(ref: string): Promise<string>;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * MCP Server Diff - Main Entry Point
3
+ *
4
+ * Diffs MCP server public interfaces by comparing
5
+ * API responses between the current branch and a reference.
6
+ */
7
+ export {};