@atrib/agent 0.1.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/LICENSE +190 -0
- package/README.md +333 -0
- package/dist/adapters/cloudflare-agent.d.ts +46 -0
- package/dist/adapters/cloudflare-agent.d.ts.map +1 -0
- package/dist/adapters/cloudflare-agent.js +124 -0
- package/dist/adapters/cloudflare-agent.js.map +1 -0
- package/dist/adapters/langchain-mcp.d.ts +172 -0
- package/dist/adapters/langchain-mcp.d.ts.map +1 -0
- package/dist/adapters/langchain-mcp.js +145 -0
- package/dist/adapters/langchain-mcp.js.map +1 -0
- package/dist/adapters/mcp-client.d.ts +94 -0
- package/dist/adapters/mcp-client.d.ts.map +1 -0
- package/dist/adapters/mcp-client.js +91 -0
- package/dist/adapters/mcp-client.js.map +1 -0
- package/dist/adapters/vercel-ai-sdk-mcp.d.ts +129 -0
- package/dist/adapters/vercel-ai-sdk-mcp.d.ts.map +1 -0
- package/dist/adapters/vercel-ai-sdk-mcp.js +115 -0
- package/dist/adapters/vercel-ai-sdk-mcp.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.d.ts +53 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +268 -0
- package/dist/middleware.js.map +1 -0
- package/dist/policy.d.ts +28 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +196 -0
- package/dist/policy.js.map +1 -0
- package/dist/session.d.ts +50 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +141 -0
- package/dist/session.js.map +1 -0
- package/dist/transaction.d.ts +52 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +123 -0
- package/dist/transaction.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter: attribute MCP tool calls flowing through a Vercel AI SDK MCP client.
|
|
3
|
+
*
|
|
4
|
+
* The Vercel AI SDK exposes `createMCPClient()` (and the legacy
|
|
5
|
+
* `experimental_createMCPClient()`) from `@ai-sdk/mcp`. The returned client is
|
|
6
|
+
* NOT a `@modelcontextprotocol/sdk` Client. it has its own JSON-RPC
|
|
7
|
+
* implementation (verified against `@ai-sdk/mcp@1.0.35`'s `dist/index.mjs`).
|
|
8
|
+
* This means our `wrapMcpClient` adapter (which expects the structural shape
|
|
9
|
+
* of `@modelcontextprotocol/sdk` Client) does not apply directly.
|
|
10
|
+
*
|
|
11
|
+
* Two structural differences make this adapter necessary:
|
|
12
|
+
*
|
|
13
|
+
* 1. **`callTool` shape mismatch.** `@ai-sdk/mcp` MCPClient.callTool takes
|
|
14
|
+
* `{ name, args, options }` (verified at `dist/index.mjs:1814`),
|
|
15
|
+
* whereas `@modelcontextprotocol/sdk` Client.callTool takes
|
|
16
|
+
* `{ name, arguments, _meta }`. Different field names, and
|
|
17
|
+
* `@ai-sdk/mcp` does not pass `_meta` through to the request layer at
|
|
18
|
+
* all. it builds the request as `{ method: 'tools/call', params: { name, arguments: args } }`
|
|
19
|
+
* with no `_meta` field (`dist/index.mjs:1819`).
|
|
20
|
+
*
|
|
21
|
+
* 2. **`tools()` builds AI-SDK-shaped tool definitions whose execute()
|
|
22
|
+
* callbacks pass through `extractStructuredContent`** when an
|
|
23
|
+
* outputSchema is set, which DROPS the `_meta` field from the result
|
|
24
|
+
* envelope (`dist/index.mjs:1989-1991`). Wrapping at the AI SDK execute
|
|
25
|
+
* layer would lose attribution data for any structured-output tool.
|
|
26
|
+
*
|
|
27
|
+
* The right integration point is **`MCPClient.request()`**. the JSON-RPC
|
|
28
|
+
* bottleneck through which every tools/call (and tools/list, resources/read,
|
|
29
|
+
* etc.) flows on its way to the transport (`dist/index.mjs:1750`). Patching
|
|
30
|
+
* here lets us:
|
|
31
|
+
*
|
|
32
|
+
* - Inject `_meta` (atrib token, traceparent, tracestate, baggage,
|
|
33
|
+
* X-Atrib-Chain) into the outbound request.params, so the upstream MCP
|
|
34
|
+
* server sees a properly contextualized tools/call request
|
|
35
|
+
* - Receive the raw CallToolResult before any AI-SDK-specific transformation,
|
|
36
|
+
* so our interceptor's `onAfterToolResponse` gets the original `_meta`
|
|
37
|
+
* including any `atrib` token from the server side
|
|
38
|
+
*
|
|
39
|
+
* Symmetry with the server side: `@atrib/mcp` patches
|
|
40
|
+
* `McpServer.server.setRequestHandler(CallToolRequestSchema, ...)`. This
|
|
41
|
+
* adapter patches `MCPClient.request` for outbound `tools/call`. Same pattern,
|
|
42
|
+
* opposite end of the wire.
|
|
43
|
+
*/
|
|
44
|
+
import type { ToolCallInterceptor } from '../middleware.js';
|
|
45
|
+
/**
|
|
46
|
+
* Minimal structural type for an `@ai-sdk/mcp` MCPClient. Mirrors the public
|
|
47
|
+
* surface we depend on without importing from `@ai-sdk/mcp` (we don't want a
|
|
48
|
+
* hard dependency on the AI SDK package).
|
|
49
|
+
*
|
|
50
|
+
* The `request` method is typed loosely (`unknown` return) because the actual
|
|
51
|
+
* `@ai-sdk/mcp` implementation uses Zod-derived result types that vary per
|
|
52
|
+
* call. The runtime-injected `_meta` we care about is a structural field on
|
|
53
|
+
* the request params, not a typed contract.
|
|
54
|
+
*/
|
|
55
|
+
export interface VercelAiSdkMcpClientLike {
|
|
56
|
+
request(args: {
|
|
57
|
+
request: {
|
|
58
|
+
method: string;
|
|
59
|
+
params?: Record<string, unknown> | undefined;
|
|
60
|
+
};
|
|
61
|
+
resultSchema?: unknown;
|
|
62
|
+
options?: {
|
|
63
|
+
signal?: AbortSignal | undefined;
|
|
64
|
+
} | undefined;
|
|
65
|
+
}): Promise<unknown>;
|
|
66
|
+
}
|
|
67
|
+
/** Options for `attributeVercelAiSdkMcp`. */
|
|
68
|
+
export interface AttributeVercelAiSdkMcpOptions {
|
|
69
|
+
/** The atrib interceptor that observes tool calls on this client. */
|
|
70
|
+
interceptor: ToolCallInterceptor;
|
|
71
|
+
/**
|
|
72
|
+
* Canonical URL of the MCP server this client connects to. Used by the
|
|
73
|
+
* interceptor for content_id derivation when emitting Path 2 transaction
|
|
74
|
+
* records (the agent fallback path described in spec §5.4.5). Recommended
|
|
75
|
+
* for any production setup; required for stdio upstreams (where no host
|
|
76
|
+
* header is available).
|
|
77
|
+
*/
|
|
78
|
+
serverUrl?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Patch a Vercel AI SDK MCP client so every outbound `tools/call` flows
|
|
82
|
+
* through atrib's interceptor lifecycle.
|
|
83
|
+
*
|
|
84
|
+
* Mutates the passed client in place by replacing its `request` method with
|
|
85
|
+
* a wrapped version. Returns the same client reference for convenience.
|
|
86
|
+
*
|
|
87
|
+
* Idempotent: calling this helper twice on the same client is a no-op the
|
|
88
|
+
* second time.
|
|
89
|
+
*
|
|
90
|
+
* Order: this helper can be called BEFORE or AFTER `mcpClient.tools()`. The
|
|
91
|
+
* AI SDK builds tool execute() callbacks that read `client.request` at
|
|
92
|
+
* invocation time, not at build time, so subsequent tool invocations will
|
|
93
|
+
* use the patched method regardless of when you patch.
|
|
94
|
+
*
|
|
95
|
+
* Usage:
|
|
96
|
+
*
|
|
97
|
+
* import { createMCPClient } from '@ai-sdk/mcp'
|
|
98
|
+
* import { streamText } from 'ai'
|
|
99
|
+
* import { atrib, attributeVercelAiSdkMcp } from '@atrib/agent'
|
|
100
|
+
*
|
|
101
|
+
* const interceptor = atrib({
|
|
102
|
+
* creatorKey: process.env.ATRIB_PRIVATE_KEY,
|
|
103
|
+
* merchantDomain: 'https://merchant.example.com',
|
|
104
|
+
* serverUrls: ['https://my-tool.example'],
|
|
105
|
+
* })
|
|
106
|
+
*
|
|
107
|
+
* const mcpClient = await createMCPClient({
|
|
108
|
+
* transport: { type: 'http', url: 'https://my-tool.example/mcp' },
|
|
109
|
+
* })
|
|
110
|
+
*
|
|
111
|
+
* attributeVercelAiSdkMcp(mcpClient, {
|
|
112
|
+
* interceptor,
|
|
113
|
+
* serverUrl: 'https://my-tool.example',
|
|
114
|
+
* })
|
|
115
|
+
*
|
|
116
|
+
* const tools = await mcpClient.tools()
|
|
117
|
+
*
|
|
118
|
+
* const result = await streamText({
|
|
119
|
+
* model: openai('gpt-4o'),
|
|
120
|
+
* tools,
|
|
121
|
+
* prompt: 'What can you do?',
|
|
122
|
+
* onFinish: async () => {
|
|
123
|
+
* await mcpClient.close()
|
|
124
|
+
* await interceptor.flush()
|
|
125
|
+
* },
|
|
126
|
+
* })
|
|
127
|
+
*/
|
|
128
|
+
export declare function attributeVercelAiSdkMcp<C extends VercelAiSdkMcpClientLike>(client: C, options: AttributeVercelAiSdkMcpOptions): C;
|
|
129
|
+
//# sourceMappingURL=vercel-ai-sdk-mcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vercel-ai-sdk-mcp.d.ts","sourceRoot":"","sources":["../../src/adapters/vercel-ai-sdk-mcp.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAQ3D;;;;;;;;;GASG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,CAAC,IAAI,EAAE;QACZ,OAAO,EAAE;YACP,MAAM,EAAE,MAAM,CAAA;YACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;SAC7C,CAAA;QACD,YAAY,CAAC,EAAE,OAAO,CAAA;QACtB,OAAO,CAAC,EAAE;YAAE,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;SAAE,GAAG,SAAS,CAAA;KAC3D,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACrB;AAED,6CAA6C;AAC7C,MAAM,WAAW,8BAA8B;IAC7C,qEAAqE;IACrE,WAAW,EAAE,mBAAmB,CAAA;IAEhC;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,wBAAwB,EACxE,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,8BAA8B,GACtC,CAAC,CAyEH"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Marker symbol set on a patched client to make repeated calls to
|
|
4
|
+
* `attributeVercelAiSdkMcp` idempotent.
|
|
5
|
+
*/
|
|
6
|
+
const ATRIB_PATCHED = Symbol.for('atrib.vercel-ai-sdk.patched');
|
|
7
|
+
/**
|
|
8
|
+
* Patch a Vercel AI SDK MCP client so every outbound `tools/call` flows
|
|
9
|
+
* through atrib's interceptor lifecycle.
|
|
10
|
+
*
|
|
11
|
+
* Mutates the passed client in place by replacing its `request` method with
|
|
12
|
+
* a wrapped version. Returns the same client reference for convenience.
|
|
13
|
+
*
|
|
14
|
+
* Idempotent: calling this helper twice on the same client is a no-op the
|
|
15
|
+
* second time.
|
|
16
|
+
*
|
|
17
|
+
* Order: this helper can be called BEFORE or AFTER `mcpClient.tools()`. The
|
|
18
|
+
* AI SDK builds tool execute() callbacks that read `client.request` at
|
|
19
|
+
* invocation time, not at build time, so subsequent tool invocations will
|
|
20
|
+
* use the patched method regardless of when you patch.
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
*
|
|
24
|
+
* import { createMCPClient } from '@ai-sdk/mcp'
|
|
25
|
+
* import { streamText } from 'ai'
|
|
26
|
+
* import { atrib, attributeVercelAiSdkMcp } from '@atrib/agent'
|
|
27
|
+
*
|
|
28
|
+
* const interceptor = atrib({
|
|
29
|
+
* creatorKey: process.env.ATRIB_PRIVATE_KEY,
|
|
30
|
+
* merchantDomain: 'https://merchant.example.com',
|
|
31
|
+
* serverUrls: ['https://my-tool.example'],
|
|
32
|
+
* })
|
|
33
|
+
*
|
|
34
|
+
* const mcpClient = await createMCPClient({
|
|
35
|
+
* transport: { type: 'http', url: 'https://my-tool.example/mcp' },
|
|
36
|
+
* })
|
|
37
|
+
*
|
|
38
|
+
* attributeVercelAiSdkMcp(mcpClient, {
|
|
39
|
+
* interceptor,
|
|
40
|
+
* serverUrl: 'https://my-tool.example',
|
|
41
|
+
* })
|
|
42
|
+
*
|
|
43
|
+
* const tools = await mcpClient.tools()
|
|
44
|
+
*
|
|
45
|
+
* const result = await streamText({
|
|
46
|
+
* model: openai('gpt-4o'),
|
|
47
|
+
* tools,
|
|
48
|
+
* prompt: 'What can you do?',
|
|
49
|
+
* onFinish: async () => {
|
|
50
|
+
* await mcpClient.close()
|
|
51
|
+
* await interceptor.flush()
|
|
52
|
+
* },
|
|
53
|
+
* })
|
|
54
|
+
*/
|
|
55
|
+
export function attributeVercelAiSdkMcp(client, options) {
|
|
56
|
+
// Idempotency check. don't double-patch a client. Repeat calls are a no-op.
|
|
57
|
+
if (client[ATRIB_PATCHED] === true) {
|
|
58
|
+
return client;
|
|
59
|
+
}
|
|
60
|
+
const { interceptor, serverUrl } = options;
|
|
61
|
+
const originalRequest = client.request.bind(client);
|
|
62
|
+
const patchedRequest = async (args) => {
|
|
63
|
+
// Forward non-tools/call methods unchanged (tools/list, resources/read,
|
|
64
|
+
// notifications, etc.). The interceptor only cares about tool invocations.
|
|
65
|
+
if (args?.request?.method !== 'tools/call') {
|
|
66
|
+
return originalRequest(args);
|
|
67
|
+
}
|
|
68
|
+
const params = args.request.params ?? {};
|
|
69
|
+
const toolName = params.name ?? '';
|
|
70
|
+
const existingMeta = params._meta ?? {};
|
|
71
|
+
// §5.4.3: Build outbound _meta. The interceptor returns a record that
|
|
72
|
+
// includes any existing _meta keys plus atrib's own (atrib token,
|
|
73
|
+
// traceparent, tracestate, baggage, X-Atrib-Chain).
|
|
74
|
+
let outboundMeta;
|
|
75
|
+
try {
|
|
76
|
+
outboundMeta = await interceptor.onBeforeToolCall(toolName, existingMeta);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
// §5.8 degradation: pass through with the original meta on failure
|
|
80
|
+
console.warn('atrib: vercel-ai-sdk-mcp onBeforeToolCall failed, passing through', err);
|
|
81
|
+
outboundMeta = undefined;
|
|
82
|
+
}
|
|
83
|
+
// Forward the request with merged _meta if the interceptor provided any.
|
|
84
|
+
// We construct a new args object so the caller's reference is not mutated.
|
|
85
|
+
const forwardedArgs = outboundMeta !== undefined
|
|
86
|
+
? {
|
|
87
|
+
...args,
|
|
88
|
+
request: {
|
|
89
|
+
...args.request,
|
|
90
|
+
params: { ...params, _meta: outboundMeta },
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
: args;
|
|
94
|
+
const result = await originalRequest(forwardedArgs);
|
|
95
|
+
// §5.4.4: Update session state from the response _meta. The Vercel AI
|
|
96
|
+
// SDK MCPClient returns the raw CallToolResult here BEFORE any
|
|
97
|
+
// extractStructuredContent transformation, so the _meta field is intact.
|
|
98
|
+
try {
|
|
99
|
+
const responseMeta = result?._meta ?? {};
|
|
100
|
+
const responseOptions = {
|
|
101
|
+
...(serverUrl !== undefined ? { serverUrl } : {}),
|
|
102
|
+
isError: result?.isError === true,
|
|
103
|
+
};
|
|
104
|
+
interceptor.onAfterToolResponse(toolName, result, responseMeta, responseOptions);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
console.warn('atrib: vercel-ai-sdk-mcp onAfterToolResponse failed, passing through', err);
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
};
|
|
111
|
+
client.request = patchedRequest;
|
|
112
|
+
client[ATRIB_PATCHED] = true;
|
|
113
|
+
return client;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=vercel-ai-sdk-mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vercel-ai-sdk-mcp.js","sourceRoot":"","sources":["../../src/adapters/vercel-ai-sdk-mcp.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAgDtC;;;GAGG;AACH,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAA;AAsC/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,UAAU,uBAAuB,CACrC,MAAS,EACT,OAAuC;IAEvC,4EAA4E;IAC5E,IAAK,MAA6C,CAAC,aAAa,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3E,OAAO,MAAM,CAAA;IACf,CAAC;IAED,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,OAAO,CAAA;IAC1C,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAEnD,MAAM,cAAc,GAAwC,KAAK,EAAE,IAAI,EAAE,EAAE;QACzE,wEAAwE;QACxE,2EAA2E;QAC3E,IAAI,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,YAAY,EAAE,CAAC;YAC3C,OAAO,eAAe,CAAC,IAAI,CAAC,CAAA;QAC9B,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAA;QACxC,MAAM,QAAQ,GAAI,MAAM,CAAC,IAAe,IAAI,EAAE,CAAA;QAC9C,MAAM,YAAY,GAAI,MAAM,CAAC,KAAiC,IAAI,EAAE,CAAA;QAEpE,sEAAsE;QACtE,kEAAkE;QAClE,oDAAoD;QACpD,IAAI,YAAiD,CAAA;QACrD,IAAI,CAAC;YACH,YAAY,GAAG,MAAM,WAAW,CAAC,gBAAgB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAA;QAC3E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,mEAAmE;YACnE,OAAO,CAAC,IAAI,CAAC,mEAAmE,EAAE,GAAG,CAAC,CAAA;YACtF,YAAY,GAAG,SAAS,CAAA;QAC1B,CAAC;QAED,yEAAyE;QACzE,2EAA2E;QAC3E,MAAM,aAAa,GACjB,YAAY,KAAK,SAAS;YACxB,CAAC,CAAC;gBACE,GAAG,IAAI;gBACP,OAAO,EAAE;oBACP,GAAG,IAAI,CAAC,OAAO;oBACf,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE;iBAC3C;aACF;YACH,CAAC,CAAC,IAAI,CAAA;QAEV,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,aAAa,CAAC,CAAA;QAEnD,sEAAsE;QACtE,+DAA+D;QAC/D,yEAAyE;QACzE,IAAI,CAAC;YACH,MAAM,YAAY,GAAI,MAA8C,EAAE,KAAK,IAAI,EAAE,CAAA;YACjF,MAAM,eAAe,GAAG;gBACtB,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjD,OAAO,EAAG,MAAkC,EAAE,OAAO,KAAK,IAAI;aAC/D,CAAA;YACD,WAAW,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,eAAe,CAAC,CAAA;QAClF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,sEAAsE,EAAE,GAAG,CAAC,CAAA;QAC3F,CAAC;QAED,OAAO,MAAM,CAAA;IACf,CAAC,CAKA;IAAC,MAAwD,CAAC,OAAO,GAAG,cAAc,CAGlF;IAAC,MAA6C,CAAC,aAAa,CAAC,GAAG,IAAI,CAAA;IAErE,OAAO,MAAM,CAAA;AACf,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { atrib } from './middleware.js';
|
|
2
|
+
export type { AgentAtribOptions, ToolCallInterceptor } from './middleware.js';
|
|
3
|
+
export { createSession, buildOutboundMeta, accumulateInboundContext } from './session.js';
|
|
4
|
+
export type { SessionState, LatestContext } from './session.js';
|
|
5
|
+
export type { GapNode } from '@atrib/verify';
|
|
6
|
+
export { detectTransaction } from './transaction.js';
|
|
7
|
+
export type { TransactionDetection } from './transaction.js';
|
|
8
|
+
export { initializeSessionPolicy } from './policy.js';
|
|
9
|
+
export type { SessionPolicyRecord, PolicyDocument, CreatorPolicyEntry } from './policy.js';
|
|
10
|
+
export { wrapMcpClient } from './adapters/mcp-client.js';
|
|
11
|
+
export type { MinimalMcpClient, WrapMcpClientOptions } from './adapters/mcp-client.js';
|
|
12
|
+
export { attributeCloudflareAgentMcp } from './adapters/cloudflare-agent.js';
|
|
13
|
+
export type { CloudflareAgentLike, AttributeCloudflareAgentMcpOptions, } from './adapters/cloudflare-agent.js';
|
|
14
|
+
export { attributeVercelAiSdkMcp } from './adapters/vercel-ai-sdk-mcp.js';
|
|
15
|
+
export type { VercelAiSdkMcpClientLike, AttributeVercelAiSdkMcpOptions, } from './adapters/vercel-ai-sdk-mcp.js';
|
|
16
|
+
export { attributeLangchainMcp } from './adapters/langchain-mcp.js';
|
|
17
|
+
export type { LangchainMcpClientLike, LangchainMultiServerMcpClientLike, AttributeLangchainMcpOptions, } from './adapters/langchain-mcp.js';
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACvC,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAG7E,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAA;AACzF,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC/D,YAAY,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAG5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAG5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AACrD,YAAY,EAAE,mBAAmB,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAG1F,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AACxD,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAKtF,OAAO,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAA;AAC5E,YAAY,EACV,mBAAmB,EACnB,kCAAkC,GACnC,MAAM,gCAAgC,CAAA;AAMvC,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAA;AACzE,YAAY,EACV,wBAAwB,EACxB,8BAA8B,GAC/B,MAAM,iCAAiC,CAAA;AAMxC,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAA;AACnE,YAAY,EACV,sBAAsB,EACtB,iCAAiC,EACjC,4BAA4B,GAC7B,MAAM,6BAA6B,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// @atrib/agent. Public API
|
|
3
|
+
// Middleware (primary export)
|
|
4
|
+
export { atrib } from './middleware.js';
|
|
5
|
+
// Session state (for advanced usage)
|
|
6
|
+
export { createSession, buildOutboundMeta, accumulateInboundContext } from './session.js';
|
|
7
|
+
// Transaction detection
|
|
8
|
+
export { detectTransaction } from './transaction.js';
|
|
9
|
+
// Policy negotiation
|
|
10
|
+
export { initializeSessionPolicy } from './policy.js';
|
|
11
|
+
// Framework adapters
|
|
12
|
+
export { wrapMcpClient } from './adapters/mcp-client.js';
|
|
13
|
+
// Cloudflare Agents adapter. wraps MCP connections on a Cloudflare `Agent`
|
|
14
|
+
// (or `AIChatAgent`) after `addMcpServer` so subsequent tool calls flow
|
|
15
|
+
// through atrib's interceptor lifecycle.
|
|
16
|
+
export { attributeCloudflareAgentMcp } from './adapters/cloudflare-agent.js';
|
|
17
|
+
// Vercel AI SDK MCP adapter. patches an `@ai-sdk/mcp` MCPClient's `request`
|
|
18
|
+
// method so every outbound `tools/call` flows through atrib's interceptor.
|
|
19
|
+
// The Vercel client has its own JSON-RPC implementation (NOT
|
|
20
|
+
// `@modelcontextprotocol/sdk` Client), so `wrapMcpClient` does not apply.
|
|
21
|
+
export { attributeVercelAiSdkMcp } from './adapters/vercel-ai-sdk-mcp.js';
|
|
22
|
+
// LangChain JS MCP adapter. patches every internal `@modelcontextprotocol/sdk`
|
|
23
|
+
// Client owned by a `@langchain/mcp-adapters` `MultiServerMCPClient` so every
|
|
24
|
+
// outbound `tools/call` (and every forked client from per-call-header
|
|
25
|
+
// workflows) flows through atrib's interceptor.
|
|
26
|
+
export { attributeLangchainMcp } from './adapters/langchain-mcp.js';
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC,2BAA2B;AAE3B,8BAA8B;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAGvC,qCAAqC;AACrC,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAA;AAIzF,wBAAwB;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAGpD,qBAAqB;AACrB,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAGrD,qBAAqB;AACrB,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAGxD,2EAA2E;AAC3E,wEAAwE;AACxE,yCAAyC;AACzC,OAAO,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAA;AAM5E,4EAA4E;AAC5E,2EAA2E;AAC3E,6DAA6D;AAC7D,0EAA0E;AAC1E,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAA;AAMzE,+EAA+E;AAC/E,8EAA8E;AAC9E,sEAAsE;AACtE,gDAAgD;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAA"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { GapNode } from '@atrib/verify';
|
|
2
|
+
import type { SessionPolicyRecord } from './policy.js';
|
|
3
|
+
/** Options for the agent atrib() middleware (§5.4.1). */
|
|
4
|
+
export interface AgentAtribOptions {
|
|
5
|
+
/** Base64url-encoded Ed25519 private key (32 bytes). Required. */
|
|
6
|
+
creatorKey?: string | undefined;
|
|
7
|
+
/** Merchant domain for policy fetch at session init. */
|
|
8
|
+
merchantDomain?: string | undefined;
|
|
9
|
+
/** Merkle log submission endpoint. */
|
|
10
|
+
logEndpoint?: string | undefined;
|
|
11
|
+
/** Session token for cross-trace linking. Auto-generated if absent. */
|
|
12
|
+
sessionToken?: string | undefined;
|
|
13
|
+
/** Server URLs for tools the agent will call (for policy fetch). */
|
|
14
|
+
serverUrls?: string[] | undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The interception surface for wrapping an agent or MCP client.
|
|
18
|
+
* The agent middleware needs to intercept outbound tool calls and
|
|
19
|
+
* inbound responses. This interface defines the minimum contract.
|
|
20
|
+
*/
|
|
21
|
+
export interface ToolCallInterceptor {
|
|
22
|
+
/**
|
|
23
|
+
* Called before a tool call is sent. Returns modified _meta to attach.
|
|
24
|
+
* MUST be awaited. session initialization happens here on the first call.
|
|
25
|
+
*/
|
|
26
|
+
onBeforeToolCall(toolName: string, meta: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
27
|
+
/**
|
|
28
|
+
* Called after a tool response is received.
|
|
29
|
+
* @param isError - if true, the response is an error and no attribution is recorded
|
|
30
|
+
*/
|
|
31
|
+
onAfterToolResponse(toolName: string, response: unknown, responseMeta: Record<string, unknown> | undefined, options?: {
|
|
32
|
+
headers?: Record<string, string | undefined>;
|
|
33
|
+
isError?: boolean;
|
|
34
|
+
/** The MCP server URL of the tool that was called (for heuristic content_id). */
|
|
35
|
+
serverUrl?: string;
|
|
36
|
+
}): void;
|
|
37
|
+
/** Get the session policy record for a context_id. */
|
|
38
|
+
getSessionPolicyRecord(contextId?: string): SessionPolicyRecord | null;
|
|
39
|
+
/** Get gap nodes recorded during this session (§1.6). */
|
|
40
|
+
getGapNodes(): GapNode[];
|
|
41
|
+
/** Flush pending log submissions. */
|
|
42
|
+
flush(): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create an atrib agent interceptor (§5.4).
|
|
46
|
+
*
|
|
47
|
+
* Returns a ToolCallInterceptor that manages attribution context.
|
|
48
|
+
* The caller is responsible for integrating this with their MCP client
|
|
49
|
+
* or agent framework by calling onBeforeToolCall/onAfterToolResponse
|
|
50
|
+
* at the appropriate points.
|
|
51
|
+
*/
|
|
52
|
+
export declare function atrib(options?: AgentAtribOptions): ToolCallInterceptor;
|
|
53
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAI5C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEtD,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;CAClC;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,gBAAgB,CACd,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACnC;;;OAGG;IACH,mBAAmB,CACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,OAAO,EACjB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EACjD,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;QAC5C,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,iFAAiF;QACjF,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,GACA,IAAI,CAAA;IACP,sDAAsD;IACtD,sBAAsB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAAA;IACtE,yDAAyD;IACzD,WAAW,IAAI,OAAO,EAAE,CAAA;IACxB,qCAAqC;IACrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,OAAO,GAAE,iBAAsB,GAAG,mBAAmB,CAmN1E"}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* @atrib/agent middleware. the atrib() wrapper for agents (§5.4).
|
|
4
|
+
*
|
|
5
|
+
* Wraps an agent or MCP client to automatically manage attribution
|
|
6
|
+
* context across tool calls. Zero ongoing surface area.
|
|
7
|
+
*/
|
|
8
|
+
import { base64urlDecode, base64urlEncode, signRecord, getPublicKey, computeContentId, hexEncode, genesisChainRoot, createSubmissionQueue, } from '@atrib/mcp';
|
|
9
|
+
import { createSession, buildOutboundMeta, accumulateInboundContext } from './session.js';
|
|
10
|
+
import { detectTransaction } from './transaction.js';
|
|
11
|
+
import { initializeSessionPolicy } from './policy.js';
|
|
12
|
+
/**
|
|
13
|
+
* Create an atrib agent interceptor (§5.4).
|
|
14
|
+
*
|
|
15
|
+
* Returns a ToolCallInterceptor that manages attribution context.
|
|
16
|
+
* The caller is responsible for integrating this with their MCP client
|
|
17
|
+
* or agent framework by calling onBeforeToolCall/onAfterToolResponse
|
|
18
|
+
* at the appropriate points.
|
|
19
|
+
*/
|
|
20
|
+
export function atrib(options = {}) {
|
|
21
|
+
// §5.8: Pass-through mode if no creatorKey
|
|
22
|
+
if (!options.creatorKey) {
|
|
23
|
+
console.warn('atrib: no creatorKey provided, operating in pass-through mode');
|
|
24
|
+
return createPassthrough();
|
|
25
|
+
}
|
|
26
|
+
const privateKey = base64urlDecode(options.creatorKey);
|
|
27
|
+
if (privateKey.length !== 32) {
|
|
28
|
+
console.warn('atrib: creatorKey must be 32 bytes, operating in pass-through mode');
|
|
29
|
+
return createPassthrough();
|
|
30
|
+
}
|
|
31
|
+
const queue = createSubmissionQueue(options.logEndpoint);
|
|
32
|
+
// Derive public key at init
|
|
33
|
+
let publicKeyB64;
|
|
34
|
+
const publicKeyReady = getPublicKey(privateKey).then((pk) => {
|
|
35
|
+
publicKeyB64 = base64urlEncode(pk);
|
|
36
|
+
});
|
|
37
|
+
// Create session state
|
|
38
|
+
const session = createSession({
|
|
39
|
+
sessionToken: options.sessionToken,
|
|
40
|
+
});
|
|
41
|
+
// Session policy record (populated after init)
|
|
42
|
+
let sessionPolicyRecord = null;
|
|
43
|
+
let initPromise = null;
|
|
44
|
+
const pendingEmissions = [];
|
|
45
|
+
/**
|
|
46
|
+
* Append a runtime warning to BOTH session.warnings and the session policy
|
|
47
|
+
* record. §5.4.6: warnings must be observable through getSessionPolicyRecord().
|
|
48
|
+
*/
|
|
49
|
+
function addRuntimeWarning(warning) {
|
|
50
|
+
session.warnings.push(warning);
|
|
51
|
+
if (sessionPolicyRecord && !sessionPolicyRecord.warnings.includes(warning)) {
|
|
52
|
+
sessionPolicyRecord.warnings.push(warning);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Run session initialization once. §5.4.2: MUST complete before
|
|
57
|
+
* the first outbound tool call.
|
|
58
|
+
*/
|
|
59
|
+
async function ensureInitialized() {
|
|
60
|
+
if (session.initialized)
|
|
61
|
+
return;
|
|
62
|
+
if (!initPromise) {
|
|
63
|
+
initPromise = (async () => {
|
|
64
|
+
try {
|
|
65
|
+
sessionPolicyRecord = await initializeSessionPolicy({
|
|
66
|
+
contextId: session.contextId,
|
|
67
|
+
merchantDomain: options.merchantDomain,
|
|
68
|
+
serverUrls: options.serverUrls,
|
|
69
|
+
});
|
|
70
|
+
session.warnings.push(...sessionPolicyRecord.warnings);
|
|
71
|
+
// §4.5.1 Step 4: embed policy record ID in baggage on subsequent calls
|
|
72
|
+
session.policyRecordId = sessionPolicyRecord.record_id;
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.warn('atrib: session initialization failed, using defaults', err);
|
|
76
|
+
session.warnings.push('session initialization failed');
|
|
77
|
+
}
|
|
78
|
+
session.initialized = true;
|
|
79
|
+
})();
|
|
80
|
+
}
|
|
81
|
+
await initPromise;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
async onBeforeToolCall(_toolName, meta) {
|
|
85
|
+
try {
|
|
86
|
+
// §5.4.2: Session init MUST complete before the first outbound tool call
|
|
87
|
+
await ensureInitialized();
|
|
88
|
+
// Build outbound _meta. passes existing so baggage/tracestate are appended,
|
|
89
|
+
// not clobbered (§5.4.3 W3C semantics)
|
|
90
|
+
const outbound = buildOutboundMeta(session, meta);
|
|
91
|
+
// Merge with existing _meta
|
|
92
|
+
return { ...meta, ...outbound };
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
console.warn('atrib: error in onBeforeToolCall, passing through', err);
|
|
96
|
+
return meta;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
onAfterToolResponse(toolName, response, responseMeta, callOptions) {
|
|
100
|
+
// §5.7: tool_call_inbound trigger only fires when isError is false.
|
|
101
|
+
// Check both the explicit option (set by adapters) and the response
|
|
102
|
+
// body's own isError field (guards direct callers who omit options).
|
|
103
|
+
if (callOptions?.isError === true)
|
|
104
|
+
return;
|
|
105
|
+
if (response?.isError === true)
|
|
106
|
+
return;
|
|
107
|
+
try {
|
|
108
|
+
// §5.7: task_created trigger. When a tasks/create response arrives,
|
|
109
|
+
// store the task ID for the session context.
|
|
110
|
+
const responseObj = response;
|
|
111
|
+
if (responseObj?.id && typeof responseObj.id === 'string') {
|
|
112
|
+
const method = responseMeta?.method ?? responseObj?.method;
|
|
113
|
+
if (method === 'tasks/create') {
|
|
114
|
+
session.taskIds.add(responseObj.id);
|
|
115
|
+
}
|
|
116
|
+
// §5.7: task_completed trigger. If this is a completed task,
|
|
117
|
+
// treat it as a successful tool_call response (apply inbound logic below).
|
|
118
|
+
if (session.taskIds.has(responseObj.id) &&
|
|
119
|
+
(responseObj.status === 'completed' || responseObj.status === 'done')) {
|
|
120
|
+
// Fall through to normal inbound processing below.
|
|
121
|
+
// The task completion is handled identically to tool_call_inbound.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// §5.4.4: Accumulate inbound context
|
|
125
|
+
const hasAtribToken = accumulateInboundContext(session, responseMeta);
|
|
126
|
+
// §1.6: If no attribution token was received, this is an unsigned hop.
|
|
127
|
+
// Record a gap node so the graph shows the contribution.
|
|
128
|
+
if (!hasAtribToken) {
|
|
129
|
+
const gapNode = {
|
|
130
|
+
type: 'gap_node',
|
|
131
|
+
tool_url: callOptions?.serverUrl ?? '',
|
|
132
|
+
tool_name: toolName,
|
|
133
|
+
context_id: session.contextId,
|
|
134
|
+
timestamp: Date.now(),
|
|
135
|
+
signed: false,
|
|
136
|
+
};
|
|
137
|
+
session.gapNodes.push(gapNode);
|
|
138
|
+
}
|
|
139
|
+
// §5.4.5: Transaction detection
|
|
140
|
+
const detection = detectTransaction(toolName, response, callOptions?.headers);
|
|
141
|
+
if (detection.detected) {
|
|
142
|
+
// Path 1: Merchant has @atrib/mcp. token present in response
|
|
143
|
+
if (hasAtribToken) {
|
|
144
|
+
// Path 1: Skip emission, merchant already emitted transaction record.
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Path 2: No attribution token. agent emits transaction record
|
|
148
|
+
// §5.4.6: warnings are appended throughout the session. push to BOTH
|
|
149
|
+
// session.warnings (for in-memory tracking) and sessionPolicyRecord.warnings
|
|
150
|
+
// (so getSessionPolicyRecord() callers see them)
|
|
151
|
+
addRuntimeWarning('transaction_emitted_by_agent');
|
|
152
|
+
if (detection.protocol === 'heuristic') {
|
|
153
|
+
addRuntimeWarning('transaction_detected_by_heuristic');
|
|
154
|
+
}
|
|
155
|
+
// Emit transaction record asynchronously
|
|
156
|
+
const emission = emitTransactionRecord(toolName, detection, callOptions?.serverUrl, session, privateKey, publicKeyReady, () => publicKeyB64, queue).catch((err) => {
|
|
157
|
+
console.warn('atrib: transaction emission failed', err);
|
|
158
|
+
}).finally(() => {
|
|
159
|
+
const idx = pendingEmissions.indexOf(emission);
|
|
160
|
+
if (idx !== -1)
|
|
161
|
+
pendingEmissions.splice(idx, 1);
|
|
162
|
+
});
|
|
163
|
+
pendingEmissions.push(emission);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
console.warn('atrib: error in onAfterToolResponse, continuing', err);
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
getSessionPolicyRecord(_contextId) {
|
|
171
|
+
return sessionPolicyRecord;
|
|
172
|
+
},
|
|
173
|
+
getGapNodes() {
|
|
174
|
+
return [...session.gapNodes];
|
|
175
|
+
},
|
|
176
|
+
async flush() {
|
|
177
|
+
// Drain in a loop: emissions arriving during our await are caught
|
|
178
|
+
// by the next iteration. Terminates when no new emissions appear.
|
|
179
|
+
while (pendingEmissions.length > 0) {
|
|
180
|
+
const snapshot = pendingEmissions.splice(0);
|
|
181
|
+
await Promise.allSettled(snapshot);
|
|
182
|
+
}
|
|
183
|
+
await queue.flush();
|
|
184
|
+
// Drain any emissions that submitted to the queue during queue.flush()
|
|
185
|
+
while (pendingEmissions.length > 0) {
|
|
186
|
+
const snapshot = pendingEmissions.splice(0);
|
|
187
|
+
await Promise.allSettled(snapshot);
|
|
188
|
+
await queue.flush();
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Emit a transaction record for Path 2 agent-side detection (§5.4.5).
|
|
195
|
+
* Derives content_id per protocol:
|
|
196
|
+
* - ACP/UCP: checkout URL from response, tool_name = "checkout"
|
|
197
|
+
* - x402/MPP: HTTP endpoint URL, tool_name = "checkout"
|
|
198
|
+
* - Heuristic: MCP server URL of tool, actual tool_name
|
|
199
|
+
*/
|
|
200
|
+
async function emitTransactionRecord(toolName, detection, callServerUrl, session, privateKey, publicKeyReady, publicKeyB64Getter, queue) {
|
|
201
|
+
await publicKeyReady;
|
|
202
|
+
const publicKeyB64 = publicKeyB64Getter();
|
|
203
|
+
if (!publicKeyB64) {
|
|
204
|
+
throw new Error('public key not ready');
|
|
205
|
+
}
|
|
206
|
+
// Derive content_id per protocol (§5.4.5)
|
|
207
|
+
let contentIdServerUrl;
|
|
208
|
+
let contentIdToolName;
|
|
209
|
+
switch (detection.protocol) {
|
|
210
|
+
case 'ACP':
|
|
211
|
+
case 'UCP':
|
|
212
|
+
contentIdServerUrl = detection.checkoutUrl ?? callServerUrl ?? '';
|
|
213
|
+
contentIdToolName = 'checkout';
|
|
214
|
+
break;
|
|
215
|
+
case 'x402':
|
|
216
|
+
case 'MPP':
|
|
217
|
+
// Use the HTTP endpoint URL that returned Payment-Receipt
|
|
218
|
+
contentIdServerUrl = callServerUrl ?? '';
|
|
219
|
+
contentIdToolName = 'checkout';
|
|
220
|
+
break;
|
|
221
|
+
case 'AP2':
|
|
222
|
+
contentIdServerUrl = callServerUrl ?? '';
|
|
223
|
+
contentIdToolName = 'checkout';
|
|
224
|
+
break;
|
|
225
|
+
case 'heuristic':
|
|
226
|
+
default:
|
|
227
|
+
// Heuristic: weakest case, use the MCP server URL of the tool that was called
|
|
228
|
+
contentIdServerUrl = callServerUrl ?? '';
|
|
229
|
+
contentIdToolName = toolName;
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
const contentId = computeContentId(contentIdServerUrl, contentIdToolName);
|
|
233
|
+
// §1.2.3: chain_root for genesis records
|
|
234
|
+
const chainRoot = session.latestContext
|
|
235
|
+
? `sha256:${hexEncode(session.latestContext.recordHash)}`
|
|
236
|
+
: genesisChainRoot(session.contextId);
|
|
237
|
+
const record = {
|
|
238
|
+
spec_version: 'atrib/1.0',
|
|
239
|
+
content_id: contentId,
|
|
240
|
+
creator_key: publicKeyB64,
|
|
241
|
+
chain_root: chainRoot,
|
|
242
|
+
event_type: 'https://atrib.dev/v1/types/transaction',
|
|
243
|
+
context_id: session.contextId,
|
|
244
|
+
timestamp: Date.now(),
|
|
245
|
+
signature: '',
|
|
246
|
+
session_token: session.sessionToken,
|
|
247
|
+
};
|
|
248
|
+
const signed = await signRecord(record, privateKey);
|
|
249
|
+
// Submit immediately. transaction records are the closing anchor
|
|
250
|
+
queue.submit(signed, 'high');
|
|
251
|
+
}
|
|
252
|
+
/** Create a no-op pass-through interceptor. */
|
|
253
|
+
function createPassthrough() {
|
|
254
|
+
return {
|
|
255
|
+
async onBeforeToolCall(_toolName, meta) {
|
|
256
|
+
return meta;
|
|
257
|
+
},
|
|
258
|
+
onAfterToolResponse() { },
|
|
259
|
+
getSessionPolicyRecord() {
|
|
260
|
+
return null;
|
|
261
|
+
},
|
|
262
|
+
getGapNodes() {
|
|
263
|
+
return [];
|
|
264
|
+
},
|
|
265
|
+
async flush() { },
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|