mcp-server-diff 2.1.0 → 2.1.5
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/README.md +108 -1
- package/dist/cli/index.js +205 -9
- package/dist/index.js +32 -5
- package/package.json +6 -1
- package/.github/dependabot.yml +0 -21
- package/.github/workflows/ci.yml +0 -51
- package/.github/workflows/publish.yml +0 -36
- package/.github/workflows/release.yml +0 -51
- package/.prettierignore +0 -3
- package/.prettierrc +0 -8
- package/CONTRIBUTING.md +0 -81
- package/action.yml +0 -250
- package/eslint.config.mjs +0 -47
- package/jest.config.mjs +0 -26
- package/src/__tests__/fixtures/http-server.ts +0 -103
- package/src/__tests__/fixtures/stdio-server.ts +0 -158
- package/src/__tests__/integration.test.ts +0 -306
- package/src/__tests__/runner.test.ts +0 -430
- package/src/cli.ts +0 -421
- package/src/diff.ts +0 -252
- package/src/git.ts +0 -262
- package/src/index.ts +0 -284
- package/src/logger.ts +0 -93
- package/src/probe.ts +0 -327
- package/src/reporter.ts +0 -214
- package/src/runner.ts +0 -902
- package/src/types.ts +0 -155
- package/tsconfig.json +0 -30
package/README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# MCP Server Diff
|
|
2
2
|
|
|
3
3
|
[](https://github.com/marketplace/actions/mcp-server-diff)
|
|
4
|
+
[](https://www.npmjs.com/package/mcp-server-diff)
|
|
4
5
|
[](https://github.com/SamMorrowDrums/mcp-server-diff/releases)
|
|
5
6
|
[](https://opensource.org/licenses/MIT)
|
|
6
7
|
|
|
7
|
-
A GitHub Action for diffing [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server **public interfaces** between versions.
|
|
8
|
+
A GitHub Action for diffing [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server **public interfaces** between versions. Compares the current branch against a baseline to surface any changes to your server's exposed tools, resources, prompts, and capabilities.
|
|
9
|
+
|
|
10
|
+
> **Also available as a standalone CLI** — see [CLI Documentation](#cli-tool) or install with `npx mcp-server-diff`
|
|
8
11
|
|
|
9
12
|
## Overview
|
|
10
13
|
|
|
@@ -496,6 +499,110 @@ jobs:
|
|
|
496
499
|
- Ensure the server binds to `0.0.0.0` or `127.0.0.1`, not just `localhost` on some systems
|
|
497
500
|
- Check firewall or container networking if running in Docker
|
|
498
501
|
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## CLI Tool
|
|
505
|
+
|
|
506
|
+
The CLI lets you diff any two MCP servers directly from your terminal—useful for local development, CI pipelines, or comparing servers across different implementations.
|
|
507
|
+
|
|
508
|
+
### Installation
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
# Run directly with npx (no install required)
|
|
512
|
+
npx mcp-server-diff --help
|
|
513
|
+
|
|
514
|
+
# Or install globally
|
|
515
|
+
npm install -g mcp-server-diff
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Basic Usage
|
|
519
|
+
|
|
520
|
+
```bash
|
|
521
|
+
# Compare two local stdio servers
|
|
522
|
+
npx mcp-server-diff -b "python -m mcp_server" -t "node dist/stdio.js"
|
|
523
|
+
|
|
524
|
+
# Compare local server vs remote HTTP endpoint
|
|
525
|
+
npx mcp-server-diff -b "go run ./cmd/server stdio" -t "https://mcp.example.com/api"
|
|
526
|
+
|
|
527
|
+
# Output formats
|
|
528
|
+
npx mcp-server-diff -b "..." -t "..." -o diff # Raw diff hunks only
|
|
529
|
+
npx mcp-server-diff -b "..." -t "..." -o json # Full JSON with details
|
|
530
|
+
npx mcp-server-diff -b "..." -t "..." -o markdown # Formatted report
|
|
531
|
+
npx mcp-server-diff -b "..." -t "..." -o summary # One-line summary (default)
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### HTTP Headers & Authentication
|
|
535
|
+
|
|
536
|
+
For authenticated HTTP endpoints, pass headers with `-H`:
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
# Direct header value
|
|
540
|
+
npx mcp-server-diff -b "./server" -t "https://api.example.com/mcp" \
|
|
541
|
+
-H "Authorization: Bearer your-token-here"
|
|
542
|
+
|
|
543
|
+
# Read from environment variable (keeps secrets out of shell history)
|
|
544
|
+
export MCP_TOKEN="your-secret-token"
|
|
545
|
+
npx mcp-server-diff -b "./server" -t "https://api.example.com/mcp" \
|
|
546
|
+
-H "Authorization: env:MCP_TOKEN"
|
|
547
|
+
|
|
548
|
+
# Prompt for secret interactively (hidden input)
|
|
549
|
+
npx mcp-server-diff -b "./server" -t "https://api.example.com/mcp" \
|
|
550
|
+
-H "Authorization: secret:"
|
|
551
|
+
|
|
552
|
+
# Multiple headers
|
|
553
|
+
npx mcp-server-diff -b "./server" -t "https://api.example.com/mcp" \
|
|
554
|
+
-H "Authorization: env:TOKEN" -H "X-Custom-Header: value"
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Config File
|
|
558
|
+
|
|
559
|
+
For complex comparisons or multiple targets, use a config file:
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
npx mcp-server-diff -c servers.json -o diff
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
```json
|
|
566
|
+
{
|
|
567
|
+
"base": {
|
|
568
|
+
"name": "python-server",
|
|
569
|
+
"transport": "stdio",
|
|
570
|
+
"start_command": "python -m mcp_server"
|
|
571
|
+
},
|
|
572
|
+
"targets": [
|
|
573
|
+
{
|
|
574
|
+
"name": "typescript-server",
|
|
575
|
+
"transport": "stdio",
|
|
576
|
+
"start_command": "node dist/stdio.js"
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
"name": "remote-server",
|
|
580
|
+
"transport": "streamable-http",
|
|
581
|
+
"server_url": "https://mcp.example.com/api",
|
|
582
|
+
"headers": {
|
|
583
|
+
"Authorization": "Bearer token"
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
]
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### CLI Options Reference
|
|
591
|
+
|
|
592
|
+
| Option | Description |
|
|
593
|
+
|--------|-------------|
|
|
594
|
+
| `-b, --base <cmd\|url>` | Base server command (stdio) or URL (http) |
|
|
595
|
+
| `-t, --target <cmd\|url>` | Target server command (stdio) or URL (http) |
|
|
596
|
+
| `-H, --header <header>` | HTTP header (repeatable). Use `env:VAR` or `secret:` for values |
|
|
597
|
+
| `-c, --config <file>` | Config file with base and targets |
|
|
598
|
+
| `-o, --output <format>` | Output: `diff`, `json`, `markdown`, `summary` (default) |
|
|
599
|
+
| `-v, --verbose` | Verbose output |
|
|
600
|
+
| `-q, --quiet` | Quiet mode (only output result) |
|
|
601
|
+
| `-h, --help` | Show help |
|
|
602
|
+
| `--version` | Show version |
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
499
606
|
## License
|
|
500
607
|
|
|
501
608
|
MIT License. See [LICENSE](LICENSE) for details.
|
package/dist/cli/index.js
CHANGED
|
@@ -38918,6 +38918,8 @@ var __webpack_exports__ = {};
|
|
|
38918
38918
|
var external_node_util_ = __nccwpck_require__(7975);
|
|
38919
38919
|
// EXTERNAL MODULE: external "fs"
|
|
38920
38920
|
var external_fs_ = __nccwpck_require__(9896);
|
|
38921
|
+
;// CONCATENATED MODULE: external "readline"
|
|
38922
|
+
const external_readline_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("readline");
|
|
38921
38923
|
;// CONCATENATED MODULE: ./node_modules/zod/v4/core/core.js
|
|
38922
38924
|
/** A special constant with type `never` */
|
|
38923
38925
|
const NEVER = Object.freeze({
|
|
@@ -56353,6 +56355,13 @@ const log = {
|
|
|
56353
56355
|
|
|
56354
56356
|
|
|
56355
56357
|
|
|
56358
|
+
/**
|
|
56359
|
+
* Check if an error is "Method not found" (-32601)
|
|
56360
|
+
*/
|
|
56361
|
+
function isMethodNotFound(error) {
|
|
56362
|
+
const errorStr = String(error);
|
|
56363
|
+
return errorStr.includes("-32601") || errorStr.includes("Method not found");
|
|
56364
|
+
}
|
|
56356
56365
|
/**
|
|
56357
56366
|
* Probes an MCP server and returns capability snapshots
|
|
56358
56367
|
*/
|
|
@@ -56425,7 +56434,12 @@ async function probeServer(options) {
|
|
|
56425
56434
|
log.info(` Listed ${result.tools.tools.length} tools`);
|
|
56426
56435
|
}
|
|
56427
56436
|
catch (error) {
|
|
56428
|
-
|
|
56437
|
+
if (isMethodNotFound(error)) {
|
|
56438
|
+
log.info(" Server does not implement tools/list");
|
|
56439
|
+
}
|
|
56440
|
+
else {
|
|
56441
|
+
log.warning(` Failed to list tools: ${error}`);
|
|
56442
|
+
}
|
|
56429
56443
|
}
|
|
56430
56444
|
}
|
|
56431
56445
|
else {
|
|
@@ -56439,7 +56453,12 @@ async function probeServer(options) {
|
|
|
56439
56453
|
log.info(` Listed ${result.prompts.prompts.length} prompts`);
|
|
56440
56454
|
}
|
|
56441
56455
|
catch (error) {
|
|
56442
|
-
|
|
56456
|
+
if (isMethodNotFound(error)) {
|
|
56457
|
+
log.info(" Server does not implement prompts/list");
|
|
56458
|
+
}
|
|
56459
|
+
else {
|
|
56460
|
+
log.warning(` Failed to list prompts: ${error}`);
|
|
56461
|
+
}
|
|
56443
56462
|
}
|
|
56444
56463
|
}
|
|
56445
56464
|
else {
|
|
@@ -56453,16 +56472,26 @@ async function probeServer(options) {
|
|
|
56453
56472
|
log.info(` Listed ${result.resources.resources.length} resources`);
|
|
56454
56473
|
}
|
|
56455
56474
|
catch (error) {
|
|
56456
|
-
|
|
56475
|
+
if (isMethodNotFound(error)) {
|
|
56476
|
+
log.info(" Server does not implement resources/list");
|
|
56477
|
+
}
|
|
56478
|
+
else {
|
|
56479
|
+
log.warning(` Failed to list resources: ${error}`);
|
|
56480
|
+
}
|
|
56457
56481
|
}
|
|
56458
|
-
// Also
|
|
56482
|
+
// Also try resource templates - some servers support resources but not templates
|
|
56459
56483
|
try {
|
|
56460
56484
|
const templatesResult = await client.listResourceTemplates();
|
|
56461
56485
|
result.resourceTemplates = templatesResult;
|
|
56462
56486
|
log.info(` Listed ${result.resourceTemplates.resourceTemplates.length} resource templates`);
|
|
56463
56487
|
}
|
|
56464
56488
|
catch (error) {
|
|
56465
|
-
|
|
56489
|
+
if (isMethodNotFound(error)) {
|
|
56490
|
+
log.info(" Server does not implement resources/templates/list");
|
|
56491
|
+
}
|
|
56492
|
+
else {
|
|
56493
|
+
log.warning(` Failed to list resource templates: ${error}`);
|
|
56494
|
+
}
|
|
56466
56495
|
}
|
|
56467
56496
|
}
|
|
56468
56497
|
else {
|
|
@@ -56831,6 +56860,7 @@ function diffsToMap(diffs) {
|
|
|
56831
56860
|
|
|
56832
56861
|
|
|
56833
56862
|
|
|
56863
|
+
|
|
56834
56864
|
/**
|
|
56835
56865
|
* Parse command line arguments
|
|
56836
56866
|
*/
|
|
@@ -56839,6 +56869,7 @@ function parseCliArgs() {
|
|
|
56839
56869
|
options: {
|
|
56840
56870
|
base: { type: "string", short: "b" },
|
|
56841
56871
|
target: { type: "string", short: "t" },
|
|
56872
|
+
header: { type: "string", short: "H", multiple: true },
|
|
56842
56873
|
config: { type: "string", short: "c" },
|
|
56843
56874
|
output: { type: "string", short: "o", default: "summary" },
|
|
56844
56875
|
verbose: { type: "boolean", short: "v", default: false },
|
|
@@ -56866,8 +56897,11 @@ USAGE:
|
|
|
56866
56897
|
OPTIONS:
|
|
56867
56898
|
-b, --base <command> Base server command (stdio) or URL (http)
|
|
56868
56899
|
-t, --target <command> Target server command (stdio) or URL (http)
|
|
56900
|
+
-H, --header <header> HTTP header (can be repeated, e.g. -H "Authorization: Bearer ...")
|
|
56901
|
+
Use env:VAR_NAME to read value from environment variable
|
|
56902
|
+
Use secret: to prompt for value securely (hidden input)
|
|
56869
56903
|
-c, --config <file> Config file with base and targets
|
|
56870
|
-
-o, --output <format> Output format: json, markdown, summary (default: summary)
|
|
56904
|
+
-o, --output <format> Output format: diff, json, markdown, summary (default: summary)
|
|
56871
56905
|
-v, --verbose Verbose output
|
|
56872
56906
|
-q, --quiet Quiet mode (only output diffs)
|
|
56873
56907
|
-h, --help Show this help
|
|
@@ -56890,6 +56924,7 @@ CONFIG FILE FORMAT:
|
|
|
56890
56924
|
}
|
|
56891
56925
|
|
|
56892
56926
|
OUTPUT FORMATS:
|
|
56927
|
+
diff - Raw diff output only
|
|
56893
56928
|
summary - One line per comparison (default)
|
|
56894
56929
|
json - Raw JSON with full diff details
|
|
56895
56930
|
markdown - Formatted markdown report
|
|
@@ -56906,6 +56941,18 @@ EXAMPLES:
|
|
|
56906
56941
|
|
|
56907
56942
|
# Output raw JSON for CI
|
|
56908
56943
|
mcp-server-diff -c servers.json -o json -q
|
|
56944
|
+
|
|
56945
|
+
# Compare with HTTP headers (for authenticated endpoints)
|
|
56946
|
+
mcp-server-diff -b "go run ./cmd/server stdio" -t "https://api.example.com/mcp" \\
|
|
56947
|
+
-H "Authorization: Bearer token" -o diff
|
|
56948
|
+
|
|
56949
|
+
# Use environment variable for secret (keeps token out of shell history)
|
|
56950
|
+
mcp-server-diff -b "./server" -t "https://api.example.com/mcp" \\
|
|
56951
|
+
-H "Authorization: env:MY_API_TOKEN"
|
|
56952
|
+
|
|
56953
|
+
# Prompt for secret interactively (hidden input)
|
|
56954
|
+
mcp-server-diff -b "./server" -t "https://api.example.com/mcp" \\
|
|
56955
|
+
-H "Authorization: secret:"
|
|
56909
56956
|
`);
|
|
56910
56957
|
}
|
|
56911
56958
|
/**
|
|
@@ -56925,12 +56972,13 @@ function loadConfig(configPath) {
|
|
|
56925
56972
|
/**
|
|
56926
56973
|
* Create a server config from a command string
|
|
56927
56974
|
*/
|
|
56928
|
-
function commandToConfig(command, name) {
|
|
56975
|
+
function commandToConfig(command, name, headers) {
|
|
56929
56976
|
if (command.startsWith("http://") || command.startsWith("https://")) {
|
|
56930
56977
|
return {
|
|
56931
56978
|
name,
|
|
56932
56979
|
transport: "streamable-http",
|
|
56933
56980
|
server_url: command,
|
|
56981
|
+
headers,
|
|
56934
56982
|
};
|
|
56935
56983
|
}
|
|
56936
56984
|
return {
|
|
@@ -56939,6 +56987,129 @@ function commandToConfig(command, name) {
|
|
|
56939
56987
|
start_command: command,
|
|
56940
56988
|
};
|
|
56941
56989
|
}
|
|
56990
|
+
/**
|
|
56991
|
+
* Parse header strings into a record
|
|
56992
|
+
* Accepts formats: "Header: value" or "Header=value"
|
|
56993
|
+
* Values starting with "env:" read from environment variables
|
|
56994
|
+
* Values starting with "secret:" will be prompted (collected separately)
|
|
56995
|
+
*/
|
|
56996
|
+
function parseHeaders(headerStrings, secretValues) {
|
|
56997
|
+
const headers = {};
|
|
56998
|
+
if (!headerStrings)
|
|
56999
|
+
return headers;
|
|
57000
|
+
for (const h of headerStrings) {
|
|
57001
|
+
const colonIdx = h.indexOf(":");
|
|
57002
|
+
const eqIdx = h.indexOf("=");
|
|
57003
|
+
const sepIdx = colonIdx > 0 ? colonIdx : eqIdx;
|
|
57004
|
+
if (sepIdx > 0) {
|
|
57005
|
+
const key = h.substring(0, sepIdx).trim();
|
|
57006
|
+
let value = h.substring(sepIdx + 1).trim();
|
|
57007
|
+
// Check for env: prefix to read from environment variable
|
|
57008
|
+
if (value.startsWith("env:")) {
|
|
57009
|
+
const envVar = value.substring(4);
|
|
57010
|
+
const envValue = process.env[envVar];
|
|
57011
|
+
if (!envValue) {
|
|
57012
|
+
throw new Error(`Environment variable ${envVar} not set (referenced in header ${key})`);
|
|
57013
|
+
}
|
|
57014
|
+
value = envValue;
|
|
57015
|
+
}
|
|
57016
|
+
else if (value.startsWith("secret:")) {
|
|
57017
|
+
// Use prompted secret value
|
|
57018
|
+
const secretKey = `${key}`;
|
|
57019
|
+
if (secretValues?.has(secretKey)) {
|
|
57020
|
+
value = secretValues.get(secretKey);
|
|
57021
|
+
}
|
|
57022
|
+
else {
|
|
57023
|
+
throw new Error(`Secret value for header ${key} not collected`);
|
|
57024
|
+
}
|
|
57025
|
+
}
|
|
57026
|
+
headers[key] = value;
|
|
57027
|
+
}
|
|
57028
|
+
}
|
|
57029
|
+
return headers;
|
|
57030
|
+
}
|
|
57031
|
+
/**
|
|
57032
|
+
* Find headers that need secret prompts
|
|
57033
|
+
*/
|
|
57034
|
+
function findSecretHeaders(headerStrings) {
|
|
57035
|
+
const secrets = [];
|
|
57036
|
+
if (!headerStrings)
|
|
57037
|
+
return secrets;
|
|
57038
|
+
for (const h of headerStrings) {
|
|
57039
|
+
const colonIdx = h.indexOf(":");
|
|
57040
|
+
const eqIdx = h.indexOf("=");
|
|
57041
|
+
const sepIdx = colonIdx > 0 ? colonIdx : eqIdx;
|
|
57042
|
+
if (sepIdx > 0) {
|
|
57043
|
+
const key = h.substring(0, sepIdx).trim();
|
|
57044
|
+
const value = h.substring(sepIdx + 1).trim();
|
|
57045
|
+
if (value.startsWith("secret:")) {
|
|
57046
|
+
secrets.push(key);
|
|
57047
|
+
}
|
|
57048
|
+
}
|
|
57049
|
+
}
|
|
57050
|
+
return secrets;
|
|
57051
|
+
}
|
|
57052
|
+
/**
|
|
57053
|
+
* Prompt for a secret value with hidden input
|
|
57054
|
+
*/
|
|
57055
|
+
async function promptSecret(prompt) {
|
|
57056
|
+
return new Promise((resolve) => {
|
|
57057
|
+
const rl = external_readline_namespaceObject.createInterface({
|
|
57058
|
+
input: process.stdin,
|
|
57059
|
+
output: process.stdout,
|
|
57060
|
+
});
|
|
57061
|
+
// Hide input by using raw mode if available
|
|
57062
|
+
if (process.stdin.isTTY) {
|
|
57063
|
+
process.stdout.write(`${prompt}: `);
|
|
57064
|
+
process.stdin.setRawMode(true);
|
|
57065
|
+
process.stdin.resume();
|
|
57066
|
+
let value = "";
|
|
57067
|
+
const onData = (char) => {
|
|
57068
|
+
const c = char.toString();
|
|
57069
|
+
if (c === "\n" || c === "\r") {
|
|
57070
|
+
process.stdin.setRawMode(false);
|
|
57071
|
+
process.stdin.removeListener("data", onData);
|
|
57072
|
+
rl.close();
|
|
57073
|
+
process.stdout.write("\n");
|
|
57074
|
+
resolve(value);
|
|
57075
|
+
}
|
|
57076
|
+
else if (c === "\u0003") {
|
|
57077
|
+
// Ctrl+C
|
|
57078
|
+
process.exit(1);
|
|
57079
|
+
}
|
|
57080
|
+
else if (c === "\u007F" || c === "\b") {
|
|
57081
|
+
// Backspace
|
|
57082
|
+
if (value.length > 0) {
|
|
57083
|
+
value = value.slice(0, -1);
|
|
57084
|
+
}
|
|
57085
|
+
}
|
|
57086
|
+
else {
|
|
57087
|
+
value += c;
|
|
57088
|
+
}
|
|
57089
|
+
};
|
|
57090
|
+
process.stdin.on("data", onData);
|
|
57091
|
+
}
|
|
57092
|
+
else {
|
|
57093
|
+
// Non-TTY: just read the line (won't be hidden)
|
|
57094
|
+
rl.question(`${prompt}: `, (answer) => {
|
|
57095
|
+
rl.close();
|
|
57096
|
+
resolve(answer);
|
|
57097
|
+
});
|
|
57098
|
+
}
|
|
57099
|
+
});
|
|
57100
|
+
}
|
|
57101
|
+
/**
|
|
57102
|
+
* Prompt for secret values with hidden input
|
|
57103
|
+
*/
|
|
57104
|
+
async function promptSecrets(headerNames) {
|
|
57105
|
+
const secrets = new Map();
|
|
57106
|
+
if (headerNames.length === 0)
|
|
57107
|
+
return secrets;
|
|
57108
|
+
for (const name of headerNames) {
|
|
57109
|
+
secrets.set(name, await promptSecret(`Enter value for header "${name}"`));
|
|
57110
|
+
}
|
|
57111
|
+
return secrets;
|
|
57112
|
+
}
|
|
56942
57113
|
/**
|
|
56943
57114
|
* Probe a server and return results
|
|
56944
57115
|
*/
|
|
@@ -57027,6 +57198,23 @@ async function runComparisons(config) {
|
|
|
57027
57198
|
}
|
|
57028
57199
|
return results;
|
|
57029
57200
|
}
|
|
57201
|
+
/**
|
|
57202
|
+
* Output raw diff only
|
|
57203
|
+
*/
|
|
57204
|
+
function outputDiff(results) {
|
|
57205
|
+
for (const result of results) {
|
|
57206
|
+
if (result.diffs.length > 0) {
|
|
57207
|
+
if (results.length > 1) {
|
|
57208
|
+
console.log(`# ${result.target}`);
|
|
57209
|
+
}
|
|
57210
|
+
for (const { endpoint, diff } of result.diffs) {
|
|
57211
|
+
console.log(`## ${endpoint}`);
|
|
57212
|
+
console.log(diff);
|
|
57213
|
+
console.log("");
|
|
57214
|
+
}
|
|
57215
|
+
}
|
|
57216
|
+
}
|
|
57217
|
+
}
|
|
57030
57218
|
/**
|
|
57031
57219
|
* Output results in summary format
|
|
57032
57220
|
*/
|
|
@@ -57133,7 +57321,7 @@ async function main() {
|
|
|
57133
57321
|
process.exit(0);
|
|
57134
57322
|
}
|
|
57135
57323
|
if (values.version) {
|
|
57136
|
-
console.log("mcp-server-diff v2.1.
|
|
57324
|
+
console.log("mcp-server-diff v2.1.1");
|
|
57137
57325
|
process.exit(0);
|
|
57138
57326
|
}
|
|
57139
57327
|
// Set up logger - CLI uses console logger by default
|
|
@@ -57148,9 +57336,14 @@ async function main() {
|
|
|
57148
57336
|
config = loadConfig(values.config);
|
|
57149
57337
|
}
|
|
57150
57338
|
else if (values.base && values.target) {
|
|
57339
|
+
const headerStrings = values.header;
|
|
57340
|
+
// Prompt for any secret: values before parsing
|
|
57341
|
+
const secretHeaderNames = findSecretHeaders(headerStrings);
|
|
57342
|
+
const secretValues = await promptSecrets(secretHeaderNames);
|
|
57343
|
+
const headers = parseHeaders(headerStrings, secretValues);
|
|
57151
57344
|
config = {
|
|
57152
57345
|
base: commandToConfig(values.base, "base"),
|
|
57153
|
-
targets: [commandToConfig(values.target, "target")],
|
|
57346
|
+
targets: [commandToConfig(values.target, "target", headers)],
|
|
57154
57347
|
};
|
|
57155
57348
|
}
|
|
57156
57349
|
else {
|
|
@@ -57161,6 +57354,9 @@ async function main() {
|
|
|
57161
57354
|
const results = await runComparisons(config);
|
|
57162
57355
|
const outputFormat = values.output || "summary";
|
|
57163
57356
|
switch (outputFormat) {
|
|
57357
|
+
case "diff":
|
|
57358
|
+
outputDiff(results);
|
|
57359
|
+
break;
|
|
57164
57360
|
case "json":
|
|
57165
57361
|
outputJson(results);
|
|
57166
57362
|
break;
|
package/dist/index.js
CHANGED
|
@@ -56605,6 +56605,13 @@ const log = {
|
|
|
56605
56605
|
|
|
56606
56606
|
|
|
56607
56607
|
|
|
56608
|
+
/**
|
|
56609
|
+
* Check if an error is "Method not found" (-32601)
|
|
56610
|
+
*/
|
|
56611
|
+
function isMethodNotFound(error) {
|
|
56612
|
+
const errorStr = String(error);
|
|
56613
|
+
return errorStr.includes("-32601") || errorStr.includes("Method not found");
|
|
56614
|
+
}
|
|
56608
56615
|
/**
|
|
56609
56616
|
* Probes an MCP server and returns capability snapshots
|
|
56610
56617
|
*/
|
|
@@ -56677,7 +56684,12 @@ async function probeServer(options) {
|
|
|
56677
56684
|
log.info(` Listed ${result.tools.tools.length} tools`);
|
|
56678
56685
|
}
|
|
56679
56686
|
catch (error) {
|
|
56680
|
-
|
|
56687
|
+
if (isMethodNotFound(error)) {
|
|
56688
|
+
log.info(" Server does not implement tools/list");
|
|
56689
|
+
}
|
|
56690
|
+
else {
|
|
56691
|
+
log.warning(` Failed to list tools: ${error}`);
|
|
56692
|
+
}
|
|
56681
56693
|
}
|
|
56682
56694
|
}
|
|
56683
56695
|
else {
|
|
@@ -56691,7 +56703,12 @@ async function probeServer(options) {
|
|
|
56691
56703
|
log.info(` Listed ${result.prompts.prompts.length} prompts`);
|
|
56692
56704
|
}
|
|
56693
56705
|
catch (error) {
|
|
56694
|
-
|
|
56706
|
+
if (isMethodNotFound(error)) {
|
|
56707
|
+
log.info(" Server does not implement prompts/list");
|
|
56708
|
+
}
|
|
56709
|
+
else {
|
|
56710
|
+
log.warning(` Failed to list prompts: ${error}`);
|
|
56711
|
+
}
|
|
56695
56712
|
}
|
|
56696
56713
|
}
|
|
56697
56714
|
else {
|
|
@@ -56705,16 +56722,26 @@ async function probeServer(options) {
|
|
|
56705
56722
|
log.info(` Listed ${result.resources.resources.length} resources`);
|
|
56706
56723
|
}
|
|
56707
56724
|
catch (error) {
|
|
56708
|
-
|
|
56725
|
+
if (isMethodNotFound(error)) {
|
|
56726
|
+
log.info(" Server does not implement resources/list");
|
|
56727
|
+
}
|
|
56728
|
+
else {
|
|
56729
|
+
log.warning(` Failed to list resources: ${error}`);
|
|
56730
|
+
}
|
|
56709
56731
|
}
|
|
56710
|
-
// Also
|
|
56732
|
+
// Also try resource templates - some servers support resources but not templates
|
|
56711
56733
|
try {
|
|
56712
56734
|
const templatesResult = await client.listResourceTemplates();
|
|
56713
56735
|
result.resourceTemplates = templatesResult;
|
|
56714
56736
|
log.info(` Listed ${result.resourceTemplates.resourceTemplates.length} resource templates`);
|
|
56715
56737
|
}
|
|
56716
56738
|
catch (error) {
|
|
56717
|
-
|
|
56739
|
+
if (isMethodNotFound(error)) {
|
|
56740
|
+
log.info(" Server does not implement resources/templates/list");
|
|
56741
|
+
}
|
|
56742
|
+
else {
|
|
56743
|
+
log.warning(` Failed to list resource templates: ${error}`);
|
|
56744
|
+
}
|
|
56718
56745
|
}
|
|
56719
56746
|
}
|
|
56720
56747
|
else {
|
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-server-diff",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.5",
|
|
4
4
|
"description": "Diff MCP server public interfaces - CLI tool and GitHub Action",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mcp-server-diff": "dist/cli/index.js"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
9
14
|
"type": "module",
|
|
10
15
|
"scripts": {
|
|
11
16
|
"build": "npm run build:action && npm run build:cli",
|
package/.github/dependabot.yml
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
version: 2
|
|
2
|
-
updates:
|
|
3
|
-
- package-ecosystem: "github-actions"
|
|
4
|
-
directory: "/"
|
|
5
|
-
schedule:
|
|
6
|
-
interval: "weekly"
|
|
7
|
-
commit-message:
|
|
8
|
-
prefix: "ci"
|
|
9
|
-
labels:
|
|
10
|
-
- "dependencies"
|
|
11
|
-
- "github-actions"
|
|
12
|
-
|
|
13
|
-
- package-ecosystem: "npm"
|
|
14
|
-
directory: "/probe"
|
|
15
|
-
schedule:
|
|
16
|
-
interval: "weekly"
|
|
17
|
-
commit-message:
|
|
18
|
-
prefix: "deps"
|
|
19
|
-
labels:
|
|
20
|
-
- "dependencies"
|
|
21
|
-
- "javascript"
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [main]
|
|
8
|
-
|
|
9
|
-
permissions:
|
|
10
|
-
contents: read
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
test:
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
strategy:
|
|
16
|
-
matrix:
|
|
17
|
-
node-version: [20, 22]
|
|
18
|
-
|
|
19
|
-
steps:
|
|
20
|
-
- name: Checkout
|
|
21
|
-
uses: actions/checkout@v4
|
|
22
|
-
|
|
23
|
-
- name: Setup Node.js ${{ matrix.node-version }}
|
|
24
|
-
uses: actions/setup-node@v6
|
|
25
|
-
with:
|
|
26
|
-
node-version: ${{ matrix.node-version }}
|
|
27
|
-
cache: 'npm'
|
|
28
|
-
|
|
29
|
-
- name: Install dependencies
|
|
30
|
-
run: npm ci
|
|
31
|
-
|
|
32
|
-
- name: Type Check
|
|
33
|
-
run: npm run typecheck
|
|
34
|
-
|
|
35
|
-
- name: Lint
|
|
36
|
-
run: npm run lint
|
|
37
|
-
|
|
38
|
-
- name: Check Formatting
|
|
39
|
-
run: npm run format:check
|
|
40
|
-
|
|
41
|
-
- name: Run Tests
|
|
42
|
-
run: npm test
|
|
43
|
-
|
|
44
|
-
- name: Build
|
|
45
|
-
run: npm run build
|
|
46
|
-
|
|
47
|
-
- name: Verify dist is up to date
|
|
48
|
-
if: matrix.node-version == 20
|
|
49
|
-
run: |
|
|
50
|
-
# Check if dist files are up to date
|
|
51
|
-
git diff --exit-code dist/ || (echo "::error::dist/ is out of date. Run 'npm run build' and commit the changes." && exit 1)
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
name: Publish to npm
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [published]
|
|
6
|
-
|
|
7
|
-
permissions:
|
|
8
|
-
contents: read
|
|
9
|
-
id-token: write # Required for npm OIDC provenance
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
publish:
|
|
13
|
-
runs-on: ubuntu-latest
|
|
14
|
-
steps:
|
|
15
|
-
- name: Checkout
|
|
16
|
-
uses: actions/checkout@v4
|
|
17
|
-
|
|
18
|
-
- name: Setup Node.js
|
|
19
|
-
uses: actions/setup-node@v4
|
|
20
|
-
with:
|
|
21
|
-
node-version: '22'
|
|
22
|
-
registry-url: 'https://registry.npmjs.org'
|
|
23
|
-
|
|
24
|
-
- name: Install dependencies
|
|
25
|
-
run: npm ci
|
|
26
|
-
|
|
27
|
-
- name: Run checks
|
|
28
|
-
run: npm run check
|
|
29
|
-
|
|
30
|
-
- name: Build
|
|
31
|
-
run: npm run build
|
|
32
|
-
|
|
33
|
-
# Tokenless publish using npm's OIDC trust
|
|
34
|
-
# Requires package to be linked to this repo on npmjs.com
|
|
35
|
-
- name: Publish to npm with provenance
|
|
36
|
-
run: npm publish --provenance --access public
|