mcp-app-lint 0.1.5 → 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,15 +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
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)
12
17
  ```
13
18
 
14
19
  Options: `--out file.sarif`, `--format sarif|text`, `--fail-on error|warning|note|none` (default
15
- `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`.
16
65
 
17
66
  ## Use in CI (GitHub code scanning)
18
67
 
@@ -41,7 +90,8 @@ Inputs: `directory` (default `.`), `fail-on` (default `error`), `upload-sarif` (
41
90
  `sarif-file` (default `mcp-app-lint.sarif`), `version` (npm dist-tag or version, default `latest`),
42
91
  `args` (passed through), `node-version` (default `22`, empty to use the runner's Node). The SARIF
43
92
  is written even when findings fail the job, so it still uploads; paths are repo-relative so code
44
- scanning maps them to the source.
93
+ scanning maps them to the source. Recorded A2UI streams go through `args`, for example
94
+ `args: --a2ui test/fixtures/surface.jsonl`.
45
95
 
46
96
  Any CI can run the CLI directly and read the exit code (`0` clean, `1` findings at or above
47
97
  `--fail-on`, `2` usage error):
@@ -73,6 +123,31 @@ npx mcp-app-lint --dir . --format sarif --out mcp-app-lint.sarif --fail-on error
73
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 |
74
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 |
75
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 |
128
+
129
+ ### A2UI rules
130
+
131
+ `--a2ui` reads A2UI v0.9 server-to-client messages (`createSurface`, `updateComponents`,
132
+ `updateDataModel`, `deleteSurface`). A2UI sends no HTML; what matters is where the agent's data may
133
+ flow. Absolute `{ "path": … }` bindings and `formatString` interpolations are resolved against the
134
+ `updateDataModel` messages of the same stream after every message, so a value swapped after the
135
+ first render is checked too, at the line of the update that brought it. Relative paths in list
136
+ templates and client function calls are left to the runtime check
137
+ ([`@render-policy/a2ui`](../a2ui)); this scan is a CI aid, not a substitute for it.
138
+
139
+ | Id | Name | Level | What it means |
140
+ | --- | --- | --- | --- |
141
+ | A2UI001 | a2ui-openurl-scheme | error | `openUrl` target that does not resolve to http or https (`javascript:`, `data:`, `mailto:`), as the basic catalog requires |
142
+ | A2UI002 | a2ui-sink-host | error | `Image`, `Video` or `AudioPlayer` url, `theme.iconUrl` or an `openUrl` target on a sink host anyone can use |
143
+ | A2UI003 | a2ui-url-encoded-payload | warning | media or icon URL, fetched without a click, that is too long or carries a long high-entropy token |
144
+ | A2UI004 | a2ui-url-template-host | warning | `formatString` takes the scheme or host of a media URL or `openUrl` target from data |
145
+ | A2UI005 | a2ui-url-template-data | note | `formatString` puts data into the path or query of a media URL |
146
+ | A2UI006 | a2ui-text-markup | warning | `Text` contains raw HTML, a Markdown image or a link (bare URLs count; code spans and fences do not), which the basic catalog excludes |
147
+ | A2UI007 | a2ui-remote-media-hosts | note | the hosts media and icons load from, for the client's allowlist |
148
+
149
+ On the 44 examples of the v0.9 basic catalog the scan reports A2UI007 notes and one A2UI006: the
150
+ Markdown example puts a link in `Text`.
76
151
 
77
152
  Hand-written means an inline script with short lines and not too many of them; bundles carry
78
153
  framework internals and the SDK bridge (which posts to `'*'` by design) and are not reported
@@ -86,10 +161,35 @@ Apps MIME type or `ui://` (hosts, renderers, SDKs) are not treated as serving a
86
161
  looked for in the package's HTML documents only: a `fetch` in server code is not bound by the CSP,
87
162
  so it does not count.
88
163
 
89
- The rules come out of the [MCP Apps UI census](../../census) in this repository; the analyzer is
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
+
187
+ The MCP Apps rules come out of the [MCP Apps UI census](../../census) in this repository; the analyzer is
90
188
  shared. The HTML sink detection is `eslint-plugin-render-policy`'s `no-unsafe-innerhtml`. The sink
91
189
  denylist is `@render-policy/core`'s. The package also exports the analysis functions (`analyzeHtml`,
92
- `analyzeUiMeta`, `compareListRead`, `scanPackageDir`, …) 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
93
193
  category (fonts, analytics, maps, storage, media, cdn, api, …) the census report tabulates; it is
94
194
  data for aggregates, not a rule.
95
195
 
@@ -0,0 +1,28 @@
1
+ import type { Finding } from './lint.mjs';
2
+ export type A2uiEntry = {
3
+ message: unknown;
4
+ line?: number;
5
+ };
6
+ export type UrlUse = 'fetched' | 'openUrl';
7
+ export type Place = {
8
+ surfaceId: string;
9
+ componentId?: string;
10
+ property: string;
11
+ dataPath?: string;
12
+ line?: number;
13
+ };
14
+ /**
15
+ * Messages from the text of a file: one message, an array of them, an object with `messages` (the
16
+ * list wrapper and the specification's samples), or JSONL. Throws on a line that does not parse.
17
+ * @param {string} text
18
+ * @returns {A2uiEntry[]}
19
+ */
20
+ export declare function parseA2uiMessages(text: string): A2uiEntry[];
21
+ /**
22
+ * Findings over a stream of A2UI v0.9 messages. `uri` is the file the messages came from.
23
+ * @param {readonly (A2uiEntry | unknown)[]} input messages, or entries with their line
24
+ * @param {string} uri
25
+ * @returns {Finding[]}
26
+ */
27
+ export declare function lintA2uiMessages(input: readonly (A2uiEntry | unknown)[], uri: string): Finding[];
28
+ //# sourceMappingURL=a2ui.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a2ui.d.mts","sourceRoot":"","sources":["../src/a2ui.mjs"],"names":[],"mappings":"AAiBG,OAAQ,KAAA,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAIrC,YAA+C,SAAS,GAA9C;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAW;AAEvD,YAAiC,MAAM,GAA7B,SAAS,GAAG,SAAS,CAAQ;AAGxC,YAA2G,KAAK,GAAtG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAO;AAmBnH;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAH3B,MAG2B,GAFzB,SAAS,EAAE,CA4BvB;AA4GD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAJ3B,SAAS,CAAC,SAAS,GAAG,OAAO,CAAC,EAIH,EAAE,GAAG,EAHhC,MAGgC,GAF9B,OAAO,EAAE,CA0KrB"}
@@ -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
@@ -8,9 +8,22 @@ export type CliOptions = {
8
8
  list?: string;
9
9
  tools?: string;
10
10
  html?: string;
11
+ a2ui?: string;
12
+ url?: string;
13
+ stdio?: string;
14
+ headers: Record<string, string>;
15
+ timeout?: number;
11
16
  out?: string;
17
+ suggestCsp?: boolean;
18
+ mcp?: boolean;
12
19
  help?: boolean;
13
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[];
14
27
  /**
15
28
  * The command line, without the process exit: resolves to the exit code (0 clean, 1 findings at or
16
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":"AAmBG,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,GAAG,AAAvB,CACA,EADW,MAAM,CACjB;IAAqB,IAAI,AAAzB,CAA0B,EAAf,OAAO,CAAQ;CAAA,CAAA;AA+C7B;;;;;;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,CAwD3B"}
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":"AAUG,OAAQ,KAAA,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAA;AAGpE,YAAkB,aAAa,GAC/B;;;;IAAoB,SAAS,AAA7B,CACA,EADW,MAAM,CACjB;;;;IAAoB,UAAU,AAA9B,CACA,EADW,MAAM,CACjB;IAAoB,aAAa,AAAjC,CAAkC,EAAvB,MAAM,CAAiB;CAAA,CAAA;AAIlC,YAAkB,cAAc,GAChC;IAAuC,UAAU,EAAtC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CACrC;;;;IAAkB,SAAS,EAAhB,GAAG,EAAE,CAChB;;;;IAAkB,QAAQ,EAAf,GAAG,EAAE,CAChB;;;;IAAkB,KAAK,EAAZ,GAAG,EAAE,CAChB;;;;IAA+C,MAAM,EAA1C;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAC/C;CAAA,CAAA;AAGD,eAAO,MAAM,cAAc,cAAc,CAAC;AAE1C,kCAAkC;AAClC,eAAO,MAAM,YAAY,EADd,CAAC,CAAC,EAAE,GAAG,KAAK,OAC4G,CAAC;AAEpI;;GAEG;AACH,cAAM,UAAU;IAGP,SAAS;IACT,UAAU;IACV,aAAa;IACb,MAAM;IACX,yCAAyC;IACpC,UAAU,EADJ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAErC,kCAAkC;IAC7B,YAAY,EADN,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IARhC,qCAAqC;IACrC,YAAY,EAAE,SAAiB,EAAE,UAA2B,EAAE,aAA4B,EAAE,GADhF,aACqF,EAShG;IAED;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAJT,MAIS,EAAE,MAAM,AAHzB,CACA,EADQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAGA,GAFpB,OAAO,CAAC,GAAG,CAAC,CAIxB;IAED;;;;OAIG;IACG,MAAM,CAAC,MAAM,EAJR,MAIQ,EAAE,MAAM,AAHxB,CACA,EADQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAGD,GAFnB,OAAO,CAAC,IAAI,CAAC,CAIzB;IAEK,UAAU,iBAUf;IAED;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAHT,MAGS,EAAE,GAAG,EAFd,MAEc,kBAWxB;IAED,aAAa,mBAEZ;IAED,SAAS,mBAER;IAED,0BAA0B;IACpB,YAAY,CAAC,GAAG,EADV,MACU,gBAGrB;IAEK,KAAK,kBAAK;CACjB;AASD;;GAEG;AACH,qBAAa,aAAc,SAAQ,UAAU;IAOpC,GAAG;IACH,KAAK;IACL,SAAS;IACT,OAAO;IACZ,4BAA4B;IACvB,SAAS,EADH,MAAM,GAAG,IAAI;IAV1B;;;OAGG;IACH,YAAY,GAAG,EAHJ,MAGI,EAAE,EAAE,SAAS,EAAE,OAAY,EAAE,KAAwB,EAAE,GAAG,OAAO,EAAE,AAF/E,CAA4H,EAApH,aAAa,GAAG;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;KAE3B,EAQtF;IAuBD;;;;OAIG;IACG,IAAI,CAAC,IAAI,EAJJ;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAI3D,EAAE,EAAE,UAAiB,EAAE,AAHnC,CACA,EADQ;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAGU,GAF9B,OAAO,CAAC,GAAG,CAAC,CAsCxB;IAED;;;OAGG;IACG,KAAK,kBAYV;CACF;AA0BD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAJ7B,MAI6B,EAAE,EAAE,AAHzC,CACA,EADQ,MAGiC,GAF/B,GAAG,CAoBf;AAED;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,UAAU;IAOrC,OAAO;IACZ,6CAA6C;IACxC,KAAK,EADC,8BAA8B;IAEzC,4HAA4H;IACvH,OAAO,EADD,GAAG,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;QAAC,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,OAAO,CAAA;KAAE,CAAC;IAEnH,UAAU;IACV,MAAM;IACX,2BAA2B;IACtB,MAAM,EADA,KAAK,GAAG,IAAI;IAbzB;;;OAGG;IACH,YAAY,OAAO,EAHR,MAGQ,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,OAAO,EAAE,AAF1C,CAA4E,EAApE,aAAa,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;KAEhB,EAwBjD;IAED,2BAA2B;IAC3B,IAAI,CAAC,KAAK,EADE,KACF,QAOT;IAED,4BAA4B;IAC5B,MAAM,CAAC,KAAK,EADA,MACA,QAgBX;IAED,2BAA2B;IAC3B,SAAS,CAAC,OAAO,EADL,GACK,QAchB;IAED,+BAA+B;IAC/B,KAAK,CAAC,OAAO,EADD,OACC,QAEZ;IA4BD;;;OAGG;IACG,KAAK,kBASV;CACF;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAHtB,GAGsB,GAFpB,MAAM,CAQlB;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAHhC,UAGgC,GAF9B,OAAO,CAAC,cAAc,CAAC,CAkBnC;AAgBD,OAAO,EAAE,UAAU,EAAE,CAAC"}
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,21 +1,30 @@
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';
1
4
  export { analyzeHtml, analyzeTools, analyzeUiMeta, compareListRead, ESCAPE_FUNCTIONS, isHandwritten, postMessageStarCalls, UI_MIME, uiMetaOf, withSinkHosts } from './analyze.mjs';
2
5
  export { categorizeDomain, categorizeHost, classifyDomains, DOMAIN_CATEGORIES, parseDomainPattern, sinkFor, sinkScope } from './domains.mjs';
3
- export { levelOf, lintHtml, lintPackageScan, lintResource, lintTools, lintUiMeta } from './lint.mjs';
6
+ export { levelOf, lintHtml, lintPackageScan, lintResource, lintSnapshot, lintTools, lintUiMeta } from './lint.mjs';
4
7
  export { downloadTarball, extractTarball, packageMeta, scanPackage, scanPackageDir, searchPackages, UI_SDKS } from './npm.mjs';
5
8
  export { RULES, RULE_INDEX, ruleByName } from './rules.mjs';
6
9
  export { toSarif, toText, TOOL_VERSION } from './sarif.mjs';
7
10
  export { run } from './cli.mjs';
11
+ export { handleMessage, serveMcp, TOOLS as MCP_TOOLS } from './server.mjs';
12
+ export type A2uiEntry = import('./a2ui.mjs').A2uiEntry;
8
13
  export type UiMeta = import('./analyze.mjs').UiMeta;
9
14
  export type CspKey = import('./analyze.mjs').CspKey;
10
15
  export type HtmlAnalysis = import('./analyze.mjs').HtmlAnalysis;
11
16
  export type HtmlSink = import('./analyze.mjs').HtmlSink;
12
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;
13
21
  export type DomainPattern = import('./domains.mjs').DomainPattern;
14
22
  export type DomainCategory = import('./domains.mjs').DomainCategory;
15
23
  export type ClassifiedDomains = import('./domains.mjs').ClassifiedDomains;
16
24
  export type SinkScope = import('./domains.mjs').SinkScope;
17
25
  export type Finding = import('./lint.mjs').Finding;
18
26
  export type ResourceInput = import('./lint.mjs').ResourceInput;
27
+ export type Snapshot = import('./lint.mjs').Snapshot;
19
28
  export type PackageMeta = import('./npm.mjs').PackageMeta;
20
29
  export type PackageScan = import('./npm.mjs').PackageScan;
21
30
  export type ScanOptions = import('./npm.mjs').ScanOptions;
@@ -23,17 +32,22 @@ export type DomainSite = import('./npm.mjs').DomainSite;
23
32
  export type Rule = import('./rules.mjs').Rule;
24
33
  export type Level = import('./rules.mjs').Level;
25
34
  /**
35
+ * @typedef {import('./a2ui.mjs').A2uiEntry} A2uiEntry
26
36
  * @typedef {import('./analyze.mjs').UiMeta} UiMeta
27
37
  * @typedef {import('./analyze.mjs').CspKey} CspKey
28
38
  * @typedef {import('./analyze.mjs').HtmlAnalysis} HtmlAnalysis
29
39
  * @typedef {import('./analyze.mjs').HtmlSink} HtmlSink
30
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
31
44
  * @typedef {import('./domains.mjs').DomainPattern} DomainPattern
32
45
  * @typedef {import('./domains.mjs').DomainCategory} DomainCategory
33
46
  * @typedef {import('./domains.mjs').ClassifiedDomains} ClassifiedDomains
34
47
  * @typedef {import('./domains.mjs').SinkScope} SinkScope
35
48
  * @typedef {import('./lint.mjs').Finding} Finding
36
49
  * @typedef {import('./lint.mjs').ResourceInput} ResourceInput
50
+ * @typedef {import('./lint.mjs').Snapshot} Snapshot
37
51
  * @typedef {import('./npm.mjs').PackageMeta} PackageMeta
38
52
  * @typedef {import('./npm.mjs').PackageScan} PackageScan
39
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,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,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;AAjB/C;;;;;;;;;;;;;;;;;;GAkBG"}
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"}
package/dist/rules.d.mts CHANGED
@@ -1,7 +1,8 @@
1
1
  /** @typedef {'error' | 'warning' | 'note'} Level */
2
2
  /**
3
- * A rule in SARIF terms: `short` and `full` become its short and full description.
4
- * @typedef {{ id: string, name: string, level: Level, short: string, full: string }} Rule
3
+ * A rule in SARIF terms: `short` and `full` become its short and full description; `tags` default to
4
+ * security and mcp-apps.
5
+ * @typedef {{ id: string, name: string, level: Level, short: string, full: string, tags?: readonly string[] }} Rule
5
6
  */
6
7
  export type Level = 'error' | 'warning' | 'note';
7
8
  export type Rule = {
@@ -10,6 +11,7 @@ export type Rule = {
10
11
  level: Level;
11
12
  short: string;
12
13
  full: string;
14
+ tags?: readonly string[];
13
15
  };
14
16
  /**
15
17
  * The rules, in SARIF terms. Ids are stable; names are what a scanner shows.
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.mts","sourceRoot":"","sources":["../src/rules.mjs"],"names":[],"mappings":"AAAA,oDAAoD;AACpD;;;GAGG;AAJC,YAAwC,KAAK,GAAnC,OAAO,GAAG,SAAS,GAAG,MAAM,CAAO;AAG9C,YAAmF,IAAI,GAA7E;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAM;AAG1F;;;GAGG;AACH,eAAO,MAAM,KAAK,EAFR,SAAS,IAAI,EAsBtB,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"}
package/dist/sarif.d.mts CHANGED
@@ -38,7 +38,7 @@ export declare function toSarif(findings: readonly Finding[], { invocation }?: {
38
38
  level: import("./rules.mjs").Level;
39
39
  };
40
40
  properties: {
41
- tags: string[];
41
+ tags: readonly string[];
42
42
  };
43
43
  }[];
44
44
  };