mcp-app-lint 0.1.6 → 0.1.8
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 +86 -3
- package/dist/analyze.d.mts +2 -0
- package/dist/analyze.d.mts.map +1 -1
- package/dist/cli.d.mts +12 -0
- package/dist/cli.d.mts.map +1 -1
- package/dist/client.d.mts +178 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/csp.d.mts +63 -0
- package/dist/csp.d.mts.map +1 -0
- package/dist/index.d.mts +12 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/lint.d.mts +26 -1
- package/dist/lint.d.mts.map +1 -1
- package/dist/rules.d.mts.map +1 -1
- package/dist/server.d.mts +33 -0
- package/dist/server.d.mts.map +1 -0
- package/package.json +5 -4
- package/src/analyze.mjs +6 -0
- package/src/cli.mjs +180 -16
- package/src/client.mjs +454 -0
- package/src/csp.mjs +196 -0
- package/src/index.mjs +8 -1
- package/src/lint.mjs +73 -10
- package/src/rules.mjs +2 -0
- package/src/server.mjs +248 -0
package/README.md
CHANGED
|
@@ -4,16 +4,72 @@ 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` (in `<style>` and `style` attributes), images (`src`,
|
|
60
|
+
`srcset`, `poster`) and media;
|
|
61
|
+
`frameDomains` for iframes. Links (`<a href>`, `rel="canonical"`, `preconnect`) need nothing. A
|
|
62
|
+
Google Fonts stylesheet brings `fonts.gstatic.com` with it. It is static: a URL the script assembles
|
|
63
|
+
at run time is not seen, so run it on the built document and add such hosts by hand. `null` means
|
|
64
|
+
no origin has a known key. MCPAPP001 carries the same suggestion in its message and in
|
|
65
|
+
`properties.suggestedCsp`.
|
|
66
|
+
|
|
67
|
+
A bundle often holds a URL as a plain string that a library fetches later: a map tile template
|
|
68
|
+
(`url: "https://tile.example.org/{z}/{x}/{y}.png"`), a CDN base URL in a template literal. The key
|
|
69
|
+
cannot be told from the code, so these origins are listed as hints, not put into the declaration:
|
|
70
|
+
on a line of their own with `--format text`, on stderr next to the JSON (stdout stays the
|
|
71
|
+
declaration to copy), and as `mentioned` from the MCP tool. Link targets, XML namespaces,
|
|
72
|
+
framework documentation hosts and placeholders such as `*.example.com` are left out.
|
|
17
73
|
|
|
18
74
|
## Use in CI (GitHub code scanning)
|
|
19
75
|
|
|
@@ -75,6 +131,8 @@ npx mcp-app-lint --dir . --format sarif --out mcp-app-lint.sarif --fail-on error
|
|
|
75
131
|
| 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
132
|
| 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
133
|
| 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 |
|
|
134
|
+
| 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 |
|
|
135
|
+
| 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
136
|
|
|
79
137
|
### A2UI rules
|
|
80
138
|
|
|
@@ -111,10 +169,35 @@ Apps MIME type or `ui://` (hosts, renderers, SDKs) are not treated as serving a
|
|
|
111
169
|
looked for in the package's HTML documents only: a `fetch` in server code is not bound by the CSP,
|
|
112
170
|
so it does not count.
|
|
113
171
|
|
|
172
|
+
## As an MCP server
|
|
173
|
+
|
|
174
|
+
`mcp-app-lint --mcp` serves the checks over stdio, so an agent that builds an MCP App can run them
|
|
175
|
+
while it works. Install the package, then register it with the client, for example in Claude Code:
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
npm install --global mcp-app-lint
|
|
179
|
+
claude mcp add mcp-app-lint -- mcp-app-lint --mcp
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
| Tool | Input | Output |
|
|
183
|
+
| --- | --- | --- |
|
|
184
|
+
| `lint_ui_resource` | `html`, `meta` (the read result's `_meta`), `list_meta`, `uri` | findings for the document and its `_meta.ui` |
|
|
185
|
+
| `suggest_csp` | `html` | the `_meta.ui.csp` the document needs, or `null` |
|
|
186
|
+
| `lint_tools` | `tools` (a `tools/list` array) | findings |
|
|
187
|
+
| `lint_a2ui` | `messages` (array, JSON or JSONL text) | findings |
|
|
188
|
+
| `lint_directory` | `path` of a package directory | findings, with file and line |
|
|
189
|
+
| `lint_server` | `url` of a Streamable HTTP endpoint, `headers` | findings for what the server publishes |
|
|
190
|
+
|
|
191
|
+
Every tool is marked read-only. Findings come as text (one line each) and as `structuredContent`
|
|
192
|
+
(`counts` by level and the findings). No tool starts a process: a local stdio server is linted from
|
|
193
|
+
the command line with `--stdio`, where the user sees the command.
|
|
194
|
+
|
|
114
195
|
The MCP Apps rules come out of the [MCP Apps UI census](../../census) in this repository; the analyzer is
|
|
115
196
|
shared. The HTML sink detection is `eslint-plugin-render-policy`'s `no-unsafe-innerhtml`. The sink
|
|
116
197
|
denylist is `@render-policy/core`'s. The package also exports the analysis functions (`analyzeHtml`,
|
|
117
|
-
`analyzeUiMeta`, `compareListRead`, `scanPackageDir`, `lintA2uiMessages`, `parseA2uiMessages`,
|
|
198
|
+
`analyzeUiMeta`, `compareListRead`, `scanPackageDir`, `lintA2uiMessages`, `parseA2uiMessages`,
|
|
199
|
+
`cspNeeds`, `suggestCsp`, `lintSnapshot`, …), the read-only clients (`McpHttpClient`,
|
|
200
|
+
`McpStdioClient`, `snapshotServer`), the MCP server (`serveMcp`, `handleMessage`) and `categorizeDomain`, the heuristic host
|
|
118
201
|
category (fonts, analytics, maps, storage, media, cdn, api, …) the census report tabulates; it is
|
|
119
202
|
data for aggregates, not a rule.
|
|
120
203
|
|
package/dist/analyze.d.mts
CHANGED
|
@@ -137,6 +137,8 @@ export declare function analyzeHtml(html: unknown): {
|
|
|
137
137
|
externalHosts: string[];
|
|
138
138
|
networkHosts: string[];
|
|
139
139
|
sinkHosts: string[];
|
|
140
|
+
cspNeeds: import("./csp.mjs").CspNeeds;
|
|
141
|
+
cspMentioned: string[];
|
|
140
142
|
};
|
|
141
143
|
/**
|
|
142
144
|
* Short lines and not too many of them: written by a person, not emitted by a bundler.
|
package/dist/analyze.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze.d.mts","sourceRoot":"","sources":["../src/analyze.mjs"],"names":[],"mappings":"
|
|
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;;kBAIL,MAAM,EAAE;EAkDpC;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.
|
package/dist/cli.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.mts","sourceRoot":"","sources":["../src/cli.mjs"],"names":[],"mappings":"
|
|
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;AAsH7B;;;;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,CA8G3B"}
|
|
@@ -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,63 @@
|
|
|
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
|
+
* Origins of absolute URL literals the document holds without a call or attribute that tells which
|
|
14
|
+
* key they need (`url: "https://tiles.example/{z}/{x}/{y}.png"` handed to a map library, a base URL
|
|
15
|
+
* in a template literal). They may or may not be fetched; `--suggest-csp` lists them so a person
|
|
16
|
+
* decides. Origins already in `needs`, link targets, XML namespace hosts, framework documentation
|
|
17
|
+
* hosts and placeholders (`*.example.com`, localhost) are left out.
|
|
18
|
+
* @param {unknown} html
|
|
19
|
+
* @param {CspNeeds} [needs]
|
|
20
|
+
* @returns {string[]}
|
|
21
|
+
*/
|
|
22
|
+
export declare function mentionedOrigins(html: unknown, needs?: CspNeeds): string[];
|
|
23
|
+
/**
|
|
24
|
+
* A suggestion in the `_meta.ui.csp` shape: only the keys with origins, or null when the document
|
|
25
|
+
* needs none (the restrictive default fits it).
|
|
26
|
+
* @param {CspNeeds} needs
|
|
27
|
+
* @returns {Partial<CspNeeds> | null}
|
|
28
|
+
*/
|
|
29
|
+
export declare function suggestCsp(needs: CspNeeds): Partial<CspNeeds> | null;
|
|
30
|
+
/**
|
|
31
|
+
* The needs of several documents (a package with more than one), merged.
|
|
32
|
+
* @param {Iterable<Partial<CspNeeds> | undefined>} all
|
|
33
|
+
* @returns {CspNeeds}
|
|
34
|
+
*/
|
|
35
|
+
export declare function mergeNeeds(all: Iterable<Partial<CspNeeds> | undefined>): CspNeeds;
|
|
36
|
+
/**
|
|
37
|
+
* Does one declared entry allow the origin? Hosts are compared, not schemes or ports: a host that
|
|
38
|
+
* builds the CSP may widen schemes (https to wss), and a false "undeclared" is worse than a missed one.
|
|
39
|
+
* @param {string} entry
|
|
40
|
+
* @param {string} origin
|
|
41
|
+
*/
|
|
42
|
+
export declare function entryCovers(entry: string, origin: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Needed origins the declared CSP does not allow, by key. The host blocks them: a broken UI.
|
|
45
|
+
* @param {Record<string, unknown> | null | undefined} csp the declared `_meta.ui.csp`
|
|
46
|
+
* @param {CspNeeds} needs
|
|
47
|
+
* @returns {Partial<CspNeeds>}
|
|
48
|
+
*/
|
|
49
|
+
export declare function undeclaredOrigins(csp: Record<string, unknown> | null | undefined, needs: CspNeeds): Partial<CspNeeds>;
|
|
50
|
+
/**
|
|
51
|
+
* Declared entries whose host the document never mentions: wider than the UI needs. Wildcards,
|
|
52
|
+
* scheme-only and local-scheme entries are left to the other rules; `baseUriDomains` is not
|
|
53
|
+
* checked. The test is a substring search over the whole document, so a host that appears only
|
|
54
|
+
* in a URL assembled at run time from parts is reported; the rule says so.
|
|
55
|
+
* @param {Record<string, unknown> | null | undefined} csp
|
|
56
|
+
* @param {unknown} html
|
|
57
|
+
* @returns {{ key: keyof CspNeeds, entry: string }[]}
|
|
58
|
+
*/
|
|
59
|
+
export declare function unusedEntries(csp: Record<string, unknown> | null | undefined, html: unknown): {
|
|
60
|
+
key: keyof CspNeeds;
|
|
61
|
+
entry: string;
|
|
62
|
+
}[];
|
|
63
|
+
//# 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;AA+BtG;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAHlB,OAGkB,GAFhB,QAAQ,CAqCpB;AAUD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAJ1B,OAI0B,EAAE,KAAK,AAHzC,CACA,EADQ,QAGkD,GAFhD,MAAM,EAAE,CAgBpB;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, mentionedOrigins, 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
|
package/dist/index.d.mts.map
CHANGED
|
@@ -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;
|
|
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
|
package/dist/lint.d.mts.map
CHANGED
|
@@ -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;
|
|
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.map
CHANGED
|
@@ -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,
|
|
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,EAwHjB,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.
|
|
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.8",
|
|
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.
|
|
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, mentionedOrigins } from './csp.mjs';
|
|
6
7
|
import { classifyDomains, sinkFor } from './domains.mjs';
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -262,7 +263,12 @@ 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),
|
|
268
|
+
// Absolute URLs in the document whose key is unknown (see mentionedOrigins); hints, never findings.
|
|
269
|
+
cspMentioned: /** @type {string[]} */ ([]),
|
|
265
270
|
};
|
|
271
|
+
result.cspMentioned = mentionedOrigins(text, result.cspNeeds);
|
|
266
272
|
result.externalHosts = hostnames([...text.matchAll(URL_ATTR_RE)].map((m) => m[1] ?? '').filter((v) => /^(https?:)?\/\//i.test(v)));
|
|
267
273
|
result.networkHosts = hostnames([...text.matchAll(NETWORK_CALL_RE)].map((m) => m[1] ?? ''));
|
|
268
274
|
for (const match of text.matchAll(SCRIPT_RE)) {
|