@webdecoy/ai-protection 0.1.0-alpha.17 → 0.1.0-alpha.18
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/MCP.md +12 -4
- package/mcp.d.mts +1 -1
- package/mcp.mjs +8 -6
- package/package.json +1 -1
package/MCP.md
CHANGED
|
@@ -11,7 +11,7 @@ resource authorization.
|
|
|
11
11
|
Available in `0.1.0-alpha.5` and later compatible alpha releases:
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
npm install @webdecoy/ai-protection@0.1.0-alpha.
|
|
14
|
+
npm install @webdecoy/ai-protection@0.1.0-alpha.18 @modelcontextprotocol/sdk@1.31.0
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Requires Node 22.22.3+ and MCP SDK **1.31.0**. The MCP SDK is an optional peer, so
|
|
@@ -161,9 +161,17 @@ Discovery uses schema-3 reports on the existing reporting endpoint; deploy a
|
|
|
161
161
|
compatible hosted runtime first. Old runtimes reject these optional reports
|
|
162
162
|
without affecting tool listing. Reporting has bounded pending work and a deadline,
|
|
163
163
|
never blocks authorization, and does not retry. Call `await handler.flush()` at a
|
|
164
|
-
host shutdown/lifecycle boundary to drain
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
host shutdown/lifecycle boundary to drain discovery and tool-action reports already
|
|
165
|
+
queued when flush begins. From alpha.18, the action reporting queue is shared by
|
|
166
|
+
all calls on one handler and is bounded by `sharedRuntime.maxPendingReports`
|
|
167
|
+
(default 100); discovery uses a separately bounded queue. Full queues drop reports
|
|
168
|
+
without retrying or blocking tool execution.
|
|
169
|
+
|
|
170
|
+
Stop accepting new requests and let active tools complete or cancel before the
|
|
171
|
+
final flush. Flush does not wait for running tools, future reports, arbitrary
|
|
172
|
+
`onEvent`/`onReport` callbacks or dashboard persistence. Reporting deadlines still
|
|
173
|
+
apply; an unavailable/timeout/drop is not delivery success. Abruptly terminated
|
|
174
|
+
hosts can lose reports. Discovery advertisements never increment tool action or request counts.
|
|
167
175
|
|
|
168
176
|
|
|
169
177
|
### Calls before discovery (alpha.8+)
|
package/mcp.d.mts
CHANGED
|
@@ -44,6 +44,6 @@ export interface ProtectedMCPOptions {
|
|
|
44
44
|
*/
|
|
45
45
|
export function createProtectedMCPHandler(options: ProtectedMCPOptions):
|
|
46
46
|
((request: IncomingMessage, response: ServerResponse) => Promise<void>) & {
|
|
47
|
-
/** Drain
|
|
47
|
+
/** Drain already queued discovery/action reports within reporting deadlines. Does not wait for running tools, future reports or observer callbacks. */
|
|
48
48
|
flush(): Promise<void>;
|
|
49
49
|
};
|
package/mcp.mjs
CHANGED
|
@@ -50,7 +50,11 @@ export function createProtectedMCPHandler(options) {
|
|
|
50
50
|
if (options.discovery) for (const [name, tool] of Object.entries(tools))
|
|
51
51
|
tool.toolSchema = Object.freeze({serverId:options.discovery.serverId,hash:hashes[name],...(decoys.has(name)?{decoy:decoys.get(name)}:{effect:inferToolEffect(name,tool.inputSchema,tool.annotations),permissions:Object.freeze({schema:1,required_scopes:tool.requiredScopes.length,application_authorization:true,additional_policy:typeof tool.policy==='function'})})});
|
|
52
52
|
// Validate after discovery supplies the schema required by tool pauses.
|
|
53
|
-
|
|
53
|
+
if (typeof options.authenticate !== 'function') throw Error('Invalid MCP authenticator');
|
|
54
|
+
// One bounded reporting runtime per handler. The caller is passed per run,
|
|
55
|
+
// never held in mutable shared request state. HTTP authentication stays above
|
|
56
|
+
// dispatch; the action boundary independently validates and snapshots it.
|
|
57
|
+
const guard = createActionProtection({ policyVersion: options.policyVersion, authenticate: caller => caller, actions: tools, sharedRuntime: options.sharedRuntime, onEvent: options.onEvent });
|
|
54
58
|
const runtime = options.sharedRuntime && { ...options.sharedRuntime };
|
|
55
59
|
const serverId = options.discovery?.serverId;
|
|
56
60
|
const catalogReporter = options.discovery ? createReporter({
|
|
@@ -177,7 +181,6 @@ export function createProtectedMCPHandler(options) {
|
|
|
177
181
|
res.end();
|
|
178
182
|
return;
|
|
179
183
|
}
|
|
180
|
-
const guard = createActionProtection({ policyVersion: options.policyVersion, authenticate: () => caller, actions: tools, sharedRuntime: options.sharedRuntime, onEvent: options.onEvent });
|
|
181
184
|
// Scope escalation belongs at HTTP level, before the SDK opens an SSE stream.
|
|
182
185
|
if (message.method === 'tools/call' && typeof message.params?.name === 'string') {
|
|
183
186
|
const definition = Object.hasOwn(tools, message.params.name) ? tools[message.params.name] : undefined;
|
|
@@ -185,7 +188,7 @@ export function createProtectedMCPHandler(options) {
|
|
|
185
188
|
// Run the same admission path for its sanitized denial evidence. It cannot
|
|
186
189
|
// dispatch with a missing required scope, regardless of discovery results.
|
|
187
190
|
try {
|
|
188
|
-
await guard.run(message.params.name, {},
|
|
191
|
+
await guard.run(message.params.name, {}, caller, { signal: disconnected.signal });
|
|
189
192
|
}
|
|
190
193
|
catch (e) {
|
|
191
194
|
if (!(e instanceof ActionDenied))
|
|
@@ -225,7 +228,7 @@ export function createProtectedMCPHandler(options) {
|
|
|
225
228
|
running.started = true;
|
|
226
229
|
const signal = AbortSignal.any([extra.signal, disconnected.signal, running.controller.signal]);
|
|
227
230
|
try {
|
|
228
|
-
return await guard.run(request.params.name, (request.params.arguments ?? {}),
|
|
231
|
+
return await guard.run(request.params.name, (request.params.arguments ?? {}), caller, { signal });
|
|
229
232
|
}
|
|
230
233
|
catch (e) {
|
|
231
234
|
if (signal.aborted)
|
|
@@ -237,7 +240,6 @@ export function createProtectedMCPHandler(options) {
|
|
|
237
240
|
}
|
|
238
241
|
finally {
|
|
239
242
|
cleanup();
|
|
240
|
-
void guard.flush();
|
|
241
243
|
}
|
|
242
244
|
});
|
|
243
245
|
await server.connect(transport);
|
|
@@ -250,5 +252,5 @@ export function createProtectedMCPHandler(options) {
|
|
|
250
252
|
res.destroy();
|
|
251
253
|
}
|
|
252
254
|
};
|
|
253
|
-
return Object.assign(handle, { flush: async () => { await catalogReporter?.flush(); } });
|
|
255
|
+
return Object.assign(handle, { flush: async () => { await Promise.all([catalogReporter?.flush(), guard.flush()]); } });
|
|
254
256
|
}
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"test:types": "tsc --strict --noEmit --module nodenext --target es2022 test/types.mts test/mcp-types.mts",
|
|
12
12
|
"test:workers": "node --test test/workers/*.test.mjs"
|
|
13
13
|
},
|
|
14
|
-
"version": "0.1.0-alpha.
|
|
14
|
+
"version": "0.1.0-alpha.18",
|
|
15
15
|
"exports": {
|
|
16
16
|
"./fetch": {
|
|
17
17
|
"types": "./fetch.d.mts",
|