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.
- package/.github/dependabot.yml +21 -0
- package/.github/workflows/ci.yml +51 -0
- package/.github/workflows/publish.yml +36 -0
- package/.github/workflows/release.yml +51 -0
- package/.prettierignore +3 -0
- package/.prettierrc +8 -0
- package/CONTRIBUTING.md +81 -0
- package/LICENSE +21 -0
- package/README.md +526 -0
- package/action.yml +250 -0
- package/dist/__tests__/fixtures/http-server.d.ts +7 -0
- package/dist/__tests__/fixtures/stdio-server.d.ts +7 -0
- package/dist/cli/__tests__/fixtures/http-server.d.ts +7 -0
- package/dist/cli/__tests__/fixtures/stdio-server.d.ts +7 -0
- package/dist/cli/cli.d.ts +7 -0
- package/dist/cli/diff.d.ts +44 -0
- package/dist/cli/git.d.ts +37 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.js +57182 -0
- package/dist/cli/licenses.txt +466 -0
- package/dist/cli/logger.d.ts +46 -0
- package/dist/cli/package.json +3 -0
- package/dist/cli/probe.d.ts +35 -0
- package/dist/cli/reporter.d.ts +20 -0
- package/dist/cli/runner.d.ts +30 -0
- package/dist/cli/types.d.ts +134 -0
- package/dist/cli.d.ts +7 -0
- package/dist/diff.d.ts +44 -0
- package/dist/git.d.ts +37 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +58032 -0
- package/dist/licenses.txt +466 -0
- package/dist/logger.d.ts +46 -0
- package/dist/package.json +3 -0
- package/dist/probe.d.ts +35 -0
- package/dist/reporter.d.ts +20 -0
- package/dist/runner.d.ts +30 -0
- package/dist/types.d.ts +134 -0
- package/eslint.config.mjs +47 -0
- package/jest.config.mjs +26 -0
- package/package.json +64 -0
- package/src/__tests__/fixtures/http-server.ts +103 -0
- package/src/__tests__/fixtures/stdio-server.ts +158 -0
- package/src/__tests__/integration.test.ts +306 -0
- package/src/__tests__/runner.test.ts +430 -0
- package/src/cli.ts +421 -0
- package/src/diff.ts +252 -0
- package/src/git.ts +262 -0
- package/src/index.ts +284 -0
- package/src/logger.ts +93 -0
- package/src/probe.ts +327 -0
- package/src/reporter.ts +214 -0
- package/src/runner.ts +902 -0
- package/src/types.ts +155 -0
- 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,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>;
|