@reclaimprotocol/agent 0.1.20 → 0.1.21
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/remap_errors.js +2 -1
- package/lib/mcp/start.js +6 -3
- package/lib/mcp/tools/agent/build.d.ts +9 -2
- package/lib/mcp/tools/agent/build.js +11 -5
- package/lib/mcp/tools/agent/index.d.ts +1 -1
- package/lib/mcp/tools/agent/index.js +1 -1
- package/lib/mcp/tools/agent/publish.d.ts +4 -1
- package/lib/mcp/tools/agent/publish.js +20 -2
- package/lib/mcp/tools/authenticate/authenticate.js +3 -2
- package/lib/old/tools/build.d.ts +8 -1
- package/lib/old/tools/build.js +6 -2
- package/lib/old/tools/publish.d.ts +10 -1
- package/lib/old/tools/publish.js +26 -2
- package/package.json +2 -2
- package/skill/SKILL.builder.md +2 -0
- package/skill/SKILL.common.md +2 -2
- package/skill/SKILL.old.md +2 -0
- package/src/mcp/remap_errors.ts +2 -1
- package/src/mcp/start.ts +12 -2
- package/src/mcp/tools/agent/build.ts +11 -4
- package/src/mcp/tools/agent/index.ts +1 -1
- package/src/mcp/tools/agent/publish.ts +24 -1
- package/src/mcp/tools/authenticate/authenticate.ts +3 -5
- package/src/old/tools/build.ts +7 -1
- package/src/old/tools/publish.ts +30 -1
package/lib/mcp/remap_errors.js
CHANGED
|
@@ -12,9 +12,10 @@ export const remapErrorAsResponse = (err) => {
|
|
|
12
12
|
content: [{ type: 'text', text }],
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
+
const data = err.data;
|
|
15
16
|
return {
|
|
16
17
|
isError: true,
|
|
17
|
-
structuredContent:
|
|
18
|
+
...(data ? { structuredContent: data } : {}),
|
|
18
19
|
content: [{ type: 'text', text }],
|
|
19
20
|
};
|
|
20
21
|
};
|
package/lib/mcp/start.js
CHANGED
|
@@ -10,7 +10,7 @@ import { ReclaimSdkClient } from "../old/sdk-client.js";
|
|
|
10
10
|
import { buildOldBackendTools } from "../old/tools/build.js";
|
|
11
11
|
import { NAME, VERSION } from "../pkg.js";
|
|
12
12
|
import { registerTools } from "./api/tools.js";
|
|
13
|
-
import { buildAgentTools, buildBuilderAuthenticationTools, buildBuilderOrganizationTools, buildCredentialTools, buildLocalCredentialTools, buildModeTools, buildVerificationTools, readSession, } from "./tools/index.js";
|
|
13
|
+
import { buildAgentState, buildAgentTools, buildBuilderAuthenticationTools, buildBuilderOrganizationTools, buildCredentialTools, buildLocalCredentialTools, buildModeTools, buildVerificationTools, readSession, } from "./tools/index.js";
|
|
14
14
|
import { BUILDER_MODE_INSTRUCTIONS } from "./instructions.js";
|
|
15
15
|
import { registerToolList } from "./server.js";
|
|
16
16
|
export async function startMcpServer() {
|
|
@@ -49,13 +49,16 @@ export async function startMcpServer() {
|
|
|
49
49
|
identity: resolveInitialIdentity(),
|
|
50
50
|
});
|
|
51
51
|
const sdkClient = new ReclaimSdkClient({ baseUrl: sdkApiUrl() });
|
|
52
|
+
// Shared with buildAgentTools below so the old-mode publish tool can see
|
|
53
|
+
// the live attach state (e.g. to flag a still-open browser session).
|
|
54
|
+
const agentState = buildAgentState();
|
|
52
55
|
registerToolList(server, buildBuilderAuthenticationTools(builderClient));
|
|
53
56
|
registerToolList(server, buildBuilderOrganizationTools(builderClient));
|
|
54
57
|
registerToolList(server, buildLocalCredentialTools());
|
|
55
|
-
registerToolList(server, buildOldBackendTools(oldClient, sdkClient));
|
|
58
|
+
registerToolList(server, buildOldBackendTools(oldClient, sdkClient, agentState.attach));
|
|
56
59
|
if (agentToolsEnabled) {
|
|
57
60
|
// No builder client → no builder publish tool (old mode has its own).
|
|
58
|
-
registerToolList(server, buildAgentTools(undefined, builderClient));
|
|
61
|
+
registerToolList(server, buildAgentTools(undefined, builderClient, agentState));
|
|
59
62
|
}
|
|
60
63
|
// NOTE: registerTools() is intentionally NOT called — generated tools off.
|
|
61
64
|
}
|
|
@@ -21,9 +21,16 @@ export interface AgentState {
|
|
|
21
21
|
};
|
|
22
22
|
captures: CaptureStore;
|
|
23
23
|
}
|
|
24
|
+
/** Exported so a mode (e.g. old-devtools) that supplies its own publish tool
|
|
25
|
+
* can build the attach/capture state up front and share the SAME instance
|
|
26
|
+
* with `buildAgentTools` — letting that publish tool see the live attach
|
|
27
|
+
* state (e.g. to remind the caller about a still-open browser session). */
|
|
28
|
+
export declare function buildAgentState(): AgentState;
|
|
24
29
|
/** Build the flat list of `RegisteredTool` entries, ready for
|
|
25
30
|
* `registerToolList`.
|
|
26
31
|
* Pass `client` to include the builder `create_provider_version_from_capture`;
|
|
27
32
|
* omit it (e.g. OLD-devtools mode) to register only the backend-independent
|
|
28
|
-
* capture/proof tools — old mode supplies its own publish tool.
|
|
29
|
-
|
|
33
|
+
* capture/proof tools — old mode supplies its own publish tool.
|
|
34
|
+
* Pass `externalState` to share attach/capture state with tools registered
|
|
35
|
+
* elsewhere (e.g. old mode's publish tool) instead of building a fresh one. */
|
|
36
|
+
export declare function buildAgentTools(client?: ReclaimClient, builderClient?: ReclaimClient | undefined, externalState?: AgentState): RegisteredTool[];
|
|
@@ -19,7 +19,11 @@ import { howItWorksTool } from "./how-it-works.js";
|
|
|
19
19
|
import { createVersionFromCaptureTool } from "./publish.js";
|
|
20
20
|
import { sessionTools } from "./session.js";
|
|
21
21
|
import { sharedAuthoringTools } from "./shared.js";
|
|
22
|
-
|
|
22
|
+
/** Exported so a mode (e.g. old-devtools) that supplies its own publish tool
|
|
23
|
+
* can build the attach/capture state up front and share the SAME instance
|
|
24
|
+
* with `buildAgentTools` — letting that publish tool see the live attach
|
|
25
|
+
* state (e.g. to remind the caller about a still-open browser session). */
|
|
26
|
+
export function buildAgentState() {
|
|
23
27
|
return {
|
|
24
28
|
attach: { config: new ConfigStore() },
|
|
25
29
|
captures: { sessions: new Map() },
|
|
@@ -29,9 +33,11 @@ function buildAgentState() {
|
|
|
29
33
|
* `registerToolList`.
|
|
30
34
|
* Pass `client` to include the builder `create_provider_version_from_capture`;
|
|
31
35
|
* omit it (e.g. OLD-devtools mode) to register only the backend-independent
|
|
32
|
-
* capture/proof tools — old mode supplies its own publish tool.
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
* capture/proof tools — old mode supplies its own publish tool.
|
|
37
|
+
* Pass `externalState` to share attach/capture state with tools registered
|
|
38
|
+
* elsewhere (e.g. old mode's publish tool) instead of building a fresh one. */
|
|
39
|
+
export function buildAgentTools(client, builderClient = client, externalState) {
|
|
40
|
+
const state = externalState ?? buildAgentState();
|
|
35
41
|
ensureAttestorCrypto();
|
|
36
42
|
const tools = [];
|
|
37
43
|
// Stateless docs tool — serve the mode-specific authoring guide to any
|
|
@@ -55,7 +61,7 @@ export function buildAgentTools(client, builderClient = client) {
|
|
|
55
61
|
tools.push(t);
|
|
56
62
|
}
|
|
57
63
|
if (client) {
|
|
58
|
-
tools.push(createVersionFromCaptureTool(client));
|
|
64
|
+
tools.push(createVersionFromCaptureTool(client, state.attach));
|
|
59
65
|
}
|
|
60
66
|
return tools;
|
|
61
67
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { buildAgentTools } from './build.ts';
|
|
1
|
+
export { buildAgentState, buildAgentTools } from './build.ts';
|
|
2
2
|
export type { AgentState } from './build.ts';
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { ReclaimClient } from '@reclaimprotocol/client';
|
|
2
2
|
import { type RegisteredTool } from '../../server.ts';
|
|
3
|
-
|
|
3
|
+
import type { AttachState } from './attach.ts';
|
|
4
|
+
export declare function createVersionFromCaptureTool(client: ReclaimClient, attachRef?: {
|
|
5
|
+
current?: AttachState;
|
|
6
|
+
}): RegisteredTool;
|
|
@@ -97,7 +97,7 @@ const ALLOWED_JS_REQUEST_SCHEMA = {
|
|
|
97
97
|
},
|
|
98
98
|
required: ['url', 'method'],
|
|
99
99
|
};
|
|
100
|
-
export function createVersionFromCaptureTool(client) {
|
|
100
|
+
export function createVersionFromCaptureTool(client, attachRef) {
|
|
101
101
|
return defineTool({
|
|
102
102
|
name: 'create_provider_version_from_capture',
|
|
103
103
|
description: 'Persist drafted ReclaimProvider(s) as a provider version. ' +
|
|
@@ -125,7 +125,11 @@ export function createVersionFromCaptureTool(client) {
|
|
|
125
125
|
'sessions (verification matches those values exactly; a missing ' +
|
|
126
126
|
'field is a 400 at session create). ' +
|
|
127
127
|
'Refine types/descriptions via `update_provider_version` while ' +
|
|
128
|
-
'still on draft.'
|
|
128
|
+
'still on draft. ' +
|
|
129
|
+
'On success, if a browser is still attached, the response\'s ' +
|
|
130
|
+
'`_notes` reminds you to confirm with the developer before ' +
|
|
131
|
+
'calling `dispose_browser` — never close it unprompted, they ' +
|
|
132
|
+
'may want it for further edits.',
|
|
129
133
|
inputSchema: {
|
|
130
134
|
type: 'object',
|
|
131
135
|
properties: {
|
|
@@ -261,6 +265,20 @@ export function createVersionFromCaptureTool(client) {
|
|
|
261
265
|
...(args.allowedJsRequests ?? []).map((r, i) => countsFor(`Injected request ${i + 1}`, r)),
|
|
262
266
|
];
|
|
263
267
|
notes.push(...matchRedactionWarnings(requestCounts));
|
|
268
|
+
// A browser session left attached after a successful publish is easy
|
|
269
|
+
// to forget about — surface it so the caller asks the developer
|
|
270
|
+
// before closing it (a builder-mode session is quota-accounted and
|
|
271
|
+
// otherwise just sits until its TTL expires).
|
|
272
|
+
const attached = attachRef?.current;
|
|
273
|
+
if (attached) {
|
|
274
|
+
notes.push(`A "${attached.mode}" browser session is still attached` +
|
|
275
|
+
(attached.browserSessionId
|
|
276
|
+
? ` (browserSessionId: ${attached.browserSessionId})`
|
|
277
|
+
: '') +
|
|
278
|
+
'. Ask the developer whether they want to keep it open for ' +
|
|
279
|
+
'further edits — only call dispose_browser once they confirm ' +
|
|
280
|
+
'they\'re done with it.');
|
|
281
|
+
}
|
|
264
282
|
// Let the dev know how/when the script runs (and the window.Reclaim gap).
|
|
265
283
|
if (args.jsUserScripts?.trim()) {
|
|
266
284
|
notes.push('jsUserScripts will run in the Builder verification client before ' +
|
|
@@ -170,8 +170,9 @@ async function exchangeCode(authBase, opts) {
|
|
|
170
170
|
client_id: opts.clientId,
|
|
171
171
|
}).toString(),
|
|
172
172
|
});
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
const body = await res.text();
|
|
174
|
+
assert(res.ok, new Error(`Token exchange failed (${res.status}): ${body}`));
|
|
175
|
+
const json = JSON.parse(body);
|
|
175
176
|
assert(json.access_token, new Error('Token exchange returned no access_token.'));
|
|
176
177
|
return {
|
|
177
178
|
access_token: json.access_token,
|
package/lib/old/tools/build.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RegisteredTool } from '../../mcp/server.ts';
|
|
2
|
+
import type { AttachState } from '../../mcp/tools/agent/attach.ts';
|
|
2
3
|
import type { ReclaimOldClient } from '../client.ts';
|
|
3
4
|
import type { ReclaimSdkClient } from '../sdk-client.ts';
|
|
4
5
|
/**
|
|
@@ -10,5 +11,11 @@ import type { ReclaimSdkClient } from '../sdk-client.ts';
|
|
|
10
11
|
* `sdkClient` is a SEPARATE, unauthenticated client for the public
|
|
11
12
|
* production SDK backend (`api.reclaimprotocol.org`) — a different host from
|
|
12
13
|
* `client`'s old-devtools dashboard backend (`devapi.reclaimprotocol.org`).
|
|
14
|
+
*
|
|
15
|
+
* `attachRef` is the SAME attach state `buildAgentTools` was given (see
|
|
16
|
+
* `mcp/start.ts`) — passing it lets the publish tool remind the caller about
|
|
17
|
+
* a still-open browser session after a successful publish.
|
|
13
18
|
*/
|
|
14
|
-
export declare function buildOldBackendTools(client: ReclaimOldClient, sdkClient: ReclaimSdkClient
|
|
19
|
+
export declare function buildOldBackendTools(client: ReclaimOldClient, sdkClient: ReclaimSdkClient, attachRef?: {
|
|
20
|
+
current?: AttachState;
|
|
21
|
+
}): RegisteredTool[];
|
package/lib/old/tools/build.js
CHANGED
|
@@ -19,11 +19,15 @@ import { verifySdkSetupTool } from "./verify-sdk-setup.js";
|
|
|
19
19
|
* `sdkClient` is a SEPARATE, unauthenticated client for the public
|
|
20
20
|
* production SDK backend (`api.reclaimprotocol.org`) — a different host from
|
|
21
21
|
* `client`'s old-devtools dashboard backend (`devapi.reclaimprotocol.org`).
|
|
22
|
+
*
|
|
23
|
+
* `attachRef` is the SAME attach state `buildAgentTools` was given (see
|
|
24
|
+
* `mcp/start.ts`) — passing it lets the publish tool remind the caller about
|
|
25
|
+
* a still-open browser session after a successful publish.
|
|
22
26
|
*/
|
|
23
|
-
export function buildOldBackendTools(client, sdkClient) {
|
|
27
|
+
export function buildOldBackendTools(client, sdkClient, attachRef) {
|
|
24
28
|
return [
|
|
25
29
|
authenticateTool(client),
|
|
26
|
-
createVersionFromCaptureTool(client),
|
|
30
|
+
createVersionFromCaptureTool(client, attachRef),
|
|
27
31
|
getMeProvidersTool(client),
|
|
28
32
|
getProviderInfoTool(sdkClient),
|
|
29
33
|
updateProviderMetadataTool(client),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type RegisteredTool } from '../../mcp/server.ts';
|
|
2
|
+
import type { AttachState } from '../../mcp/tools/agent/attach.ts';
|
|
2
3
|
import type { ReclaimOldClient } from '../client.ts';
|
|
3
4
|
/**
|
|
4
5
|
* Publish a drafted provider to the OLD devtools backend. Keeps the builder
|
|
@@ -12,5 +13,13 @@ import type { ReclaimOldClient } from '../client.ts';
|
|
|
12
13
|
* highest existing version).
|
|
13
14
|
*
|
|
14
15
|
* Get a `providerId` from `get_me_providers`.
|
|
16
|
+
*
|
|
17
|
+
* `attachRef`, when given, is checked after a successful publish: if a
|
|
18
|
+
* browser is still attached, a `_notes` entry reminds the caller to confirm
|
|
19
|
+
* with the developer before closing it (see `dispose_browser`) rather than
|
|
20
|
+
* closing it unprompted — the developer may still want the session for
|
|
21
|
+
* further edits.
|
|
15
22
|
*/
|
|
16
|
-
export declare function createVersionFromCaptureTool(client: ReclaimOldClient
|
|
23
|
+
export declare function createVersionFromCaptureTool(client: ReclaimOldClient, attachRef?: {
|
|
24
|
+
current?: AttachState;
|
|
25
|
+
}): RegisteredTool;
|
package/lib/old/tools/publish.js
CHANGED
|
@@ -46,8 +46,14 @@ function rethrowOwnershipError(err, providerId) {
|
|
|
46
46
|
* highest existing version).
|
|
47
47
|
*
|
|
48
48
|
* Get a `providerId` from `get_me_providers`.
|
|
49
|
+
*
|
|
50
|
+
* `attachRef`, when given, is checked after a successful publish: if a
|
|
51
|
+
* browser is still attached, a `_notes` entry reminds the caller to confirm
|
|
52
|
+
* with the developer before closing it (see `dispose_browser`) rather than
|
|
53
|
+
* closing it unprompted — the developer may still want the session for
|
|
54
|
+
* further edits.
|
|
49
55
|
*/
|
|
50
|
-
export function createVersionFromCaptureTool(client) {
|
|
56
|
+
export function createVersionFromCaptureTool(client, attachRef) {
|
|
51
57
|
return defineTool({
|
|
52
58
|
name: 'create_provider_version_from_capture',
|
|
53
59
|
description: 'Publish drafted ReclaimProvider(s) to the devtools backend. ' +
|
|
@@ -93,7 +99,11 @@ export function createVersionFromCaptureTool(client) {
|
|
|
93
99
|
'default include), `urlType` (REGEX/CONSTANT/TEMPLATE, default ' +
|
|
94
100
|
'derived from {{placeholders}}), and `isOptional` on each ' +
|
|
95
101
|
'responseMatch (default false). \n\n' +
|
|
96
|
-
'Captured requests must be GET/POST. OPRF redactions are supported.'
|
|
102
|
+
'Captured requests must be GET/POST. OPRF redactions are supported. ' +
|
|
103
|
+
'On success, if a browser is still attached, the response\'s ' +
|
|
104
|
+
'`_notes` reminds you to confirm with the developer before ' +
|
|
105
|
+
'calling `dispose_browser` — never close it unprompted, they ' +
|
|
106
|
+
'may want it for further edits.',
|
|
97
107
|
inputSchema: {
|
|
98
108
|
type: 'object',
|
|
99
109
|
properties: {
|
|
@@ -427,6 +437,20 @@ export function createVersionFromCaptureTool(client) {
|
|
|
427
437
|
// Non-blocking advisory per request — never alters or blocks the
|
|
428
438
|
// publish; the author keeps full control. Attaches under `_notes`.
|
|
429
439
|
const notes = matchRedactionWarnings(providers.map((p, i) => countsFor(`Request ${i + 1}`, p)));
|
|
440
|
+
// A browser session left attached after a successful publish is
|
|
441
|
+
// easy to forget about — surface it so the caller asks the
|
|
442
|
+
// developer before closing it (a builder-mode session is
|
|
443
|
+
// quota-accounted and otherwise just sits until its TTL expires).
|
|
444
|
+
const attached = attachRef?.current;
|
|
445
|
+
if (attached) {
|
|
446
|
+
notes.push(`A "${attached.mode}" browser session is still attached` +
|
|
447
|
+
(attached.browserSessionId
|
|
448
|
+
? ` (browserSessionId: ${attached.browserSessionId})`
|
|
449
|
+
: '') +
|
|
450
|
+
'. Ask the developer whether they want to keep it open for ' +
|
|
451
|
+
'further edits — only call dispose_browser once they confirm ' +
|
|
452
|
+
'they\'re done with it.');
|
|
453
|
+
}
|
|
430
454
|
const withNotes = (result) => {
|
|
431
455
|
if (notes.length && result && typeof result === 'object') {
|
|
432
456
|
return { ...result, _notes: notes };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reclaimprotocol/agent",
|
|
3
3
|
"title": "Reclaim Agent",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.21",
|
|
5
5
|
"description": "Reclaim Protocol's tools for agents — Reclaim Protocol lets your app verify **data points** about its users — a follower count, an account balance, an order history. This package gives your AI coding agent the tools to **find, build, test and fix the _data verification providers_** (the recipes that verify a data point) by inspecting and replaying captured requests, checking sessions, evaluate proofs, publishing updates, and much more.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./lib/index.js",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
84
84
|
"@noble/hashes": "^2.2.0",
|
|
85
85
|
"@reclaimprotocol/attestor-core": "^5.0.8",
|
|
86
|
-
"@reclaimprotocol/client": "0.1.
|
|
86
|
+
"@reclaimprotocol/client": "0.1.21",
|
|
87
87
|
"@reclaimprotocol/tls": "^0.1.3",
|
|
88
88
|
"chrome-remote-interface": "^0.33.0",
|
|
89
89
|
"domhandler": "^6.0.0",
|
package/skill/SKILL.builder.md
CHANGED
|
@@ -173,6 +173,8 @@ The value of any parameter marked secret is not included in the final proof, omi
|
|
|
173
173
|
|
|
174
174
|
When done, tell the developer the version is live (PRIVATE) or submitted for review (PUBLIC), and that they can integrate using the `providerId`.
|
|
175
175
|
|
|
176
|
+
- **If a browser is still attached**, `create_provider_version_from_capture`'s response carries a `_notes` reminder about it. Surface that to the dev and ask whether they want to keep the session open for further edits — only call `dispose_browser` once they confirm they're done. Never close it unprompted; a `builder`-mode session is quota-accounted, but the dev may still be mid-workflow.
|
|
177
|
+
|
|
176
178
|
<!-- common: concerns-section -->
|
|
177
179
|
|
|
178
180
|
## Things to never do
|
package/skill/SKILL.common.md
CHANGED
|
@@ -17,8 +17,8 @@ You drive the browser through the capture → draft → replay → prove loop us
|
|
|
17
17
|
1. **Attach.** Call `attach_browser`. If it returns `needsChoice: true`, ask the dev which browser source they want:
|
|
18
18
|
- `dedicated` — agent auto-launches Chrome with a dedicated profile under `~/.reclaim/chrome-profile`. Easiest local path; works out of the box.
|
|
19
19
|
- `attach` — connect to a Chrome the dev is already running. **Important Chrome 136+ gotcha:** the dev's Chrome must be launched with BOTH `--remote-debugging-port=9222` AND `--user-data-dir=<some-non-default-path>`. Since Chrome 136 (May 2025), the debug-port flag is silently ignored when the default user profile is in use. If `attach` fails, tell them this verbatim and offer to switch to `dedicated`.
|
|
20
|
-
- `builder` — allocate a quota-accounted remote Popcorn browser. Obtain an `orgId` with `list_builder_organizations` in old-devtools mode or the generated `list_orgs` tool in Builder mode. If Builder answers 401, authenticate with `authenticate_builder` in old-devtools mode or the existing `authenticate` flow in Builder mode, then retry. Return the `liveViewUrl` to the dev so they
|
|
21
|
-
- `custom` — connect to a `ws://` or `wss://` CDP URL supplied by the dev. The URL is a credential: never repeat it, log it, or persist it. An optional `liveViewUrl` may be returned and
|
|
20
|
+
- `builder` — allocate a quota-accounted remote Popcorn browser. Obtain an `orgId` with `list_builder_organizations` in old-devtools mode or the generated `list_orgs` tool in Builder mode. If Builder answers 401, authenticate with `authenticate_builder` in old-devtools mode or the existing `authenticate` flow in Builder mode, then retry. Return the FULL `liveViewUrl` to the dev verbatim — on its own line, never abbreviated, elided, or wrapped in prose that truncates it — so it stays one clean, copy-pasteable string; they'll share it with the volunteer operating the browser. This works in both publishing modes and consumes one VERIFICATION-pool use under the chosen organization's normal plan restrictions.
|
|
21
|
+
- `custom` — connect to a `ws://` or `wss://` CDP URL supplied by the dev. The URL is a credential: never repeat it, log it, or persist it. An optional `liveViewUrl` may be returned — if so, share it FULL and verbatim, same as `builder` above.
|
|
22
22
|
|
|
23
23
|
Re-call with the chosen `mode` and its required fields.
|
|
24
24
|
|
package/skill/SKILL.old.md
CHANGED
|
@@ -141,6 +141,8 @@ A secret value CAN be used in the URL, geoLocation, request body, response match
|
|
|
141
141
|
|
|
142
142
|
When done, tell the developer the version is live (PRIVATE) or submitted for review (PUBLIC), and that they can integrate using the `providerId`.
|
|
143
143
|
|
|
144
|
+
- **If a browser is still attached**, `create_provider_version_from_capture`'s response carries a `_notes` reminder about it. Surface that to the dev and ask whether they want to keep the session open for further edits — only call `dispose_browser` once they confirm they're done. Never close it unprompted; a `builder`-mode session is quota-accounted, but the dev may still be mid-workflow.
|
|
145
|
+
|
|
144
146
|
<!-- common: concerns-section -->
|
|
145
147
|
|
|
146
148
|
## Things to never do
|
package/src/mcp/remap_errors.ts
CHANGED
|
@@ -20,9 +20,10 @@ export const remapErrorAsResponse = (
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
const data = (err as Boom<ProblemDetails>).data
|
|
23
24
|
return {
|
|
24
25
|
isError: true as const,
|
|
25
|
-
structuredContent:
|
|
26
|
+
...(data ? { structuredContent: data } : {}),
|
|
26
27
|
content: [{ type: 'text' as const, text }],
|
|
27
28
|
}
|
|
28
29
|
}
|
package/src/mcp/start.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { buildOldBackendTools } from '../old/tools/build.ts'
|
|
|
19
19
|
import { NAME, VERSION } from '../pkg.ts'
|
|
20
20
|
import { registerTools } from './api/tools.ts'
|
|
21
21
|
import {
|
|
22
|
+
buildAgentState,
|
|
22
23
|
buildAgentTools,
|
|
23
24
|
buildBuilderAuthenticationTools,
|
|
24
25
|
buildBuilderOrganizationTools,
|
|
@@ -73,14 +74,23 @@ export async function startMcpServer() {
|
|
|
73
74
|
identity: resolveInitialIdentity(),
|
|
74
75
|
})
|
|
75
76
|
const sdkClient = new ReclaimSdkClient({ baseUrl: sdkApiUrl() })
|
|
77
|
+
// Shared with buildAgentTools below so the old-mode publish tool can see
|
|
78
|
+
// the live attach state (e.g. to flag a still-open browser session).
|
|
79
|
+
const agentState = buildAgentState()
|
|
76
80
|
|
|
77
81
|
registerToolList(server, buildBuilderAuthenticationTools(builderClient))
|
|
78
82
|
registerToolList(server, buildBuilderOrganizationTools(builderClient))
|
|
79
83
|
registerToolList(server, buildLocalCredentialTools())
|
|
80
|
-
registerToolList(
|
|
84
|
+
registerToolList(
|
|
85
|
+
server,
|
|
86
|
+
buildOldBackendTools(oldClient, sdkClient, agentState.attach),
|
|
87
|
+
)
|
|
81
88
|
if(agentToolsEnabled) {
|
|
82
89
|
// No builder client → no builder publish tool (old mode has its own).
|
|
83
|
-
registerToolList(
|
|
90
|
+
registerToolList(
|
|
91
|
+
server,
|
|
92
|
+
buildAgentTools(undefined, builderClient, agentState),
|
|
93
|
+
)
|
|
84
94
|
}
|
|
85
95
|
// NOTE: registerTools() is intentionally NOT called — generated tools off.
|
|
86
96
|
} else {
|
|
@@ -28,7 +28,11 @@ export interface AgentState {
|
|
|
28
28
|
captures: CaptureStore
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
/** Exported so a mode (e.g. old-devtools) that supplies its own publish tool
|
|
32
|
+
* can build the attach/capture state up front and share the SAME instance
|
|
33
|
+
* with `buildAgentTools` — letting that publish tool see the live attach
|
|
34
|
+
* state (e.g. to remind the caller about a still-open browser session). */
|
|
35
|
+
export function buildAgentState(): AgentState {
|
|
32
36
|
return {
|
|
33
37
|
attach: { config: new ConfigStore() },
|
|
34
38
|
captures: { sessions: new Map() },
|
|
@@ -39,12 +43,15 @@ function buildAgentState(): AgentState {
|
|
|
39
43
|
* `registerToolList`.
|
|
40
44
|
* Pass `client` to include the builder `create_provider_version_from_capture`;
|
|
41
45
|
* omit it (e.g. OLD-devtools mode) to register only the backend-independent
|
|
42
|
-
* capture/proof tools — old mode supplies its own publish tool.
|
|
46
|
+
* capture/proof tools — old mode supplies its own publish tool.
|
|
47
|
+
* Pass `externalState` to share attach/capture state with tools registered
|
|
48
|
+
* elsewhere (e.g. old mode's publish tool) instead of building a fresh one. */
|
|
43
49
|
export function buildAgentTools(
|
|
44
50
|
client?: ReclaimClient,
|
|
45
51
|
builderClient: ReclaimClient | undefined = client,
|
|
52
|
+
externalState?: AgentState,
|
|
46
53
|
): RegisteredTool[] {
|
|
47
|
-
const state = buildAgentState()
|
|
54
|
+
const state = externalState ?? buildAgentState()
|
|
48
55
|
ensureAttestorCrypto()
|
|
49
56
|
const tools: RegisteredTool[] = []
|
|
50
57
|
// Stateless docs tool — serve the mode-specific authoring guide to any
|
|
@@ -75,7 +82,7 @@ export function buildAgentTools(
|
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
if(client) {
|
|
78
|
-
tools.push(createVersionFromCaptureTool(client))
|
|
85
|
+
tools.push(createVersionFromCaptureTool(client, state.attach))
|
|
79
86
|
}
|
|
80
87
|
|
|
81
88
|
return tools
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Barrel for the agent tool group (Chrome CDP capture, provider synthesis,
|
|
2
2
|
// replay, zkTLS proof). Import from here, not the individual files.
|
|
3
|
-
export { buildAgentTools } from './build.ts'
|
|
3
|
+
export { buildAgentState, buildAgentTools } from './build.ts'
|
|
4
4
|
export type { AgentState } from './build.ts'
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
providerToCreateVersionRequest,
|
|
13
13
|
} from '../../../provider/to-version.ts'
|
|
14
14
|
import { defineTool, type RegisteredTool } from '../../server.ts'
|
|
15
|
+
import type { AttachState } from './attach.ts'
|
|
15
16
|
|
|
16
17
|
/** Schemas shared by the top-level `provider`/`providers` and the lighter
|
|
17
18
|
* `allowedJsRequests` drafts — keeps the request shape from drifting between
|
|
@@ -142,6 +143,7 @@ interface PublishArgs {
|
|
|
142
143
|
|
|
143
144
|
export function createVersionFromCaptureTool(
|
|
144
145
|
client: ReclaimClient,
|
|
146
|
+
attachRef?: { current?: AttachState },
|
|
145
147
|
): RegisteredTool {
|
|
146
148
|
return defineTool<PublishArgs>(
|
|
147
149
|
{
|
|
@@ -172,7 +174,11 @@ export function createVersionFromCaptureTool(
|
|
|
172
174
|
'sessions (verification matches those values exactly; a missing ' +
|
|
173
175
|
'field is a 400 at session create). ' +
|
|
174
176
|
'Refine types/descriptions via `update_provider_version` while ' +
|
|
175
|
-
'still on draft.'
|
|
177
|
+
'still on draft. ' +
|
|
178
|
+
'On success, if a browser is still attached, the response\'s ' +
|
|
179
|
+
'`_notes` reminds you to confirm with the developer before ' +
|
|
180
|
+
'calling `dispose_browser` — never close it unprompted, they ' +
|
|
181
|
+
'may want it for further edits.',
|
|
176
182
|
inputSchema: {
|
|
177
183
|
type: 'object',
|
|
178
184
|
properties: {
|
|
@@ -328,6 +334,23 @@ export function createVersionFromCaptureTool(
|
|
|
328
334
|
]
|
|
329
335
|
notes.push(...matchRedactionWarnings(requestCounts))
|
|
330
336
|
|
|
337
|
+
// A browser session left attached after a successful publish is easy
|
|
338
|
+
// to forget about — surface it so the caller asks the developer
|
|
339
|
+
// before closing it (a builder-mode session is quota-accounted and
|
|
340
|
+
// otherwise just sits until its TTL expires).
|
|
341
|
+
const attached = attachRef?.current
|
|
342
|
+
if(attached) {
|
|
343
|
+
notes.push(
|
|
344
|
+
`A "${attached.mode}" browser session is still attached` +
|
|
345
|
+
(attached.browserSessionId
|
|
346
|
+
? ` (browserSessionId: ${attached.browserSessionId})`
|
|
347
|
+
: '') +
|
|
348
|
+
'. Ask the developer whether they want to keep it open for ' +
|
|
349
|
+
'further edits — only call dispose_browser once they confirm ' +
|
|
350
|
+
'they\'re done with it.',
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
|
|
331
354
|
// Let the dev know how/when the script runs (and the window.Reclaim gap).
|
|
332
355
|
if(args.jsUserScripts?.trim()) {
|
|
333
356
|
notes.push(
|
|
@@ -233,12 +233,10 @@ async function exchangeCode(
|
|
|
233
233
|
client_id: opts.clientId,
|
|
234
234
|
}).toString(),
|
|
235
235
|
})
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
new Error(`Token exchange failed (${res.status}): ${await res.text()}`),
|
|
239
|
-
)
|
|
236
|
+
const body = await res.text()
|
|
237
|
+
assert(res.ok, new Error(`Token exchange failed (${res.status}): ${body}`))
|
|
240
238
|
|
|
241
|
-
const json =
|
|
239
|
+
const json = JSON.parse(body) as {
|
|
242
240
|
access_token?: string
|
|
243
241
|
expires_in?: number
|
|
244
242
|
}
|
package/src/old/tools/build.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RegisteredTool } from '../../mcp/server.ts'
|
|
2
|
+
import type { AttachState } from '../../mcp/tools/agent/attach.ts'
|
|
2
3
|
import type { ReclaimOldClient } from '../client.ts'
|
|
3
4
|
import type { ReclaimSdkClient } from '../sdk-client.ts'
|
|
4
5
|
import { authenticateTool } from './authenticate.ts'
|
|
@@ -23,14 +24,19 @@ import { verifySdkSetupTool } from './verify-sdk-setup.ts'
|
|
|
23
24
|
* `sdkClient` is a SEPARATE, unauthenticated client for the public
|
|
24
25
|
* production SDK backend (`api.reclaimprotocol.org`) — a different host from
|
|
25
26
|
* `client`'s old-devtools dashboard backend (`devapi.reclaimprotocol.org`).
|
|
27
|
+
*
|
|
28
|
+
* `attachRef` is the SAME attach state `buildAgentTools` was given (see
|
|
29
|
+
* `mcp/start.ts`) — passing it lets the publish tool remind the caller about
|
|
30
|
+
* a still-open browser session after a successful publish.
|
|
26
31
|
*/
|
|
27
32
|
export function buildOldBackendTools(
|
|
28
33
|
client: ReclaimOldClient,
|
|
29
34
|
sdkClient: ReclaimSdkClient,
|
|
35
|
+
attachRef?: { current?: AttachState },
|
|
30
36
|
): RegisteredTool[] {
|
|
31
37
|
return [
|
|
32
38
|
authenticateTool(client),
|
|
33
|
-
createVersionFromCaptureTool(client),
|
|
39
|
+
createVersionFromCaptureTool(client, attachRef),
|
|
34
40
|
getMeProvidersTool(client),
|
|
35
41
|
getProviderInfoTool(sdkClient),
|
|
36
42
|
updateProviderMetadataTool(client),
|
package/src/old/tools/publish.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Boom, isBoom } from '@hapi/boom'
|
|
2
2
|
import { defineTool, type RegisteredTool } from '../../mcp/server.ts'
|
|
3
|
+
import type { AttachState } from '../../mcp/tools/agent/attach.ts'
|
|
3
4
|
import { INJECTION_GUIDE } from '../../provider/injection-doc.ts'
|
|
4
5
|
import {
|
|
5
6
|
countsFor,
|
|
@@ -70,9 +71,16 @@ function rethrowOwnershipError(err: unknown, providerId: string): never {
|
|
|
70
71
|
* highest existing version).
|
|
71
72
|
*
|
|
72
73
|
* Get a `providerId` from `get_me_providers`.
|
|
74
|
+
*
|
|
75
|
+
* `attachRef`, when given, is checked after a successful publish: if a
|
|
76
|
+
* browser is still attached, a `_notes` entry reminds the caller to confirm
|
|
77
|
+
* with the developer before closing it (see `dispose_browser`) rather than
|
|
78
|
+
* closing it unprompted — the developer may still want the session for
|
|
79
|
+
* further edits.
|
|
73
80
|
*/
|
|
74
81
|
export function createVersionFromCaptureTool(
|
|
75
82
|
client: ReclaimOldClient,
|
|
83
|
+
attachRef?: { current?: AttachState },
|
|
76
84
|
): RegisteredTool {
|
|
77
85
|
return defineTool<{
|
|
78
86
|
provider?: ReclaimProvider
|
|
@@ -139,7 +147,11 @@ export function createVersionFromCaptureTool(
|
|
|
139
147
|
'default include), `urlType` (REGEX/CONSTANT/TEMPLATE, default ' +
|
|
140
148
|
'derived from {{placeholders}}), and `isOptional` on each ' +
|
|
141
149
|
'responseMatch (default false). \n\n' +
|
|
142
|
-
'Captured requests must be GET/POST. OPRF redactions are supported.'
|
|
150
|
+
'Captured requests must be GET/POST. OPRF redactions are supported. ' +
|
|
151
|
+
'On success, if a browser is still attached, the response\'s ' +
|
|
152
|
+
'`_notes` reminds you to confirm with the developer before ' +
|
|
153
|
+
'calling `dispose_browser` — never close it unprompted, they ' +
|
|
154
|
+
'may want it for further edits.',
|
|
143
155
|
inputSchema: {
|
|
144
156
|
type: 'object',
|
|
145
157
|
properties: {
|
|
@@ -507,6 +519,23 @@ export function createVersionFromCaptureTool(
|
|
|
507
519
|
providers.map((p, i) => countsFor(`Request ${i + 1}`, p)),
|
|
508
520
|
)
|
|
509
521
|
|
|
522
|
+
// A browser session left attached after a successful publish is
|
|
523
|
+
// easy to forget about — surface it so the caller asks the
|
|
524
|
+
// developer before closing it (a builder-mode session is
|
|
525
|
+
// quota-accounted and otherwise just sits until its TTL expires).
|
|
526
|
+
const attached = attachRef?.current
|
|
527
|
+
if(attached) {
|
|
528
|
+
notes.push(
|
|
529
|
+
`A "${attached.mode}" browser session is still attached` +
|
|
530
|
+
(attached.browserSessionId
|
|
531
|
+
? ` (browserSessionId: ${attached.browserSessionId})`
|
|
532
|
+
: '') +
|
|
533
|
+
'. Ask the developer whether they want to keep it open for ' +
|
|
534
|
+
'further edits — only call dispose_browser once they confirm ' +
|
|
535
|
+
'they\'re done with it.',
|
|
536
|
+
)
|
|
537
|
+
}
|
|
538
|
+
|
|
510
539
|
const withNotes = (result: unknown) => {
|
|
511
540
|
if(notes.length && result && typeof result === 'object') {
|
|
512
541
|
return { ...result, _notes: notes }
|