@absolutejs/mcp 0.4.3 → 0.5.0
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 +43 -0
- package/dist/index.js +246 -9
- package/dist/manifest.js +22 -3
- package/dist/manifest.json +2 -2
- package/dist/src/index.d.ts +2 -1
- package/dist/src/jsonrpc.d.ts +2 -1
- package/dist/src/tasks.d.ts +14 -0
- package/dist/src/types.d.ts +53 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -7,6 +7,49 @@ package owns the JSON-RPC protocol, protocol-version negotiation, RFC 9728
|
|
|
7
7
|
discovery metadata, and the `401` challenge that lets a client find your
|
|
8
8
|
authorization server.
|
|
9
9
|
|
|
10
|
+
## Agent action enforcement
|
|
11
|
+
|
|
12
|
+
Tools carrying manifest contract 2 `authorization` metadata fail closed unless
|
|
13
|
+
an `agency` enforcement point is configured. Every call becomes an exact-input
|
|
14
|
+
action request; allowed calls execute through a short-lived single-use lease and
|
|
15
|
+
produce a receipt. Requestable denials return an `absolute.action_decision`
|
|
16
|
+
payload containing the action id for an approval workflow.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { createAgency, createMemoryAgencyStore } from "@absolutejs/agency";
|
|
20
|
+
|
|
21
|
+
const agency = createAgency({ policy, store: createMemoryAgencyStore() });
|
|
22
|
+
|
|
23
|
+
mcpServer<Caller>({
|
|
24
|
+
agency: {
|
|
25
|
+
enforcement: agency,
|
|
26
|
+
resolveActor: ({ caller, scopes }) => ({
|
|
27
|
+
agentId: caller.agentId,
|
|
28
|
+
delegationId: caller.delegationId,
|
|
29
|
+
scopes,
|
|
30
|
+
userId: caller.userId,
|
|
31
|
+
}),
|
|
32
|
+
},
|
|
33
|
+
// normal MCP config…
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Durable Tasks
|
|
38
|
+
|
|
39
|
+
The package implements the final `io.modelcontextprotocol/tasks` extension from
|
|
40
|
+
SEP-2663: server-directed task creation, `tasks/get`, `tasks/update`, and
|
|
41
|
+
`tasks/cancel`. There is intentionally no `tasks/list`; task handles are bound
|
|
42
|
+
to an authorization key and checked on every request.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
tasks: {
|
|
46
|
+
authorizationKey: (caller) => caller.userId,
|
|
47
|
+
shouldCreate: ({ name }) => name === "long_running_report",
|
|
48
|
+
store: createMemoryMcpTaskStore(), // use a durable shared store in production
|
|
49
|
+
ttlMs: 60 * 60 * 1000,
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
10
53
|
Nothing here depends on a model. The tool shape is structurally compatible with
|
|
11
54
|
[`@absolutejs/ai`](https://github.com/absolutejs/ai)'s `AIToolMap`, so an AI tool
|
|
12
55
|
registry serves over MCP without conversion — but any typed tool registry works.
|
package/dist/index.js
CHANGED
|
@@ -285,12 +285,32 @@ var createMcpClient = (options) => {
|
|
|
285
285
|
};
|
|
286
286
|
return { callTool, initialize, listResources, listTools, ping, readResource };
|
|
287
287
|
};
|
|
288
|
+
// node_modules/@absolutejs/agency/dist/authzen.js
|
|
289
|
+
var createCoazActionInput = ({
|
|
290
|
+
actor,
|
|
291
|
+
call,
|
|
292
|
+
effects = ["read"],
|
|
293
|
+
requiredScopes = []
|
|
294
|
+
}) => ({
|
|
295
|
+
action: "call_tool",
|
|
296
|
+
actor,
|
|
297
|
+
context: { required_scopes: requiredScopes, source: "mcp" },
|
|
298
|
+
effects,
|
|
299
|
+
input: call.arguments,
|
|
300
|
+
resource: {
|
|
301
|
+
id: call.name,
|
|
302
|
+
properties: { server_id: call.serverId },
|
|
303
|
+
type: "mcp_tool"
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
288
307
|
// src/jsonrpc.ts
|
|
289
308
|
var JSONRPC_PARSE_ERROR = -32700;
|
|
290
309
|
var JSONRPC_INVALID_REQUEST = -32600;
|
|
291
310
|
var JSONRPC_METHOD_NOT_FOUND = -32601;
|
|
292
311
|
var JSONRPC_INVALID_PARAMS = -32602;
|
|
293
312
|
var JSONRPC_INTERNAL_ERROR = -32603;
|
|
313
|
+
var JSONRPC_MISSING_REQUIRED_CLIENT_CAPABILITY = -32003;
|
|
294
314
|
var HTTP_ACCEPTED = 202;
|
|
295
315
|
var HTTP_NO_CONTENT = 204;
|
|
296
316
|
var HTTP_UNAUTHORIZED = 401;
|
|
@@ -301,7 +321,11 @@ var jsonHeaders = {
|
|
|
301
321
|
var rpcResult = (id, result) => new Response(JSON.stringify({ id, jsonrpc: "2.0", result }), {
|
|
302
322
|
headers: jsonHeaders
|
|
303
323
|
});
|
|
304
|
-
var rpcError = (id, code, message) => new Response(JSON.stringify({
|
|
324
|
+
var rpcError = (id, code, message, data) => new Response(JSON.stringify({
|
|
325
|
+
error: { code, message, ...data === undefined ? {} : { data } },
|
|
326
|
+
id,
|
|
327
|
+
jsonrpc: "2.0"
|
|
328
|
+
}), {
|
|
305
329
|
headers: jsonHeaders
|
|
306
330
|
});
|
|
307
331
|
var notificationAck = () => new Response(null, { status: HTTP_ACCEPTED });
|
|
@@ -317,6 +341,49 @@ var unauthorized = (metadataUrl, detail) => new Response(JSON.stringify({
|
|
|
317
341
|
status: HTTP_UNAUTHORIZED
|
|
318
342
|
});
|
|
319
343
|
|
|
344
|
+
// src/tasks.ts
|
|
345
|
+
var clone = (value) => structuredClone(value);
|
|
346
|
+
var createMemoryMcpTaskStore = () => {
|
|
347
|
+
const tasks = new Map;
|
|
348
|
+
return {
|
|
349
|
+
cancel: (taskId) => {
|
|
350
|
+
const task = tasks.get(taskId);
|
|
351
|
+
if (task === undefined)
|
|
352
|
+
return;
|
|
353
|
+
tasks.set(taskId, {
|
|
354
|
+
...task,
|
|
355
|
+
lastUpdatedAt: new Date().toISOString(),
|
|
356
|
+
status: "cancelled"
|
|
357
|
+
});
|
|
358
|
+
},
|
|
359
|
+
get: (taskId) => {
|
|
360
|
+
const task = tasks.get(taskId);
|
|
361
|
+
return task === undefined ? null : clone(task);
|
|
362
|
+
},
|
|
363
|
+
save: (task) => {
|
|
364
|
+
tasks.set(task.taskId, clone(task));
|
|
365
|
+
},
|
|
366
|
+
update: (taskId, update) => {
|
|
367
|
+
const task = tasks.get(taskId);
|
|
368
|
+
if (task === undefined)
|
|
369
|
+
return null;
|
|
370
|
+
if (["cancelled", "completed", "failed"].includes(task.status)) {
|
|
371
|
+
return clone(task);
|
|
372
|
+
}
|
|
373
|
+
const next = {
|
|
374
|
+
...task,
|
|
375
|
+
...update,
|
|
376
|
+
lastUpdatedAt: new Date().toISOString()
|
|
377
|
+
};
|
|
378
|
+
tasks.set(taskId, next);
|
|
379
|
+
return clone(next);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
};
|
|
383
|
+
var publicMcpTask = ({ authorizationKey, ...task }) => {
|
|
384
|
+
return task;
|
|
385
|
+
};
|
|
386
|
+
|
|
320
387
|
// src/dispatch.ts
|
|
321
388
|
var DEFAULT_PROTOCOLS = ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
322
389
|
var DEFAULT_RESOURCE_MIME = "text/markdown";
|
|
@@ -347,6 +414,16 @@ var negotiateProtocol = (supported, params) => {
|
|
|
347
414
|
return supported.includes(requested) ? requested : preferred;
|
|
348
415
|
};
|
|
349
416
|
var scopeAllows = (tool, scopes) => tool.scope === undefined || scopes.includes(tool.scope);
|
|
417
|
+
var valueAtPath = (value, path) => {
|
|
418
|
+
let current = value;
|
|
419
|
+
for (const segment of path.split(".")) {
|
|
420
|
+
if (!isRecord(current))
|
|
421
|
+
return;
|
|
422
|
+
current = current[segment];
|
|
423
|
+
}
|
|
424
|
+
return current;
|
|
425
|
+
};
|
|
426
|
+
var agencyAllows = (config, tool, scopes) => tool.authorization === undefined || config.agency !== undefined && (tool.authorization.requiredScopes ?? []).every((scope) => scopes.includes(scope));
|
|
350
427
|
var normalizeResult = (value) => {
|
|
351
428
|
if (typeof value === "string") {
|
|
352
429
|
return { content: [{ text: value, type: "text" }], isError: false };
|
|
@@ -365,6 +442,9 @@ var initialize = async (config, id, params, context) => {
|
|
|
365
442
|
const capabilities = {
|
|
366
443
|
tools: { listChanged: false }
|
|
367
444
|
};
|
|
445
|
+
if (config.tasks !== undefined) {
|
|
446
|
+
capabilities.extensions = { "io.modelcontextprotocol/tasks": {} };
|
|
447
|
+
}
|
|
368
448
|
if (config.prompts)
|
|
369
449
|
capabilities.prompts = { listChanged: false };
|
|
370
450
|
if (config.resources) {
|
|
@@ -384,7 +464,7 @@ var initialize = async (config, id, params, context) => {
|
|
|
384
464
|
};
|
|
385
465
|
var toolsList = async (config, caller, scopes, id, params) => {
|
|
386
466
|
const tools = await config.tools({ caller, meta: {} });
|
|
387
|
-
const visible = Object.entries(tools).filter(([, tool]) => scopeAllows(tool, scopes)).map(([name, tool]) => ({
|
|
467
|
+
const visible = Object.entries(tools).filter(([, tool]) => scopeAllows(tool, scopes) && agencyAllows(config, tool, scopes)).map(([name, tool]) => ({
|
|
388
468
|
annotations: tool.annotations,
|
|
389
469
|
description: tool.description,
|
|
390
470
|
inputSchema: tool.inputSchema,
|
|
@@ -411,11 +491,70 @@ var SSE_HEADERS = {
|
|
|
411
491
|
var sseFrame = (message) => `data: ${JSON.stringify(message)}
|
|
412
492
|
|
|
413
493
|
`;
|
|
414
|
-
var runTool = async (config, caller, id, name, args, meta, tool, context) => {
|
|
494
|
+
var runTool = async (config, caller, scopes, id, name, args, meta, tool, context) => {
|
|
415
495
|
let ok = false;
|
|
416
496
|
let payload;
|
|
417
497
|
try {
|
|
418
|
-
const
|
|
498
|
+
const invoke = async () => normalizeResult(await tool.handler(args, context));
|
|
499
|
+
let result;
|
|
500
|
+
if (tool.authorization === undefined) {
|
|
501
|
+
result = await invoke();
|
|
502
|
+
} else {
|
|
503
|
+
const agency = config.agency;
|
|
504
|
+
if (agency === undefined)
|
|
505
|
+
throw new Error("Agent action policy is not configured");
|
|
506
|
+
const actor = await agency.resolveActor({ caller, scopes });
|
|
507
|
+
const actionInput = createCoazActionInput({
|
|
508
|
+
actor,
|
|
509
|
+
call: {
|
|
510
|
+
arguments: args,
|
|
511
|
+
name,
|
|
512
|
+
serverId: agency.serverId ?? config.serverInfo.name
|
|
513
|
+
},
|
|
514
|
+
effects: tool.authorization.effects,
|
|
515
|
+
requiredScopes: tool.authorization.requiredScopes
|
|
516
|
+
});
|
|
517
|
+
const amount = tool.authorization.spend ? valueAtPath(args, tool.authorization.spend.amountMinorField) : undefined;
|
|
518
|
+
const currency = tool.authorization.spend ? valueAtPath(args, tool.authorization.spend.currencyField) : undefined;
|
|
519
|
+
const idempotencyKey = tool.authorization.idempotencyKeyField ? valueAtPath(args, tool.authorization.idempotencyKeyField) : undefined;
|
|
520
|
+
const requested = await agency.enforcement.request({
|
|
521
|
+
...actionInput,
|
|
522
|
+
context: {
|
|
523
|
+
...actionInput.context,
|
|
524
|
+
manifest_authorization: tool.authorization
|
|
525
|
+
},
|
|
526
|
+
idempotencyKey: typeof idempotencyKey === "string" ? idempotencyKey : undefined,
|
|
527
|
+
spend: typeof amount === "number" && typeof currency === "string" ? { amountMinor: amount, currency } : undefined
|
|
528
|
+
});
|
|
529
|
+
meta.agencyActionId = requested.action.actionId;
|
|
530
|
+
meta.agencyDecisionId = requested.decision.decisionId;
|
|
531
|
+
if (requested.decision.kind === "deny") {
|
|
532
|
+
result = {
|
|
533
|
+
content: [
|
|
534
|
+
{
|
|
535
|
+
text: requested.decision.requestable ? `Action requires approval (${requested.action.actionId})` : `Action denied: ${requested.decision.reason}`,
|
|
536
|
+
type: "text"
|
|
537
|
+
}
|
|
538
|
+
],
|
|
539
|
+
isError: true,
|
|
540
|
+
structuredContent: {
|
|
541
|
+
actionId: requested.action.actionId,
|
|
542
|
+
decision: requested.decision,
|
|
543
|
+
type: "absolute.action_decision"
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
} else {
|
|
547
|
+
const lease = await agency.enforcement.issueLease(requested.action.actionId);
|
|
548
|
+
const executed = await agency.enforcement.execute({
|
|
549
|
+
executor: `mcp:${config.serverInfo.name}/${name}`,
|
|
550
|
+
leaseId: lease.leaseId,
|
|
551
|
+
run: invoke
|
|
552
|
+
});
|
|
553
|
+
meta.agencyLeaseId = lease.leaseId;
|
|
554
|
+
meta.agencyReceiptId = executed.receipt.receiptId;
|
|
555
|
+
result = executed.result;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
419
558
|
ok = result.isError !== true;
|
|
420
559
|
payload = { id, jsonrpc: "2.0", result };
|
|
421
560
|
} catch (error) {
|
|
@@ -433,7 +572,7 @@ var runTool = async (config, caller, id, name, args, meta, tool, context) => {
|
|
|
433
572
|
await config.onCall({ args, caller, meta, name, ok });
|
|
434
573
|
return payload;
|
|
435
574
|
};
|
|
436
|
-
var toolsCallStreaming = (config, caller, id, name, args, meta, tool, sessions, canElicit) => {
|
|
575
|
+
var toolsCallStreaming = (config, caller, scopes, id, name, args, meta, tool, sessions, canElicit) => {
|
|
437
576
|
const encoder = new TextEncoder;
|
|
438
577
|
const body = new ReadableStream({
|
|
439
578
|
async start(controller) {
|
|
@@ -462,7 +601,7 @@ var toolsCallStreaming = (config, caller, id, name, args, meta, tool, sessions,
|
|
|
462
601
|
return await pending.answer;
|
|
463
602
|
}
|
|
464
603
|
};
|
|
465
|
-
const payload = await runTool(config, caller, id, name, args, meta, tool, context);
|
|
604
|
+
const payload = await runTool(config, caller, scopes, id, name, args, meta, tool, context);
|
|
466
605
|
send(payload);
|
|
467
606
|
if (open) {
|
|
468
607
|
try {
|
|
@@ -489,19 +628,101 @@ var toolsCall = async (config, caller, scopes, id, params, context) => {
|
|
|
489
628
|
}
|
|
490
629
|
const tools = await config.tools({ caller, meta });
|
|
491
630
|
const tool = tools[name];
|
|
492
|
-
if (!tool || !scopeAllows(tool, scopes)) {
|
|
631
|
+
if (!tool || !scopeAllows(tool, scopes) || !agencyAllows(config, tool, scopes)) {
|
|
493
632
|
return rpcError(id, JSONRPC_INVALID_PARAMS, `Unknown tool: ${name}`);
|
|
494
633
|
}
|
|
634
|
+
const tasks = config.tasks;
|
|
635
|
+
if (tasks !== undefined && await tasks.shouldCreate({ args, caller, name })) {
|
|
636
|
+
if (!supportsTasks(params)) {
|
|
637
|
+
return rpcError(id, JSONRPC_MISSING_REQUIRED_CLIENT_CAPABILITY, "Missing required client capability", {
|
|
638
|
+
requiredCapabilities: {
|
|
639
|
+
extensions: { "io.modelcontextprotocol/tasks": {} }
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
const createdAt = new Date().toISOString();
|
|
644
|
+
const task = {
|
|
645
|
+
authorizationKey: await tasks.authorizationKey(caller),
|
|
646
|
+
createdAt,
|
|
647
|
+
lastUpdatedAt: createdAt,
|
|
648
|
+
pollIntervalMs: tasks.pollIntervalMs,
|
|
649
|
+
status: "working",
|
|
650
|
+
taskId: crypto.randomUUID(),
|
|
651
|
+
ttlMs: tasks.ttlMs ?? null
|
|
652
|
+
};
|
|
653
|
+
await tasks.store.save(task);
|
|
654
|
+
setTimeout(() => {
|
|
655
|
+
runTool(config, caller, scopes, id, name, args, meta, tool, noElicit).then(async (payload2) => {
|
|
656
|
+
const result = isRecord(payload2) && isRecord(payload2.result) ? payload2.result : { content: [], isError: true };
|
|
657
|
+
await tasks.store.update(task.taskId, {
|
|
658
|
+
result,
|
|
659
|
+
status: "completed"
|
|
660
|
+
});
|
|
661
|
+
}).catch(async (error) => {
|
|
662
|
+
await tasks.store.update(task.taskId, {
|
|
663
|
+
error: {
|
|
664
|
+
code: JSONRPC_INTERNAL_ERROR,
|
|
665
|
+
message: error instanceof Error ? error.message : "Task failed"
|
|
666
|
+
},
|
|
667
|
+
status: "failed"
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
}, 0);
|
|
671
|
+
return rpcResult(id, { ...publicMcpTask(task), resultType: "task" });
|
|
672
|
+
}
|
|
495
673
|
const sessions = context.sessions;
|
|
496
674
|
const session = sessions ? await sessions.get(context.sessionId ?? null) : null;
|
|
497
675
|
if (tool.mayElicit === true && config.elicitation?.enabled === true && sessions && session) {
|
|
498
|
-
return toolsCallStreaming(config, caller, id, name, args, meta, tool, sessions, session.canElicit);
|
|
676
|
+
return toolsCallStreaming(config, caller, scopes, id, name, args, meta, tool, sessions, session.canElicit);
|
|
499
677
|
}
|
|
500
|
-
const payload = await runTool(config, caller, id, name, args, meta, tool, noElicit);
|
|
678
|
+
const payload = await runTool(config, caller, scopes, id, name, args, meta, tool, noElicit);
|
|
501
679
|
return new Response(JSON.stringify(payload), {
|
|
502
680
|
headers: { "content-type": "application/json" }
|
|
503
681
|
});
|
|
504
682
|
};
|
|
683
|
+
var supportsTasks = (params) => {
|
|
684
|
+
if (!isRecord(params) || !isRecord(params._meta))
|
|
685
|
+
return false;
|
|
686
|
+
const capabilities = params._meta["io.modelcontextprotocol/clientCapabilities"];
|
|
687
|
+
if (!isRecord(capabilities) || !isRecord(capabilities.extensions))
|
|
688
|
+
return false;
|
|
689
|
+
return isRecord(capabilities.extensions["io.modelcontextprotocol/tasks"]);
|
|
690
|
+
};
|
|
691
|
+
var authorizedTask = async (config, caller, params) => {
|
|
692
|
+
if (config.tasks === undefined || !isRecord(params) || typeof params.taskId !== "string") {
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
const task = await config.tasks.store.get(params.taskId);
|
|
696
|
+
if (task === null)
|
|
697
|
+
return null;
|
|
698
|
+
const authorizationKey = await config.tasks.authorizationKey(caller);
|
|
699
|
+
return task.authorizationKey === authorizationKey ? task : null;
|
|
700
|
+
};
|
|
701
|
+
var tasksGet = async (config, caller, id, params) => {
|
|
702
|
+
const task = await authorizedTask(config, caller, params);
|
|
703
|
+
if (task === null)
|
|
704
|
+
return rpcError(id, JSONRPC_INVALID_PARAMS, "Unknown task");
|
|
705
|
+
return rpcResult(id, { ...publicMcpTask(task), resultType: "complete" });
|
|
706
|
+
};
|
|
707
|
+
var tasksUpdate = async (config, caller, id, params) => {
|
|
708
|
+
const task = await authorizedTask(config, caller, params);
|
|
709
|
+
if (task === null || !isRecord(params) || !isRecord(params.inputResponses)) {
|
|
710
|
+
return rpcError(id, JSONRPC_INVALID_PARAMS, "Unknown task or invalid inputResponses");
|
|
711
|
+
}
|
|
712
|
+
await config.tasks?.onUpdate?.({
|
|
713
|
+
caller,
|
|
714
|
+
inputResponses: params.inputResponses,
|
|
715
|
+
task
|
|
716
|
+
});
|
|
717
|
+
return rpcResult(id, { resultType: "complete" });
|
|
718
|
+
};
|
|
719
|
+
var tasksCancel = async (config, caller, id, params) => {
|
|
720
|
+
const task = await authorizedTask(config, caller, params);
|
|
721
|
+
if (task === null)
|
|
722
|
+
return rpcError(id, JSONRPC_INVALID_PARAMS, "Unknown task");
|
|
723
|
+
await config.tasks?.store.cancel(task.taskId);
|
|
724
|
+
return rpcResult(id, { resultType: "complete" });
|
|
725
|
+
};
|
|
505
726
|
var promptsList = (config, id, params) => {
|
|
506
727
|
const definitions = config.prompts?.definitions ?? {};
|
|
507
728
|
const all = Object.entries(definitions).map(([name, def]) => ({
|
|
@@ -593,6 +814,14 @@ var dispatchMcp = async (config, caller, scopes, message, context = {}) => {
|
|
|
593
814
|
if (method === "initialize") {
|
|
594
815
|
return await initialize(config, id, params, context);
|
|
595
816
|
}
|
|
817
|
+
if (method === "server/discover") {
|
|
818
|
+
return rpcResult(id, {
|
|
819
|
+
capabilities: {
|
|
820
|
+
extensions: config.tasks === undefined ? {} : { "io.modelcontextprotocol/tasks": {} }
|
|
821
|
+
},
|
|
822
|
+
serverInfo: config.serverInfo
|
|
823
|
+
});
|
|
824
|
+
}
|
|
596
825
|
if (method === "ping")
|
|
597
826
|
return rpcResult(id, {});
|
|
598
827
|
if (method === "tools/list") {
|
|
@@ -601,6 +830,12 @@ var dispatchMcp = async (config, caller, scopes, message, context = {}) => {
|
|
|
601
830
|
if (method === "tools/call") {
|
|
602
831
|
return toolsCall(config, caller, scopes, id, params, context);
|
|
603
832
|
}
|
|
833
|
+
if (method === "tasks/get")
|
|
834
|
+
return tasksGet(config, caller, id, params);
|
|
835
|
+
if (method === "tasks/update")
|
|
836
|
+
return tasksUpdate(config, caller, id, params);
|
|
837
|
+
if (method === "tasks/cancel")
|
|
838
|
+
return tasksCancel(config, caller, id, params);
|
|
604
839
|
if (method === "prompts/list")
|
|
605
840
|
return promptsList(config, id, params);
|
|
606
841
|
if (method === "prompts/get")
|
|
@@ -896,12 +1131,14 @@ var mcpServer = (config) => {
|
|
|
896
1131
|
};
|
|
897
1132
|
export {
|
|
898
1133
|
verifyBearer,
|
|
1134
|
+
publicMcpTask,
|
|
899
1135
|
protectedResourceMetadata,
|
|
900
1136
|
metadataPathFor,
|
|
901
1137
|
mcpServer,
|
|
902
1138
|
feedbackTools,
|
|
903
1139
|
dispatchMcp,
|
|
904
1140
|
createSessionRegistry,
|
|
1141
|
+
createMemoryMcpTaskStore,
|
|
905
1142
|
createMcpHandler,
|
|
906
1143
|
createMcpClient,
|
|
907
1144
|
McpClientError,
|
package/dist/manifest.js
CHANGED
|
@@ -5883,8 +5883,27 @@ var toolAnnotations = Type.Object({
|
|
|
5883
5883
|
readOnlyHint: Type.Optional(Type.Boolean()),
|
|
5884
5884
|
title: Type.Optional(Type.String())
|
|
5885
5885
|
});
|
|
5886
|
+
var toolAuthorization = Type.Object({
|
|
5887
|
+
approval: Type.Optional(Type.Union([
|
|
5888
|
+
Type.Literal("always"),
|
|
5889
|
+
Type.Literal("never"),
|
|
5890
|
+
Type.Literal("policy")
|
|
5891
|
+
])),
|
|
5892
|
+
compensatingTool: Type.Optional(Type.String({ pattern: TOOL_NAME_PATTERN.source })),
|
|
5893
|
+
destinations: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
5894
|
+
effects: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
|
|
5895
|
+
idempotencyKeyField: Type.Optional(Type.String({ minLength: 1 })),
|
|
5896
|
+
requiredScopes: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
5897
|
+
reversible: Type.Optional(Type.Boolean()),
|
|
5898
|
+
spend: Type.Optional(Type.Object({
|
|
5899
|
+
amountMinorField: Type.String({ minLength: 1 }),
|
|
5900
|
+
currencyField: Type.String({ minLength: 1 }),
|
|
5901
|
+
maximumAmountMinor: Type.Optional(Type.Integer({ minimum: 0 }))
|
|
5902
|
+
}))
|
|
5903
|
+
});
|
|
5886
5904
|
var serializedTool = Type.Object({
|
|
5887
5905
|
annotations: Type.Optional(toolAnnotations),
|
|
5906
|
+
authorization: Type.Optional(toolAuthorization),
|
|
5888
5907
|
capabilities: Type.Optional(Type.Array(Type.Union([
|
|
5889
5908
|
Type.Literal("exec"),
|
|
5890
5909
|
Type.Literal("glob"),
|
|
@@ -5896,7 +5915,7 @@ var serializedTool = Type.Object({
|
|
|
5896
5915
|
kind: Type.Union([Type.Literal("runtime"), Type.Literal("workspace")])
|
|
5897
5916
|
});
|
|
5898
5917
|
var manifestSchema = Type.Object({
|
|
5899
|
-
contract: Type.Literal(1),
|
|
5918
|
+
contract: Type.Union([Type.Literal(1), Type.Literal(2)]),
|
|
5900
5919
|
identity: Type.Object({
|
|
5901
5920
|
accent: Type.Optional(Type.String({ pattern: "^#[0-9a-fA-F]{3,8}$" })),
|
|
5902
5921
|
category: Type.String({ minLength: 1 }),
|
|
@@ -8477,11 +8496,11 @@ var Type2 = exports_type6;
|
|
|
8477
8496
|
|
|
8478
8497
|
// src/manifest.ts
|
|
8479
8498
|
var manifest = defineManifest()({
|
|
8480
|
-
contract:
|
|
8499
|
+
contract: 2,
|
|
8481
8500
|
identity: {
|
|
8482
8501
|
accent: "#0ea5e9",
|
|
8483
8502
|
category: "ai",
|
|
8484
|
-
description: "Serve a remote Model Context Protocol endpoint
|
|
8503
|
+
description: "Serve a remote Model Context Protocol endpoint with OAuth discovery, agency action enforcement, approval receipts, and caller-bound durable Tasks. Bridge installed AbsoluteJS packages' manifests with `toMcpToolRegistry` and enforce their declared effects before execution.",
|
|
8485
8504
|
docsUrl: "https://github.com/absolutejs/mcp",
|
|
8486
8505
|
name: "@absolutejs/mcp",
|
|
8487
8506
|
tagline: "Let AI assistants connect to your site and use its tools."
|
package/dist/manifest.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"contract":
|
|
2
|
+
"contract": 2,
|
|
3
3
|
"identity": {
|
|
4
4
|
"accent": "#0ea5e9",
|
|
5
5
|
"category": "ai",
|
|
6
|
-
"description": "Serve a remote Model Context Protocol endpoint
|
|
6
|
+
"description": "Serve a remote Model Context Protocol endpoint with OAuth discovery, agency action enforcement, approval receipts, and caller-bound durable Tasks. Bridge installed AbsoluteJS packages' manifests with `toMcpToolRegistry` and enforce their declared effects before execution.",
|
|
7
7
|
"docsUrl": "https://github.com/absolutejs/mcp",
|
|
8
8
|
"name": "@absolutejs/mcp",
|
|
9
9
|
"tagline": "Let AI assistants connect to your site and use its tools."
|
package/dist/src/index.d.ts
CHANGED
|
@@ -38,4 +38,5 @@ export { createMcpHandler } from "./handler";
|
|
|
38
38
|
export { metadataPathFor, protectedResourceMetadata, type ProtectedResourceMetadata, } from "./metadata";
|
|
39
39
|
export { mcpServer } from "./server";
|
|
40
40
|
export { createSessionRegistry, type SessionRegistry } from "./sessions";
|
|
41
|
-
export
|
|
41
|
+
export { createMemoryMcpTaskStore, publicMcpTask } from "./tasks";
|
|
42
|
+
export type { McpAgencyOptions, McpAudioContent, McpElicitAnswer, McpElicitationRequest, McpElicitBus, McpElicitResult, McpSessionStore, McpAuthResult, McpCallGate, McpCallMeta, McpContent, McpImageContent, McpPromptArgument, McpPromptDefinition, McpPrompts, McpResource, McpResourceLink, McpResources, McpServerConfig, McpServerInfo, McpTextContent, McpTask, McpTaskStatus, McpTaskStore, McpTasksOptions, McpTool, McpToolAnnotations, McpToolCallContext, McpToolContext, McpToolRegistry, McpToolResult, McpToolReturn, } from "./types";
|
package/dist/src/jsonrpc.d.ts
CHANGED
|
@@ -3,13 +3,14 @@ export declare const JSONRPC_INVALID_REQUEST = -32600;
|
|
|
3
3
|
export declare const JSONRPC_METHOD_NOT_FOUND = -32601;
|
|
4
4
|
export declare const JSONRPC_INVALID_PARAMS = -32602;
|
|
5
5
|
export declare const JSONRPC_INTERNAL_ERROR = -32603;
|
|
6
|
+
export declare const JSONRPC_MISSING_REQUIRED_CLIENT_CAPABILITY = -32003;
|
|
6
7
|
export declare const HTTP_ACCEPTED = 202;
|
|
7
8
|
export declare const HTTP_NO_CONTENT = 204;
|
|
8
9
|
export declare const HTTP_UNAUTHORIZED = 401;
|
|
9
10
|
export declare const HTTP_METHOD_NOT_ALLOWED = 405;
|
|
10
11
|
export type JsonRpcId = string | number | null;
|
|
11
12
|
export declare const rpcResult: (id: JsonRpcId, result: unknown) => Response;
|
|
12
|
-
export declare const rpcError: (id: JsonRpcId, code: number, message: string) => Response;
|
|
13
|
+
export declare const rpcError: (id: JsonRpcId, code: number, message: string, data?: Record<string, unknown>) => Response;
|
|
13
14
|
export declare const notificationAck: () => Response;
|
|
14
15
|
/** 401 with the RFC 9728 `WWW-Authenticate` challenge pointing at the
|
|
15
16
|
* protected-resource metadata, so the client can discover the auth server. */
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { McpTask, McpTaskStore } from "./types";
|
|
2
|
+
export declare const createMemoryMcpTaskStore: () => McpTaskStore;
|
|
3
|
+
export declare const publicMcpTask: ({ authorizationKey, ...task }: McpTask) => {
|
|
4
|
+
createdAt: string;
|
|
5
|
+
error?: Record<string, unknown>;
|
|
6
|
+
inputRequests?: Record<string, unknown>;
|
|
7
|
+
lastUpdatedAt: string;
|
|
8
|
+
pollIntervalMs?: number;
|
|
9
|
+
result?: Record<string, unknown>;
|
|
10
|
+
status: import("./types").McpTaskStatus;
|
|
11
|
+
statusMessage?: string;
|
|
12
|
+
taskId: string;
|
|
13
|
+
ttlMs: number | null;
|
|
14
|
+
};
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Agency, AgentActor } from "@absolutejs/agency";
|
|
2
|
+
import type { ToolAuthorization } from "@absolutejs/manifest";
|
|
1
3
|
/** MCP behaviour hints, passed straight through to the client on `tools/list`.
|
|
2
4
|
* Structurally identical to `@absolutejs/ai`'s `AIToolAnnotations`, so a tool
|
|
3
5
|
* map from that package satisfies this without conversion. All optional. */
|
|
@@ -108,6 +110,9 @@ export type McpToolCallContext = {
|
|
|
108
110
|
/** One callable tool. `inputSchema` is a JSON Schema object. */
|
|
109
111
|
export type McpTool = {
|
|
110
112
|
annotations?: McpToolAnnotations;
|
|
113
|
+
/** Enforceable semantic effects from manifest contract 2. A tool carrying
|
|
114
|
+
* this is hidden unless the server configures `agency`. */
|
|
115
|
+
authorization?: ToolAuthorization;
|
|
111
116
|
description: string;
|
|
112
117
|
handler: (args: unknown, context: McpToolCallContext) => McpToolReturn | Promise<McpToolReturn>;
|
|
113
118
|
inputSchema: Record<string, unknown>;
|
|
@@ -165,6 +170,50 @@ export type McpToolContext<Caller> = {
|
|
|
165
170
|
caller: Caller;
|
|
166
171
|
meta: McpCallMeta;
|
|
167
172
|
};
|
|
173
|
+
export type McpAgencyOptions<Caller> = {
|
|
174
|
+
enforcement: Agency;
|
|
175
|
+
resolveActor: (context: {
|
|
176
|
+
caller: Caller;
|
|
177
|
+
scopes: string[];
|
|
178
|
+
}) => Promise<AgentActor> | AgentActor;
|
|
179
|
+
serverId?: string;
|
|
180
|
+
};
|
|
181
|
+
export type McpTaskStatus = "cancelled" | "completed" | "failed" | "input_required" | "working";
|
|
182
|
+
export type McpTask = {
|
|
183
|
+
authorizationKey: string;
|
|
184
|
+
createdAt: string;
|
|
185
|
+
error?: Record<string, unknown>;
|
|
186
|
+
inputRequests?: Record<string, unknown>;
|
|
187
|
+
lastUpdatedAt: string;
|
|
188
|
+
pollIntervalMs?: number;
|
|
189
|
+
result?: Record<string, unknown>;
|
|
190
|
+
status: McpTaskStatus;
|
|
191
|
+
statusMessage?: string;
|
|
192
|
+
taskId: string;
|
|
193
|
+
ttlMs: number | null;
|
|
194
|
+
};
|
|
195
|
+
export type McpTaskStore = {
|
|
196
|
+
cancel: (taskId: string) => Promise<void> | void;
|
|
197
|
+
get: (taskId: string) => Promise<McpTask | null> | McpTask | null;
|
|
198
|
+
save: (task: McpTask) => Promise<void> | void;
|
|
199
|
+
update: (taskId: string, update: Partial<Omit<McpTask, "authorizationKey" | "createdAt" | "taskId">>) => Promise<McpTask | null> | McpTask | null;
|
|
200
|
+
};
|
|
201
|
+
export type McpTasksOptions<Caller> = {
|
|
202
|
+
authorizationKey: (caller: Caller) => Promise<string> | string;
|
|
203
|
+
onUpdate?: (context: {
|
|
204
|
+
caller: Caller;
|
|
205
|
+
inputResponses: Record<string, unknown>;
|
|
206
|
+
task: McpTask;
|
|
207
|
+
}) => Promise<void> | void;
|
|
208
|
+
pollIntervalMs?: number;
|
|
209
|
+
shouldCreate: (context: {
|
|
210
|
+
args: unknown;
|
|
211
|
+
caller: Caller;
|
|
212
|
+
name: string;
|
|
213
|
+
}) => Promise<boolean> | boolean;
|
|
214
|
+
store: McpTaskStore;
|
|
215
|
+
ttlMs?: number | null;
|
|
216
|
+
};
|
|
168
217
|
export type McpPrompts<Caller> = {
|
|
169
218
|
definitions: Record<string, McpPromptDefinition>;
|
|
170
219
|
get: (ctx: {
|
|
@@ -190,6 +239,8 @@ export type McpServerInfo = {
|
|
|
190
239
|
version: string;
|
|
191
240
|
};
|
|
192
241
|
export type McpServerConfig<Caller> = {
|
|
242
|
+
/** Per-tool action policy enforcement, approval, leases, and receipts. */
|
|
243
|
+
agency?: McpAgencyOptions<Caller>;
|
|
193
244
|
/** Resolve the request into a caller, or a reason for the 401. The package
|
|
194
245
|
* emits the 401 + RFC 9728 `WWW-Authenticate` challenge; you decide who is
|
|
195
246
|
* allowed in. See {@link verifyBearer} for the standard token checks. */
|
|
@@ -244,6 +295,8 @@ export type McpServerConfig<Caller> = {
|
|
|
244
295
|
/** Protocol versions this endpoint accepts; the first is the preferred one.
|
|
245
296
|
* Defaults to the versions this package knows. */
|
|
246
297
|
supportedProtocols?: string[];
|
|
298
|
+
/** Final SEP-2663 `io.modelcontextprotocol/tasks` extension support. */
|
|
299
|
+
tasks?: McpTasksOptions<Caller>;
|
|
247
300
|
/** Build the tool registry for this caller. Called once per request. */
|
|
248
301
|
tools: (ctx: McpToolContext<Caller>) => McpToolRegistry | Promise<McpToolRegistry>;
|
|
249
302
|
};
|
package/package.json
CHANGED
|
@@ -11,12 +11,13 @@
|
|
|
11
11
|
"elysia": ">=1.1.0"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@absolutejs/
|
|
14
|
+
"@absolutejs/agency": "^0.1.0",
|
|
15
|
+
"@absolutejs/manifest": "^0.2.0",
|
|
15
16
|
"@sinclair/typebox": "^0.34.0"
|
|
16
17
|
},
|
|
17
18
|
"license": "BUSL-1.1",
|
|
18
19
|
"absolutejs": {
|
|
19
|
-
"manifestContract":
|
|
20
|
+
"manifestContract": 2
|
|
20
21
|
},
|
|
21
22
|
"exports": {
|
|
22
23
|
".": {
|
|
@@ -57,5 +58,5 @@
|
|
|
57
58
|
"typecheck": "tsc --noEmit --project tsconfig.json"
|
|
58
59
|
},
|
|
59
60
|
"types": "./dist/src/index.d.ts",
|
|
60
|
-
"version": "0.
|
|
61
|
+
"version": "0.5.0"
|
|
61
62
|
}
|