mcp-server-diff 2.1.5 → 2.1.8
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 +17 -9
- package/dist/cli/index.js +74 -59
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -533,25 +533,26 @@ npx mcp-server-diff -b "..." -t "..." -o summary # One-line summary (default)
|
|
|
533
533
|
|
|
534
534
|
### HTTP Headers & Authentication
|
|
535
535
|
|
|
536
|
-
For authenticated HTTP endpoints, pass headers with `-H`:
|
|
536
|
+
For authenticated HTTP endpoints, pass headers with `-H` (target) or `--base-header`:
|
|
537
537
|
|
|
538
538
|
```bash
|
|
539
|
-
# Direct header value
|
|
539
|
+
# Direct header value for target
|
|
540
540
|
npx mcp-server-diff -b "./server" -t "https://api.example.com/mcp" \
|
|
541
541
|
-H "Authorization: Bearer your-token-here"
|
|
542
542
|
|
|
543
543
|
# Read from environment variable (keeps secrets out of shell history)
|
|
544
544
|
export MCP_TOKEN="your-secret-token"
|
|
545
545
|
npx mcp-server-diff -b "./server" -t "https://api.example.com/mcp" \
|
|
546
|
-
-H "Authorization: env:MCP_TOKEN"
|
|
546
|
+
-H "Authorization: Bearer env:MCP_TOKEN"
|
|
547
547
|
|
|
548
|
-
# Prompt for secret interactively (hidden input)
|
|
548
|
+
# Prompt for secret interactively (hidden input, named "token")
|
|
549
549
|
npx mcp-server-diff -b "./server" -t "https://api.example.com/mcp" \
|
|
550
|
-
-H "Authorization: secret:"
|
|
550
|
+
-H "Authorization: Bearer secret:token"
|
|
551
551
|
|
|
552
|
-
#
|
|
553
|
-
npx mcp-server-diff
|
|
554
|
-
-
|
|
552
|
+
# Headers for both sides (e.g., comparing two authenticated servers)
|
|
553
|
+
npx mcp-server-diff \
|
|
554
|
+
-b "https://api.example.com/v1/mcp" --base-header "Authorization: Bearer secret:v1token" \
|
|
555
|
+
-t "https://api.example.com/v2/mcp" -H "Authorization: Bearer secret:v2token"
|
|
555
556
|
```
|
|
556
557
|
|
|
557
558
|
### Config File
|
|
@@ -593,7 +594,9 @@ npx mcp-server-diff -c servers.json -o diff
|
|
|
593
594
|
|--------|-------------|
|
|
594
595
|
| `-b, --base <cmd\|url>` | Base server command (stdio) or URL (http) |
|
|
595
596
|
| `-t, --target <cmd\|url>` | Target server command (stdio) or URL (http) |
|
|
596
|
-
| `-H, --header <header>` | HTTP header (repeatable)
|
|
597
|
+
| `-H, --header <header>` | HTTP header for target (repeatable) |
|
|
598
|
+
| `-B, --base-header <header>` | HTTP header for base server (repeatable) |
|
|
599
|
+
| `-T, --target-header <header>` | HTTP header for target (same as `-H`) |
|
|
597
600
|
| `-c, --config <file>` | Config file with base and targets |
|
|
598
601
|
| `-o, --output <format>` | Output: `diff`, `json`, `markdown`, `summary` (default) |
|
|
599
602
|
| `-v, --verbose` | Verbose output |
|
|
@@ -601,6 +604,11 @@ npx mcp-server-diff -c servers.json -o diff
|
|
|
601
604
|
| `-h, --help` | Show help |
|
|
602
605
|
| `--version` | Show version |
|
|
603
606
|
|
|
607
|
+
**Header value patterns:**
|
|
608
|
+
- `Bearer your-token` — literal value
|
|
609
|
+
- `Bearer env:VAR_NAME` — read from environment variable
|
|
610
|
+
- `Bearer secret:name` — prompt once for "name", reuse if used multiple times
|
|
611
|
+
|
|
604
612
|
---
|
|
605
613
|
|
|
606
614
|
## License
|
package/dist/cli/index.js
CHANGED
|
@@ -56870,6 +56870,8 @@ function parseCliArgs() {
|
|
|
56870
56870
|
base: { type: "string", short: "b" },
|
|
56871
56871
|
target: { type: "string", short: "t" },
|
|
56872
56872
|
header: { type: "string", short: "H", multiple: true },
|
|
56873
|
+
"base-header": { type: "string", short: "B", multiple: true },
|
|
56874
|
+
"target-header": { type: "string", short: "T", multiple: true },
|
|
56873
56875
|
config: { type: "string", short: "c" },
|
|
56874
56876
|
output: { type: "string", short: "o", default: "summary" },
|
|
56875
56877
|
verbose: { type: "boolean", short: "v", default: false },
|
|
@@ -56895,17 +56897,18 @@ USAGE:
|
|
|
56895
56897
|
mcp-server-diff --config servers.json
|
|
56896
56898
|
|
|
56897
56899
|
OPTIONS:
|
|
56898
|
-
-b, --base <command>
|
|
56899
|
-
-t, --target <command>
|
|
56900
|
-
-H, --header <header>
|
|
56901
|
-
|
|
56902
|
-
|
|
56903
|
-
|
|
56904
|
-
-
|
|
56905
|
-
-
|
|
56906
|
-
-
|
|
56907
|
-
-
|
|
56908
|
-
|
|
56900
|
+
-b, --base <command> Base server command (stdio) or URL (http)
|
|
56901
|
+
-t, --target <command> Target server command (stdio) or URL (http)
|
|
56902
|
+
-H, --header <header> HTTP header for target (repeatable)
|
|
56903
|
+
-B, --base-header <header> HTTP header for base server (repeatable)
|
|
56904
|
+
-T, --target-header <hdr> HTTP header for target server (repeatable, same as -H)
|
|
56905
|
+
Values support: env:VAR_NAME, secret:name, "Bearer secret:token"
|
|
56906
|
+
-c, --config <file> Config file with base and targets
|
|
56907
|
+
-o, --output <format> Output format: diff, json, markdown, summary (default: summary)
|
|
56908
|
+
-v, --verbose Verbose output
|
|
56909
|
+
-q, --quiet Quiet mode (only output diffs)
|
|
56910
|
+
-h, --help Show this help
|
|
56911
|
+
--version Show version
|
|
56909
56912
|
|
|
56910
56913
|
CONFIG FILE FORMAT:
|
|
56911
56914
|
{
|
|
@@ -56990,8 +56993,10 @@ function commandToConfig(command, name, headers) {
|
|
|
56990
56993
|
/**
|
|
56991
56994
|
* Parse header strings into a record
|
|
56992
56995
|
* Accepts formats: "Header: value" or "Header=value"
|
|
56993
|
-
*
|
|
56994
|
-
*
|
|
56996
|
+
* Special value patterns:
|
|
56997
|
+
* env:VAR_NAME - reads from environment variable
|
|
56998
|
+
* secret:name - prompts for secret (name is the prompt label)
|
|
56999
|
+
* "Bearer secret:token" - prefix + secret (prompts for "token", prepends "Bearer ")
|
|
56995
57000
|
*/
|
|
56996
57001
|
function parseHeaders(headerStrings, secretValues) {
|
|
56997
57002
|
const headers = {};
|
|
@@ -57013,14 +57018,17 @@ function parseHeaders(headerStrings, secretValues) {
|
|
|
57013
57018
|
}
|
|
57014
57019
|
value = envValue;
|
|
57015
57020
|
}
|
|
57016
|
-
else if (value.
|
|
57017
|
-
//
|
|
57018
|
-
const
|
|
57019
|
-
if (
|
|
57020
|
-
|
|
57021
|
-
|
|
57022
|
-
|
|
57023
|
-
|
|
57021
|
+
else if (value.includes("secret:")) {
|
|
57022
|
+
// Replace secret:name with the prompted value
|
|
57023
|
+
const secretMatch = value.match(/secret:(\w+)/);
|
|
57024
|
+
if (secretMatch) {
|
|
57025
|
+
const secretName = secretMatch[1];
|
|
57026
|
+
if (secretValues?.has(secretName)) {
|
|
57027
|
+
value = value.replace(`secret:${secretName}`, secretValues.get(secretName));
|
|
57028
|
+
}
|
|
57029
|
+
else {
|
|
57030
|
+
throw new Error(`Secret value for "${secretName}" not collected`);
|
|
57031
|
+
}
|
|
57024
57032
|
}
|
|
57025
57033
|
}
|
|
57026
57034
|
headers[key] = value;
|
|
@@ -57029,21 +57037,19 @@ function parseHeaders(headerStrings, secretValues) {
|
|
|
57029
57037
|
return headers;
|
|
57030
57038
|
}
|
|
57031
57039
|
/**
|
|
57032
|
-
* Find
|
|
57040
|
+
* Find secrets that need prompts, returns array of {name, label} objects
|
|
57033
57041
|
*/
|
|
57034
|
-
function
|
|
57042
|
+
function findSecrets(headerStrings) {
|
|
57035
57043
|
const secrets = [];
|
|
57036
57044
|
if (!headerStrings)
|
|
57037
57045
|
return secrets;
|
|
57038
57046
|
for (const h of headerStrings) {
|
|
57039
|
-
const
|
|
57040
|
-
|
|
57041
|
-
|
|
57042
|
-
|
|
57043
|
-
|
|
57044
|
-
|
|
57045
|
-
if (value.startsWith("secret:")) {
|
|
57046
|
-
secrets.push(key);
|
|
57047
|
+
const secretMatch = h.match(/secret:(\w+)/);
|
|
57048
|
+
if (secretMatch) {
|
|
57049
|
+
const name = secretMatch[1];
|
|
57050
|
+
// Don't add duplicates
|
|
57051
|
+
if (!secrets.find((s) => s.name === name)) {
|
|
57052
|
+
secrets.push({ name, label: name });
|
|
57047
57053
|
}
|
|
57048
57054
|
}
|
|
57049
57055
|
}
|
|
@@ -57054,44 +57060,46 @@ function findSecretHeaders(headerStrings) {
|
|
|
57054
57060
|
*/
|
|
57055
57061
|
async function promptSecret(prompt) {
|
|
57056
57062
|
return new Promise((resolve) => {
|
|
57057
|
-
|
|
57058
|
-
input: process.stdin,
|
|
57059
|
-
output: process.stdout,
|
|
57060
|
-
});
|
|
57063
|
+
process.stdout.write(`${prompt}: `);
|
|
57061
57064
|
// Hide input by using raw mode if available
|
|
57062
|
-
if (process.stdin.isTTY) {
|
|
57063
|
-
process.stdout.write(`${prompt}: `);
|
|
57065
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
57064
57066
|
process.stdin.setRawMode(true);
|
|
57065
57067
|
process.stdin.resume();
|
|
57068
|
+
process.stdin.setEncoding("utf8");
|
|
57066
57069
|
let value = "";
|
|
57067
57070
|
const onData = (char) => {
|
|
57068
|
-
|
|
57069
|
-
if (c === "\n" || c === "\r") {
|
|
57071
|
+
if (char === "\n" || char === "\r" || char === "\u0004") {
|
|
57070
57072
|
process.stdin.setRawMode(false);
|
|
57073
|
+
process.stdin.pause();
|
|
57071
57074
|
process.stdin.removeListener("data", onData);
|
|
57072
|
-
rl.close();
|
|
57073
57075
|
process.stdout.write("\n");
|
|
57074
57076
|
resolve(value);
|
|
57075
57077
|
}
|
|
57076
|
-
else if (
|
|
57078
|
+
else if (char === "\u0003") {
|
|
57077
57079
|
// Ctrl+C
|
|
57080
|
+
process.stdin.setRawMode(false);
|
|
57081
|
+
process.stdout.write("\n");
|
|
57078
57082
|
process.exit(1);
|
|
57079
57083
|
}
|
|
57080
|
-
else if (
|
|
57084
|
+
else if (char === "\u007F" || char === "\b") {
|
|
57081
57085
|
// Backspace
|
|
57082
57086
|
if (value.length > 0) {
|
|
57083
57087
|
value = value.slice(0, -1);
|
|
57084
57088
|
}
|
|
57085
57089
|
}
|
|
57086
57090
|
else {
|
|
57087
|
-
value +=
|
|
57091
|
+
value += char;
|
|
57088
57092
|
}
|
|
57089
57093
|
};
|
|
57090
57094
|
process.stdin.on("data", onData);
|
|
57091
57095
|
}
|
|
57092
57096
|
else {
|
|
57093
|
-
// Non-TTY:
|
|
57094
|
-
rl
|
|
57097
|
+
// Non-TTY: use readline (won't be hidden)
|
|
57098
|
+
const rl = external_readline_namespaceObject.createInterface({
|
|
57099
|
+
input: process.stdin,
|
|
57100
|
+
output: process.stdout,
|
|
57101
|
+
});
|
|
57102
|
+
rl.question("", (answer) => {
|
|
57095
57103
|
rl.close();
|
|
57096
57104
|
resolve(answer);
|
|
57097
57105
|
});
|
|
@@ -57101,14 +57109,14 @@ async function promptSecret(prompt) {
|
|
|
57101
57109
|
/**
|
|
57102
57110
|
* Prompt for secret values with hidden input
|
|
57103
57111
|
*/
|
|
57104
|
-
async function promptSecrets(
|
|
57105
|
-
const
|
|
57106
|
-
if (
|
|
57107
|
-
return
|
|
57108
|
-
for (const name of
|
|
57109
|
-
|
|
57112
|
+
async function promptSecrets(secrets) {
|
|
57113
|
+
const values = new Map();
|
|
57114
|
+
if (secrets.length === 0)
|
|
57115
|
+
return values;
|
|
57116
|
+
for (const { name, label } of secrets) {
|
|
57117
|
+
values.set(name, await promptSecret(`Enter ${label}`));
|
|
57110
57118
|
}
|
|
57111
|
-
return
|
|
57119
|
+
return values;
|
|
57112
57120
|
}
|
|
57113
57121
|
/**
|
|
57114
57122
|
* Probe a server and return results
|
|
@@ -57336,14 +57344,21 @@ async function main() {
|
|
|
57336
57344
|
config = loadConfig(values.config);
|
|
57337
57345
|
}
|
|
57338
57346
|
else if (values.base && values.target) {
|
|
57339
|
-
|
|
57340
|
-
|
|
57341
|
-
const
|
|
57342
|
-
|
|
57343
|
-
|
|
57347
|
+
// Combine -H and --target-header for target, use --base-header for base
|
|
57348
|
+
const baseHeaderStrings = values["base-header"];
|
|
57349
|
+
const targetHeaderStrings = [
|
|
57350
|
+
...(values.header || []),
|
|
57351
|
+
...(values["target-header"] || []),
|
|
57352
|
+
];
|
|
57353
|
+
// Find all secrets needed from both header sets
|
|
57354
|
+
const allHeaderStrings = [...(baseHeaderStrings || []), ...targetHeaderStrings];
|
|
57355
|
+
const secrets = findSecrets(allHeaderStrings);
|
|
57356
|
+
const secretValues = await promptSecrets(secrets);
|
|
57357
|
+
const baseHeaders = parseHeaders(baseHeaderStrings, secretValues);
|
|
57358
|
+
const targetHeaders = parseHeaders(targetHeaderStrings.length > 0 ? targetHeaderStrings : undefined, secretValues);
|
|
57344
57359
|
config = {
|
|
57345
|
-
base: commandToConfig(values.base, "base"),
|
|
57346
|
-
targets: [commandToConfig(values.target, "target",
|
|
57360
|
+
base: commandToConfig(values.base, "base", baseHeaders),
|
|
57361
|
+
targets: [commandToConfig(values.target, "target", targetHeaders)],
|
|
57347
57362
|
};
|
|
57348
57363
|
}
|
|
57349
57364
|
else {
|