@reclaimprotocol/agent 0.1.31 → 0.1.33
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/lib/mcp/api/operations.gen.d.ts +9 -0
- package/lib/mcp/api/operations.gen.js +102 -14
- package/lib/mcp/api/tools.d.ts +10 -2
- package/lib/mcp/api/tools.js +42 -4
- package/lib/mcp/instructions.js +1 -1
- package/lib/mcp/start.js +6 -1
- package/lib/mcp/tools/verification/result.js +49 -12
- package/lib/old/tools/get-provider-info.js +3 -1
- package/package.json +2 -2
- package/skill/SKILL.builder.md +15 -3
- package/skill/SKILL.old.md +4 -2
- package/skill/builder-mode.md +52 -9
- package/skill/old-mode.md +12 -0
- package/skill/verification-client-integration.md +115 -15
- package/src/mcp/api/operations.gen.ts +102 -14
- package/src/mcp/api/tools.ts +57 -5
- package/src/mcp/instructions.ts +1 -1
- package/src/mcp/start.ts +6 -1
- package/src/mcp/tools/verification/result.ts +55 -13
- package/src/old/tools/get-provider-info.ts +3 -1
package/src/mcp/api/tools.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { unauthorized } from '@hapi/boom'
|
|
1
2
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
-
import
|
|
3
|
+
import { ReclaimClient } from '@reclaimprotocol/client/api'
|
|
3
4
|
import type {
|
|
4
5
|
IOperationArgs,
|
|
5
6
|
IOperationId
|
|
6
7
|
} from '@reclaimprotocol/client/openapi'
|
|
8
|
+
import assert from 'node:assert'
|
|
9
|
+
import { orgSecret } from '../../consts.ts'
|
|
7
10
|
import { remapErrorAsResponse } from '../remap_errors.ts'
|
|
8
11
|
import { successResponse } from '../success-response.ts'
|
|
9
12
|
import { MCP_TOOLS } from './operations.gen.ts'
|
|
@@ -22,8 +25,29 @@ function toSnakeCase(name: string): string {
|
|
|
22
25
|
.toLowerCase()
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
/** The client for a call that presents the organization's API key. */
|
|
29
|
+
export type OrgClientFactory = (orgToken: string) => ReclaimClient
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Register a tool per generated operation. Calls go out with the developer's
|
|
33
|
+
* session token, except operations only an org API key (`rorg_…`) authorizes:
|
|
34
|
+
* those take an `orgToken` input (else the server's RECLAIM_ORG_SECRET) and go
|
|
35
|
+
* out through `orgClient` with that key.
|
|
36
|
+
*/
|
|
37
|
+
export function registerTools(
|
|
38
|
+
server: McpServer,
|
|
39
|
+
client: ReclaimClient,
|
|
40
|
+
orgClient: OrgClientFactory = (orgToken) => new ReclaimClient({
|
|
41
|
+
baseUrl: client.baseUrl,
|
|
42
|
+
token: orgToken,
|
|
43
|
+
}),
|
|
44
|
+
) {
|
|
45
|
+
for(const [opId, tool] of Object.entries(MCP_TOOLS)) {
|
|
46
|
+
const orgTokenOnly = 'orgTokenOnly' in tool && tool.orgTokenOnly === true
|
|
47
|
+
const shape = {
|
|
48
|
+
description: tool.description,
|
|
49
|
+
inputSchema: tool.inputSchema,
|
|
50
|
+
}
|
|
27
51
|
if(!isOperationId(opId)) {
|
|
28
52
|
throw new Error(
|
|
29
53
|
`Generated MCP operation is not in the API client: ${opId}`,
|
|
@@ -37,13 +61,21 @@ export function registerTools(server: McpServer, client: ReclaimClient) {
|
|
|
37
61
|
server.registerTool(
|
|
38
62
|
toSnakeCase(opId),
|
|
39
63
|
shape,
|
|
40
|
-
async(
|
|
64
|
+
async(input: unknown) => {
|
|
41
65
|
try {
|
|
66
|
+
if(!isRecord(input)) {
|
|
67
|
+
throw new Error(`Invalid generated MCP arguments for ${opId}`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { orgToken, ...args } = input
|
|
42
71
|
if(!isOperationArgs(opId, args)) {
|
|
43
72
|
throw new Error(`Invalid generated MCP arguments for ${opId}`)
|
|
44
73
|
}
|
|
45
74
|
|
|
46
|
-
const
|
|
75
|
+
const caller = orgTokenOnly
|
|
76
|
+
? orgClient(orgTokenFor(toSnakeCase(opId), orgToken))
|
|
77
|
+
: client
|
|
78
|
+
const result = await caller.call(
|
|
47
79
|
opId,
|
|
48
80
|
args,
|
|
49
81
|
)
|
|
@@ -79,3 +111,23 @@ function isOperationArgs<T extends IOperationId>(
|
|
|
79
111
|
): value is IOperationArgs<T> {
|
|
80
112
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
81
113
|
}
|
|
114
|
+
|
|
115
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
116
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The org API key a call authenticates with: its `orgToken`, else the
|
|
120
|
+
* server's RECLAIM_ORG_SECRET. The session token (the developer's sign-in)
|
|
121
|
+
* can't stand in, since these routes accept only an org key. */
|
|
122
|
+
function orgTokenFor(tool: string, orgToken: unknown) {
|
|
123
|
+
const token = (typeof orgToken === 'string' && orgToken) || orgSecret()
|
|
124
|
+
assert(
|
|
125
|
+
token,
|
|
126
|
+
unauthorized(
|
|
127
|
+
`${tool} authenticates with the organization's API key. Pass `
|
|
128
|
+
+ 'orgToken: one from issue_org_token, or the app\'s '
|
|
129
|
+
+ 'RECLAIM_ORG_SECRET. Or set RECLAIM_ORG_SECRET for the MCP server.',
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
return token
|
|
133
|
+
}
|
package/src/mcp/instructions.ts
CHANGED
|
@@ -78,7 +78,7 @@ const TOPIC_SUMMARIES: Record<string, string> = {
|
|
|
78
78
|
'discover': 'Finding providers, resolving them, and creating a verification '
|
|
79
79
|
+ 'session.',
|
|
80
80
|
'integration': 'Set up a Builder token, callback, frontend and backend '
|
|
81
|
-
+ '
|
|
81
|
+
+ 'over the HTTP session API; direct `api=2` API and result '
|
|
82
82
|
+ 'verification.',
|
|
83
83
|
'user-script': 'Authoring jsUserScripts / customInjection to drive '
|
|
84
84
|
+ 'login-gated pages.',
|
package/src/mcp/start.ts
CHANGED
|
@@ -116,7 +116,12 @@ export async function startMcpServer() {
|
|
|
116
116
|
|
|
117
117
|
// Add auto-generated tools with snake_case wrappers
|
|
118
118
|
// for every MCP-tagged operation.
|
|
119
|
-
|
|
119
|
+
// Org-key-only operations go out with the call's `orgToken` instead.
|
|
120
|
+
registerTools(server, client, (orgToken) => new ReclaimClient({
|
|
121
|
+
baseUrl: apiUrl(),
|
|
122
|
+
token: orgToken,
|
|
123
|
+
headers: { 'User-Agent': `${NAME}/${VERSION}` },
|
|
124
|
+
}))
|
|
120
125
|
|
|
121
126
|
if(agentToolsEnabled) {
|
|
122
127
|
agentState = buildAgentState()
|
|
@@ -17,19 +17,29 @@ const TERMINAL = new Set([
|
|
|
17
17
|
'expired',
|
|
18
18
|
])
|
|
19
19
|
|
|
20
|
+
/** Events whose delivery is a signed result; the rest are plaintext event
|
|
21
|
+
* notifications. Mirrors `verification_result_events()`. */
|
|
22
|
+
const RESULT_EVENTS = new Set([
|
|
23
|
+
'verification_success',
|
|
24
|
+
'verification_rejected',
|
|
25
|
+
'verification_error',
|
|
26
|
+
'verification_cancelled',
|
|
27
|
+
])
|
|
28
|
+
|
|
20
29
|
/**
|
|
21
30
|
* Resolve the org secret used to authenticate verification reads. Auth is a
|
|
22
|
-
* single per-org bearer secret (`rorg_…`), not an eth signature
|
|
23
|
-
* `RECLAIM_ORG_SECRET`.
|
|
31
|
+
* single per-org bearer secret (`rorg_…`), not an eth signature: the call's
|
|
32
|
+
* `orgToken`, else `RECLAIM_ORG_SECRET`.
|
|
24
33
|
*/
|
|
25
|
-
function resolveOrgSecret(): string {
|
|
26
|
-
const secret = orgSecret()?.trim()
|
|
34
|
+
function resolveOrgSecret(orgToken?: string): string {
|
|
35
|
+
const secret = orgToken?.trim() || orgSecret()?.trim()
|
|
27
36
|
assert(
|
|
28
37
|
secret,
|
|
29
38
|
new Error(
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
39
|
+
'No organization API key. Verification reads authenticate with the org '
|
|
40
|
+
+ 'secret (rorg_…): pass orgToken (from issue_org_token, or the '
|
|
41
|
+
+ 'app\'s RECLAIM_ORG_SECRET), or export RECLAIM_ORG_SECRET for the '
|
|
42
|
+
+ 'MCP server.',
|
|
33
43
|
),
|
|
34
44
|
)
|
|
35
45
|
return secret
|
|
@@ -46,12 +56,15 @@ function resolveOrgSecret(): string {
|
|
|
46
56
|
* Decryption, when needed, happens here.
|
|
47
57
|
*/
|
|
48
58
|
export function verificationResultTool(): RegisteredTool {
|
|
49
|
-
return defineTool<{ reclaimSessionId: string }>(
|
|
59
|
+
return defineTool<{ reclaimSessionId: string, orgToken?: string }>(
|
|
50
60
|
{
|
|
51
61
|
name: 'get_verification_result',
|
|
52
62
|
description:
|
|
53
|
-
'Fetches and verifies a terminal Builder result for one session
|
|
54
|
-
+ '
|
|
63
|
+
'Fetches and verifies a terminal Builder result for one session, '
|
|
64
|
+
+ 'while Builder still holds it: until its callback is delivered, or '
|
|
65
|
+
+ '7 days undelivered. The callback is the normal way to get results. '
|
|
66
|
+
+ 'Authenticates with the org API key: `orgToken`, else '
|
|
67
|
+
+ '`RECLAIM_ORG_SECRET`. Encrypted deliveries also require '
|
|
55
68
|
+ 'the matching ETH decryption key. Returns `ready: false` until a '
|
|
56
69
|
+ 'payload is available. Invalid results fail verification and are not '
|
|
57
70
|
+ 'returned. Use `list_verification_events` to diagnose '
|
|
@@ -65,11 +78,20 @@ export function verificationResultTool(): RegisteredTool {
|
|
|
65
78
|
description:
|
|
66
79
|
'The verification session id from create_verification_session.',
|
|
67
80
|
},
|
|
81
|
+
orgToken: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
pattern: '^rorg_',
|
|
84
|
+
maxLength: 200,
|
|
85
|
+
description: 'The organization API key (`rorg_…`): from '
|
|
86
|
+
+ '`issue_org_token`, or the app\'s RECLAIM_ORG_SECRET. '
|
|
87
|
+
+ 'Defaults to the MCP server\'s RECLAIM_ORG_SECRET. Never echo '
|
|
88
|
+
+ 'it back to the user.',
|
|
89
|
+
},
|
|
68
90
|
},
|
|
69
91
|
},
|
|
70
92
|
},
|
|
71
93
|
async(args) => {
|
|
72
|
-
const secret = resolveOrgSecret()
|
|
94
|
+
const secret = resolveOrgSecret(args.orgToken)
|
|
73
95
|
// Optional eth decryption key — absent ⇒ plaintext-only.
|
|
74
96
|
const decryptionKey: DecryptionKey | undefined =
|
|
75
97
|
await resolveDecryptionKey()
|
|
@@ -91,14 +113,34 @@ export function verificationResultTool(): RegisteredTool {
|
|
|
91
113
|
const resultRes = await reclaim.api('GetVerificationResult', {
|
|
92
114
|
params: { sessionId: args.reclaimSessionId },
|
|
93
115
|
})
|
|
94
|
-
const
|
|
116
|
+
const signed = resultRes.data.items
|
|
117
|
+
.filter((item) => RESULT_EVENTS.has(item.event))
|
|
118
|
+
if(!signed.length) {
|
|
119
|
+
return {
|
|
120
|
+
reclaimSessionId: args.reclaimSessionId,
|
|
121
|
+
status,
|
|
122
|
+
ready: false,
|
|
123
|
+
note: 'Session is terminal but no result is stored: none submitted '
|
|
124
|
+
+ 'yet, or no callback subscription covers its ending event.',
|
|
125
|
+
}
|
|
126
|
+
}
|
|
95
127
|
|
|
128
|
+
// Builder keeps a result (which can carry proofs) only until its
|
|
129
|
+
// callback is delivered, or 7 days undelivered.
|
|
130
|
+
const items = signed.flatMap((item) => (
|
|
131
|
+
item.payload === null ? [] : [{ ...item, payload: item.payload }]
|
|
132
|
+
))
|
|
96
133
|
if(!items.length) {
|
|
97
134
|
return {
|
|
98
135
|
reclaimSessionId: args.reclaimSessionId,
|
|
99
136
|
status,
|
|
100
137
|
ready: false,
|
|
101
|
-
|
|
138
|
+
delivered: signed.some((item) => (
|
|
139
|
+
item.deliveryStatus === 'delivered'
|
|
140
|
+
)),
|
|
141
|
+
note: 'Builder no longer holds this result: it was delivered to '
|
|
142
|
+
+ 'the callback (read it there), or went undelivered for 7 days. '
|
|
143
|
+
+ 'See list_verification_deliveries.',
|
|
102
144
|
}
|
|
103
145
|
}
|
|
104
146
|
|
|
@@ -41,7 +41,9 @@ export function getProviderInfoTool(client: ReclaimSdkClient): RegisteredTool {
|
|
|
41
41
|
'Read a PUBLISHED provider from the public production SDK backend ' +
|
|
42
42
|
'(api.reclaimprotocol.org — what the verification SDK/attestor ' +
|
|
43
43
|
'itself fetches at runtime), for ANY provider, no auth required. ' +
|
|
44
|
-
'This is the provider-read tool for old-devtools mode.
|
|
44
|
+
'This is the provider-read tool for old-devtools mode. A ' +
|
|
45
|
+
'published provider is usable as-is: don\'t re-verify it ' +
|
|
46
|
+
'(capture/replay/proof) unless the user asks to test it.\n\n' +
|
|
45
47
|
'Returns two complementary reads for the given version (latest ' +
|
|
46
48
|
'when `versionNumber` is omitted): `provider` (the FULL provider ' +
|
|
47
49
|
'+ providerConfig — `customInjection`, `userAgent`, ' +
|