@yawlabs/mcp-compliance 0.14.2 → 0.14.4

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 CHANGED
@@ -9,9 +9,9 @@
9
9
 
10
10
  Built and maintained by [Yaw Labs](https://yaw.sh).
11
11
 
12
- [![Add to mcp.hosting](https://mcp.hosting/install-button.svg)](https://mcp.hosting/install?name=mcp-compliance&command=npx&args=-y%2C%40yawlabs%2Fmcp-compliance&description=Test%20any%20MCP%20server%20against%20the%20spec%20-%2088-test%20suite%20with%20letter-grade%20scoring&source=https%3A%2F%2Fgithub.com%2FYawLabs%2Fmcp-compliance)
12
+ [![Add to Yaw MCP](https://yaw.sh/yaw-mcp-button.svg)](https://yaw.sh/mcp/install?name=mcp-compliance&command=npx&args=-y%2C%40yawlabs%2Fmcp-compliance&description=Test%20any%20MCP%20server%20against%20the%20spec%20-%2088-test%20suite%20with%20letter-grade%20scoring&source=https%3A%2F%2Fgithub.com%2FYawLabs%2Fmcp-compliance)
13
13
 
14
- One click adds this to your [mcp.hosting](https://mcp.hosting) account so it syncs to every MCP client you use. Or install manually below.
14
+ One click adds this to your local Yaw MCP config so it's available in every Yaw Terminal session. Or install manually below.
15
15
 
16
16
  ## Why this tool?
17
17
 
@@ -32,20 +32,20 @@ This tool solves that:
32
32
  **Remote HTTP server:**
33
33
 
34
34
  ```bash
35
- npx @yawlabs/mcp-compliance test https://my-server.com/mcp
35
+ npx @yawlabs/mcp-compliance@latest test https://my-server.com/mcp
36
36
  ```
37
37
 
38
38
  **Local stdio server** (the vast majority of MCP servers on npm):
39
39
 
40
40
  ```bash
41
41
  # Pass the command directly, Inspector-style
42
- npx @yawlabs/mcp-compliance test npx @modelcontextprotocol/server-filesystem /tmp
42
+ npx @yawlabs/mcp-compliance@latest test npx @modelcontextprotocol/server-filesystem /tmp
43
43
 
44
44
  # Or a local build
45
- npx @yawlabs/mcp-compliance test node ./dist/server.js
45
+ npx @yawlabs/mcp-compliance@latest test node ./dist/server.js
46
46
 
47
47
  # With env vars
48
- npx @yawlabs/mcp-compliance test -E GITHUB_TOKEN=$GITHUB_TOKEN -- npx @modelcontextprotocol/server-github
48
+ npx @yawlabs/mcp-compliance@latest test -E GITHUB_TOKEN=$GITHUB_TOKEN -- npx @modelcontextprotocol/server-github
49
49
  ```
50
50
 
51
51
  **Install globally:**
@@ -421,27 +421,27 @@ Required tests are worth 70% of the score, optional tests 30%. See the [full sco
421
421
  ```yaml
422
422
  # GitHub Actions example
423
423
  - name: MCP Compliance Check
424
- run: npx @yawlabs/mcp-compliance test ${{ env.MCP_SERVER_URL }} --strict
424
+ run: npx @yawlabs/mcp-compliance@latest test ${{ env.MCP_SERVER_URL }} --strict
425
425
  ```
426
426
 
427
427
  ```yaml
428
428
  # With JSON output for parsing
429
429
  - name: MCP Compliance Check
430
430
  run: |
431
- npx @yawlabs/mcp-compliance test ${{ env.MCP_SERVER_URL }} --format json > compliance.json
431
+ npx @yawlabs/mcp-compliance@latest test ${{ env.MCP_SERVER_URL }} --format json > compliance.json
432
432
  cat compliance.json | jq '.grade'
433
433
  ```
434
434
 
435
435
  ```yaml
436
436
  # With retries for flaky network conditions
437
437
  - name: MCP Compliance Check
438
- run: npx @yawlabs/mcp-compliance test ${{ env.MCP_SERVER_URL }} --strict --retries 2 --timeout 30000
438
+ run: npx @yawlabs/mcp-compliance@latest test ${{ env.MCP_SERVER_URL }} --strict --retries 2 --timeout 30000
439
439
  ```
440
440
 
441
441
  ```yaml
442
442
  # SARIF output for GitHub Code Scanning
443
443
  - name: MCP Compliance Check
444
- run: npx @yawlabs/mcp-compliance test ${{ env.MCP_SERVER_URL }} --format sarif > compliance.sarif
444
+ run: npx @yawlabs/mcp-compliance@latest test ${{ env.MCP_SERVER_URL }} --format sarif > compliance.sarif
445
445
  - name: Upload SARIF
446
446
  uses: github/codeql-action/upload-sarif@v3
447
447
  with:
@@ -457,7 +457,7 @@ This package also exposes an MCP server with 3 tools that can be used from Claud
457
457
  **Claude Code (one-liner):**
458
458
 
459
459
  ```bash
460
- claude mcp add mcp-compliance -- npx -y @yawlabs/mcp-compliance mcp
460
+ claude mcp add mcp-compliance -- npx -y @yawlabs/mcp-compliance@latest mcp
461
461
  ```
462
462
 
463
463
  **Or create `.mcp.json` in your project root:**
@@ -469,7 +469,7 @@ macOS / Linux / WSL:
469
469
  "mcpServers": {
470
470
  "mcp-compliance": {
471
471
  "command": "npx",
472
- "args": ["-y", "@yawlabs/mcp-compliance", "mcp"]
472
+ "args": ["-y", "@yawlabs/mcp-compliance@latest", "mcp"]
473
473
  }
474
474
  }
475
475
  }
@@ -482,7 +482,7 @@ Windows:
482
482
  "mcpServers": {
483
483
  "mcp-compliance": {
484
484
  "command": "cmd",
485
- "args": ["/c", "npx", "-y", "@yawlabs/mcp-compliance", "mcp"]
485
+ "args": ["/c", "npx", "-y", "@yawlabs/mcp-compliance@latest", "mcp"]
486
486
  }
487
487
  }
488
488
  }
@@ -127,10 +127,17 @@ function createHttpTransport(opts) {
127
127
  }
128
128
  return out;
129
129
  }
130
- async function doRawRequest(method, body, extraHeaders, timeout) {
130
+ async function doRawRequest(method, body, extraHeaders, timeout, omitUserHeaders) {
131
+ const base = sessionHeaders();
132
+ if (omitUserHeaders && omitUserHeaders.length > 0) {
133
+ const drop = new Set(omitUserHeaders.map((h) => h.toLowerCase()));
134
+ for (const key of Object.keys(base)) {
135
+ if (drop.has(key.toLowerCase())) delete base[key];
136
+ }
137
+ }
131
138
  const headers = {
132
139
  Accept: "application/json, text/event-stream",
133
- ...sessionHeaders(),
140
+ ...base,
134
141
  ...extraHeaders
135
142
  };
136
143
  if (body !== void 0 && !("Content-Type" in headers) && !("content-type" in headers)) {
@@ -155,7 +162,7 @@ function createHttpTransport(opts) {
155
162
  async request(method, params, nextId, init) {
156
163
  const id = nextId();
157
164
  const body = JSON.stringify({ jsonrpc: "2.0", id, method, params: params ?? {} });
158
- const raw = await doRawRequest("POST", body, init.headers ?? {}, init.timeout);
165
+ const raw = await doRawRequest("POST", body, init.headers ?? {}, init.timeout, init.omitUserHeaders);
159
166
  const contentType = (raw.headers["content-type"] || "").toLowerCase();
160
167
  let parsed;
161
168
  if (contentType.includes("text/event-stream")) {
@@ -185,7 +192,7 @@ function createHttpTransport(opts) {
185
192
  },
186
193
  async notify(method, params, init) {
187
194
  const body = JSON.stringify({ jsonrpc: "2.0", method, ...params ? { params } : {} });
188
- const raw = await doRawRequest("POST", body, init.headers ?? {}, init.timeout);
195
+ const raw = await doRawRequest("POST", body, init.headers ?? {}, init.timeout, init.omitUserHeaders);
189
196
  return { statusCode: raw.statusCode, headers: raw.headers };
190
197
  },
191
198
  async close() {
@@ -1451,10 +1458,11 @@ async function runComplianceSuite(target, options = {}) {
1451
1458
  const retries = options.retries || 0;
1452
1459
  let sessionId = null;
1453
1460
  let negotiatedProtocolVersion = null;
1454
- async function mcpRequest(_backendUrl, method, params, idCounter, extraHeaders, timeoutMs) {
1461
+ async function mcpRequest(_backendUrl, method, params, idCounter, extraHeaders, timeoutMs, omitUserHeaders) {
1455
1462
  const res = await transport.request(method, params, idCounter, {
1456
1463
  timeout: timeoutMs,
1457
- headers: extraHeaders
1464
+ headers: extraHeaders,
1465
+ omitUserHeaders
1458
1466
  });
1459
1467
  return {
1460
1468
  statusCode: res.statusCode ?? 200,
@@ -3168,7 +3176,9 @@ async function runComplianceSuite(target, options = {}) {
3168
3176
  const noAuthHeaders = {};
3169
3177
  if (sessionId) noAuthHeaders["mcp-session-id"] = sessionId;
3170
3178
  try {
3171
- const res = await mcpRequest(backendUrl, "ping", void 0, nextId, noAuthHeaders, timeout);
3179
+ const res = await mcpRequest(backendUrl, "ping", void 0, nextId, noAuthHeaders, timeout, [
3180
+ "authorization"
3181
+ ]);
3172
3182
  if (res.statusCode === 401 || res.statusCode === 403) {
3173
3183
  return { passed: true, details: `HTTP ${res.statusCode} (unauthenticated request rejected)` };
3174
3184
  }
@@ -3191,7 +3201,9 @@ async function runComplianceSuite(target, options = {}) {
3191
3201
  const noAuthHeaders = {};
3192
3202
  if (sessionId) noAuthHeaders["mcp-session-id"] = sessionId;
3193
3203
  try {
3194
- const res = await mcpRequest(backendUrl, "ping", void 0, nextId, noAuthHeaders, timeout);
3204
+ const res = await mcpRequest(backendUrl, "ping", void 0, nextId, noAuthHeaders, timeout, [
3205
+ "authorization"
3206
+ ]);
3195
3207
  if (res.statusCode === 401) {
3196
3208
  const wwwAuth = res.headers["www-authenticate"];
3197
3209
  if (wwwAuth) {
@@ -3226,7 +3238,9 @@ async function runComplianceSuite(target, options = {}) {
3226
3238
  };
3227
3239
  if (sessionId) malformedHeaders["mcp-session-id"] = sessionId;
3228
3240
  try {
3229
- const res = await mcpRequest(backendUrl, "ping", void 0, nextId, malformedHeaders, timeout);
3241
+ const res = await mcpRequest(backendUrl, "ping", void 0, nextId, malformedHeaders, timeout, [
3242
+ "authorization"
3243
+ ]);
3230
3244
  if (res.statusCode === 401 || res.statusCode === 403) {
3231
3245
  return { passed: true, details: `HTTP ${res.statusCode} (malformed auth rejected)` };
3232
3246
  }
@@ -3316,7 +3330,9 @@ async function runComplianceSuite(target, options = {}) {
3316
3330
  "mcp-session-id": sessionId
3317
3331
  };
3318
3332
  try {
3319
- const res = await mcpRequest(backendUrl, "ping", void 0, nextId, sessionOnlyHeaders, timeout);
3333
+ const res = await mcpRequest(backendUrl, "ping", void 0, nextId, sessionOnlyHeaders, timeout, [
3334
+ "authorization"
3335
+ ]);
3320
3336
  if (res.statusCode === 401 || res.statusCode === 403) {
3321
3337
  return { passed: true, details: `HTTP ${res.statusCode} (session ID alone not sufficient for auth)` };
3322
3338
  }
package/dist/index.js CHANGED
@@ -117,10 +117,17 @@ function createHttpTransport(opts) {
117
117
  }
118
118
  return out;
119
119
  }
120
- async function doRawRequest(method, body, extraHeaders, timeout) {
120
+ async function doRawRequest(method, body, extraHeaders, timeout, omitUserHeaders) {
121
+ const base = sessionHeaders();
122
+ if (omitUserHeaders && omitUserHeaders.length > 0) {
123
+ const drop = new Set(omitUserHeaders.map((h) => h.toLowerCase()));
124
+ for (const key of Object.keys(base)) {
125
+ if (drop.has(key.toLowerCase())) delete base[key];
126
+ }
127
+ }
121
128
  const headers = {
122
129
  Accept: "application/json, text/event-stream",
123
- ...sessionHeaders(),
130
+ ...base,
124
131
  ...extraHeaders
125
132
  };
126
133
  if (body !== void 0 && !("Content-Type" in headers) && !("content-type" in headers)) {
@@ -145,7 +152,7 @@ function createHttpTransport(opts) {
145
152
  async request(method, params, nextId, init) {
146
153
  const id = nextId();
147
154
  const body = JSON.stringify({ jsonrpc: "2.0", id, method, params: params ?? {} });
148
- const raw = await doRawRequest("POST", body, init.headers ?? {}, init.timeout);
155
+ const raw = await doRawRequest("POST", body, init.headers ?? {}, init.timeout, init.omitUserHeaders);
149
156
  const contentType = (raw.headers["content-type"] || "").toLowerCase();
150
157
  let parsed;
151
158
  if (contentType.includes("text/event-stream")) {
@@ -175,7 +182,7 @@ function createHttpTransport(opts) {
175
182
  },
176
183
  async notify(method, params, init) {
177
184
  const body = JSON.stringify({ jsonrpc: "2.0", method, ...params ? { params } : {} });
178
- const raw = await doRawRequest("POST", body, init.headers ?? {}, init.timeout);
185
+ const raw = await doRawRequest("POST", body, init.headers ?? {}, init.timeout, init.omitUserHeaders);
179
186
  return { statusCode: raw.statusCode, headers: raw.headers };
180
187
  },
181
188
  async close() {
@@ -1808,10 +1815,11 @@ async function runComplianceSuite(target, options = {}) {
1808
1815
  const retries = options.retries || 0;
1809
1816
  let sessionId = null;
1810
1817
  let negotiatedProtocolVersion = null;
1811
- async function mcpRequest(_backendUrl, method, params, idCounter, extraHeaders, timeoutMs) {
1818
+ async function mcpRequest(_backendUrl, method, params, idCounter, extraHeaders, timeoutMs, omitUserHeaders) {
1812
1819
  const res = await transport.request(method, params, idCounter, {
1813
1820
  timeout: timeoutMs,
1814
- headers: extraHeaders
1821
+ headers: extraHeaders,
1822
+ omitUserHeaders
1815
1823
  });
1816
1824
  return {
1817
1825
  statusCode: res.statusCode ?? 200,
@@ -3525,7 +3533,9 @@ async function runComplianceSuite(target, options = {}) {
3525
3533
  const noAuthHeaders = {};
3526
3534
  if (sessionId) noAuthHeaders["mcp-session-id"] = sessionId;
3527
3535
  try {
3528
- const res = await mcpRequest(backendUrl, "ping", void 0, nextId, noAuthHeaders, timeout);
3536
+ const res = await mcpRequest(backendUrl, "ping", void 0, nextId, noAuthHeaders, timeout, [
3537
+ "authorization"
3538
+ ]);
3529
3539
  if (res.statusCode === 401 || res.statusCode === 403) {
3530
3540
  return { passed: true, details: `HTTP ${res.statusCode} (unauthenticated request rejected)` };
3531
3541
  }
@@ -3548,7 +3558,9 @@ async function runComplianceSuite(target, options = {}) {
3548
3558
  const noAuthHeaders = {};
3549
3559
  if (sessionId) noAuthHeaders["mcp-session-id"] = sessionId;
3550
3560
  try {
3551
- const res = await mcpRequest(backendUrl, "ping", void 0, nextId, noAuthHeaders, timeout);
3561
+ const res = await mcpRequest(backendUrl, "ping", void 0, nextId, noAuthHeaders, timeout, [
3562
+ "authorization"
3563
+ ]);
3552
3564
  if (res.statusCode === 401) {
3553
3565
  const wwwAuth = res.headers["www-authenticate"];
3554
3566
  if (wwwAuth) {
@@ -3583,7 +3595,9 @@ async function runComplianceSuite(target, options = {}) {
3583
3595
  };
3584
3596
  if (sessionId) malformedHeaders["mcp-session-id"] = sessionId;
3585
3597
  try {
3586
- const res = await mcpRequest(backendUrl, "ping", void 0, nextId, malformedHeaders, timeout);
3598
+ const res = await mcpRequest(backendUrl, "ping", void 0, nextId, malformedHeaders, timeout, [
3599
+ "authorization"
3600
+ ]);
3587
3601
  if (res.statusCode === 401 || res.statusCode === 403) {
3588
3602
  return { passed: true, details: `HTTP ${res.statusCode} (malformed auth rejected)` };
3589
3603
  }
@@ -3673,7 +3687,9 @@ async function runComplianceSuite(target, options = {}) {
3673
3687
  "mcp-session-id": sessionId
3674
3688
  };
3675
3689
  try {
3676
- const res = await mcpRequest(backendUrl, "ping", void 0, nextId, sessionOnlyHeaders, timeout);
3690
+ const res = await mcpRequest(backendUrl, "ping", void 0, nextId, sessionOnlyHeaders, timeout, [
3691
+ "authorization"
3692
+ ]);
3677
3693
  if (res.statusCode === 401 || res.statusCode === 403) {
3678
3694
  return { passed: true, details: `HTTP ${res.statusCode} (session ID alone not sufficient for auth)` };
3679
3695
  }
@@ -2,7 +2,7 @@ import {
2
2
  SPEC_BASE,
3
3
  TEST_DEFINITIONS,
4
4
  runComplianceSuite
5
- } from "../chunk-6PF56RRO.js";
5
+ } from "../chunk-2CXRMEZ3.js";
6
6
 
7
7
  // src/mcp/server.ts
8
8
  import { existsSync, readFileSync, realpathSync } from "fs";
package/dist/runner.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  previewTests,
11
11
  runComplianceSuite,
12
12
  urlHash
13
- } from "./chunk-6PF56RRO.js";
13
+ } from "./chunk-2CXRMEZ3.js";
14
14
  export {
15
15
  SPEC_BASE,
16
16
  SPEC_VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/mcp-compliance",
3
- "version": "0.14.2",
3
+ "version": "0.14.4",
4
4
  "mcpName": "io.github.YawLabs/mcp-compliance",
5
5
  "description": "CLI tool and MCP server that tests MCP servers for spec compliance",
6
6
  "license": "MIT",
@@ -20,7 +20,7 @@
20
20
  "main": "./dist/runner.js",
21
21
  "types": "./dist/runner.d.ts",
22
22
  "bin": {
23
- "mcp-compliance": "./dist/index.js"
23
+ "mcp-compliance": "dist/index.js"
24
24
  },
25
25
  "files": [
26
26
  "dist",