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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unicode.js","sourceRoot":"","sources":["../../../../src/security/static/unicode.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,GAA4D;IAC1E,EAAE,IAAI,EAAE,sBAAsB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE;IACtG,EAAE,IAAI,EAAE,wBAAwB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,MAAM,IAAI,EAAE,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,MAAM,IAAI,EAAE,IAAI,MAAM,CAAC,EAAE;IACnH,EAAE,IAAI,EAAE,uBAAuB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,OAAO,IAAI,EAAE,IAAI,OAAO,EAAE;IAChF,EAAE,IAAI,EAAE,8BAA8B,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,MAAM,EAAE;CACvE,CAAC;AAQF,MAAM,UAAU,WAAW,CAAC,MAAsB;IAChD,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAuB,CAAC;QAC5C,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YAC1B,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YAC7B,IAAI,EAAE,KAAK,SAAS;gBAAE,SAAS;YAC/B,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;gBAC7B,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;oBAClB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,EAAU,CAAC;oBACpD,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBACZ,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;QACH,CAAC;QACD,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;YACzF,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,gBAAgB;gBACxB,KAAK,EAAE,YAAY;gBACnB,QAAQ,EAAE,MAAM;gBAChB,UAAU,EAAE,MAAM;gBAClB,OAAO,EAAE,UAAU,GAAG,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,KAAK,mCAAmC;gBAChG,QAAQ,EAAE,GAAG,CAAC,QAAQ;aACvB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,11 @@
1
+ // Deterministic hashing for schema snapshots: stable (key-sorted) stringify + SHA-256.
2
+ // (json-stable-stringify / JCS nuances can be swapped in later behind this one module.)
3
+ import { stringify } from "safe-stable-stringify";
4
+ import { createHash } from "node:crypto";
5
+ export function sha256(input) {
6
+ return createHash("sha256").update(input).digest("hex");
7
+ }
8
+ export function canonicalHash(obj) {
9
+ return sha256(stringify(obj) ?? "null");
10
+ }
11
+ //# sourceMappingURL=canonicalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonicalize.js","sourceRoot":"","sources":["../../../src/snapshot/canonicalize.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,wFAAwF;AACxF,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,OAAO,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { canonicalHash } from "./canonicalize.js";
2
+ export function capture(tools) {
3
+ const entries = {};
4
+ for (const t of tools) {
5
+ entries[t.name] = {
6
+ hash: canonicalHash({
7
+ name: t.name,
8
+ description: t.description,
9
+ inputSchema: t.inputSchema,
10
+ outputSchema: t.outputSchema,
11
+ annotations: t.annotations,
12
+ }),
13
+ shash: canonicalHash({
14
+ name: t.name,
15
+ inputSchema: t.inputSchema,
16
+ outputSchema: t.outputSchema,
17
+ annotations: t.annotations,
18
+ }),
19
+ };
20
+ }
21
+ const rootHash = canonicalHash(Object.keys(entries)
22
+ .sort()
23
+ .map((n) => [n, entries[n].hash]));
24
+ return { version: 1, createdAt: new Date().toISOString(), rootHash, tools: entries };
25
+ }
26
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../../../src/snapshot/capture.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAclD,MAAM,UAAU,OAAO,CAAC,KAAiB;IACvC,MAAM,OAAO,GAAiC,EAAE,CAAC;IACjD,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG;YAChB,IAAI,EAAE,aAAa,CAAC;gBAClB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,YAAY,EAAE,CAAC,CAAC,YAAY;gBAC5B,WAAW,EAAE,CAAC,CAAC,WAAW;aAC3B,CAAC;YACF,KAAK,EAAE,aAAa,CAAC;gBACnB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,YAAY,EAAE,CAAC,CAAC,YAAY;gBAC5B,WAAW,EAAE,CAAC,CAAC,WAAW;aAC3B,CAAC;SACH,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,aAAa,CAC5B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;SACjB,IAAI,EAAE;SACN,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CACpC,CAAC;IACF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACvF,CAAC"}
@@ -0,0 +1,26 @@
1
+ export function diff(prev, next) {
2
+ const added = [];
3
+ const removed = [];
4
+ const schemaChanged = [];
5
+ const descriptionOnly = [];
6
+ for (const name of Object.keys(next.tools))
7
+ if (!prev.tools[name])
8
+ added.push(name);
9
+ for (const name of Object.keys(prev.tools)) {
10
+ const a = prev.tools[name];
11
+ const b = next.tools[name];
12
+ if (!b) {
13
+ removed.push(name);
14
+ continue;
15
+ }
16
+ if (a.hash !== b.hash) {
17
+ if (a.shash !== b.shash)
18
+ schemaChanged.push(name);
19
+ else
20
+ descriptionOnly.push(name);
21
+ }
22
+ }
23
+ const changed = added.length + removed.length + schemaChanged.length + descriptionOnly.length > 0;
24
+ return { hasSnapshot: true, changed, added, removed, schemaChanged, descriptionOnly };
25
+ }
26
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../../src/snapshot/diff.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,IAAI,CAAC,IAAc,EAAE,IAAc;IACjD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,MAAM,eAAe,GAAa,EAAE,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEpF,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,SAAS;QACX,CAAC;QACD,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;YACtB,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;gBAAE,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;;gBAC7C,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,aAAa,CAAC,MAAM,GAAG,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC;IAClG,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,CAAC;AACxF,CAAC"}
@@ -0,0 +1,3 @@
1
+ // Shared, typed models. The whole pipeline produces a single `Report`.
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,uEAAuE"}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "meritmcp",
3
+ "version": "0.1.1",
4
+ "description": "The CI quality gate for MCP servers — functional tests + official conformance + OWASP-MCP-Top-10 security in one command, with SARIF, a 0-100 safety score, and a README badge.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "'//e0",
8
+ "homepage": "https://github.com/meritmcp/meritmcp#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/meritmcp/meritmcp.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/meritmcp/meritmcp/issues"
15
+ },
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "bin": {
20
+ "merit": "dist/src/cli.js"
21
+ },
22
+ "files": [
23
+ "dist/src",
24
+ "config",
25
+ "schemas",
26
+ "action.yml",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.json",
32
+ "dev": "tsx src/cli.ts",
33
+ "test": "vitest run",
34
+ "test:watch": "vitest",
35
+ "typecheck": "tsc -p tsconfig.json --noEmit",
36
+ "prepack": "npm run build",
37
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build",
38
+ "fixture:clean": "tsx fixtures/clean-server/server.ts",
39
+ "fixture:vulnerable": "tsx fixtures/vulnerable-server/server.ts",
40
+ "demo:pass": "npm run build && node dist/src/cli.js run --stdio \"node dist/fixtures/clean-server/server.js\" --tests merit.tests.yaml",
41
+ "demo:fail": "npm run build && node dist/src/cli.js run --stdio \"node dist/fixtures/vulnerable-server/server.js\" --tests merit.tests.yaml --probe"
42
+ },
43
+ "keywords": [
44
+ "mcp",
45
+ "model-context-protocol",
46
+ "security",
47
+ "conformance",
48
+ "testing",
49
+ "owasp",
50
+ "sarif",
51
+ "badge",
52
+ "ci"
53
+ ],
54
+ "dependencies": {
55
+ "@modelcontextprotocol/sdk": "^1.29.0",
56
+ "@modelcontextprotocol/conformance": "0.1.16",
57
+ "ajv": "^8.17.1",
58
+ "ajv-formats": "^3.0.1",
59
+ "commander": "^14.0.0",
60
+ "safe-stable-stringify": "^2.5.0",
61
+ "yaml": "^2.7.0",
62
+ "zod": "^3.25.0"
63
+ },
64
+ "devDependencies": {
65
+ "@types/node": "^22.10.0",
66
+ "tsx": "^4.19.0",
67
+ "typescript": "^5.7.0",
68
+ "vitest": "^2.1.0"
69
+ }
70
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://meritmcp.dev/schemas/tests.schema.json",
4
+ "title": "Merit functional test spec",
5
+ "type": "object",
6
+ "required": ["tests"],
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "version": { "const": 1 },
10
+ "target": {
11
+ "type": "object",
12
+ "additionalProperties": false,
13
+ "properties": {
14
+ "transport": { "enum": ["stdio", "http"] },
15
+ "command": { "type": "string" },
16
+ "url": { "type": "string" },
17
+ "env": { "type": "object", "additionalProperties": { "type": "string" } }
18
+ }
19
+ },
20
+ "tests": {
21
+ "type": "array",
22
+ "items": {
23
+ "type": "object",
24
+ "required": ["name", "op", "assert"],
25
+ "additionalProperties": false,
26
+ "properties": {
27
+ "name": { "type": "string" },
28
+ "op": { "enum": ["list_tools", "call_tool"] },
29
+ "tool": { "type": "string" },
30
+ "arguments": { "type": "object" },
31
+ "assert": {
32
+ "type": "object",
33
+ "additionalProperties": false,
34
+ "properties": {
35
+ "not_error": { "type": "boolean" },
36
+ "is_error": { "type": "boolean" },
37
+ "content_contains": { "type": "string" },
38
+ "content_matches": { "type": "string" },
39
+ "error_contains": { "type": "string" },
40
+ "tool_count": { "type": "integer" },
41
+ "contains_tools": { "type": "array", "items": { "type": "string" } },
42
+ "structured": {
43
+ "type": "object",
44
+ "additionalProperties": false,
45
+ "properties": { "json_schema": { "type": "object" } }
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }