@reclaimprotocol/agent 0.1.28 → 0.1.31

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.
Files changed (89) hide show
  1. package/README.md +28 -9
  2. package/lib/consts.d.ts +5 -5
  3. package/lib/consts.js +7 -7
  4. package/lib/live/tunnel-watchdog.d.ts +1 -0
  5. package/lib/live/tunnel-watchdog.js +66 -0
  6. package/lib/live/tunnel.d.ts +30 -7
  7. package/lib/live/tunnel.js +217 -40
  8. package/lib/mcp/api/operations.gen.d.ts +12 -0
  9. package/lib/mcp/api/operations.gen.js +537 -59
  10. package/lib/mcp/api/tools.js +27 -5
  11. package/lib/mcp/instructions.js +25 -32
  12. package/lib/mcp/remap_errors.js +6 -0
  13. package/lib/mcp/server.js +2 -8
  14. package/lib/mcp/start.js +3 -5
  15. package/lib/mcp/success-response.d.ts +5 -0
  16. package/lib/mcp/success-response.js +21 -0
  17. package/lib/mcp/tools/agent/attach.js +9 -66
  18. package/lib/mcp/tools/agent/build.d.ts +0 -3
  19. package/lib/mcp/tools/agent/build.js +3 -4
  20. package/lib/mcp/tools/agent/how-it-works.d.ts +5 -15
  21. package/lib/mcp/tools/agent/how-it-works.js +9 -22
  22. package/lib/mcp/tools/agent/live-view.d.ts +1 -15
  23. package/lib/mcp/tools/agent/live-view.js +71 -96
  24. package/lib/mcp/tools/agent/propose.js +7 -23
  25. package/lib/mcp/tools/agent/publish.js +80 -18
  26. package/lib/mcp/tools/agent/session.d.ts +4 -7
  27. package/lib/mcp/tools/agent/session.js +8 -23
  28. package/lib/mcp/tools/verification/result.d.ts +2 -1
  29. package/lib/mcp/tools/verification/result.js +29 -28
  30. package/lib/old/client.d.ts +2 -2
  31. package/lib/old/flag.d.ts +2 -5
  32. package/lib/old/flag.js +2 -5
  33. package/lib/old/instructions.d.ts +1 -1
  34. package/lib/old/instructions.js +1 -1
  35. package/lib/old/to-register.d.ts +4 -3
  36. package/lib/old/tools/publish.js +11 -14
  37. package/lib/proof/attestor-auth.d.ts +9 -1
  38. package/lib/proof/attestor-auth.js +15 -2
  39. package/lib/proof/attestor.d.ts +2 -2
  40. package/lib/proof/attestor.js +20 -2
  41. package/lib/proof/pod-prover.js +1 -1
  42. package/lib/proof/proof-core.d.ts +4 -0
  43. package/lib/proof/proof-core.js +20 -0
  44. package/lib/provider/injection-doc.d.ts +3 -2
  45. package/lib/provider/injection-doc.js +12 -10
  46. package/lib/provider/matcher.js +19 -15
  47. package/lib/provider/to-version.d.ts +3 -0
  48. package/lib/provider/to-version.js +2 -1
  49. package/package.json +3 -3
  50. package/skill/SKILL.builder.md +32 -85
  51. package/skill/SKILL.common.md +38 -22
  52. package/skill/SKILL.old.md +17 -3
  53. package/skill/builder-mode.md +37 -4
  54. package/skill/community-contributions.md +12 -3
  55. package/skill/hash-validation.md +9 -9
  56. package/skill/interception.md +39 -46
  57. package/skill/old-mode.md +13 -0
  58. package/skill/user-script.md +24 -8
  59. package/skill/verification-client-integration.md +130 -0
  60. package/src/consts.ts +9 -7
  61. package/src/mcp/api/operations.gen.ts +539 -59
  62. package/src/mcp/api/tools.ts +38 -7
  63. package/src/mcp/instructions.ts +35 -32
  64. package/src/mcp/remap_errors.ts +6 -0
  65. package/src/mcp/server.ts +2 -8
  66. package/src/mcp/start.ts +3 -5
  67. package/src/mcp/success-response.ts +26 -0
  68. package/src/mcp/tools/agent/attach.ts +9 -71
  69. package/src/mcp/tools/agent/build.ts +4 -7
  70. package/src/mcp/tools/agent/how-it-works.ts +9 -22
  71. package/src/mcp/tools/agent/live-view.ts +76 -116
  72. package/src/mcp/tools/agent/propose.ts +7 -23
  73. package/src/mcp/tools/agent/publish.ts +89 -18
  74. package/src/mcp/tools/agent/session.ts +6 -23
  75. package/src/mcp/tools/verification/result.ts +30 -29
  76. package/src/old/client.ts +2 -2
  77. package/src/old/flag.ts +2 -5
  78. package/src/old/instructions.ts +1 -1
  79. package/src/old/to-register.ts +4 -3
  80. package/src/old/tools/publish.ts +16 -14
  81. package/src/proof/attestor-auth.ts +18 -1
  82. package/src/proof/attestor.ts +24 -3
  83. package/src/proof/pod-prover.ts +1 -1
  84. package/src/proof/proof-core.ts +28 -0
  85. package/src/provider/injection-doc.ts +12 -10
  86. package/src/provider/matcher.ts +25 -17
  87. package/src/provider/to-version.ts +7 -1
  88. package/src/live/cloudflared.ts +0 -348
  89. package/src/live/tunnel.ts +0 -254
@@ -5,6 +5,7 @@ import type {
5
5
  IOperationId
6
6
  } from '@reclaimprotocol/client/openapi'
7
7
  import { remapErrorAsResponse } from '../remap_errors.ts'
8
+ import { successResponse } from '../success-response.ts'
8
9
  import { MCP_TOOLS } from './operations.gen.ts'
9
10
 
10
11
  /**
@@ -23,6 +24,12 @@ function toSnakeCase(name: string): string {
23
24
 
24
25
  export function registerTools(server: McpServer, client: ReclaimClient) {
25
26
  for(const [opId, shape] of Object.entries(MCP_TOOLS)) {
27
+ if(!isOperationId(opId)) {
28
+ throw new Error(
29
+ `Generated MCP operation is not in the API client: ${opId}`,
30
+ )
31
+ }
32
+
26
33
  // snake_case to match the hand-written tools. A name that collides with
27
34
  // a hand-written tool throws here at startup — intentional: hand-written
28
35
  // and generated names must stay disjoint (see start.ts), so a clash is a
@@ -32,15 +39,15 @@ export function registerTools(server: McpServer, client: ReclaimClient) {
32
39
  shape,
33
40
  async(args: unknown) => {
34
41
  try {
42
+ if(!isOperationArgs(opId, args)) {
43
+ throw new Error(`Invalid generated MCP arguments for ${opId}`)
44
+ }
45
+
35
46
  const result = await client.call(
36
- opId as IOperationId,
37
- args as IOperationArgs<IOperationId>,
47
+ opId,
48
+ args,
38
49
  )
39
- return {
40
- content: [{
41
- type: 'text' as const, text: JSON.stringify(result.data, null, 2)
42
- }],
43
- }
50
+ return successResponse(result.data)
44
51
  } catch(err) {
45
52
  return remapErrorAsResponse(err)
46
53
  }
@@ -48,3 +55,27 @@ export function registerTools(server: McpServer, client: ReclaimClient) {
48
55
  )
49
56
  }
50
57
  }
58
+
59
+ /**
60
+ * Always true in practice — `value` comes from `Object.entries(MCP_TOOLS)`
61
+ * itself — but the check still earns its keep: it narrows `opId` to
62
+ * `IOperationId` for `client.call` below without an unchecked cast, and it
63
+ * turns "the generator and the API client drifted apart" into a startup
64
+ * error instead of a silent `as`.
65
+ */
66
+ function isOperationId(value: string): value is IOperationId {
67
+ return value in MCP_TOOLS
68
+ }
69
+
70
+ /**
71
+ * The MCP SDK parses `args` through the generated Zod schema before invoking
72
+ * this callback. Keep a runtime object check at this TypeScript boundary so
73
+ * generated operation arguments retain their operation-specific type without
74
+ * an unchecked cast.
75
+ */
76
+ function isOperationArgs<T extends IOperationId>(
77
+ _operationId: T,
78
+ value: unknown,
79
+ ): value is IOperationArgs<T> {
80
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
81
+ }
@@ -1,29 +1,10 @@
1
1
  /**
2
- * The authoring documentation, assembled from `skill/*.md` at module load.
2
+ * Loads concise, always-on mode instructions and topic guides served on
3
+ * demand by `how_it_works`. Keeping detailed guidance out of tool schemas
4
+ * avoids adding it to every request's context.
3
5
  *
4
- * Two things are served from here:
5
- * - the always-on MCP server `instructions` (one file per backend mode) —
6
- * deliberately small, since every client pays for them on every turn;
7
- * - the deep authoring guide, split into TOPICS and served on demand by the
8
- * `how_it_works` tool. Nothing here is inlined into a tool schema: a doc
9
- * embedded in an input-schema description is paid for on every request
10
- * whether or not it's ever relevant.
11
- *
12
- * The prose lives in markdown (published via package.json's `files`) so it
13
- * can be edited without touching code. Paths resolve identically from
14
- * `src/mcp` (dev) and `lib/mcp` (published) — both are two levels below the
15
- * package root.
16
- *
17
- * Assembly has three steps:
18
- * 1. `<!-- common: name -->` splices a shared block out of `SKILL.common.md`
19
- * (delimited there by `<!-- section: name -->`), so the two mode guides
20
- * don't drift on the ~60% of prose that is identical.
21
- * 2. `%CTX%` expands to the mode's consumer-param namespace — `context.` on
22
- * the builder, `context_` on old-devtools. That is the only textual
23
- * difference across most shared blocks, so tokenizing it keeps them
24
- * shared.
25
- * 3. `<!-- topic: name -->` splits the result into the units `how_it_works`
26
- * serves. Backend-independent topics come from their own files instead.
6
+ * `<!-- common: name -->` includes shared sections, `%CTX%` selects the
7
+ * mode-specific context prefix, and `<!-- topic: name -->` creates a topic.
27
8
  */
28
9
  import assert from 'node:assert'
29
10
  import { readFileSync } from 'node:fs'
@@ -32,7 +13,7 @@ import { fileURLToPath } from 'node:url'
32
13
  function readSkill(file: string): string {
33
14
  const url = new URL(`../../skill/${file}`, import.meta.url)
34
15
  return readFileSync(fileURLToPath(url), 'utf8')
35
- // Strip any leading frontmatter defensively.
16
+ // These files are internal topic fragments, but tolerate skill frontmatter.
36
17
  .replace(/^---\n[\s\S]*?\n---\n/, '')
37
18
  .trim()
38
19
  }
@@ -48,7 +29,7 @@ function splitBlocks(text: string, marker: string): Map<string, string> {
48
29
  'g',
49
30
  )
50
31
  for(const match of text.matchAll(re)) {
51
- // Trim blank LINES only, not inline indentation — some blocks are
32
+ // Trim blank lines only, not inline indentation. Some blocks are
52
33
  // list-continuation text that's intentionally indented on its first line.
53
34
  blocks.set(match[1], match[2].replace(/^\n+/, '').replace(/\n+$/, ''))
54
35
  }
@@ -65,11 +46,19 @@ const SHARED_TOPIC_FILES: Record<string, string> = {
65
46
  'hash-validation': 'hash-validation.md',
66
47
  }
67
48
 
49
+ /** Detailed topics that exist only in Builder mode. */
50
+ const BUILDER_TOPIC_FILES: Record<string, string> = {
51
+ 'contributions': 'community-contributions.md',
52
+ 'integration': 'verification-client-integration.md',
53
+ }
54
+
68
55
  /** One line per topic, shown in the `how_it_works` index so a caller can pick
69
56
  * the right one without fetching several. Keyed across both modes; only the
70
57
  * topics a mode actually has are listed back to it. */
71
58
  const TOPIC_SUMMARIES: Record<string, string> = {
72
59
  'overview': 'Terminology, your stance, and the whole loop at a glance.',
60
+ 'contributions': 'Contributing a public provider as a volunteer: the '
61
+ + 'candidate-and-review workflow and the extra rules it adds.',
73
62
  'auth': 'Signing in to the backend, and which token authorizes what.',
74
63
  'devtools-migration': 'Importing a public legacy provider with the same '
75
64
  + 'UUID.',
@@ -88,15 +77,16 @@ const TOPIC_SUMMARIES: Record<string, string> = {
88
77
  + 'and going live.',
89
78
  'discover': 'Finding providers, resolving them, and creating a verification '
90
79
  + 'session.',
91
- 'integration': 'Builder Verification Client integration: the `api=2` bridge, '
92
- + 'result verification, and optional TEE binding.',
80
+ 'integration': 'Set up a Builder token, callback, frontend and backend '
81
+ + 'with @reclaimprotocol/client; direct `api=2` API and result '
82
+ + 'verification.',
93
83
  'user-script': 'Authoring jsUserScripts / customInjection to drive '
94
84
  + 'login-gated pages.',
95
85
  'interception': 'injectionType / interceptorType, live interception, the '
96
86
  + 'document-replay flag, and what to do when a site won\'t load or '
97
87
  + 'won\'t let the user sign in.',
98
- 'hash-validation': 'bodySniff, and how verifyProof({ providerId }) gates on '
99
- + 'the declared spec hash.',
88
+ 'hash-validation': 'bodySniff, and how verifyRequestProof({ providerId }) '
89
+ + 'gates on the declared spec hash.',
100
90
  'credentials': 'The eth proof-owner key run_proof signs with.',
101
91
  'never-do': 'The short list of things that always break a provider.',
102
92
  'troubleshooting': 'Expired drafts, browser problems, and where to look '
@@ -112,7 +102,11 @@ export interface Guide {
112
102
  full: string
113
103
  }
114
104
 
115
- function buildGuide(file: string, ctxPrefix: string): Guide {
105
+ function buildGuide(
106
+ file: string,
107
+ ctxPrefix: string,
108
+ modeTopicFiles: Record<string, string> = {},
109
+ ): Guide {
116
110
  const raw = readSkill(file)
117
111
  .replace(/<!-- common: (\S+) -->/g, (_whole, name: string) => {
118
112
  const section = COMMON_SECTIONS.get(name)
@@ -127,6 +121,11 @@ function buildGuide(file: string, ctxPrefix: string): Guide {
127
121
  topics.set(name, readSkill(path))
128
122
  }
129
123
 
124
+ for(const [name, path] of Object.entries(modeTopicFiles)) {
125
+ assert(topics.has(name), `${file}: no topic marker named "${name}"`)
126
+ topics.set(name, readSkill(path))
127
+ }
128
+
130
129
  const index = [...topics.keys()].map((topic) => {
131
130
  const summary = TOPIC_SUMMARIES[topic]
132
131
  assert(summary, `${file}: no TOPIC_SUMMARIES entry for "${topic}"`)
@@ -136,7 +135,11 @@ function buildGuide(file: string, ctxPrefix: string): Guide {
136
135
  return { topics, index, full: [...topics.values()].join('\n\n') }
137
136
  }
138
137
 
139
- export const GUIDE_BUILDER = buildGuide('SKILL.builder.md', 'context.')
138
+ export const GUIDE_BUILDER = buildGuide(
139
+ 'SKILL.builder.md',
140
+ 'context.',
141
+ BUILDER_TOPIC_FILES,
142
+ )
140
143
  export const GUIDE_OLD = buildGuide('SKILL.old.md', 'context_')
141
144
 
142
145
  export function guideFor(oldMode: boolean): Guide {
@@ -10,6 +10,12 @@ export const remapErrorAsResponse = (
10
10
  ): CallToolResult => {
11
11
  const text = errorAsToolText(err)
12
12
 
13
+ // Two unrelated things can carry structured data here: the client's own
14
+ // ProblemError (an RFC 9457 response from a Builder API call), and a
15
+ // hand-thrown Boom error whose second argument is arbitrary tool context,
16
+ // for example `badRequest('invalid provider', { field: 'providerId' })`.
17
+ // `isProblemDetails` only rejects a wrongly typed KNOWN field, so it lets
18
+ // that arbitrary shape through too — this isn't a strict RFC 9457 check.
13
19
  const data = isProblemError(err)
14
20
  ? err.data
15
21
  : isBoom(err) && err.output?.statusCode
package/src/mcp/server.ts CHANGED
@@ -2,6 +2,7 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
2
  import { type RegisteredTool } from '../tools/registry.ts'
3
3
  import { toZod } from './api/to-zod.ts'
4
4
  import { remapErrorAsResponse } from './remap_errors.ts'
5
+ import { successResponse } from './success-response.ts'
5
6
 
6
7
  // The tool primitive lives in `../tools/registry.ts` (SDK-free, so the cloud
7
8
  // loop can use it too). Re-exported here so the MCP-side tool files can keep
@@ -27,14 +28,7 @@ export function registerToolList(server: McpServer, tools: RegisteredTool[]) {
27
28
  },
28
29
  async(args) => {
29
30
  try {
30
- return {
31
- content: [
32
- {
33
- type: 'text' as const,
34
- text: JSON.stringify(await tool.handler(args), null, 2),
35
- },
36
- ],
37
- }
31
+ return successResponse(await tool.handler(args))
38
32
  } catch(err) {
39
33
  return remapErrorAsResponse(err)
40
34
  }
package/src/mcp/start.ts CHANGED
@@ -36,7 +36,7 @@ import { BUILDER_MODE_INSTRUCTIONS } from './instructions.ts'
36
36
  import { registerToolList } from './server.ts'
37
37
 
38
38
  export async function startMcpServer() {
39
- // Mode precedence: explicit USE_OLD_DEVTOOLS env → persisted config → old.
39
+ // Mode precedence: explicit env → persisted config → builder.
40
40
  // The same store instance backs the set_devtools_mode toggle below.
41
41
  const config = new ConfigStore()
42
42
  const oldMode = resolveOldMode(config.read()?.mode)
@@ -69,8 +69,7 @@ export async function startMcpServer() {
69
69
  registerToolList(server, buildModeTools(config, oldMode))
70
70
 
71
71
  if(oldMode) {
72
- // OLD-devtools mode (the default): the legacy backend is still in
73
- // production while builder is built. All auto-generated builder tools
72
+ // Explicit OLD-devtools mode. All auto-generated builder tools
74
73
  // are disabled; a small ReclaimOldClient serves authenticate / publish /
75
74
  // get_me_providers instead.
76
75
  const oldClient = new ReclaimOldClient({
@@ -128,8 +127,7 @@ export async function startMcpServer() {
128
127
  // Anything the session holds has to be released when the session ends, and
129
128
  // the session can end without a single tool call: the developer closes their
130
129
  // agent, the client kills the server, stdin drops. Nothing ran here before,
131
- // which left a container up and — worse — a public tunnel still pointing at a
132
- // live browser after the session that made it was gone.
130
+ // which left a container up after the session that made it was gone.
133
131
  if(agentToolsEnabled) {
134
132
  installShutdownHooks(() => agentState)
135
133
  }
@@ -0,0 +1,26 @@
1
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
2
+
3
+ /** Return successful tool data in both MCP representations. Older clients read
4
+ * the JSON text; newer clients can consume `structuredContent` without asking
5
+ * the model to parse or reconstruct identifiers, URLs, and state. */
6
+ export function successResponse(value: unknown): CallToolResult {
7
+ return {
8
+ structuredContent: structured(value),
9
+ content: [{
10
+ type: 'text',
11
+ text: JSON.stringify(value, null, 2) ?? 'null',
12
+ }],
13
+ }
14
+ }
15
+
16
+ function structured(value: unknown): Record<string, unknown> {
17
+ if(isRecord(value)) {
18
+ return value
19
+ }
20
+
21
+ return { result: value ?? null }
22
+ }
23
+
24
+ function isRecord(value: unknown): value is Record<string, unknown> {
25
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
26
+ }
@@ -85,22 +85,8 @@ interface AttachArgs {
85
85
  viewerPort?: number
86
86
  }
87
87
 
88
- /**
89
- * The browser sources, written once and used twice: rendered into this tool's
90
- * DESCRIPTION (so an agent can put the choice to the developer straight from
91
- * the tool list, without a discovery round-trip) and returned verbatim as
92
- * structured `options` when the tool is called without a `mode` anyway.
93
- *
94
- * Being the single source for both is the point — an option list that a model
95
- * reads in the description but that disagrees with what the tool returns is
96
- * worse than either alone.
97
- *
98
- * Ordered and labelled BY USE CASE, because that is the question a developer
99
- * can actually answer. "Which CDP transport?" is not; "does anyone else need to
100
- * get into this browser?" is, and it decides two of the four. `useCase` is the
101
- * grouping key, so the rendered list reads as two pairs rather than four
102
- * unrelated transports.
103
- */
88
+ /** Browser sources returned when the caller omits `mode`. Keep selection detail
89
+ * here instead of repeating it in the always-loaded tool description. */
104
90
  const BROWSER_OPTIONS = [
105
91
  {
106
92
  mode: 'dedicated',
@@ -181,33 +167,6 @@ const BROWSER_OPTIONS = [
181
167
  },
182
168
  ] as const
183
169
 
184
- /**
185
- * The options as prose, grouped by use case.
186
- *
187
- * The grouping is the useful part: it turns "pick one of five transports" into
188
- * "answer one question, then pick one of two".
189
- */
190
- function renderBrowserOptions(): string {
191
- const seen = new Set<string>()
192
- const lines: string[] = []
193
- for(const option of BROWSER_OPTIONS) {
194
- if(!seen.has(option.useCase)) {
195
- seen.add(option.useCase)
196
- lines.push(`— ${option.useCase.toUpperCase()} —`)
197
- }
198
-
199
- const needs = option.requires.length
200
- ? ` Needs: ${option.requires.join('; ')}.`
201
- : ''
202
- lines.push(
203
- `(${option.mode}) ${option.label}. ${option.detail}${needs}`
204
- + ` Live view: ${option.liveView}`,
205
- )
206
- }
207
-
208
- return lines.join(' ')
209
- }
210
-
211
170
  interface AttachDependencies {
212
171
  connectLocal: typeof connectCdp
213
172
  connectRemote: typeof connectCdpUrl
@@ -238,34 +197,13 @@ export function attachTool(
238
197
  {
239
198
  name: 'attach_browser',
240
199
  description:
241
- 'Connect the provider-authoring tools to a browser. Never choose '
242
- + 'for them, never assume a previous session\'s choice still '
243
- + 'applies, and call this ONCE with the `mode` they pick. '
244
- + 'ASK THIS FIRST, before listing anything: "Will you be doing this '
245
- + 'yourself, or does someone else need to sign in — for example the '
246
- + 'person whose account it is?" That answer decides half the list, '
247
- + 'and a developer can answer it without knowing what CDP is. '
248
- + 'THEMSELVES → offer `dedicated` or `attach`. SOMEONE ELSE, or the '
249
- + 'site only works on a phone → offer `container` or `builder`, '
250
- + 'which are the only two that produce a link another person can '
251
- + 'open. Say which of those two costs money: `container` is free '
252
- + 'but needs Docker, `builder` needs no install but is billed '
253
- + 'against the org\'s quota. '
254
- + 'Then relay the matching options with their live-view lines. You '
255
- + 'do not need a discovery call — the full list is here, and calling '
256
- + 'with no `mode` returns it as structured `options` if you would '
257
- + 'rather read it back verbatim. '
258
- + 'OPTIONS — ' + renderBrowserOptions() + ' '
259
- + 'A `container` browser returns a local `viewerUrl`; relay it. When '
260
- + 'someone remote needs in, call share_browser_view and relay the '
261
- + 'FULL public url verbatim, on its own line. `builder` returns a '
262
- + 'hosted `liveViewUrl` — relay that the same way. `dedicated`, '
263
- + '`attach` and `custom` cannot be shared at all: if they ask for a '
264
- + 'link after picking one, say so and offer to re-attach with '
265
- + '`container`. '
266
- + 'Builder and custom CDP credentials are held in memory and are '
267
- + 'never returned. '
268
- + 'More: how_it_works({ topic: "browser" }).',
200
+ 'Connects provider-authoring tools to a browser. Call without '
201
+ + '`mode` to receive the current options, then ask the developer to '
202
+ + 'choose; `builder` consumes quota and only `container` and `builder` '
203
+ + 'support a shareable live view. Call again once with the chosen '
204
+ + 'mode. Relay returned live-view URLs verbatim. For selection, '
205
+ + 'geolocation, and lifecycle guidance, call '
206
+ + 'how_it_works({ topic: "browser" }).',
269
207
  inputSchema: {
270
208
  type: 'object',
271
209
  properties: {
@@ -19,7 +19,7 @@ import { type AttachState, attachTool } from './attach.ts'
19
19
  import { type CaptureStore, captureTools, resetCaptures } from './capture.ts'
20
20
  import { listTabsTool } from './eval.ts'
21
21
  import { howItWorksTool } from './how-it-works.ts'
22
- import { type LiveShareRef, liveViewTools } from './live-view.ts'
22
+ import { liveViewTools } from './live-view.ts'
23
23
  import { podProofTools } from './pod-proof.ts'
24
24
  import { createVersionFromCaptureTool } from './publish.ts'
25
25
  import { resetAll, sessionTools } from './session.ts'
@@ -28,8 +28,6 @@ import { sharedAuthoringTools } from './shared.ts'
28
28
  export interface AgentState {
29
29
  attach: { current?: AttachState, config: ConfigStore }
30
30
  captures: CaptureStore
31
- /** The one public live view, when `share_browser_view` has opened it. */
32
- liveShare: LiveShareRef
33
31
  }
34
32
 
35
33
  /** Exported so a mode (for example, old-devtools) that supplies its own
@@ -41,7 +39,6 @@ export function buildAgentState(): AgentState {
41
39
  return {
42
40
  attach: { config: new ConfigStore() },
43
41
  captures: { sessions: new Map() },
44
- liveShare: {},
45
42
  }
46
43
  }
47
44
 
@@ -71,12 +68,12 @@ export function buildAgentTools(
71
68
  tools.push(t)
72
69
  }
73
70
 
74
- for(const t of sessionTools(state.attach, state.captures, state.liveShare)) {
71
+ for(const t of sessionTools(state.attach, state.captures)) {
75
72
  tools.push(t)
76
73
  }
77
74
 
78
75
  // Local-CDP only: a builder-mode browser already has a hosted live view.
79
- for(const t of liveViewTools(state.attach, state.liveShare)) {
76
+ for(const t of liveViewTools(state.attach)) {
80
77
  tools.push(t)
81
78
  }
82
79
 
@@ -104,5 +101,5 @@ export function buildAgentTools(
104
101
  * safe when nothing was ever attached.
105
102
  */
106
103
  export async function disposeAgentState(state: AgentState): Promise<void> {
107
- await resetAll(state.attach, state.captures, state.liveShare)
104
+ await resetAll(state.attach, state.captures)
108
105
  }
@@ -2,21 +2,11 @@ import { guideFor } from '../../instructions.ts'
2
2
  import { defineTool, type RegisteredTool } from '../../server.ts'
3
3
 
4
4
  /**
5
- * Serves the provider-authoring guide on demand, ONE TOPIC AT A TIME. The
6
- * server's always-on `instructions` are a terse tool-flow reference; this tool
7
- * is the deep version, so any MCP client — not just Claude Code, which alone
8
- * auto-loads skills — can pull it.
9
- *
10
- * Topic-scoped rather than all-at-once because the full guide is ~7k tokens:
11
- * paying that to answer "how do I mark a header secret?" crowds out the actual
12
- * task, which is exactly what makes an agent slow and prone to inventing
13
- * details. Every response carries the topic index, so a caller that guessed
14
- * wrong (or called with no topic at all) always learns what else is available
15
- * without a second round-trip.
16
- *
17
- * `oldMode` selects the backend-specific guide, so the agent is never told to
18
- * call auth/publish tools that aren't registered in its mode. Stateless:
19
- * callable before `attach_browser`.
5
+ * Serve one mode-specific guide topic without loading the full guide (~7k
6
+ * tokens) — paying that cost to answer one question would crowd out the
7
+ * actual task. Every response includes the topic index, so a caller that
8
+ * guessed the topic name wrong still learns what else exists in the same
9
+ * round-trip.
20
10
  */
21
11
  export function howItWorksTool(oldMode: boolean): RegisteredTool {
22
12
  const guide = guideFor(oldMode)
@@ -26,13 +16,10 @@ export function howItWorksTool(oldMode: boolean): RegisteredTool {
26
16
  {
27
17
  name: 'how_it_works',
28
18
  description:
29
- 'Reclaim provider-authoring guide for THIS server, by topic. Call it '
30
- + 'with no arguments for the overview plus the topic index, then again '
31
- + 'for whichever topic you need. Topics: '
32
- + names.join(', ')
33
- + '. Use `all` for the entire guide at once (large — prefer a topic). '
34
- + 'Read the relevant topic BEFORE acting unless you are already fluent '
35
- + 'in that part of the flow.',
19
+ 'Returns topic-specific provider-authoring guidance for the active '
20
+ + 'backend. Call without a topic for the overview and index. Before an '
21
+ + 'unfamiliar or sensitive step, request that topic. Prefer one topic; '
22
+ + '`all` returns the much larger complete guide.',
36
23
  inputSchema: {
37
24
  type: 'object',
38
25
  properties: {