meritmcp 0.1.1

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.

Potentially problematic release.


This version of meritmcp might be problematic. Click here for more details.

Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/action.yml +120 -0
  4. package/config/scoring.config.json +9 -0
  5. package/dist/src/adapter/mcpClient.js +72 -0
  6. package/dist/src/adapter/mcpClient.js.map +1 -0
  7. package/dist/src/cli.js +121 -0
  8. package/dist/src/cli.js.map +1 -0
  9. package/dist/src/conformance/stdio-proxy.js +102 -0
  10. package/dist/src/conformance/stdio-proxy.js.map +1 -0
  11. package/dist/src/conformance/wrapper.js +139 -0
  12. package/dist/src/conformance/wrapper.js.map +1 -0
  13. package/dist/src/functional/asserts.js +42 -0
  14. package/dist/src/functional/asserts.js.map +1 -0
  15. package/dist/src/functional/parsers.js +44 -0
  16. package/dist/src/functional/parsers.js.map +1 -0
  17. package/dist/src/functional/runner.js +27 -0
  18. package/dist/src/functional/runner.js.map +1 -0
  19. package/dist/src/orchestrator.js +96 -0
  20. package/dist/src/orchestrator.js.map +1 -0
  21. package/dist/src/report/badge.js +10 -0
  22. package/dist/src/report/badge.js.map +1 -0
  23. package/dist/src/report/console.js +100 -0
  24. package/dist/src/report/console.js.map +1 -0
  25. package/dist/src/report/prcomment.js +52 -0
  26. package/dist/src/report/prcomment.js.map +1 -0
  27. package/dist/src/report/sarif.js +118 -0
  28. package/dist/src/report/sarif.js.map +1 -0
  29. package/dist/src/report/score.js +59 -0
  30. package/dist/src/report/score.js.map +1 -0
  31. package/dist/src/security/engine.js +27 -0
  32. package/dist/src/security/engine.js.map +1 -0
  33. package/dist/src/security/owasp-map.js +56 -0
  34. package/dist/src/security/owasp-map.js.map +1 -0
  35. package/dist/src/security/probe/authz.js +17 -0
  36. package/dist/src/security/probe/authz.js.map +1 -0
  37. package/dist/src/security/probe/injection.js +79 -0
  38. package/dist/src/security/probe/injection.js.map +1 -0
  39. package/dist/src/security/static/deps-osv.js +129 -0
  40. package/dist/src/security/static/deps-osv.js.map +1 -0
  41. package/dist/src/security/static/secrets.js +57 -0
  42. package/dist/src/security/static/secrets.js.map +1 -0
  43. package/dist/src/security/static/unicode.js +37 -0
  44. package/dist/src/security/static/unicode.js.map +1 -0
  45. package/dist/src/snapshot/canonicalize.js +11 -0
  46. package/dist/src/snapshot/canonicalize.js.map +1 -0
  47. package/dist/src/snapshot/capture.js +26 -0
  48. package/dist/src/snapshot/capture.js.map +1 -0
  49. package/dist/src/snapshot/diff.js +26 -0
  50. package/dist/src/snapshot/diff.js.map +1 -0
  51. package/dist/src/types.js +3 -0
  52. package/dist/src/types.js.map +1 -0
  53. package/package.json +70 -0
  54. package/schemas/tests.schema.json +53 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 '//e0
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # Merit
2
+
3
+ > **The CI quality gate for MCP servers** — one command that runs functional tests, the official MCP conformance suite, and OWASP-MCP-Top-10 security checks, then emits a PASS/FAIL verdict, a 0–100 safety score, SARIF for GitHub code scanning, and a README badge.
4
+
5
+ [![Merit](https://img.shields.io/badge/Merit-100%2F100%20·%20passing-brightgreen)](https://github.com/meritmcp/meritmcp)
6
+ [![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
7
+
8
+ > ⚠️ **Status: alpha (0.1.x).** Wraps two fast-moving upstreams (`@modelcontextprotocol/sdk`, `@modelcontextprotocol/conformance`) which are pinned and isolated behind adapters. APIs may change before 1.0.
9
+
10
+ ---
11
+
12
+ ## Why
13
+
14
+ MCP servers are shipped fast and "vibe-tested." There's no standard way to answer, in one shot: *is my server functionally correct, spec-conformant, and not leaking secrets or running shell input?* Merit is that one shot — and the bundle is the point: **conformance ≠ correct ≠ secure.** A server can pass the official conformance suite while returning wrong answers and exposing a command-injection tool.
15
+
16
+ ## Quickstart
17
+
18
+ ```bash
19
+ # Point it at a stdio server…
20
+ npx meritmcp run --stdio "node dist/server.js"
21
+
22
+ # …or a Streamable HTTP server
23
+ npx meritmcp run --http https://your-host/mcp
24
+ ```
25
+
26
+ You'll get a verdict like:
27
+
28
+ ```
29
+ Merit — PASS · Safety score 93/100
30
+
31
+ Functional: 12/12 passed
32
+ Conformance: 8/8 applicable scenarios passed (score 100/100) · 22 N/A skipped
33
+ Security: 1 medium (score 96/100)
34
+ ▲ [MCP07:2025] Server accepted an unauthenticated MCP session …
35
+ ```
36
+
37
+ Exit code is `0` on PASS, `1` on FAIL (gate your CI), `2` on a setup error.
38
+
39
+ ## What it checks
40
+
41
+ | Engine | What | Weight |
42
+ |---|---|---|
43
+ | **Functional** | Your YAML tests (tool calls + assertions) **and** schema-snapshot drift, incl. description-only "rug-pull" detection | 30% |
44
+ | **Conformance** | The **official** MCP conformance suite, wrapped — never reimplemented. Capability- & transport-aware so a tools-only server isn't punished for unimplemented optional features | 30% |
45
+ | **Security** | OWASP-MCP-Top-10. Static (always on): MCP01 secrets · MCP03a invisible-Unicode poisoning · MCP04 dependency CVEs via [OSV.dev](https://osv.dev). Live probes (**opt-in `--probe`**, only on a server you control): MCP05 command-injection · MCP07 HTTP auth | 40% |
46
+
47
+ > The conformance suite is HTTP-only. For stdio servers, Merit transparently spins up an in-process stdio→HTTP proxy so the official suite can test them.
48
+
49
+ ## CLI
50
+
51
+ ```bash
52
+ meritmcp run --stdio "<command>" | --http <url> [options]
53
+ --tests <path> YAML functional spec (default: merit.tests.yaml)
54
+ --snapshot <path> schema snapshot file (default: merit.snapshot.json)
55
+ --src <dir> server source dir → enables dependency CVE scanning (OSV)
56
+ --conformance-baseline <path> YAML of expected conformance failures
57
+ --min-score <n> fail if the score is below n
58
+ --sarif <path> write SARIF 2.1.0 (GitHub code scanning)
59
+ --out <path> write the full report JSON (open, reproducible scoring)
60
+ --badge <path> write a shields.io endpoint badge JSON
61
+ --pr-comment <path> write the PR-comment markdown
62
+ --probe run LIVE security probes (side effects! only on servers you control)
63
+ --no-security | --no-conformance
64
+ --json print the raw report JSON
65
+
66
+ meritmcp snapshot --stdio "<command>" --out merit.snapshot.json # capture/refresh the tool surface
67
+ meritmcp init # write a starter merit.tests.yaml
68
+ ```
69
+
70
+ ### Functional test spec (`merit.tests.yaml`)
71
+
72
+ ```yaml
73
+ version: 1
74
+ tests:
75
+ - name: "tool catalog is stable"
76
+ op: list_tools
77
+ assert:
78
+ contains_tools: [search, fetch]
79
+ - name: "add returns the sum"
80
+ op: call_tool
81
+ tool: add
82
+ arguments: { a: 2, b: 3 }
83
+ assert:
84
+ not_error: true
85
+ content_contains: "5"
86
+ structured:
87
+ json_schema: { type: object, required: [sum], properties: { sum: { const: 5 } } }
88
+ ```
89
+
90
+ ## GitHub Action
91
+
92
+ ```yaml
93
+ # .github/workflows/meritmcp.yml
94
+ name: Merit
95
+ on: [pull_request]
96
+ permissions:
97
+ contents: read
98
+ security-events: write # upload SARIF
99
+ pull-requests: write # sticky PR comment
100
+ jobs:
101
+ meritmcp:
102
+ runs-on: ubuntu-latest
103
+ steps:
104
+ - uses: actions/checkout@v4
105
+ - run: npm ci && npm run build
106
+ - uses: meritmcp/meritmcp@v0 # or: meritmcp/meritmcp@<sha>
107
+ with:
108
+ command: "node dist/server.js" # or: url: https://your-host/mcp
109
+ src: "." # enable dependency CVE scanning
110
+ min-score: "70"
111
+ ```
112
+
113
+ The Action runs Merit, uploads SARIF to the **Security** tab, and posts a sticky PR comment with the verdict. Outputs: `score`, `verdict`.
114
+
115
+ ## The badge
116
+
117
+ `meritmcp run … --badge badge.json` writes a [shields.io endpoint](https://shields.io/endpoint) file. Publish it (gh-pages / a Gist) and embed:
118
+
119
+ ```markdown
120
+ ![Merit](https://img.shields.io/endpoint?url=https://<you>.github.io/<repo>/badge.json)
121
+ ```
122
+
123
+ ## Scoring (open + reproducible)
124
+
125
+ ```
126
+ score = 0.30·functional + 0.30·conformance + 0.40·security
127
+ ```
128
+
129
+ - Engines that don't run have their weight redistributed across the rest.
130
+ - **Security cap:** any *high-confidence* Critical/High finding caps the total at **49 and forces FAIL** — a pretty score can't hide a real hole.
131
+ - A description-only schema change (rug-pull) costs 15 points.
132
+ - Every weight lives in [`config/scoring.config.json`](./config/scoring.config.json) and every finding's contribution is in `merit.json`, so anyone can recompute the score by hand.
133
+
134
+ Low-confidence/heuristic findings are reported as notes — they never hard-FAIL. Detection methodology and confidence per OWASP risk: [`04-SECURITY-OWASP.md`](./04-SECURITY-OWASP.md).
135
+
136
+ ## Development
137
+
138
+ ```bash
139
+ npm install
140
+ npm run demo:pass # green PASS against a clean fixture
141
+ npm run demo:fail # red FAIL against a deliberately broken+insecure fixture
142
+ npm test # unit tests
143
+ ```
144
+
145
+ Design docs: [`01-DESIGN.md`](./01-DESIGN.md) · [`03-ARCHITECTURE.md`](./03-ARCHITECTURE.md) · [`04-SECURITY-OWASP.md`](./04-SECURITY-OWASP.md).
146
+
147
+ ## License
148
+
149
+ [MIT](./LICENSE)
package/action.yml ADDED
@@ -0,0 +1,120 @@
1
+ name: "Merit"
2
+ description: "The CI quality gate for MCP servers — functional + official conformance + OWASP-MCP-Top-10 security, with a SARIF report and a score badge."
3
+ branding:
4
+ icon: "shield"
5
+ color: "green"
6
+
7
+ inputs:
8
+ command:
9
+ description: "stdio command to launch the MCP server (e.g. 'node dist/server.js'). Use this OR url."
10
+ required: false
11
+ url:
12
+ description: "Streamable HTTP MCP server URL. Use this OR command."
13
+ required: false
14
+ tests:
15
+ description: "Path to the YAML functional test spec."
16
+ required: false
17
+ default: "merit.tests.yaml"
18
+ src:
19
+ description: "Server source dir (enables dependency CVE scanning via OSV)."
20
+ required: false
21
+ snapshot:
22
+ description: "Schema snapshot file."
23
+ required: false
24
+ default: "merit.snapshot.json"
25
+ min-score:
26
+ description: "Fail the job if the score is below this (0-100)."
27
+ required: false
28
+ default: "0"
29
+ conformance:
30
+ description: "Run the official MCP conformance suite ('true'/'false')."
31
+ required: false
32
+ default: "true"
33
+ conformance-baseline:
34
+ description: "YAML of expected conformance failures (baseline)."
35
+ required: false
36
+ version:
37
+ description: "meritmcp npm version / dist-tag to run."
38
+ required: false
39
+ default: "latest"
40
+ node-version:
41
+ description: "Node version to set up."
42
+ required: false
43
+ default: "20"
44
+ comment-pr:
45
+ description: "Post a sticky PR comment with the results ('true'/'false')."
46
+ required: false
47
+ default: "true"
48
+
49
+ outputs:
50
+ score:
51
+ description: "The 0-100 safety score."
52
+ value: ${{ steps.meritmcp.outputs.score }}
53
+ verdict:
54
+ description: "PASS or FAIL."
55
+ value: ${{ steps.meritmcp.outputs.verdict }}
56
+
57
+ runs:
58
+ using: "composite"
59
+ steps:
60
+ - uses: actions/setup-node@v4
61
+ with:
62
+ node-version: ${{ inputs.node-version }}
63
+
64
+ - id: meritmcp
65
+ shell: bash
66
+ env:
67
+ IN_COMMAND: ${{ inputs.command }}
68
+ IN_URL: ${{ inputs.url }}
69
+ IN_TESTS: ${{ inputs.tests }}
70
+ IN_SRC: ${{ inputs.src }}
71
+ IN_SNAPSHOT: ${{ inputs.snapshot }}
72
+ IN_MIN_SCORE: ${{ inputs.min-score }}
73
+ IN_CONFORMANCE: ${{ inputs.conformance }}
74
+ IN_BASELINE: ${{ inputs.conformance-baseline }}
75
+ IN_VERSION: ${{ inputs.version }}
76
+ run: |
77
+ set -u
78
+ ARGS=(run --tests "$IN_TESTS" --snapshot "$IN_SNAPSHOT" --min-score "$IN_MIN_SCORE"
79
+ --sarif merit.sarif --out merit.json --badge merit-badge.json --pr-comment merit-pr.md)
80
+ [ -n "$IN_COMMAND" ] && ARGS+=(--stdio "$IN_COMMAND")
81
+ [ -n "$IN_URL" ] && ARGS+=(--http "$IN_URL")
82
+ [ -n "$IN_SRC" ] && ARGS+=(--src "$IN_SRC")
83
+ [ "$IN_CONFORMANCE" = "false" ] && ARGS+=(--no-conformance)
84
+ [ -n "$IN_BASELINE" ] && ARGS+=(--conformance-baseline "$IN_BASELINE")
85
+
86
+ set +e
87
+ npx -y "meritmcp@${IN_VERSION}" "${ARGS[@]}"
88
+ CODE=$?
89
+ set -e
90
+
91
+ if [ -f merit.json ]; then
92
+ echo "score=$(node -p "require('./merit.json').score")" >> "$GITHUB_OUTPUT"
93
+ echo "verdict=$(node -p "require('./merit.json').verdict")" >> "$GITHUB_OUTPUT"
94
+ fi
95
+ exit $CODE
96
+
97
+ - name: Upload SARIF to code scanning
98
+ if: ${{ always() && hashFiles('merit.sarif') != '' }}
99
+ uses: github/codeql-action/upload-sarif@v3
100
+ with:
101
+ sarif_file: merit.sarif
102
+
103
+ - name: Sticky PR comment
104
+ if: ${{ always() && github.event_name == 'pull_request' && inputs.comment-pr == 'true' }}
105
+ uses: actions/github-script@v7
106
+ with:
107
+ script: |
108
+ const fs = require('fs');
109
+ if (!fs.existsSync('merit-pr.md')) return;
110
+ const body = fs.readFileSync('merit-pr.md', 'utf8');
111
+ const marker = '<!-- merit-report -->';
112
+ const { owner, repo } = context.repo;
113
+ const issue_number = context.issue.number;
114
+ const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number });
115
+ const existing = comments.find((c) => c.body && c.body.includes(marker));
116
+ if (existing) {
117
+ await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
118
+ } else {
119
+ await github.rest.issues.createComment({ owner, repo, issue_number, body });
120
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "_comment": "Open, reproducible scoring weights. Mirrors DEFAULT_SCORING in src/report/score.ts. Score = 0.30*Functional + 0.30*Conformance + 0.40*Security; engines that don't run have their weight redistributed.",
3
+ "weights": { "functional": 0.3, "conformance": 0.3, "security": 0.4 },
4
+ "securityPenalties": { "critical": 40, "high": 20, "medium": 8, "low": 3 },
5
+ "confidenceMultiplier": { "high": 1.0, "medium": 0.5, "low": 0.2 },
6
+ "rugPullPenalty": 15,
7
+ "capScore": 49,
8
+ "bands": { "brightgreen": 85, "green": 70, "yellow": 50, "red": 30 }
9
+ }
@@ -0,0 +1,72 @@
1
+ // THE single SDK chokepoint. Every @modelcontextprotocol/sdk call lives here so
2
+ // the coming v2 package split is a one-file migration (see 03-ARCHITECTURE.md).
3
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
5
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
6
+ export class McpClient {
7
+ client;
8
+ constructor(name = "merit", version = "0.1.0") {
9
+ this.client = new Client({ name, version }, { capabilities: {} });
10
+ }
11
+ async connect(opts) {
12
+ if (opts.transport === "http") {
13
+ if (!opts.url)
14
+ throw new Error('HTTP transport requires --http <url>');
15
+ await this.client.connect(new StreamableHTTPClientTransport(new URL(opts.url)));
16
+ return;
17
+ }
18
+ if (!opts.command)
19
+ throw new Error('stdio transport requires --stdio "<command>"');
20
+ const { command, args } = splitCommand(opts.command);
21
+ await this.client.connect(new StdioClientTransport({ command, args, env: { ...currentEnv(), ...(opts.env ?? {}) } }));
22
+ }
23
+ /** Enumerate all tools, following pagination cursors. */
24
+ async listTools() {
25
+ const out = [];
26
+ let cursor;
27
+ do {
28
+ const page = (await this.client.listTools(cursor ? { cursor } : undefined));
29
+ out.push(...(page.tools ?? []));
30
+ cursor = page.nextCursor;
31
+ } while (cursor);
32
+ return out;
33
+ }
34
+ async callTool(name, args) {
35
+ const res = (await this.client.callTool({ name, arguments: args }));
36
+ const text = (res.content ?? [])
37
+ .filter((c) => c.type === "text" && typeof c.text === "string")
38
+ .map((c) => c.text)
39
+ .join("\n");
40
+ return { text, isError: Boolean(res.isError), structured: res.structuredContent, raw: res };
41
+ }
42
+ /** Server capabilities negotiated during initialize (available after connect). */
43
+ serverCapabilities() {
44
+ return (this.client.getServerCapabilities() ?? {});
45
+ }
46
+ async close() {
47
+ try {
48
+ await this.client.close();
49
+ }
50
+ catch {
51
+ /* server may already be gone; ignore */
52
+ }
53
+ }
54
+ }
55
+ /** Split a shell-ish command string into command + args, respecting quotes. */
56
+ export function splitCommand(cmd) {
57
+ const matches = cmd.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [];
58
+ const parts = matches.map((s) => s.replace(/^["']|["']$/g, ""));
59
+ const [command, ...args] = parts;
60
+ if (!command)
61
+ throw new Error(`could not parse command: ${cmd}`);
62
+ return { command, args };
63
+ }
64
+ /** process.env with undefined values dropped (StdioClientTransport wants Record<string,string>). */
65
+ export function currentEnv() {
66
+ const out = {};
67
+ for (const [k, v] of Object.entries(process.env))
68
+ if (typeof v === "string")
69
+ out[k] = v;
70
+ return out;
71
+ }
72
+ //# sourceMappingURL=mcpClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcpClient.js","sourceRoot":"","sources":["../../../src/adapter/mcpClient.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,gFAAgF;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAwBnG,MAAM,OAAO,SAAS;IACZ,MAAM,CAAS;IAEvB,YAAY,IAAI,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO;QAC3C,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,IAAoB;QAChC,IAAI,IAAI,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;YAC9B,IAAI,CAAC,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;YACvE,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,6BAA6B,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAChF,OAAO;QACT,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;QACnF,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrD,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CACvB,IAAI,oBAAoB,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,GAAG,UAAU,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAC3F,CAAC;IACJ,CAAC;IAED,yDAAyD;IACzD,KAAK,CAAC,SAAS;QACb,MAAM,GAAG,GAAe,EAAE,CAAC;QAC3B,IAAI,MAA0B,CAAC;QAC/B,GAAG,CAAC;YACF,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAGzE,CAAC;YACF,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;YAChC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;QAC3B,CAAC,QAAQ,MAAM,EAAE;QACjB,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,IAA6B;QACxD,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAIjE,CAAC;QACF,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;aAC7B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;aAC9D,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAc,CAAC;aAC5B,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,iBAAiB,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAC9F,CAAC;IAED,kFAAkF;IAClF,kBAAkB;QAChB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,qBAAqB,EAAE,IAAI,EAAE,CAA4B,CAAC;IAChF,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,wCAAwC;QAC1C,CAAC;IACH,CAAC;CACF;AAED,+EAA+E;AAC/E,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,gCAAgC,CAAC,IAAI,EAAE,CAAC;IAClE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,CAAC;IAChE,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC;IACjC,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,EAAE,CAAC,CAAC;IACjE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,UAAU;IACxB,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACxF,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { writeFileSync, existsSync } from "node:fs";
4
+ import { run } from "./orchestrator.js";
5
+ import { McpClient } from "./adapter/mcpClient.js";
6
+ import { capture } from "./snapshot/capture.js";
7
+ import { printReport } from "./report/console.js";
8
+ import { toSarif } from "./report/sarif.js";
9
+ import { toBadge } from "./report/badge.js";
10
+ import { toMarkdown } from "./report/prcomment.js";
11
+ import { DEFAULT_SCORING } from "./report/score.js";
12
+ const STARTER_TESTS = `version: 1
13
+ # Merit functional tests. Point the CLI at your server with --stdio / --http.
14
+ tests:
15
+ - name: "tool catalog is stable"
16
+ op: list_tools
17
+ assert:
18
+ contains_tools: [] # e.g. [search, fetch]
19
+ # - name: "add returns the sum"
20
+ # op: call_tool
21
+ # tool: add
22
+ # arguments: { a: 2, b: 3 }
23
+ # assert:
24
+ # not_error: true
25
+ # content_contains: "5"
26
+ `;
27
+ const program = new Command();
28
+ program.name("merit").description("The CI quality gate for MCP servers").version("0.1.0");
29
+ function connectOpts(opts) {
30
+ return opts.http ? { transport: "http", url: opts.http } : { transport: "stdio", command: opts.stdio };
31
+ }
32
+ program
33
+ .command("run")
34
+ .description("Connect to an MCP server and run the quality gate: functional tests + schema-drift + official conformance + OWASP-MCP-Top-10 security.")
35
+ .option("--stdio <command>", 'launch a stdio MCP server, e.g. "node dist/server.js"')
36
+ .option("--http <url>", "connect to a Streamable HTTP MCP server URL")
37
+ .option("--tests <path>", "YAML functional test spec", "merit.tests.yaml")
38
+ .option("--snapshot <path>", "schema snapshot file", "merit.snapshot.json")
39
+ .option("--src <dir>", "server source dir (enables dependency CVE scanning via OSV)")
40
+ .option("--no-security", "skip the OWASP-MCP-Top-10 security checks")
41
+ .option("--probe", "run LIVE security probes — sends canary payloads to the server's tools (may trigger real tool actions; only use on a server you control). Off by default.", false)
42
+ .option("--no-conformance", "skip the official MCP conformance suite")
43
+ .option("--conformance-baseline <path>", "YAML of expected conformance failures (baseline)")
44
+ .option("--min-score <n>", "fail if the score is below this", "0")
45
+ .option("--sarif <path>", "write SARIF 2.1.0 (for GitHub code scanning)")
46
+ .option("--out <path>", "write the full report JSON (open, reproducible scoring)")
47
+ .option("--badge <path>", "write a shields.io endpoint badge JSON")
48
+ .option("--pr-comment <path>", "write the PR-comment markdown")
49
+ .option("--json", "print the raw report as JSON instead of the console verdict", false)
50
+ .action(async (opts) => {
51
+ if (!opts.stdio && !opts.http) {
52
+ console.error('✖ provide --stdio "<command>" or --http <url>');
53
+ process.exit(2);
54
+ }
55
+ try {
56
+ const report = await run({
57
+ connect: connectOpts(opts),
58
+ testsPath: opts.tests,
59
+ snapshotPath: opts.snapshot,
60
+ srcDir: opts.src,
61
+ security: opts.security,
62
+ probe: opts.probe,
63
+ conformance: opts.conformance,
64
+ conformanceBaseline: opts.conformanceBaseline,
65
+ });
66
+ if (opts.sarif)
67
+ writeFileSync(opts.sarif, JSON.stringify(toSarif(report), null, 2));
68
+ if (opts.badge)
69
+ writeFileSync(opts.badge, JSON.stringify(toBadge(report), null, 2));
70
+ if (opts.prComment)
71
+ writeFileSync(opts.prComment, toMarkdown(report));
72
+ if (opts.out)
73
+ writeFileSync(opts.out, JSON.stringify({ ...report, scoring: DEFAULT_SCORING, generatedAt: new Date().toISOString() }, null, 2));
74
+ if (opts.json)
75
+ console.log(JSON.stringify(report, null, 2));
76
+ else
77
+ printReport(report);
78
+ const ok = report.verdict === "PASS" && report.score >= Number(opts.minScore);
79
+ process.exit(ok ? 0 : 1);
80
+ }
81
+ catch (e) {
82
+ console.error(`✖ ${e.message}`);
83
+ process.exit(2);
84
+ }
85
+ });
86
+ program
87
+ .command("snapshot")
88
+ .description("Capture/refresh the schema snapshot of a server's tool surface.")
89
+ .option("--stdio <command>", "launch a stdio MCP server")
90
+ .option("--http <url>", "connect to a Streamable HTTP MCP server URL")
91
+ .option("--out <path>", "snapshot output file", "merit.snapshot.json")
92
+ .action(async (opts) => {
93
+ if (!opts.stdio && !opts.http) {
94
+ console.error("✖ provide --stdio or --http");
95
+ process.exit(2);
96
+ }
97
+ const client = new McpClient();
98
+ try {
99
+ await client.connect(connectOpts(opts));
100
+ const snap = capture(await client.listTools());
101
+ writeFileSync(opts.out, JSON.stringify(snap, null, 2));
102
+ console.log(`✔ wrote ${opts.out} (rootHash ${snap.rootHash.slice(0, 12)}…, ${Object.keys(snap.tools).length} tools)`);
103
+ }
104
+ finally {
105
+ await client.close();
106
+ }
107
+ });
108
+ program
109
+ .command("init")
110
+ .description("Write a starter merit.tests.yaml.")
111
+ .option("--out <path>", "output path", "merit.tests.yaml")
112
+ .action((opts) => {
113
+ if (existsSync(opts.out)) {
114
+ console.error(`✖ ${opts.out} already exists`);
115
+ process.exit(1);
116
+ }
117
+ writeFileSync(opts.out, STARTER_TESTS);
118
+ console.log(`✔ wrote ${opts.out} — edit it, then: merit run --stdio "node dist/server.js"`);
119
+ });
120
+ program.parseAsync();
121
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ // stdio→HTTP proxy. The official conformance suite is HTTP-only (--url, no --stdio),
2
+ // so to test a stdio server we expose it over Streamable HTTP here. This is a transparent
3
+ // JSON-RPC relay: an HTTP server transport (facing conformance) bridged to a fresh stdio
4
+ // child per session (the server-under-test). Messages are forwarded verbatim — no parsing.
5
+ import { createServer } from "node:http";
6
+ import { randomUUID } from "node:crypto";
7
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
8
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
9
+ import { splitCommand, currentEnv } from "../adapter/mcpClient.js";
10
+ export async function startStdioHttpProxy(commandString, path = "/mcp") {
11
+ const { command, args } = splitCommand(commandString);
12
+ const env = currentEnv();
13
+ const sessions = new Map();
14
+ async function newSession() {
15
+ const child = new StdioClientTransport({ command, args, env });
16
+ const http = new StreamableHTTPServerTransport({
17
+ sessionIdGenerator: () => randomUUID(),
18
+ onsessioninitialized: (sid) => {
19
+ sessions.set(sid, { http, close });
20
+ },
21
+ });
22
+ // Idempotent teardown — both transports' onclose route here, and so does proxy.close(),
23
+ // so the underlying handles are only closed once (avoids libuv double-close on Windows).
24
+ let closed = false;
25
+ async function close() {
26
+ if (closed)
27
+ return;
28
+ closed = true;
29
+ await http.close().catch(() => { });
30
+ await child.close().catch(() => { });
31
+ }
32
+ // Transparent bidirectional relay (verbatim JSON-RPC).
33
+ http.onmessage = (msg) => void child.send(msg).catch(() => { });
34
+ child.onmessage = (msg) => void http.send(msg).catch(() => { });
35
+ http.onclose = () => void close();
36
+ child.onclose = () => void close();
37
+ await child.start();
38
+ await http.start();
39
+ return http;
40
+ }
41
+ const server = createServer(async (req, res) => {
42
+ try {
43
+ if (!req.url || !req.url.startsWith(path)) {
44
+ res.statusCode = 404;
45
+ res.end();
46
+ return;
47
+ }
48
+ const sid = req.headers["mcp-session-id"];
49
+ let transport = sid ? sessions.get(sid)?.http : undefined;
50
+ const body = await readBody(req);
51
+ if (!transport) {
52
+ if (isInitialize(body)) {
53
+ transport = await newSession();
54
+ }
55
+ else {
56
+ res.statusCode = 400;
57
+ res.setHeader("content-type", "application/json");
58
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message: "No valid session" }, id: null }));
59
+ return;
60
+ }
61
+ }
62
+ await transport.handleRequest(req, res, body);
63
+ }
64
+ catch {
65
+ if (!res.headersSent) {
66
+ res.statusCode = 500;
67
+ res.end();
68
+ }
69
+ }
70
+ });
71
+ await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
72
+ const port = server.address().port;
73
+ return {
74
+ url: `http://127.0.0.1:${port}${path}`,
75
+ close: async () => {
76
+ for (const s of sessions.values())
77
+ await s.close();
78
+ await new Promise((resolve) => server.close(() => resolve()));
79
+ },
80
+ };
81
+ }
82
+ function isInitialize(body) {
83
+ const first = Array.isArray(body) ? body[0] : body;
84
+ return Boolean(first) && typeof first === "object" && first.method === "initialize";
85
+ }
86
+ async function readBody(req) {
87
+ if (req.method !== "POST")
88
+ return undefined;
89
+ const chunks = [];
90
+ for await (const c of req)
91
+ chunks.push(c);
92
+ const raw = Buffer.concat(chunks).toString("utf8");
93
+ if (!raw)
94
+ return undefined;
95
+ try {
96
+ return JSON.parse(raw);
97
+ }
98
+ catch {
99
+ return raw;
100
+ }
101
+ }
102
+ //# sourceMappingURL=stdio-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio-proxy.js","sourceRoot":"","sources":["../../../src/conformance/stdio-proxy.ts"],"names":[],"mappings":"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"}