mcp-app-lint 0.1.6 → 0.1.7

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
@@ -4,16 +4,64 @@ SARIF findings about what an MCP App declares and what its HTML does. Meant to b
4
4
  scanners and CI, or by hand on a package, a directory, or the JSON a server returned.
5
5
 
6
6
  ```
7
+ npx mcp-app-lint --url https://example.com/mcp # a running server over Streamable HTTP
8
+ npx mcp-app-lint --stdio "node dist/server.js" # a local server, started as a process
7
9
  npx mcp-app-lint --dir ./my-server # a package directory, built or source
8
10
  npx mcp-app-lint --package @scope/name # an npm package: downloaded, scanned, deleted
9
11
  npx mcp-app-lint --read read.json --list list.json --tools tools.json
10
- # results of resources/read, resources/list, tools/list
12
+ # saved results of resources/read, resources/list, tools/list
11
13
  npx mcp-app-lint --html app.html # one UI document
12
14
  npx mcp-app-lint --a2ui stream.jsonl # A2UI v0.9 messages: one, a JSON array, { messages } or JSONL
15
+ npx mcp-app-lint --html dist/app.html --suggest-csp # the _meta.ui.csp the document needs
16
+ npx mcp-app-lint --mcp # run as an MCP server over stdio (see below)
13
17
  ```
14
18
 
15
19
  Options: `--out file.sarif`, `--format sarif|text`, `--fail-on error|warning|note|none` (default
16
- `error`; the exit code is 1 when a finding reaches the threshold, 2 on usage errors).
20
+ `error`; the exit code is 1 when a finding reaches the threshold, 2 on usage errors and on a
21
+ server that cannot be reached), `--header "Name: value"` (repeatable, for `--url`), `--timeout ms`
22
+ (per request to a live server, default 20000), `--suggest-csp`.
23
+
24
+ ## A running server
25
+
26
+ `--url` and `--stdio` ask the server what it publishes, the same way a host does: `initialize`,
27
+ `resources/list`, `resources/read` for every UI resource (`text/html` or `ui://`), `tools/list`.
28
+ They never call a tool. Findings point at the resource (`ui://…`) or at `tools/list`, and a summary
29
+ of what was read goes to stderr, so stdout stays a clean SARIF log.
30
+
31
+ ```
32
+ npx mcp-app-lint --url https://example.com/mcp --header "Authorization: Bearer $TOKEN" --format text
33
+ npx mcp-app-lint --stdio "node dist/index.js --stdio" --format text
34
+ ```
35
+
36
+ `--url` speaks Streamable HTTP (JSON or SSE answers, session header, a `DELETE` to end the session).
37
+ A 401 or 403 means the server wants credentials: pass them with `--header`. `--stdio` runs the
38
+ command through the shell in the current directory, keeps the server's stderr out of the output
39
+ (its last lines go into the error if it exits), answers its `ping`, and closes stdin when done. The
40
+ SARIF log records the command line with header values, URL credentials and query values, and
41
+ `NAME=value` assignments in the `--stdio` command masked, since code scanning stores it.
42
+
43
+ ## Suggested CSP
44
+
45
+ `--suggest-csp` prints, instead of findings, the `_meta.ui.csp` each UI document needs, as JSON
46
+ keyed by document (or one block per document with `--format text`):
47
+
48
+ ```json
49
+ {
50
+ "ui://weather/view": {
51
+ "connectDomains": ["https://api.weather.example", "wss://live.weather.example"],
52
+ "resourceDomains": ["https://fonts.googleapis.com", "https://fonts.gstatic.com", "https://img.weather.example"]
53
+ }
54
+ }
55
+ ```
56
+
57
+ The keys follow the host formula in the specification: `connectDomains` for `fetch`, `WebSocket`,
58
+ `EventSource`, XHR and `sendBeacon`; `resourceDomains` for scripts (including module imports from a
59
+ URL), stylesheets and CSS `url()`/`@import`, images (`src`, `srcset`, `poster`) and media;
60
+ `frameDomains` for iframes. Links (`<a href>`, `rel="canonical"`, `preconnect`) need nothing. A
61
+ Google Fonts stylesheet brings `fonts.gstatic.com` with it. It is static: a URL the script assembles
62
+ at run time is not seen, so run it on the built document and add such hosts by hand. `null` means
63
+ the document references no external origin and the restrictive default fits it. MCPAPP001 carries
64
+ the same suggestion in its message and in `properties.suggestedCsp`.
17
65
 
18
66
  ## Use in CI (GitHub code scanning)
19
67
 
@@ -75,6 +123,8 @@ npx mcp-app-lint --dir . --format sarif --out mcp-app-lint.sarif --fail-on error
75
123
  | MCPAPP017 | meta-list-read-mismatch | warning | `resources/read` and `resources/list` both declare a field (`csp`, `permissions`, `domain`) with different values; a listing with no CSP is not a mismatch, since the SDK puts the CSP in the read result |
76
124
  | MCPAPP018 | meta-read-wider-than-list | error | both declare a CSP and the read-time one adds an entry (domain or wildcard) the listing did not declare under the same key |
77
125
  | MCPAPP019 | csp-sink-tenant-host | note | one bucket, worker or app on a storage or serverless service from the denylist (`acct.blob.core.windows.net`, `pub-<id>.r2.dev`); fine while the owner keeps the name |
126
+ | MCPAPP020 | csp-unused-domain | note | a declared entry whose host the document never mentions: the policy is wider than the UI needs. Only where the exact document is known (a read result, a live server); a host the script assembles from parts at run time is reported too |
127
+ | MCPAPP021 | csp-host-undeclared | warning | the UI declares a CSP, but the document loads from or calls an origin under a key that does not allow it (see Suggested CSP); the host blocks the request and the UI breaks |
78
128
 
79
129
  ### A2UI rules
80
130
 
@@ -111,10 +161,35 @@ Apps MIME type or `ui://` (hosts, renderers, SDKs) are not treated as serving a
111
161
  looked for in the package's HTML documents only: a `fetch` in server code is not bound by the CSP,
112
162
  so it does not count.
113
163
 
164
+ ## As an MCP server
165
+
166
+ `mcp-app-lint --mcp` serves the checks over stdio, so an agent that builds an MCP App can run them
167
+ while it works. Install the package, then register it with the client, for example in Claude Code:
168
+
169
+ ```
170
+ npm install --global mcp-app-lint
171
+ claude mcp add mcp-app-lint -- mcp-app-lint --mcp
172
+ ```
173
+
174
+ | Tool | Input | Output |
175
+ | --- | --- | --- |
176
+ | `lint_ui_resource` | `html`, `meta` (the read result's `_meta`), `list_meta`, `uri` | findings for the document and its `_meta.ui` |
177
+ | `suggest_csp` | `html` | the `_meta.ui.csp` the document needs, or `null` |
178
+ | `lint_tools` | `tools` (a `tools/list` array) | findings |
179
+ | `lint_a2ui` | `messages` (array, JSON or JSONL text) | findings |
180
+ | `lint_directory` | `path` of a package directory | findings, with file and line |
181
+ | `lint_server` | `url` of a Streamable HTTP endpoint, `headers` | findings for what the server publishes |
182
+
183
+ Every tool is marked read-only. Findings come as text (one line each) and as `structuredContent`
184
+ (`counts` by level and the findings). No tool starts a process: a local stdio server is linted from
185
+ the command line with `--stdio`, where the user sees the command.
186
+
114
187
  The MCP Apps rules come out of the [MCP Apps UI census](../../census) in this repository; the analyzer is
115
188
  shared. The HTML sink detection is `eslint-plugin-render-policy`'s `no-unsafe-innerhtml`. The sink
116
189
  denylist is `@render-policy/core`'s. The package also exports the analysis functions (`analyzeHtml`,
117
- `analyzeUiMeta`, `compareListRead`, `scanPackageDir`, `lintA2uiMessages`, `parseA2uiMessages`, …) and `categorizeDomain`, the heuristic host
190
+ `analyzeUiMeta`, `compareListRead`, `scanPackageDir`, `lintA2uiMessages`, `parseA2uiMessages`,
191
+ `cspNeeds`, `suggestCsp`, `lintSnapshot`, …), the read-only clients (`McpHttpClient`,
192
+ `McpStdioClient`, `snapshotServer`), the MCP server (`serveMcp`, `handleMessage`) and `categorizeDomain`, the heuristic host
118
193
  category (fonts, analytics, maps, storage, media, cdn, api, …) the census report tabulates; it is
119
194
  data for aggregates, not a rule.
120
195
 
@@ -137,6 +137,7 @@ export declare function analyzeHtml(html: unknown): {
137
137
  externalHosts: string[];
138
138
  networkHosts: string[];
139
139
  sinkHosts: string[];
140
+ cspNeeds: import("./csp.mjs").CspNeeds;
140
141
  };
141
142
  /**
142
143
  * Short lines and not too many of them: written by a person, not emitted by a bundler.
@@ -1 +1 @@
1
- {"version":3,"file":"analyze.d.mts","sourceRoot":"","sources":["../src/analyze.mjs"],"names":[],"mappings":"AAgBG,YAA+B,MAAM,GAA3B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAQ;AAEpC,YAAoF,MAAM,GAAhF,gBAAgB,GAAG,iBAAiB,GAAG,cAAc,GAAG,gBAAgB,CAAQ;AAK9F,eAAO,MAAM,OAAO,8BAA8B,CAAC;AAEnD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAHlB,OAGkB,GAFhB,MAAM,GAAG,IAAI,CAWzB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAFvB,OAEuB;;;;;;;;;;;;;;;;;;;;;;EAoBjC;AAeD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAH7B,OAG6B,EAAE,QAAQ,EAFvC,OAEuC;;;;;;;;;;;;;;EAWjD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAFvB,OAEuB;;;;;;EAkBjC;AAED,+FAA+F;AAC/F,eAAO,MAAM,gBAAgB,UAA0G,CAAC;AAyDrI,YAA+D,eAAe,GAApE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAiB;AAI9E,YAAkB,QAAQ,GAC1B;IAAmB,IAAI,EAAZ,MAAM,CACjB;IAAmB,MAAM,EAAd,MAAM,CACjB;;;;IAAoB,WAAW,EAApB,OAAO,CAClB;;;;IAAoB,OAAO,EAAhB,OAAO,CAClB;IAAmB,OAAO,EAAf,MAAM,CACnB;CAAA,CAAA;AAZD;;;GAGG;AACH;;;;;;;;GAQG;AAEH;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAH9B,MAG8B,GAF5B,eAAe,EAAE,CAc7B;AAqBD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAFrB,OAEqB;;;;qBAMA,MAAM,EAAE;;;;;;;kBAOX,KAAK,CAAC,eAAe,GAAG;QAAE,WAAW,EAAE,OAAO,CAAA;KAAE,CAAC;;;;WAIxD,QAAQ,EAAE;;;;;;mBAMF,MAAM,EAAE;kBAET,MAAM,EAAE;eACX,MAAM,EAAE;EAiDjC;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAFvB,MAEuB,WAKjC;AAEG,YAA0C,YAAY,GAA5C,UAAU,CAAC,OAAO,WAAW,CAAC,CAAc;AAA1D,6DAA6D;AAE7D;;;;;GAKG;AACH,wBAAgB,aAAa,CAJc,CAAC,SAA9B;IAAE,aAAa,EAAE,MAAM,EAAE,CAAA;CAAK,EAId,QAAQ,EAH3B,CAG2B,GAFzB,CAAC,GAAG;IAAE,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CAIvC"}
1
+ {"version":3,"file":"analyze.d.mts","sourceRoot":"","sources":["../src/analyze.mjs"],"names":[],"mappings":"AAiBG,YAA+B,MAAM,GAA3B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAQ;AAEpC,YAAoF,MAAM,GAAhF,gBAAgB,GAAG,iBAAiB,GAAG,cAAc,GAAG,gBAAgB,CAAQ;AAK9F,eAAO,MAAM,OAAO,8BAA8B,CAAC;AAEnD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAHlB,OAGkB,GAFhB,MAAM,GAAG,IAAI,CAWzB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAFvB,OAEuB;;;;;;;;;;;;;;;;;;;;;;EAoBjC;AAeD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAH7B,OAG6B,EAAE,QAAQ,EAFvC,OAEuC;;;;;;;;;;;;;;EAWjD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAFvB,OAEuB;;;;;;EAkBjC;AAED,+FAA+F;AAC/F,eAAO,MAAM,gBAAgB,UAA0G,CAAC;AAyDrI,YAA+D,eAAe,GAApE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAiB;AAI9E,YAAkB,QAAQ,GAC1B;IAAmB,IAAI,EAAZ,MAAM,CACjB;IAAmB,MAAM,EAAd,MAAM,CACjB;;;;IAAoB,WAAW,EAApB,OAAO,CAClB;;;;IAAoB,OAAO,EAAhB,OAAO,CAClB;IAAmB,OAAO,EAAf,MAAM,CACnB;CAAA,CAAA;AAZD;;;GAGG;AACH;;;;;;;;GAQG;AAEH;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAH9B,MAG8B,GAF5B,eAAe,EAAE,CAc7B;AAqBD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAFrB,OAEqB;;;;qBAMA,MAAM,EAAE;;;;;;;kBAOX,KAAK,CAAC,eAAe,GAAG;QAAE,WAAW,EAAE,OAAO,CAAA;KAAE,CAAC;;;;WAIxD,QAAQ,EAAE;;;;;;mBAMF,MAAM,EAAE;kBAET,MAAM,EAAE;eACX,MAAM,EAAE;;EAmDjC;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAFvB,MAEuB,WAKjC;AAEG,YAA0C,YAAY,GAA5C,UAAU,CAAC,OAAO,WAAW,CAAC,CAAc;AAA1D,6DAA6D;AAE7D;;;;;GAKG;AACH,wBAAgB,aAAa,CAJc,CAAC,SAA9B;IAAE,aAAa,EAAE,MAAM,EAAE,CAAA;CAAK,EAId,QAAQ,EAH3B,CAG2B,GAFzB,CAAC,GAAG;IAAE,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CAIvC"}
package/dist/cli.d.mts CHANGED
@@ -9,9 +9,21 @@ export type CliOptions = {
9
9
  tools?: string;
10
10
  html?: string;
11
11
  a2ui?: string;
12
+ url?: string;
13
+ stdio?: string;
14
+ headers: Record<string, string>;
15
+ timeout?: number;
12
16
  out?: string;
17
+ suggestCsp?: boolean;
18
+ mcp?: boolean;
13
19
  help?: boolean;
14
20
  };
21
+ /**
22
+ * The command line as the SARIF log records it, which code scanning stores and shows: header values,
23
+ * credentials and query values in a URL, and environment assignments in a --stdio command are masked.
24
+ * @param {readonly string[]} argv
25
+ */
26
+ export declare function redactArgv(argv: readonly string[]): string[];
15
27
  /**
16
28
  * The command line, without the process exit: resolves to the exit code (0 clean, 1 findings at or
17
29
  * above --fail-on, 2 usage error). Throws on unreadable input or a failed download.
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.mts","sourceRoot":"","sources":["../src/cli.mjs"],"names":[],"mappings":"AAqBG,OAAQ,KAAA,EAAE,QAAQ,EAAE,MAAM,aAAa,CACvC;AAGA,YAAkB,UAAU,GAC5B;IAAmB,MAAM,EAAd,MAAM,CACjB;IAAmB,MAAM,EAAd,MAAM,CACjB;IAAoB,GAAG,AAAvB,CACA,EADW,MAAM,CACjB;IAAoB,OAAO,AAA3B,CACA,EADW,MAAM,CACjB;IAAoB,IAAI,AAAxB,CACA,EADW,MAAM,CACjB;IAAoB,IAAI,AAAxB,CACA,EADW,MAAM,CACjB;IAAoB,KAAK,AAAzB,CACA,EADW,MAAM,CACjB;IAAoB,IAAI,AAAxB,CACA,EADW,MAAM,CACjB;IAAoB,IAAI,AAAxB,CACA,EADW,MAAM,CACjB;IAAoB,GAAG,AAAvB,CACA,EADW,MAAM,CACjB;IAAqB,IAAI,AAAzB,CAA0B,EAAf,OAAO,CAAQ;CAAA,CAAA;AAgD7B;;;;;;GAMG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAJnB,SAAS,MAAM,EAII,EAAE,EAAE,MAAuB,EAAE,MAAuB,EAAE,AAHjF,CACA,EADQ;IAAE,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;CAGU,GAF5E,OAAO,CAAC,MAAM,CAAC,CA2D3B"}
1
+ {"version":3,"file":"cli.d.mts","sourceRoot":"","sources":["../src/cli.mjs"],"names":[],"mappings":"AA4BG,OAAQ,KAAA,EAAE,QAAQ,EAAE,MAAM,aAAa,CACvC;AAMA,YAAkB,UAAU,GAC5B;IAAmB,MAAM,EAAd,MAAM,CACjB;IAAmB,MAAM,EAAd,MAAM,CACjB;IAAoB,GAAG,AAAvB,CACA,EADW,MAAM,CACjB;IAAoB,OAAO,AAA3B,CACA,EADW,MAAM,CACjB;IAAoB,IAAI,AAAxB,CACA,EADW,MAAM,CACjB;IAAoB,IAAI,AAAxB,CACA,EADW,MAAM,CACjB;IAAoB,KAAK,AAAzB,CACA,EADW,MAAM,CACjB;IAAoB,IAAI,AAAxB,CACA,EADW,MAAM,CACjB;IAAoB,IAAI,AAAxB,CACA,EADW,MAAM,CACjB;IAAoB,GAAG,AAAvB,CACA,EADW,MAAM,CACjB;IAAoB,KAAK,AAAzB,CACA,EADW,MAAM,CACjB;IAAmC,OAAO,EAA/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CACjC;IAAoB,OAAO,AAA3B,CACA,EADW,MAAM,CACjB;IAAoB,GAAG,AAAvB,CACA,EADW,MAAM,CACjB;IAAqB,UAAU,AAA/B,CACA,EADW,OAAO,CAClB;IAAqB,GAAG,AAAxB,CACA,EADW,OAAO,CAClB;IAAqB,IAAI,AAAzB,CAA0B,EAAf,OAAO,CAAQ;CAAA,CAAA;AA+G7B;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAFpB,SAAS,MAAM,EAEK,YAoB9B;AAED;;;;;;GAMG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAJnB,SAAS,MAAM,EAII,EAAE,EAAE,MAAuB,EAAE,MAAuB,EAAE,AAHjF,CACA,EADQ;IAAE,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;CAGU,GAF5E,OAAO,CAAC,MAAM,CAAC,CAkG3B"}
@@ -0,0 +1,178 @@
1
+ import type { ChildProcessWithoutNullStreams } from 'node:child_process';
2
+ export type ClientOptions = {
3
+ /**
4
+ * per request
5
+ */
6
+ timeoutMs?: number;
7
+ /**
8
+ * `clientInfo.name` sent at initialize
9
+ */
10
+ clientName?: string;
11
+ clientVersion?: string;
12
+ };
13
+ export type ServerSnapshot = {
14
+ serverInfo: Record<string, any> | null;
15
+ /**
16
+ * the resources/list entries
17
+ */
18
+ resources: any[];
19
+ /**
20
+ * the resources/read content items of the UI resources
21
+ */
22
+ contents: any[];
23
+ /**
24
+ * the tools/list entries ([] when the server has no tools capability)
25
+ */
26
+ tools: any[];
27
+ /**
28
+ * reads that failed; the rest is still linted
29
+ */
30
+ errors: {
31
+ uri: string;
32
+ message: string;
33
+ }[];
34
+ };
35
+ export declare const UI_MIME_PREFIX = "text/html";
36
+ /** @type {(r: any) => boolean} */
37
+ export declare const isUiResource: (r: any) => boolean;
38
+ /**
39
+ * initialize, then the requests every client shares. Subclasses provide `request` and `notify`.
40
+ */
41
+ declare class BaseClient {
42
+ timeoutMs: number;
43
+ clientName: string;
44
+ clientVersion: string;
45
+ nextId: number;
46
+ /** @type {Record<string, any> | null} */
47
+ serverInfo: Record<string, any> | null;
48
+ /** @type {Record<string, any>} */
49
+ capabilities: Record<string, any>;
50
+ /** @param {ClientOptions} options */
51
+ constructor({ timeoutMs, clientName, clientVersion }?: ClientOptions);
52
+ /**
53
+ * @param {string} method
54
+ * @param {Record<string, unknown>} [params]
55
+ * @returns {Promise<any>}
56
+ */
57
+ request(method: string, params?: Record<string, unknown>): Promise<any>;
58
+ /**
59
+ * @param {string} method
60
+ * @param {Record<string, unknown>} [params]
61
+ * @returns {Promise<void>}
62
+ */
63
+ notify(method: string, params?: Record<string, unknown>): Promise<void>;
64
+ initialize(): Promise<any>;
65
+ /**
66
+ * Every page of a paginated list (at most 50 pages).
67
+ * @param {string} method
68
+ * @param {string} key
69
+ */
70
+ listAll(method: string, key: string): Promise<any[]>;
71
+ listResources(): Promise<any[]>;
72
+ listTools(): Promise<any[]>;
73
+ /** @param {string} uri */
74
+ readResource(uri: string): Promise<any>;
75
+ close(): Promise<void>;
76
+ }
77
+ /**
78
+ * Streamable HTTP. `headers` go on every request (for example an Authorization header).
79
+ */
80
+ export declare class McpHttpClient extends BaseClient {
81
+ url: string;
82
+ fetch: typeof fetch;
83
+ userAgent: string;
84
+ headers: Record<string, string>;
85
+ /** @type {string | null} */
86
+ sessionId: string | null;
87
+ /**
88
+ * @param {string} url
89
+ * @param {ClientOptions & { userAgent?: string, headers?: Record<string, string>, fetch?: typeof globalThis.fetch }} [options]
90
+ */
91
+ constructor(url: string, { userAgent, headers, fetch, ...options }?: ClientOptions & {
92
+ userAgent?: string;
93
+ headers?: Record<string, string>;
94
+ fetch?: typeof globalThis.fetch;
95
+ });
96
+ /**
97
+ * @param {{ jsonrpc: string, id?: number, method: string, params: unknown }} body
98
+ * @param {{ expectBody?: boolean }} [options]
99
+ * @returns {Promise<any>}
100
+ */
101
+ post(body: {
102
+ jsonrpc: string;
103
+ id?: number;
104
+ method: string;
105
+ params: unknown;
106
+ }, { expectBody }?: {
107
+ expectBody?: boolean;
108
+ }): Promise<any>;
109
+ /**
110
+ * Ends the session on the server, if it gave one; a server that does not support it is fine.
111
+ * @override
112
+ */
113
+ close(): Promise<void>;
114
+ }
115
+ /**
116
+ * The JSON-RPC message with the given id (or the first one) out of an SSE body.
117
+ * @param {string} text
118
+ * @param {number} [id]
119
+ * @returns {any}
120
+ */
121
+ export declare function firstJsonRpcFromSse(text: string, id?: number): any;
122
+ /**
123
+ * A local server started as a child process: `command` runs through the shell, so it is written
124
+ * the way the user types it (`node dist/server.js`, `uv run server.py`). The server's stderr is
125
+ * kept (the last lines go into a startup error) and not printed.
126
+ */
127
+ export declare class McpStdioClient extends BaseClient {
128
+ command: string;
129
+ /** @type {ChildProcessWithoutNullStreams} */
130
+ child: ChildProcessWithoutNullStreams;
131
+ /** @type {Map<number, { resolve: (v: any) => void, reject: (e: Error) => void, method: string, timer: NodeJS.Timeout }>} */
132
+ pending: Map<number, {
133
+ resolve: (v: any) => void;
134
+ reject: (e: Error) => void;
135
+ method: string;
136
+ timer: NodeJS.Timeout;
137
+ }>;
138
+ stderrTail: string;
139
+ buffer: string;
140
+ /** @type {Error | null} */
141
+ exited: Error | null;
142
+ /**
143
+ * @param {string} command
144
+ * @param {ClientOptions & { cwd?: string, env?: NodeJS.ProcessEnv }} [options]
145
+ */
146
+ constructor(command: string, { cwd, env, ...options }?: ClientOptions & {
147
+ cwd?: string;
148
+ env?: NodeJS.ProcessEnv;
149
+ });
150
+ /** @param {Error} error */
151
+ fail(error: Error): void;
152
+ /** @param {string} chunk */
153
+ onData(chunk: string): void;
154
+ /** @param {any} message */
155
+ onMessage(message: any): void;
156
+ /** @param {unknown} message */
157
+ write(message: unknown): void;
158
+ /**
159
+ * Closes stdin, the shutdown the stdio transport defines, then terminates a server that lingers.
160
+ * @override
161
+ */
162
+ close(): Promise<void>;
163
+ }
164
+ /**
165
+ * Classify an error thrown by a client: auth, egress-blocked, not-mcp, http, timeout, network, spawn, exit, protocol.
166
+ * @param {any} error
167
+ * @returns {string}
168
+ */
169
+ export declare function failureKind(error: any): string;
170
+ /**
171
+ * Everything the linter reads from a live server: listing, the read result of every UI resource,
172
+ * tools. A failed read is recorded and the rest goes on; a failed initialize or listing throws.
173
+ * @param {BaseClient} client
174
+ * @returns {Promise<ServerSnapshot>}
175
+ */
176
+ export declare function snapshotServer(client: BaseClient): Promise<ServerSnapshot>;
177
+ export { BaseClient };
178
+ //# sourceMappingURL=client.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.mts","sourceRoot":"","sources":["../src/client.mjs"],"names":[],"mappings":"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"}
package/dist/csp.d.mts ADDED
@@ -0,0 +1,52 @@
1
+ export type CspNeeds = {
2
+ connectDomains: string[];
3
+ resourceDomains: string[];
4
+ frameDomains: string[];
5
+ };
6
+ /**
7
+ * The origins a document needs, by CSP key, sorted and without duplicates.
8
+ * @param {unknown} html
9
+ * @returns {CspNeeds}
10
+ */
11
+ export declare function cspNeeds(html: unknown): CspNeeds;
12
+ /**
13
+ * A suggestion in the `_meta.ui.csp` shape: only the keys with origins, or null when the document
14
+ * needs none (the restrictive default fits it).
15
+ * @param {CspNeeds} needs
16
+ * @returns {Partial<CspNeeds> | null}
17
+ */
18
+ export declare function suggestCsp(needs: CspNeeds): Partial<CspNeeds> | null;
19
+ /**
20
+ * The needs of several documents (a package with more than one), merged.
21
+ * @param {Iterable<Partial<CspNeeds> | undefined>} all
22
+ * @returns {CspNeeds}
23
+ */
24
+ export declare function mergeNeeds(all: Iterable<Partial<CspNeeds> | undefined>): CspNeeds;
25
+ /**
26
+ * Does one declared entry allow the origin? Hosts are compared, not schemes or ports: a host that
27
+ * builds the CSP may widen schemes (https to wss), and a false "undeclared" is worse than a missed one.
28
+ * @param {string} entry
29
+ * @param {string} origin
30
+ */
31
+ export declare function entryCovers(entry: string, origin: string): boolean;
32
+ /**
33
+ * Needed origins the declared CSP does not allow, by key. The host blocks them: a broken UI.
34
+ * @param {Record<string, unknown> | null | undefined} csp the declared `_meta.ui.csp`
35
+ * @param {CspNeeds} needs
36
+ * @returns {Partial<CspNeeds>}
37
+ */
38
+ export declare function undeclaredOrigins(csp: Record<string, unknown> | null | undefined, needs: CspNeeds): Partial<CspNeeds>;
39
+ /**
40
+ * Declared entries whose host the document never mentions: wider than the UI needs. Wildcards,
41
+ * scheme-only and local-scheme entries are left to the other rules; `baseUriDomains` is not
42
+ * checked. The test is a substring search over the whole document, so a host that appears only
43
+ * in a URL assembled at run time from parts is reported; the rule says so.
44
+ * @param {Record<string, unknown> | null | undefined} csp
45
+ * @param {unknown} html
46
+ * @returns {{ key: keyof CspNeeds, entry: string }[]}
47
+ */
48
+ export declare function unusedEntries(csp: Record<string, unknown> | null | undefined, html: unknown): {
49
+ key: keyof CspNeeds;
50
+ entry: string;
51
+ }[];
52
+ //# sourceMappingURL=csp.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csp.d.mts","sourceRoot":"","sources":["../src/csp.mjs"],"names":[],"mappings":"AAUG,YAA2F,QAAQ,GAAzF;IAAE,cAAc,EAAE,MAAM,EAAE,CAAC;IAAC,eAAe,EAAE,MAAM,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,CAAU;AA6BtG;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAHlB,OAGkB,GAFhB,QAAQ,CAmCpB;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAHrB,QAGqB,GAFnB,OAAO,CAAC,QAAQ,CAAC,GAAG,IAAI,CAKpC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAHnB,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,SAAS,CAGnB,GAFjB,QAAQ,CASpB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAHtB,MAGsB,EAAE,MAAM,EAF9B,MAE8B,WAOxC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAJ1B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAIP,EAAE,KAAK,EAHjC,QAGiC,GAF/B,OAAO,CAAC,QAAQ,CAAC,CAW7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAJtB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAIX,EAAE,IAAI,EAH5B,OAG4B,GAF1B;IAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAcpD"}
package/dist/index.d.mts CHANGED
@@ -1,23 +1,30 @@
1
1
  export { lintA2uiMessages, parseA2uiMessages } from './a2ui.mjs';
2
+ export { failureKind, firstJsonRpcFromSse, isUiResource, McpHttpClient, McpStdioClient, snapshotServer } from './client.mjs';
3
+ export { cspNeeds, entryCovers, mergeNeeds, suggestCsp, undeclaredOrigins, unusedEntries } from './csp.mjs';
2
4
  export { analyzeHtml, analyzeTools, analyzeUiMeta, compareListRead, ESCAPE_FUNCTIONS, isHandwritten, postMessageStarCalls, UI_MIME, uiMetaOf, withSinkHosts } from './analyze.mjs';
3
5
  export { categorizeDomain, categorizeHost, classifyDomains, DOMAIN_CATEGORIES, parseDomainPattern, sinkFor, sinkScope } from './domains.mjs';
4
- export { levelOf, lintHtml, lintPackageScan, lintResource, lintTools, lintUiMeta } from './lint.mjs';
6
+ export { levelOf, lintHtml, lintPackageScan, lintResource, lintSnapshot, lintTools, lintUiMeta } from './lint.mjs';
5
7
  export { downloadTarball, extractTarball, packageMeta, scanPackage, scanPackageDir, searchPackages, UI_SDKS } from './npm.mjs';
6
8
  export { RULES, RULE_INDEX, ruleByName } from './rules.mjs';
7
9
  export { toSarif, toText, TOOL_VERSION } from './sarif.mjs';
8
10
  export { run } from './cli.mjs';
11
+ export { handleMessage, serveMcp, TOOLS as MCP_TOOLS } from './server.mjs';
9
12
  export type A2uiEntry = import('./a2ui.mjs').A2uiEntry;
10
13
  export type UiMeta = import('./analyze.mjs').UiMeta;
11
14
  export type CspKey = import('./analyze.mjs').CspKey;
12
15
  export type HtmlAnalysis = import('./analyze.mjs').HtmlAnalysis;
13
16
  export type HtmlSink = import('./analyze.mjs').HtmlSink;
14
17
  export type PostMessageCall = import('./analyze.mjs').PostMessageCall;
18
+ export type ClientOptions = import('./client.mjs').ClientOptions;
19
+ export type ServerSnapshot = import('./client.mjs').ServerSnapshot;
20
+ export type CspNeeds = import('./csp.mjs').CspNeeds;
15
21
  export type DomainPattern = import('./domains.mjs').DomainPattern;
16
22
  export type DomainCategory = import('./domains.mjs').DomainCategory;
17
23
  export type ClassifiedDomains = import('./domains.mjs').ClassifiedDomains;
18
24
  export type SinkScope = import('./domains.mjs').SinkScope;
19
25
  export type Finding = import('./lint.mjs').Finding;
20
26
  export type ResourceInput = import('./lint.mjs').ResourceInput;
27
+ export type Snapshot = import('./lint.mjs').Snapshot;
21
28
  export type PackageMeta = import('./npm.mjs').PackageMeta;
22
29
  export type PackageScan = import('./npm.mjs').PackageScan;
23
30
  export type ScanOptions = import('./npm.mjs').ScanOptions;
@@ -31,12 +38,16 @@ export type Level = import('./rules.mjs').Level;
31
38
  * @typedef {import('./analyze.mjs').HtmlAnalysis} HtmlAnalysis
32
39
  * @typedef {import('./analyze.mjs').HtmlSink} HtmlSink
33
40
  * @typedef {import('./analyze.mjs').PostMessageCall} PostMessageCall
41
+ * @typedef {import('./client.mjs').ClientOptions} ClientOptions
42
+ * @typedef {import('./client.mjs').ServerSnapshot} ServerSnapshot
43
+ * @typedef {import('./csp.mjs').CspNeeds} CspNeeds
34
44
  * @typedef {import('./domains.mjs').DomainPattern} DomainPattern
35
45
  * @typedef {import('./domains.mjs').DomainCategory} DomainCategory
36
46
  * @typedef {import('./domains.mjs').ClassifiedDomains} ClassifiedDomains
37
47
  * @typedef {import('./domains.mjs').SinkScope} SinkScope
38
48
  * @typedef {import('./lint.mjs').Finding} Finding
39
49
  * @typedef {import('./lint.mjs').ResourceInput} ResourceInput
50
+ * @typedef {import('./lint.mjs').Snapshot} Snapshot
40
51
  * @typedef {import('./npm.mjs').PackageMeta} PackageMeta
41
52
  * @typedef {import('./npm.mjs').PackageScan} PackageScan
42
53
  * @typedef {import('./npm.mjs').ScanOptions} ScanOptions
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.mjs"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAAE,aAAa,EAAE,oBAAoB,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnL,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC7I,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACrG,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC/H,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAG7B,YAA0C,SAAS,GAAzC,OAAO,YAAY,EAAE,SAAS,CAAW;AACnD,YAA0C,MAAM,GAAtC,OAAO,eAAe,EAAE,MAAM,CAAQ;AAChD,YAA0C,MAAM,GAAtC,OAAO,eAAe,EAAE,MAAM,CAAQ;AAChD,YAAgD,YAAY,GAAlD,OAAO,eAAe,EAAE,YAAY,CAAc;AAC5D,YAA4C,QAAQ,GAA1C,OAAO,eAAe,EAAE,QAAQ,CAAU;AACpD,YAAmD,eAAe,GAAxD,OAAO,eAAe,EAAE,eAAe,CAAiB;AAClE,YAAiD,aAAa,GAApD,OAAO,eAAe,EAAE,aAAa,CAAe;AAC9D,YAAkD,cAAc,GAAtD,OAAO,eAAe,EAAE,cAAc,CAAgB;AAChE,YAAqD,iBAAiB,GAA5D,OAAO,eAAe,EAAE,iBAAiB,CAAmB;AACtE,YAA6C,SAAS,GAA5C,OAAO,eAAe,EAAE,SAAS,CAAW;AACtD,YAAwC,OAAO,GAArC,OAAO,YAAY,EAAE,OAAO,CAAS;AAC/C,YAA8C,aAAa,GAAjD,OAAO,YAAY,EAAE,aAAa,CAAe;AAC3D,YAA2C,WAAW,GAA5C,OAAO,WAAW,EAAE,WAAW,CAAa;AACtD,YAA2C,WAAW,GAA5C,OAAO,WAAW,EAAE,WAAW,CAAa;AACtD,YAA2C,WAAW,GAA5C,OAAO,WAAW,EAAE,WAAW,CAAa;AACtD,YAA0C,UAAU,GAA1C,OAAO,WAAW,EAAE,UAAU,CAAY;AACpD,YAAsC,IAAI,GAAhC,OAAO,aAAa,EAAE,IAAI,CAAM;AAC1C,YAAuC,KAAK,GAAlC,OAAO,aAAa,EAAE,KAAK,CAAO;AAlB/C;;;;;;;;;;;;;;;;;;;GAmBG"}
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.mjs"],"names":[],"mappings":"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"}
package/dist/lint.d.mts CHANGED
@@ -2,6 +2,7 @@
2
2
  * From analysis to findings (see Finding below).
3
3
  */
4
4
  import { analyzeTools } from './analyze.mjs';
5
+ import type { CspNeeds } from './csp.mjs';
5
6
  import type { PackageScan } from './npm.mjs';
6
7
  import type { Level } from './rules.mjs';
7
8
  export type Finding = {
@@ -36,8 +37,9 @@ export type ResourceInput = {
36
37
  * @param {unknown} meta
37
38
  * @param {string} uri
38
39
  * @param {readonly string[]} [hosts]
40
+ * @param {Partial<CspNeeds>} [needs] the origins the UI needs, for the suggestion MCPAPP001 carries
39
41
  */
40
- export declare function lintUiMeta(meta: unknown, uri: string, hosts?: readonly string[]): Finding[];
42
+ export declare function lintUiMeta(meta: unknown, uri: string, hosts?: readonly string[], needs?: Partial<CspNeeds>): Finding[];
41
43
  /**
42
44
  * Findings about a UI resource as served: listing entry, read result content item, HTML.
43
45
  * @param {ResourceInput} resource
@@ -55,6 +57,29 @@ export declare function lintHtml(html: string, uri: string): Finding[];
55
57
  * @param {string} [uri]
56
58
  */
57
59
  export declare function lintTools(tools: unknown, uri?: string): Finding[];
60
+ export type Snapshot = {
61
+ /**
62
+ * resources/read content items (each with its `uri`)
63
+ */
64
+ contents: readonly any[];
65
+ /**
66
+ * resources/list entries, for the list/read comparison
67
+ */
68
+ resources?: readonly any[];
69
+ /**
70
+ * tools/list entries
71
+ */
72
+ tools?: readonly any[];
73
+ };
74
+ /**
75
+ * Findings about everything a server publishes: each UI resource as listed and read, and its tools.
76
+ * @param {Snapshot} snapshot
77
+ * @param {{ uri?: string, toolsUri?: string }} [where] fallback uri of a content item without one; location of tool findings
78
+ */
79
+ export declare function lintSnapshot({ contents, resources, tools }: Snapshot, { uri, toolsUri }?: {
80
+ uri?: string;
81
+ toolsUri?: string;
82
+ }): Finding[];
58
83
  /**
59
84
  * Findings about a scanned package directory (see scanPackageDir). `root` prefixes file locations.
60
85
  * @param {PackageScan} scan
@@ -1 +1 @@
1
- {"version":3,"file":"lint.d.mts","sourceRoot":"","sources":["../src/lint.mjs"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAe,YAAY,EAA2D,MAAM,eAAe,CAAC;AAMhH,OAAQ,KAAA,EAAE,WAAW,EAAE,MAAM,WAAW,CACxC;AAAA,OAAQ,KAAA,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAMpC,YAAkB,OAAO,GACzB;IAAmB,MAAM,EAAd,MAAM,CACjB;;;;IAAmB,KAAK,AAAxB,CACA,EADW,KAAK,CAChB;IAAmB,OAAO,EAAf,MAAM,CACjB;IAAmB,GAAG,EAAX,MAAM,CACjB;IAAyD,MAAM,AAA/D,CACA,EADW;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CACtD;IAA4C,OAAO,AAAnD,CACA,EADW;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CACzC;IAAqC,UAAU,AAA/C,CAAgD,EAArC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAc;CAAA,CAAA;AAE/C,YAAuD,YAAY,GAAzD,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC,CAAc;AAIpE,YAAkB,aAAa,GAC/B;IAAmB,GAAG,EAAX,MAAM,CACjB;IAAqB,QAAQ,AAA7B,CACA,EADW,OAAO,CAClB;IAAqB,QAAQ,AAA7B,CACA,EADW,OAAO,CAClB;IAA2B,IAAI,AAA/B,CAAgC,EAArB,MAAM,GAAG,IAAI,CAAQ;CAAA,CAAA;AAgDnC;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAJpB,OAIoB,EAAE,GAAG,EAHzB,MAGyB,EAAE,KAAK,AAFxC,CAAkC,EAA1B,SAAS,MAAM,EAEsB,aAgB/C;AASD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAAE,GAAG,EAAE,QAAe,EAAE,QAAe,EAAE,IAAW,EAAE,EAFxE,aAEwE,aAalF;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAHlB,MAGkB,EAAE,GAAG,EAFvB,MAEuB,aAEjC;AAiCD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAHpB,OAGoB,EAAE,GAAG,AAFjC,CAAqB,EAAb,MAEwC,aAelD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAHzB,WAGyB,EAAE,IAAI,AAFvC,CAAsB,EAAd,MAEqC,aAsB/C;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAHnB,MAGmB,GAFjB,KAAK,CAIjB;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"lint.d.mts","sourceRoot":"","sources":["../src/lint.mjs"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAe,YAAY,EAA2D,MAAM,eAAe,CAAC;AAOhH,OAAQ,KAAA,EAAE,QAAQ,EAAE,MAAM,WAAW,CACrC;AAAA,OAAQ,KAAA,EAAE,WAAW,EAAE,MAAM,WAAW,CACxC;AAAA,OAAQ,KAAA,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAMpC,YAAkB,OAAO,GACzB;IAAmB,MAAM,EAAd,MAAM,CACjB;;;;IAAmB,KAAK,AAAxB,CACA,EADW,KAAK,CAChB;IAAmB,OAAO,EAAf,MAAM,CACjB;IAAmB,GAAG,EAAX,MAAM,CACjB;IAAyD,MAAM,AAA/D,CACA,EADW;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CACtD;IAA4C,OAAO,AAAnD,CACA,EADW;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CACzC;IAAqC,UAAU,AAA/C,CAAgD,EAArC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAc;CAAA,CAAA;AAE/C,YAAuD,YAAY,GAAzD,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC,CAAc;AAIpE,YAAkB,aAAa,GAC/B;IAAmB,GAAG,EAAX,MAAM,CACjB;IAAqB,QAAQ,AAA7B,CACA,EADW,OAAO,CAClB;IAAqB,QAAQ,AAA7B,CACA,EADW,OAAO,CAClB;IAA2B,IAAI,AAA/B,CAAgC,EAArB,MAAM,GAAG,IAAI,CAAQ;CAAA,CAAA;AAgDnC;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EALpB,OAKoB,EAAE,GAAG,EAJzB,MAIyB,EAAE,KAAK,AAHxC,CACA,EADQ,SAAS,MAAM,EAGsB,EAAE,KAAK,AAFpD,CACF,EADU,OAAO,CAAC,QAAQ,CAEwC,aAgBlE;AAmCD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAAE,GAAG,EAAE,QAAe,EAAE,QAAe,EAAE,IAAW,EAAE,EAFxE,aAEwE,aAclF;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAHlB,MAGkB,EAAE,GAAG,EAFvB,MAEuB,aAEjC;AAiCD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAHpB,OAGoB,EAAE,GAAG,AAFjC,CAAqB,EAAb,MAEwC,aAelD;AAIE,YAAkB,QAAQ,GAC1B;;;;IAA2B,QAAQ,EAAxB,SAAS,GAAG,EAAE,CACzB;;;;IAA4B,SAAS,AAArC,CACA,EADW,SAAS,GAAG,EAAE,CACzB;;;;IAA4B,KAAK,AAAjC,CACF,EADa,SAAS,GAAG,EAAE,CAC3B;CAAA,CAAA;AAKD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,SAAc,EAAE,KAAU,EAAE,EAH1D,QAG0D,EAAE,EAAE,GAAsB,EAAE,QAAuB,EAAE,AAFvH,CACF,EADU;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAEmF,aAU9H;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAHzB,WAGyB,EAAE,IAAI,AAFvC,CAAsB,EAAd,MAEqC,aA2B/C;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAHnB,MAGmB,GAFjB,KAAK,CAIjB;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.mts","sourceRoot":"","sources":["../src/rules.mjs"],"names":[],"mappings":"AAAA,oDAAoD;AACpD;;;;GAIG;AALC,YAAwC,KAAK,GAAnC,OAAO,GAAG,SAAS,GAAG,MAAM,CAAO;AAI9C,YAA6G,IAAI,GAAvG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAM;AAKpH;;;GAGG;AACH,eAAO,MAAM,KAAK,EAFR,SAAS,IAAI,EA6BtB,CAAC;AAEF,eAAO,MAAM,UAAU,qBAAwD,CAAC;AAChF,2BAA2B;AAC3B,eAAO,MAAM,UAAU,SADX,MAAM,qBACoD,CAAC"}
1
+ {"version":3,"file":"rules.d.mts","sourceRoot":"","sources":["../src/rules.mjs"],"names":[],"mappings":"AAAA,oDAAoD;AACpD;;;;GAIG;AALC,YAAwC,KAAK,GAAnC,OAAO,GAAG,SAAS,GAAG,MAAM,CAAO;AAI9C,YAA6G,IAAI,GAAvG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAM;AAKpH;;;GAGG;AACH,eAAO,MAAM,KAAK,EAFR,SAAS,IAAI,EA+BtB,CAAC;AAEF,eAAO,MAAM,UAAU,qBAAwD,CAAC;AAChF,2BAA2B;AAC3B,eAAO,MAAM,UAAU,SADX,MAAM,qBACoD,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { Readable, Writable } from 'node:stream';
2
+ export type ToolDef = {
3
+ name: string;
4
+ title: string;
5
+ description: string;
6
+ inputSchema: Record<string, unknown>;
7
+ annotations: {
8
+ readOnlyHint: true;
9
+ openWorldHint: boolean;
10
+ };
11
+ run: (args: Record<string, any>) => Promise<{
12
+ text: string;
13
+ structured: Record<string, unknown>;
14
+ }>;
15
+ };
16
+ /** @type {ToolDef[]} */
17
+ export declare const TOOLS: ToolDef[];
18
+ /**
19
+ * The answer to one JSON-RPC message, or null for a notification.
20
+ * @param {any} message
21
+ * @returns {Promise<Record<string, unknown> | null>}
22
+ */
23
+ export declare function handleMessage(message: any): Promise<Record<string, unknown> | null>;
24
+ /**
25
+ * Serves newline-delimited JSON-RPC until the input ends. Nothing but protocol messages goes to the output.
26
+ * @param {{ input?: Readable, output?: Pick<Writable, 'write'> }} [io]
27
+ * @returns {Promise<void>}
28
+ */
29
+ export declare function serveMcp({ input, output }?: {
30
+ input?: Readable;
31
+ output?: Pick<Writable, 'write'>;
32
+ }): Promise<void>;
33
+ //# sourceMappingURL=server.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.mts","sourceRoot":"","sources":["../src/server.mjs"],"names":[],"mappings":"AAgBG,OAAQ,KAAA,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CACjD;AAGA,YAAkB,OAAO,GACzB;IAAmB,IAAI,EAAZ,MAAM,CACjB;IAAmB,KAAK,EAAb,MAAM,CACjB;IAAmB,WAAW,EAAnB,MAAM,CACjB;IAAoC,WAAW,EAApC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAClC;IAA2D,WAAW,EAA3D;QAAE,YAAY,EAAE,IAAI,CAAC;QAAC,aAAa,EAAE,OAAO,CAAA;KAAE,CACzD;IAA0G,GAAG,EAAlG,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAC1G;CAAA,CAAA;AA6BD,wBAAwB;AACxB,eAAO,MAAM,KAAK,EADP,OAAO,EAmHjB,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAHhC,GAGgC,GAF9B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAsCnD;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,EAAE,KAAqB,EAAE,MAAuB,EAAE,AAHxE,CACA,EADQ;IAAE,KAAK,CAAC,EAAE,QAAQ,CAAC;IAAC,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;CAGiB,GAFnE,OAAO,CAAC,IAAI,CAAC,CAuBzB"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-app-lint",
3
- "version": "0.1.6",
4
- "description": "SARIF findings about what an MCP App declares (CSP domain lists, permissions, tool visibility) and what its HTML does (innerHTML sinks, postMessage to '*', external hosts, data sinks); A2UI message streams (openUrl schemes, sink hosts, data in URLs, markup in Text).",
3
+ "version": "0.1.7",
4
+ "description": "SARIF findings about what an MCP App declares (CSP domain lists, permissions, tool visibility) and what its HTML does (innerHTML sinks, postMessage to '*', external hosts, data sinks), read from a package, saved results or a live server over HTTP or stdio; suggested CSP; an MCP server mode; A2UI message streams (openUrl schemes, sink hosts, data in URLs, markup in Text).",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@render-policy/core": "^0.1.4",
33
- "eslint": "^10.11.0",
33
+ "eslint": "^10.12.0",
34
34
  "eslint-plugin-render-policy": "^0.1.2"
35
35
  },
36
36
  "publishConfig": {
@@ -43,7 +43,8 @@
43
43
  "sarif",
44
44
  "security",
45
45
  "csp",
46
- "lint"
46
+ "lint",
47
+ "mcp-server"
47
48
  ],
48
49
  "repository": {
49
50
  "type": "git",
package/src/analyze.mjs CHANGED
@@ -3,6 +3,7 @@
3
3
  * its tools, and the HTML of the resources themselves. No network here.
4
4
  */
5
5
  import { createRequire } from 'node:module';
6
+ import { cspNeeds } from './csp.mjs';
6
7
  import { classifyDomains, sinkFor } from './domains.mjs';
7
8
 
8
9
  /**
@@ -262,6 +263,8 @@ export function analyzeHtml(html) {
262
263
  // Hosts that script calls with an absolute URL (fetch, WebSocket, …), bundles included.
263
264
  networkHosts: /** @type {string[]} */ ([]),
264
265
  sinkHosts: /** @type {string[]} */ ([]),
266
+ // The origins the document loads from or calls, by the _meta.ui.csp key that must allow them.
267
+ cspNeeds: cspNeeds(text),
265
268
  };
266
269
  result.externalHosts = hostnames([...text.matchAll(URL_ATTR_RE)].map((m) => m[1] ?? '').filter((v) => /^(https?:)?\/\//i.test(v)));
267
270
  result.networkHosts = hostnames([...text.matchAll(NETWORK_CALL_RE)].map((m) => m[1] ?? ''));