@reclaimprotocol/agent 0.1.0-dev.1
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 +287 -0
- package/lib/cdp/capture.d.ts +22 -0
- package/lib/cdp/capture.js +159 -0
- package/lib/cdp/client.d.ts +9 -0
- package/lib/cdp/client.js +11 -0
- package/lib/cdp/eviction.d.ts +16 -0
- package/lib/cdp/eviction.js +38 -0
- package/lib/cdp/launcher.d.ts +14 -0
- package/lib/cdp/launcher.js +102 -0
- package/lib/cdp/tabs.d.ts +6 -0
- package/lib/cdp/tabs.js +10 -0
- package/lib/cli/args.d.ts +14 -0
- package/lib/cli/args.js +19 -0
- package/lib/cli/commands.d.ts +1 -0
- package/lib/cli/commands.js +19 -0
- package/lib/cli.d.ts +1 -0
- package/lib/cli.js +7 -0
- package/lib/config/store.d.ts +45 -0
- package/lib/config/store.js +46 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +7 -0
- package/lib/logger.d.ts +2 -0
- package/lib/logger.js +7 -0
- package/lib/mcp/api/operations.gen.d.ts +152 -0
- package/lib/mcp/api/operations.gen.js +2300 -0
- package/lib/mcp/api/tools.d.ts +3 -0
- package/lib/mcp/api/tools.js +36 -0
- package/lib/mcp/remap_errors.d.ts +2 -0
- package/lib/mcp/remap_errors.js +27 -0
- package/lib/mcp/server.d.ts +38 -0
- package/lib/mcp/server.js +76 -0
- package/lib/mcp/start.d.ts +1 -0
- package/lib/mcp/start.js +63 -0
- package/lib/mcp/tools/agent/analyze.d.ts +3 -0
- package/lib/mcp/tools/agent/analyze.js +26 -0
- package/lib/mcp/tools/agent/attach.d.ts +11 -0
- package/lib/mcp/tools/agent/attach.js +72 -0
- package/lib/mcp/tools/agent/build.d.ts +33 -0
- package/lib/mcp/tools/agent/build.js +74 -0
- package/lib/mcp/tools/agent/capture.d.ts +10 -0
- package/lib/mcp/tools/agent/capture.js +142 -0
- package/lib/mcp/tools/agent/eval.d.ts +5 -0
- package/lib/mcp/tools/agent/eval.js +127 -0
- package/lib/mcp/tools/agent/find.d.ts +3 -0
- package/lib/mcp/tools/agent/find.js +60 -0
- package/lib/mcp/tools/agent/proof.d.ts +3 -0
- package/lib/mcp/tools/agent/proof.js +214 -0
- package/lib/mcp/tools/agent/propose.d.ts +9 -0
- package/lib/mcp/tools/agent/propose.js +75 -0
- package/lib/mcp/tools/agent/publish.d.ts +3 -0
- package/lib/mcp/tools/agent/publish.js +122 -0
- package/lib/mcp/tools/agent/replay.d.ts +3 -0
- package/lib/mcp/tools/agent/replay.js +46 -0
- package/lib/mcp/tools/agent/session.d.ts +10 -0
- package/lib/mcp/tools/agent/session.js +29 -0
- package/lib/mcp/tools/credentials/authenticate.d.ts +3 -0
- package/lib/mcp/tools/credentials/authenticate.js +108 -0
- package/lib/mcp/tools/credentials/build.d.ts +16 -0
- package/lib/mcp/tools/credentials/build.js +33 -0
- package/lib/mcp/tools/credentials/cache.d.ts +70 -0
- package/lib/mcp/tools/credentials/cache.js +114 -0
- package/lib/mcp/tools/credentials/env-file.d.ts +25 -0
- package/lib/mcp/tools/credentials/env-file.js +71 -0
- package/lib/mcp/tools/credentials/import.d.ts +2 -0
- package/lib/mcp/tools/credentials/import.js +52 -0
- package/lib/mcp/tools/credentials/issue.d.ts +2 -0
- package/lib/mcp/tools/credentials/issue.js +84 -0
- package/lib/mcp/tools/credentials/list-cached.d.ts +2 -0
- package/lib/mcp/tools/credentials/list-cached.js +55 -0
- package/lib/mcp/tools/credentials/register.d.ts +18 -0
- package/lib/mcp/tools/credentials/register.js +30 -0
- package/lib/mcp/tools/credentials/resolve-owner-key.d.ts +12 -0
- package/lib/mcp/tools/credentials/resolve-owner-key.js +68 -0
- package/lib/mcp/tools/credentials/session-cache.d.ts +14 -0
- package/lib/mcp/tools/credentials/session-cache.js +19 -0
- package/lib/mcp/tools/credentials/status.d.ts +3 -0
- package/lib/mcp/tools/credentials/status.js +62 -0
- package/lib/mcp-bin.d.ts +1 -0
- package/lib/mcp-bin.js +7 -0
- package/lib/old/client.d.ts +67 -0
- package/lib/old/client.js +146 -0
- package/lib/old/flag.d.ts +11 -0
- package/lib/old/flag.js +13 -0
- package/lib/old/identity.d.ts +22 -0
- package/lib/old/identity.js +47 -0
- package/lib/old/instructions.d.ts +7 -0
- package/lib/old/instructions.js +29 -0
- package/lib/old/login-cdp.d.ts +23 -0
- package/lib/old/login-cdp.js +125 -0
- package/lib/old/to-register.d.ts +47 -0
- package/lib/old/to-register.js +133 -0
- package/lib/old/tools/authenticate.d.ts +18 -0
- package/lib/old/tools/authenticate.js +127 -0
- package/lib/old/tools/build.d.ts +9 -0
- package/lib/old/tools/build.js +16 -0
- package/lib/old/tools/me-providers.d.ts +10 -0
- package/lib/old/tools/me-providers.js +32 -0
- package/lib/old/tools/publish.d.ts +16 -0
- package/lib/old/tools/publish.js +97 -0
- package/lib/paths.d.ts +11 -0
- package/lib/paths.js +32 -0
- package/lib/pkg.d.ts +3 -0
- package/lib/pkg.js +13 -0
- package/lib/proof/attestor.d.ts +39 -0
- package/lib/proof/attestor.js +207 -0
- package/lib/proof/replay.d.ts +8 -0
- package/lib/proof/replay.js +73 -0
- package/lib/provider/constraints.d.ts +2 -0
- package/lib/provider/constraints.js +122 -0
- package/lib/provider/draft.d.ts +6 -0
- package/lib/provider/draft.js +68 -0
- package/lib/provider/graphql.d.ts +2 -0
- package/lib/provider/graphql.js +35 -0
- package/lib/provider/matcher.d.ts +36 -0
- package/lib/provider/matcher.js +436 -0
- package/lib/provider/normalize.d.ts +4 -0
- package/lib/provider/normalize.js +51 -0
- package/lib/provider/schema.d.ts +76 -0
- package/lib/provider/schema.js +1 -0
- package/lib/provider/secrets.d.ts +5 -0
- package/lib/provider/secrets.js +22 -0
- package/lib/provider/to-version.d.ts +22 -0
- package/lib/provider/to-version.js +68 -0
- package/lib/types/openapi.d.ts +4 -0
- package/lib/types/openapi.js +1 -0
- package/package.json +88 -0
- package/scripts/download-zk-files.ts +42 -0
- package/scripts/prepend-shebang.ts +17 -0
- package/skill/SKILL.md +248 -0
- package/src/cdp/capture.ts +215 -0
- package/src/cdp/client.ts +20 -0
- package/src/cdp/eviction.ts +52 -0
- package/src/cdp/launcher.ts +133 -0
- package/src/cdp/tabs.ts +22 -0
- package/src/cli/args.ts +20 -0
- package/src/cli/commands.ts +27 -0
- package/src/cli.ts +7 -0
- package/src/config/store.ts +86 -0
- package/src/index.ts +12 -0
- package/src/logger.ts +8 -0
- package/src/mcp/api/operations.gen.ts +2397 -0
- package/src/mcp/api/tools.ts +45 -0
- package/src/mcp/remap_errors.ts +35 -0
- package/src/mcp/server.ts +119 -0
- package/src/mcp/start.ts +78 -0
- package/src/mcp/tools/agent/analyze.ts +34 -0
- package/src/mcp/tools/agent/attach.ts +95 -0
- package/src/mcp/tools/agent/build.ts +99 -0
- package/src/mcp/tools/agent/capture.ts +202 -0
- package/src/mcp/tools/agent/eval.ts +161 -0
- package/src/mcp/tools/agent/find.ts +73 -0
- package/src/mcp/tools/agent/proof.ts +272 -0
- package/src/mcp/tools/agent/propose.ts +111 -0
- package/src/mcp/tools/agent/publish.ts +155 -0
- package/src/mcp/tools/agent/replay.ts +58 -0
- package/src/mcp/tools/agent/session.ts +46 -0
- package/src/mcp/tools/credentials/authenticate.ts +135 -0
- package/src/mcp/tools/credentials/build.ts +37 -0
- package/src/mcp/tools/credentials/cache.ts +161 -0
- package/src/mcp/tools/credentials/env-file.ts +101 -0
- package/src/mcp/tools/credentials/import.ts +72 -0
- package/src/mcp/tools/credentials/issue.ts +107 -0
- package/src/mcp/tools/credentials/list-cached.ts +66 -0
- package/src/mcp/tools/credentials/register.ts +49 -0
- package/src/mcp/tools/credentials/resolve-owner-key.ts +78 -0
- package/src/mcp/tools/credentials/session-cache.ts +31 -0
- package/src/mcp/tools/credentials/status.ts +82 -0
- package/src/mcp-bin.ts +7 -0
- package/src/old/client.ts +236 -0
- package/src/old/flag.ts +13 -0
- package/src/old/identity.ts +56 -0
- package/src/old/instructions.ts +30 -0
- package/src/old/login-cdp.ts +153 -0
- package/src/old/to-register.ts +234 -0
- package/src/old/tools/authenticate.ts +152 -0
- package/src/old/tools/build.ts +21 -0
- package/src/old/tools/me-providers.ts +42 -0
- package/src/old/tools/publish.ts +135 -0
- package/src/paths.ts +37 -0
- package/src/pkg.ts +24 -0
- package/src/proof/attestor.ts +286 -0
- package/src/proof/replay.ts +102 -0
- package/src/provider/constraints.ts +150 -0
- package/src/provider/draft.ts +105 -0
- package/src/provider/graphql.ts +42 -0
- package/src/provider/matcher.ts +616 -0
- package/src/provider/normalize.ts +65 -0
- package/src/provider/schema.ts +92 -0
- package/src/provider/secrets.ts +29 -0
- package/src/provider/to-version.ts +90 -0
- package/src/types/openapi.ts +7 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { remapErrorAsResponse } from "../remap_errors.js";
|
|
2
|
+
import { MCP_TOOLS } from "./operations.gen.js";
|
|
3
|
+
/**
|
|
4
|
+
* The generated `MCP_TOOLS` are keyed by OpenAPI `operationId` (PascalCase,
|
|
5
|
+
* e.g. `GetMe`). MCP tools are conventionally snake_case, matching the
|
|
6
|
+
* hand-written tools in this server. Convert the public tool name here while
|
|
7
|
+
* still dispatching to the API with the original operationId — doing this in
|
|
8
|
+
* the (non-generated) registration keeps it from being reverted on regen.
|
|
9
|
+
*/
|
|
10
|
+
function toSnakeCase(name) {
|
|
11
|
+
return name
|
|
12
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
13
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
|
14
|
+
.toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
export function registerTools(server, client) {
|
|
17
|
+
for (const [opId, shape] of Object.entries(MCP_TOOLS)) {
|
|
18
|
+
// snake_case to match the hand-written tools. A name that collides with
|
|
19
|
+
// a hand-written tool throws here at startup — intentional: hand-written
|
|
20
|
+
// and generated names must stay disjoint (see start.ts), so a clash is a
|
|
21
|
+
// bug to surface, not silently shadow.
|
|
22
|
+
server.registerTool(toSnakeCase(opId), shape, async (args) => {
|
|
23
|
+
try {
|
|
24
|
+
const result = (await client.call(opId, args));
|
|
25
|
+
return {
|
|
26
|
+
content: [{
|
|
27
|
+
type: 'text', text: JSON.stringify(result.data, null, 2)
|
|
28
|
+
}],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
return remapErrorAsResponse(err);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isBoom } from '@hapi/boom';
|
|
2
|
+
// Remap all errors here if we return different response based on error.
|
|
3
|
+
// Doing this so we can reuse tool error handler that returns same response
|
|
4
|
+
// across different tools or error.
|
|
5
|
+
// For every other error, let them get thrown so the main try-catch
|
|
6
|
+
// handles them in tool registration.
|
|
7
|
+
export const remapErrorAsResponse = (err) => {
|
|
8
|
+
// The client throws a Boom-style ProblemError, so the status lives on
|
|
9
|
+
// `output.statusCode` / `problem.status`, not `status`.
|
|
10
|
+
if (!isBoom(err) || !err.output?.statusCode) {
|
|
11
|
+
return {
|
|
12
|
+
isError: true,
|
|
13
|
+
content: [{ type: 'text', text: err.message }],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const problemError = err;
|
|
17
|
+
return {
|
|
18
|
+
isError: true,
|
|
19
|
+
structuredContent: problemError.data,
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: 'text',
|
|
23
|
+
text: `${problemError.message}: ${problemError.data?.detail}`
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { type JSONSchema } from 'zod-from-json-schema';
|
|
3
|
+
export interface ToolDefinition {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: JSONSchema;
|
|
7
|
+
}
|
|
8
|
+
export interface RegisteredTool {
|
|
9
|
+
definition: ToolDefinition;
|
|
10
|
+
/**
|
|
11
|
+
* @param args Tool arguments
|
|
12
|
+
* @returns A JSON stringifiable content to be returned to the llm
|
|
13
|
+
* @throws Any error encountered during tool execution will be caught and
|
|
14
|
+
* returned as a response using
|
|
15
|
+
* [remapErrorAsResponse](./src/mcp/remap_errors.ts).
|
|
16
|
+
*/
|
|
17
|
+
handler: (args: unknown) => Promise<unknown>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Define a tool with a single inputSchema (JSON Schema) used as the
|
|
21
|
+
* source of truth. The handler receives args already parsed and
|
|
22
|
+
* type-narrowed via a Zod schema derived from that same JSON Schema —
|
|
23
|
+
* so the description sent to the MCP client and the validation applied
|
|
24
|
+
* to inbound args cannot drift.
|
|
25
|
+
*/
|
|
26
|
+
export declare function defineTool<TArgs>(definition: ToolDefinition, handler: (args: TArgs) => Promise<unknown>): RegisteredTool;
|
|
27
|
+
/**
|
|
28
|
+
* Registers a list of RegisteredTool entries on a high-level McpServer.
|
|
29
|
+
* Converts JSON Schema definitions to Zod and provides consistent
|
|
30
|
+
* stringification and error catching for tool execution.
|
|
31
|
+
*/
|
|
32
|
+
export declare function registerToolList(server: McpServer, tools: RegisteredTool[]): void;
|
|
33
|
+
export declare class ReclaimMcpServer {
|
|
34
|
+
#private;
|
|
35
|
+
constructor();
|
|
36
|
+
register(tool: RegisteredTool): void;
|
|
37
|
+
listen(): Promise<void>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import { convertJsonSchemaToZod } from 'zod-from-json-schema';
|
|
5
|
+
import { remapErrorAsResponse } from "./remap_errors.js";
|
|
6
|
+
/**
|
|
7
|
+
* Define a tool with a single inputSchema (JSON Schema) used as the
|
|
8
|
+
* source of truth. The handler receives args already parsed and
|
|
9
|
+
* type-narrowed via a Zod schema derived from that same JSON Schema —
|
|
10
|
+
* so the description sent to the MCP client and the validation applied
|
|
11
|
+
* to inbound args cannot drift.
|
|
12
|
+
*/
|
|
13
|
+
export function defineTool(definition, handler) {
|
|
14
|
+
const schema = convertJsonSchemaToZod(definition.inputSchema);
|
|
15
|
+
return {
|
|
16
|
+
definition,
|
|
17
|
+
handler: async (raw) => {
|
|
18
|
+
const args = schema.parse(raw);
|
|
19
|
+
return await handler(args);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Registers a list of RegisteredTool entries on a high-level McpServer.
|
|
25
|
+
* Converts JSON Schema definitions to Zod and provides consistent
|
|
26
|
+
* stringification and error catching for tool execution.
|
|
27
|
+
*/
|
|
28
|
+
export function registerToolList(server, tools) {
|
|
29
|
+
for (const tool of tools) {
|
|
30
|
+
server.registerTool(tool.definition.name, {
|
|
31
|
+
description: tool.definition.description,
|
|
32
|
+
inputSchema: convertJsonSchemaToZod(tool.definition.inputSchema),
|
|
33
|
+
}, async (args) => {
|
|
34
|
+
try {
|
|
35
|
+
return {
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: 'text',
|
|
39
|
+
text: JSON.stringify(await tool.handler(args), null, 2),
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
return remapErrorAsResponse(err);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export class ReclaimMcpServer {
|
|
51
|
+
#tools = new Map();
|
|
52
|
+
#server;
|
|
53
|
+
constructor() {
|
|
54
|
+
this.#server = new Server({ name: 'reclaim-agent', version: '0.1.0' }, { capabilities: { tools: {} } });
|
|
55
|
+
this.#server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
56
|
+
// Double-cast: our `ToolDefinition` carries the same fields as the
|
|
57
|
+
// SDK's `Tool` but the SDK's type is strict-narrowed and won't
|
|
58
|
+
// accept ours without going through `unknown`.
|
|
59
|
+
tools: [...this.#tools.values()].map((t) => t.definition),
|
|
60
|
+
}));
|
|
61
|
+
this.#server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
62
|
+
const tool = this.#tools.get(req.params.name);
|
|
63
|
+
if (!tool) {
|
|
64
|
+
throw new Error(`unknown tool: ${req.params.name}`);
|
|
65
|
+
}
|
|
66
|
+
const result = await tool.handler(req.params.arguments ?? {});
|
|
67
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
register(tool) {
|
|
71
|
+
this.#tools.set(tool.definition.name, tool);
|
|
72
|
+
}
|
|
73
|
+
async listen() {
|
|
74
|
+
await this.#server.connect(new StdioServerTransport());
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function startMcpServer(): Promise<void>;
|
package/lib/mcp/start.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { DEFAULT_BASE_URL, ReclaimClient } from '@reclaimprotocol/client';
|
|
4
|
+
import { DEFAULT_OLD_API_URL, ReclaimOldClient } from "../old/client.js";
|
|
5
|
+
import { useOldDevtools } from "../old/flag.js";
|
|
6
|
+
import { resolveInitialIdentity } from "../old/identity.js";
|
|
7
|
+
import { OLD_MODE_INSTRUCTIONS } from "../old/instructions.js";
|
|
8
|
+
import { buildOldBackendTools } from "../old/tools/build.js";
|
|
9
|
+
import { NAME, VERSION } from "../pkg.js";
|
|
10
|
+
import { registerTools } from "./api/tools.js";
|
|
11
|
+
import { buildAgentTools } from "./tools/agent/build.js";
|
|
12
|
+
import { buildCredentialTools, buildLocalCredentialTools, } from "./tools/credentials/build.js";
|
|
13
|
+
import { readSession } from "./tools/credentials/session-cache.js";
|
|
14
|
+
import { registerToolList } from "./server.js";
|
|
15
|
+
export async function startMcpServer() {
|
|
16
|
+
const oldMode = useOldDevtools();
|
|
17
|
+
const server = new McpServer({ name: NAME, version: VERSION }, oldMode ? { instructions: OLD_MODE_INSTRUCTIONS } : undefined);
|
|
18
|
+
// Agent tools (Chrome CDP capture, provider synthesis, replay, proof) are
|
|
19
|
+
// backend-independent and registered in both modes. Skipped in hosted
|
|
20
|
+
// deployments — set RECLAIM_AGENT_DISABLED=1 to register only the API
|
|
21
|
+
// surface.
|
|
22
|
+
const agentToolsEnabled = process.env.RECLAIM_AGENT_DISABLED !== '1';
|
|
23
|
+
if (oldMode) {
|
|
24
|
+
// OLD-devtools mode (the default): the legacy backend is still in
|
|
25
|
+
// production while builder is built. All auto-generated builder tools
|
|
26
|
+
// are disabled; a small ReclaimOldClient serves authenticate / publish /
|
|
27
|
+
// get_me_providers instead.
|
|
28
|
+
const oldClient = new ReclaimOldClient({
|
|
29
|
+
baseUrl: process.env.RECLAIM_OLD_API_URL || DEFAULT_OLD_API_URL,
|
|
30
|
+
identity: resolveInitialIdentity(),
|
|
31
|
+
});
|
|
32
|
+
registerToolList(server, buildLocalCredentialTools());
|
|
33
|
+
registerToolList(server, buildOldBackendTools(oldClient));
|
|
34
|
+
if (agentToolsEnabled) {
|
|
35
|
+
// No builder client → no builder publish tool (old mode has its own).
|
|
36
|
+
registerToolList(server, buildAgentTools());
|
|
37
|
+
}
|
|
38
|
+
// NOTE: registerTools() is intentionally NOT called — generated tools off.
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const client = new ReclaimClient({
|
|
42
|
+
baseUrl: process.env.RECLAIM_API_URL || DEFAULT_BASE_URL,
|
|
43
|
+
// Priority: explicit env var → cached session token → unauthenticated
|
|
44
|
+
token: process.env.RECLAIM_API_TOKEN || readSession()?.token,
|
|
45
|
+
headers: { 'User-Agent': `${NAME}/${VERSION}` },
|
|
46
|
+
});
|
|
47
|
+
// Tool names must stay disjoint across these three groups: hand-written
|
|
48
|
+
// tools are higher-level orchestrations (multi-call flows, ReclaimProvider
|
|
49
|
+
// translation) and must NOT reuse a generated operation's snake_case name.
|
|
50
|
+
// A clash throws at startup on purpose — it surfaces an accidental
|
|
51
|
+
// collision instead of one tool silently shadowing the other.
|
|
52
|
+
// Register credential tools for ETH credential management.
|
|
53
|
+
registerToolList(server, buildCredentialTools(client));
|
|
54
|
+
// Add auto-generated tools with snake_case wrappers
|
|
55
|
+
// for every MCP-tagged operation.
|
|
56
|
+
registerTools(server, client);
|
|
57
|
+
if (agentToolsEnabled) {
|
|
58
|
+
registerToolList(server, buildAgentTools(client));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const transport = new StdioServerTransport();
|
|
62
|
+
await server.connect(transport);
|
|
63
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { detectConcerns } from "../../../provider/constraints.js";
|
|
2
|
+
import { defineTool } from "../../server.js";
|
|
3
|
+
export function analyzeTool(captures) {
|
|
4
|
+
return defineTool({
|
|
5
|
+
name: 'analyze_request_constraints',
|
|
6
|
+
description: 'Flag potential provider-authoring concerns for a captured request.',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
captureId: { type: 'string' },
|
|
11
|
+
requestId: { type: 'string' },
|
|
12
|
+
},
|
|
13
|
+
required: ['captureId', 'requestId'],
|
|
14
|
+
},
|
|
15
|
+
}, async ({ captureId, requestId }) => {
|
|
16
|
+
const session = captures.sessions.get(captureId);
|
|
17
|
+
if (!session) {
|
|
18
|
+
throw new Error(`no capture ${captureId}`);
|
|
19
|
+
}
|
|
20
|
+
const r = session.requests.get(requestId);
|
|
21
|
+
if (!r) {
|
|
22
|
+
throw new Error(`no request ${requestId} in capture ${captureId}`);
|
|
23
|
+
}
|
|
24
|
+
return { concerns: detectConcerns(r) };
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type CdpHandles } from '../../../cdp/client.ts';
|
|
2
|
+
import type { ConfigStore } from '../../../config/store.ts';
|
|
3
|
+
import { type RegisteredTool } from '../../server.ts';
|
|
4
|
+
export interface AttachState {
|
|
5
|
+
handles: CdpHandles;
|
|
6
|
+
port: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function attachTool(state: {
|
|
9
|
+
current?: AttachState;
|
|
10
|
+
config: ConfigStore;
|
|
11
|
+
}): RegisteredTool;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { connectCdp } from "../../../cdp/client.js";
|
|
2
|
+
import { defaultProfileDir, launchChrome, waitForCdpReady, } from "../../../cdp/launcher.js";
|
|
3
|
+
import { defaultConfigPath } from "../../../config/store.js";
|
|
4
|
+
import { defineTool } from "../../server.js";
|
|
5
|
+
export function attachTool(state) {
|
|
6
|
+
return defineTool({
|
|
7
|
+
name: 'attach_chrome',
|
|
8
|
+
description: 'Attach to a Chrome instance via CDP. ' +
|
|
9
|
+
'Auto-launches a dedicated-profile Chrome ' +
|
|
10
|
+
'on first call if no attachMode is persisted. ' +
|
|
11
|
+
'Persists the chosen mode in ~/.reclaim/config.json.',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
attachMode: { type: 'string', enum: ['dedicated', 'attach'] },
|
|
16
|
+
port: { type: 'number' },
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
}, async (parsed) => {
|
|
20
|
+
let cfg = state.config.read()?.browser_agent;
|
|
21
|
+
if (parsed.attachMode || parsed.port) {
|
|
22
|
+
cfg = {
|
|
23
|
+
attachMode: parsed.attachMode ?? cfg?.attachMode ?? 'dedicated',
|
|
24
|
+
port: parsed.port ?? cfg?.port ?? 9222,
|
|
25
|
+
};
|
|
26
|
+
state.config.write({ browser_agent: cfg });
|
|
27
|
+
}
|
|
28
|
+
if (!cfg) {
|
|
29
|
+
return {
|
|
30
|
+
needsChoice: true,
|
|
31
|
+
message: 'First-run: choose attach mode. ' +
|
|
32
|
+
'Call attach_chrome again with attachMode="dedicated" ' +
|
|
33
|
+
'(auto-launch a Chrome profile under ~/.reclaim/chrome-profile) ' +
|
|
34
|
+
'or attachMode="attach" ' +
|
|
35
|
+
'(connect to a Chrome you launched yourself with ' +
|
|
36
|
+
'BOTH --remote-debugging-port=9222 AND ' +
|
|
37
|
+
'--user-data-dir=<some-non-default-path>; ' +
|
|
38
|
+
'since Chrome 136 the debug-port flag is silently ' +
|
|
39
|
+
'ignored if the default profile is in use).',
|
|
40
|
+
configPath: defaultConfigPath(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (cfg.attachMode === 'dedicated') {
|
|
44
|
+
try {
|
|
45
|
+
await waitForCdpReady(cfg.port, 500);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
launchChrome({ port: cfg.port, profileDir: defaultProfileDir() });
|
|
49
|
+
await waitForCdpReady(cfg.port, 10_000);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
await waitForCdpReady(cfg.port, 2_000).catch(() => {
|
|
54
|
+
throw new Error(`attach mode: nothing answering CDP on port ${cfg.port}. ` +
|
|
55
|
+
'Chrome must be launched with BOTH ' +
|
|
56
|
+
`--remote-debugging-port=${cfg.port} AND ` +
|
|
57
|
+
'--user-data-dir=<some-non-default-path>. ' +
|
|
58
|
+
'Since Chrome 136 (May 2025), the debug-port flag is ' +
|
|
59
|
+
'silently ignored when the default ' +
|
|
60
|
+
"user profile is in use — that's why nothing opens the port. " +
|
|
61
|
+
'Either re-launch Chrome with ' +
|
|
62
|
+
'a custom --user-data-dir, or switch to attachMode="dedicated".');
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (state.current) {
|
|
66
|
+
await state.current.handles.close().catch(() => { });
|
|
67
|
+
}
|
|
68
|
+
const handles = await connectCdp(cfg.port);
|
|
69
|
+
state.current = { handles, port: cfg.port };
|
|
70
|
+
return { attached: true, port: cfg.port, mode: cfg.attachMode };
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory entry-point for embedding the reclaim-agent's MCP tools into
|
|
3
|
+
* a host MCP server. Two consumers:
|
|
4
|
+
*
|
|
5
|
+
* - The agent's own standalone `src/index.ts` (uses `ReclaimMcpServer`,
|
|
6
|
+
* the low-level wrapper around `@modelcontextprotocol/sdk`'s Server).
|
|
7
|
+
* - `@reclaimprotocol/client`'s MCP server (uses `McpServer`, the
|
|
8
|
+
* high-level SDK wrapper). Calls `registerAgentTools` to fold the
|
|
9
|
+
* agent's 16 tools alongside its auto-generated API tools.
|
|
10
|
+
*
|
|
11
|
+
* Shared state — Chrome attach state, capture store, secret store — is
|
|
12
|
+
* built once per server and threaded through every tool factory.
|
|
13
|
+
*/
|
|
14
|
+
import type { ReclaimClient } from '@reclaimprotocol/client';
|
|
15
|
+
import { ConfigStore } from '../../../config/store.ts';
|
|
16
|
+
import { type RegisteredTool } from '../../server.ts';
|
|
17
|
+
import { type AttachState } from './attach.ts';
|
|
18
|
+
import { type CaptureStore } from './capture.ts';
|
|
19
|
+
import { type SecretStore } from './propose.ts';
|
|
20
|
+
export interface AgentState {
|
|
21
|
+
attach: {
|
|
22
|
+
current?: AttachState;
|
|
23
|
+
config: ConfigStore;
|
|
24
|
+
};
|
|
25
|
+
captures: CaptureStore;
|
|
26
|
+
secrets: SecretStore;
|
|
27
|
+
}
|
|
28
|
+
/** Build the flat list of `RegisteredTool` entries. Useful when the host
|
|
29
|
+
* uses our internal `ReclaimMcpServer` interface (`.register(tool)`).
|
|
30
|
+
* Pass `client` to include the builder `create_provider_version_from_capture`;
|
|
31
|
+
* 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
|
+
export declare function buildAgentTools(client?: ReclaimClient): RegisteredTool[];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory entry-point for embedding the reclaim-agent's MCP tools into
|
|
3
|
+
* a host MCP server. Two consumers:
|
|
4
|
+
*
|
|
5
|
+
* - The agent's own standalone `src/index.ts` (uses `ReclaimMcpServer`,
|
|
6
|
+
* the low-level wrapper around `@modelcontextprotocol/sdk`'s Server).
|
|
7
|
+
* - `@reclaimprotocol/client`'s MCP server (uses `McpServer`, the
|
|
8
|
+
* high-level SDK wrapper). Calls `registerAgentTools` to fold the
|
|
9
|
+
* agent's 16 tools alongside its auto-generated API tools.
|
|
10
|
+
*
|
|
11
|
+
* Shared state — Chrome attach state, capture store, secret store — is
|
|
12
|
+
* built once per server and threaded through every tool factory.
|
|
13
|
+
*/
|
|
14
|
+
import { setCryptoImplementation } from '@reclaimprotocol/tls';
|
|
15
|
+
import { webcryptoCrypto } from '@reclaimprotocol/tls/webcrypto';
|
|
16
|
+
import { ConfigStore } from "../../../config/store.js";
|
|
17
|
+
import { analyzeTool } from "./analyze.js";
|
|
18
|
+
import { attachTool } from "./attach.js";
|
|
19
|
+
import { captureTools } from "./capture.js";
|
|
20
|
+
import { browserTools } from "./eval.js";
|
|
21
|
+
import { findTool } from "./find.js";
|
|
22
|
+
import { proofTool } from "./proof.js";
|
|
23
|
+
import { makeSecretStore, proposeTool, } from "./propose.js";
|
|
24
|
+
import { createVersionFromCaptureTool } from "./publish.js";
|
|
25
|
+
import { replayTool } from "./replay.js";
|
|
26
|
+
import { sessionTools } from "./session.js";
|
|
27
|
+
let cryptoInitialized = false;
|
|
28
|
+
/** One-time crypto backend init for @reclaimprotocol/tls. The package
|
|
29
|
+
* ships with an empty `crypto = {}` and defers backend choice to the
|
|
30
|
+
* caller; without this, the first proof call dies inside the SDK with
|
|
31
|
+
* `crypto.randomBytes is not a function`. */
|
|
32
|
+
function ensureCryptoInit() {
|
|
33
|
+
if (cryptoInitialized) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
setCryptoImplementation(webcryptoCrypto);
|
|
37
|
+
cryptoInitialized = true;
|
|
38
|
+
}
|
|
39
|
+
function buildAgentState() {
|
|
40
|
+
return {
|
|
41
|
+
attach: { config: new ConfigStore() },
|
|
42
|
+
captures: { sessions: new Map() },
|
|
43
|
+
secrets: makeSecretStore(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Build the flat list of `RegisteredTool` entries. Useful when the host
|
|
47
|
+
* uses our internal `ReclaimMcpServer` interface (`.register(tool)`).
|
|
48
|
+
* Pass `client` to include the builder `create_provider_version_from_capture`;
|
|
49
|
+
* omit it (e.g. OLD-devtools mode) to register only the backend-independent
|
|
50
|
+
* capture/proof tools — old mode supplies its own publish tool. */
|
|
51
|
+
export function buildAgentTools(client) {
|
|
52
|
+
const state = buildAgentState();
|
|
53
|
+
ensureCryptoInit();
|
|
54
|
+
const tools = [];
|
|
55
|
+
tools.push(attachTool(state.attach));
|
|
56
|
+
for (const t of captureTools(state.attach, state.captures)) {
|
|
57
|
+
tools.push(t);
|
|
58
|
+
}
|
|
59
|
+
tools.push(findTool(state.captures));
|
|
60
|
+
for (const t of browserTools(state.attach)) {
|
|
61
|
+
tools.push(t);
|
|
62
|
+
}
|
|
63
|
+
for (const t of sessionTools(state.attach, state.captures, state.secrets)) {
|
|
64
|
+
tools.push(t);
|
|
65
|
+
}
|
|
66
|
+
tools.push(analyzeTool(state.captures));
|
|
67
|
+
tools.push(proposeTool(state.captures, state.secrets));
|
|
68
|
+
tools.push(replayTool(state.secrets));
|
|
69
|
+
tools.push(proofTool(state.secrets));
|
|
70
|
+
if (client) {
|
|
71
|
+
tools.push(createVersionFromCaptureTool(client));
|
|
72
|
+
}
|
|
73
|
+
return tools;
|
|
74
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type CaptureSession } from '../../../cdp/capture.ts';
|
|
2
|
+
import { type RegisteredTool } from '../../server.ts';
|
|
3
|
+
import type { AttachState } from './attach.ts';
|
|
4
|
+
export interface CaptureStore {
|
|
5
|
+
sessions: Map<string, CaptureSession>;
|
|
6
|
+
activeTab?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function captureTools(attachRef: {
|
|
9
|
+
current?: AttachState;
|
|
10
|
+
}, captures: CaptureStore): RegisteredTool[];
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { startCapture } from "../../../cdp/capture.js";
|
|
2
|
+
import { defineTool } from "../../server.js";
|
|
3
|
+
export function captureTools(attachRef, captures) {
|
|
4
|
+
const requireAttached = () => {
|
|
5
|
+
if (!attachRef.current) {
|
|
6
|
+
throw new Error('Not attached. Call attach_chrome first.');
|
|
7
|
+
}
|
|
8
|
+
return attachRef.current;
|
|
9
|
+
};
|
|
10
|
+
const startTool = defineTool({
|
|
11
|
+
name: 'start_capture',
|
|
12
|
+
description: 'Begin recording network requests on the specified tab. ' +
|
|
13
|
+
'Returns a captureId.',
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: { tabId: { type: 'string' } },
|
|
17
|
+
required: ['tabId'],
|
|
18
|
+
},
|
|
19
|
+
}, async ({ tabId }) => {
|
|
20
|
+
const attached = requireAttached();
|
|
21
|
+
const tabClient = await attached.handles.attachToTab(tabId);
|
|
22
|
+
const session = await startCapture(tabClient);
|
|
23
|
+
captures.sessions.set(session.captureId, session);
|
|
24
|
+
captures.activeTab = tabId;
|
|
25
|
+
return { captureId: session.captureId };
|
|
26
|
+
});
|
|
27
|
+
const stopTool = defineTool({
|
|
28
|
+
name: 'stop_capture',
|
|
29
|
+
description: 'Stop recording on the given capture. ' +
|
|
30
|
+
'Captured data remains queryable until reset_session.',
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: { captureId: { type: 'string' } },
|
|
34
|
+
required: ['captureId'],
|
|
35
|
+
},
|
|
36
|
+
}, async ({ captureId }) => {
|
|
37
|
+
const session = captures.sessions.get(captureId);
|
|
38
|
+
if (!session) {
|
|
39
|
+
throw new Error(`no capture ${captureId}`);
|
|
40
|
+
}
|
|
41
|
+
await session.stop();
|
|
42
|
+
return { stopped: true, requestCount: session.requests.size };
|
|
43
|
+
});
|
|
44
|
+
const listTool = defineTool({
|
|
45
|
+
name: 'list_requests',
|
|
46
|
+
description: 'List metadata for captured requests. ' +
|
|
47
|
+
'Filter by method, URL glob, status, content-type, ' +
|
|
48
|
+
'or graphqlOp. Returns only metadata, never bodies — ' +
|
|
49
|
+
'use get_request for those.',
|
|
50
|
+
inputSchema: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
captureId: { type: 'string' },
|
|
54
|
+
filter: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
method: { type: 'string' },
|
|
58
|
+
urlGlob: { type: 'string' },
|
|
59
|
+
statusMin: { type: 'number' },
|
|
60
|
+
statusMax: { type: 'number' },
|
|
61
|
+
contentType: { type: 'string' },
|
|
62
|
+
graphqlOp: { type: 'string' },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
required: ['captureId'],
|
|
67
|
+
},
|
|
68
|
+
}, async ({ captureId, filter }) => {
|
|
69
|
+
const session = captures.sessions.get(captureId);
|
|
70
|
+
if (!session) {
|
|
71
|
+
throw new Error(`no capture ${captureId}`);
|
|
72
|
+
}
|
|
73
|
+
const out = [];
|
|
74
|
+
for (const r of session.requests.values()) {
|
|
75
|
+
if (filter?.method && r.method !== filter.method.toUpperCase()) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (filter?.urlGlob && !globMatch(filter.urlGlob, r.url)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (filter?.statusMin !== undefined && r.status < filter.statusMin) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (filter?.statusMax !== undefined && r.status > filter.statusMax) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (filter?.contentType &&
|
|
88
|
+
!r.contentType.includes(filter.contentType)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (filter?.graphqlOp && r.graphqlOp !== filter.graphqlOp) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const item = {
|
|
95
|
+
requestId: r.requestId,
|
|
96
|
+
method: r.method,
|
|
97
|
+
url: r.url,
|
|
98
|
+
status: r.status,
|
|
99
|
+
contentType: r.contentType,
|
|
100
|
+
size: r.size,
|
|
101
|
+
};
|
|
102
|
+
if (r.graphqlOp !== undefined) {
|
|
103
|
+
item.graphqlOp = r.graphqlOp;
|
|
104
|
+
}
|
|
105
|
+
out.push(item);
|
|
106
|
+
}
|
|
107
|
+
return { requests: out };
|
|
108
|
+
});
|
|
109
|
+
const getTool = defineTool({
|
|
110
|
+
name: 'get_request',
|
|
111
|
+
description: 'Return full headers + bodies for a specific captured request.',
|
|
112
|
+
inputSchema: {
|
|
113
|
+
type: 'object',
|
|
114
|
+
properties: {
|
|
115
|
+
captureId: { type: 'string' },
|
|
116
|
+
requestId: { type: 'string' },
|
|
117
|
+
},
|
|
118
|
+
required: ['captureId', 'requestId'],
|
|
119
|
+
},
|
|
120
|
+
}, async ({ captureId, requestId }) => {
|
|
121
|
+
const session = captures.sessions.get(captureId);
|
|
122
|
+
if (!session) {
|
|
123
|
+
throw new Error(`no capture ${captureId}`);
|
|
124
|
+
}
|
|
125
|
+
const r = session.requests.get(requestId);
|
|
126
|
+
if (!r) {
|
|
127
|
+
throw new Error(`no request ${requestId} in capture ${captureId}`);
|
|
128
|
+
}
|
|
129
|
+
const body = session.bodies.get(requestId);
|
|
130
|
+
if (!body.available) {
|
|
131
|
+
return { ...r, bodyAvailable: false, reason: body.reason };
|
|
132
|
+
}
|
|
133
|
+
return { ...r, responseBody: body.body, bodyAvailable: true };
|
|
134
|
+
});
|
|
135
|
+
return [startTool, stopTool, listTool, getTool];
|
|
136
|
+
}
|
|
137
|
+
function globMatch(pattern, str) {
|
|
138
|
+
const re = new RegExp('^' +
|
|
139
|
+
pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*') +
|
|
140
|
+
'$');
|
|
141
|
+
return re.test(str);
|
|
142
|
+
}
|