mcp-scraper 0.3.14 → 0.3.16
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/dist/bin/api-server.cjs +2261 -511
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/browser-agent-stdio-server.cjs +6 -6
- package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
- package/dist/bin/browser-agent-stdio-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs +663 -48
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.js +6 -5
- package/dist/bin/mcp-scraper-combined-stdio-server.js.map +1 -1
- package/dist/bin/mcp-scraper-install.cjs +4 -4
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +655 -40
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/{chunk-DBQDG7EH.js → chunk-5H22TOXQ.js} +28 -1
- package/dist/chunk-5H22TOXQ.js.map +1 -0
- package/dist/{chunk-HM6FHV5U.js → chunk-7XARJHS6.js} +662 -41
- package/dist/chunk-7XARJHS6.js.map +1 -0
- package/dist/{chunk-KPXMPAJ3.js → chunk-ACIUCZ27.js} +4 -4
- package/dist/chunk-ACIUCZ27.js.map +1 -0
- package/dist/{chunk-AX7UBYLG.js → chunk-ISZWGIWL.js} +7 -7
- package/dist/chunk-ISZWGIWL.js.map +1 -0
- package/dist/chunk-SOMBZK3V.js +7 -0
- package/dist/chunk-SOMBZK3V.js.map +1 -0
- package/dist/{chunk-AZ5PKU4F.js → chunk-ZAUMSBV3.js} +2 -2
- package/dist/{db-BE4JVB3V.js → db-P76GVIFN.js} +2 -2
- package/dist/{server-ILIVSPNY.js → server-LPVBSE2E.js} +1133 -93
- package/dist/server-LPVBSE2E.js.map +1 -0
- package/dist/{worker-FG7ZWEGA.js → worker-2WVKKCC7.js} +3 -3
- package/docs/final-tooling-spec.md +206 -0
- package/docs/mcp-tool-design-guide.md +225 -0
- package/docs/mcp-tool-manifest.generated.json +57 -22
- package/docs/seo-crawl-report-spec.md +287 -0
- package/docs/tool-catalog-spec.md +388 -0
- package/package.json +2 -1
- package/dist/chunk-3QHZPR4U.js +0 -7
- package/dist/chunk-3QHZPR4U.js.map +0 -1
- package/dist/chunk-AX7UBYLG.js.map +0 -1
- package/dist/chunk-DBQDG7EH.js.map +0 -1
- package/dist/chunk-HM6FHV5U.js.map +0 -1
- package/dist/chunk-KPXMPAJ3.js.map +0 -1
- package/dist/server-ILIVSPNY.js.map +0 -1
- /package/dist/{chunk-AZ5PKU4F.js.map → chunk-ZAUMSBV3.js.map} +0 -0
- /package/dist/{db-BE4JVB3V.js.map → db-P76GVIFN.js.map} +0 -0
- /package/dist/{worker-FG7ZWEGA.js.map → worker-2WVKKCC7.js.map} +0 -0
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
createHarvestAttemptRecorder,
|
|
5
5
|
harvestProblemResponse,
|
|
6
6
|
serializeHarvestProblem
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-ZAUMSBV3.js";
|
|
8
8
|
import {
|
|
9
9
|
claimPendingJob,
|
|
10
10
|
completeJob,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
debitMc,
|
|
13
13
|
failJob,
|
|
14
14
|
listHarvestAttempts
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-5H22TOXQ.js";
|
|
16
16
|
import {
|
|
17
17
|
harvest
|
|
18
18
|
} from "./chunk-4OPKIDON.js";
|
|
@@ -125,4 +125,4 @@ export {
|
|
|
125
125
|
startWorker,
|
|
126
126
|
tickOnce
|
|
127
127
|
};
|
|
128
|
-
//# sourceMappingURL=worker-
|
|
128
|
+
//# sourceMappingURL=worker-2WVKKCC7.js.map
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Final Tooling Spec — rename, server instructions, description optimization, catalog
|
|
2
|
+
|
|
3
|
+
Execution blueprint consolidating the decisions reached: (A) server namespace rename
|
|
4
|
+
`mcp-scraper → scraper`, (B) `*_workflow` rename of workflow-class tools, (C) a front-loaded
|
|
5
|
+
**server-instructions block** carrying the cross-tool routing map, (D) a per-tool description
|
|
6
|
+
optimization pass that moves routing *out* of descriptions, and (E) catalog integration.
|
|
7
|
+
|
|
8
|
+
Grounded in: the SDK's `instructions?: string` option
|
|
9
|
+
(`node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.d.ts:15`), verified tool registrations
|
|
10
|
+
in `src/mcp/paa-mcp-server.ts` and `src/mcp/browser-agent-mcp-server.ts`, and the executor map in
|
|
11
|
+
`src/mcp/http-mcp-tool-executor.ts`.
|
|
12
|
+
|
|
13
|
+
Governing principle (from `docs/mcp-tool-design-guide.md`): **the model routes on names + the
|
|
14
|
+
server-instructions block before it fetches any schema.** Therefore the cross-tool routing map must
|
|
15
|
+
live in the instructions block, never solely in per-tool descriptions that may never be fetched.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Part A — Server namespace rename: `mcp-scraper` → `scraper`
|
|
20
|
+
|
|
21
|
+
The client prepends `mcp__`, so today every tool is `mcp__mcp-scraper__…` (stutter). Renaming the
|
|
22
|
+
**server name** to `scraper` yields `mcp__scraper__…`.
|
|
23
|
+
|
|
24
|
+
### A.1 Exact edits — CORRECTED after verification
|
|
25
|
+
|
|
26
|
+
> **The namespace `mcp__<key>__` comes from the CLIENT REGISTRATION KEY, not the McpServer `name`.**
|
|
27
|
+
> Verified: `src/cli/agent-config.ts` registers the server with the key `mcp-scraper` via
|
|
28
|
+
> `claude mcp add mcp-scraper …` and the Desktop config block `'mcp-scraper': {…}`. THAT key is what
|
|
29
|
+
> Claude turns into `mcp__mcp-scraper__*`. Changing only `new McpServer({name})` does NOT change the
|
|
30
|
+
> prefix. The full set of edits to actually rename the namespace:
|
|
31
|
+
|
|
32
|
+
- `src/cli/agent-config.ts:54` — `['mcp', 'remove', 'mcp-scraper', …]` → `'scraper'`.
|
|
33
|
+
- `src/cli/agent-config.ts:58` — `['mcp', 'add', 'mcp-scraper', …]` → `'scraper'`.
|
|
34
|
+
- `src/cli/agent-config.ts:75` — Desktop config key `'mcp-scraper': {…}` → `'scraper': {…}`.
|
|
35
|
+
- `scripts/build-mcpb.mjs:61` — `.mcpb` manifest server `name: 'mcp-scraper'` → `'scraper'` (Desktop
|
|
36
|
+
extension registration key). Line 50 (`mcp-scraper-desktop-extension`) is the extension *package* id; leave it.
|
|
37
|
+
- `src/mcp/paa-mcp-server.ts:152` and `bin/mcp-scraper-combined-stdio-server.ts:61` — the McpServer
|
|
38
|
+
`name` is cosmetic self-description; update to `'scraper'` for consistency but it is NOT what drives the prefix.
|
|
39
|
+
- **Tests:** `tests/unit/cli-agent-config.test.ts:32` asserts `'mcp-scraper'` — update.
|
|
40
|
+
- **Migration is harder than a code change:** existing users already have the server registered under
|
|
41
|
+
`mcp-scraper` in *their* client config. Renaming the key here only affects NEW installs; existing
|
|
42
|
+
installs keep `mcp__mcp-scraper__*` until they `claude mcp remove mcp-scraper && claude mcp add scraper`
|
|
43
|
+
(or reinstall the `.mcpb`). Document this explicitly; consider shipping a migration note/command.
|
|
44
|
+
|
|
45
|
+
### A.2 Leave alone (URL / artifact stability)
|
|
46
|
+
- Bundle filenames/paths in `build-mcpb.mjs` (`mcp-scraper.mcpb`, `mcp-scraper-${version}.mcpb`,
|
|
47
|
+
`build/mcpb/mcp-scraper`, `dist/bin/mcp-scraper-combined-stdio-server.js`) — **keep**. These are
|
|
48
|
+
download URLs and build paths; changing them breaks existing download links for no namespace benefit.
|
|
49
|
+
- Repo name, GitHub URLs — keep.
|
|
50
|
+
|
|
51
|
+
### A.3 Breaking-change handling (REQUIRED — do not skip)
|
|
52
|
+
Renaming the server changes **every tool identifier** clients see → invalidates:
|
|
53
|
+
- User permission allowlists referencing `mcp__mcp-scraper__*`.
|
|
54
|
+
- Any saved agent configs / scripted tool calls by full name.
|
|
55
|
+
|
|
56
|
+
Mitigations:
|
|
57
|
+
- **Do it in a single release, called out in the changelog** as a breaking namespace change with a
|
|
58
|
+
one-line migration ("re-allow tools; the namespace is now `mcp__scraper__…`").
|
|
59
|
+
- Bump a **minor/major** version, not a patch.
|
|
60
|
+
- Ship in the same release as Parts B–D so users re-onboard once, not repeatedly.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Part B — Workflow-class tool renames (`*_workflow` suffix)
|
|
65
|
+
|
|
66
|
+
Rule: a tool whose mental model is "run a multi-step workflow" carries `_workflow` so the keyword is in
|
|
67
|
+
the name (matches a user saying "run the … workflow / research / directory"). Engine plumbing
|
|
68
|
+
(`workflow_run/step/status/list/suggest/artifact_read`) is **not** renamed — it's infrastructure, not a
|
|
69
|
+
user-named capability.
|
|
70
|
+
|
|
71
|
+
### B.1 Rename map
|
|
72
|
+
| Current name | New name | File(s) |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `rank_tracker_blueprint` | `rank_tracker_workflow` | `paa-mcp-server.ts:346`, schema, manifest, catalog |
|
|
75
|
+
| `browser_capture_fanout` | `query_fanout_workflow` | `browser-agent-mcp-server.ts:791`, `browser-agent-tool-schemas.ts:189`, manifest, catalog |
|
|
76
|
+
| `directory_workflow` | *(unchanged — already correct)* | — |
|
|
77
|
+
| *(new, optional)* | `deep_research_workflow` | thin wrapper → `workflow_run` for the `deep-research` workflow; register in `paa-mcp-server.ts` |
|
|
78
|
+
|
|
79
|
+
### B.2 Per-tool change checklist (apply to each renamed tool)
|
|
80
|
+
1. **Registration** — `server.registerTool('<new_name>', { … })` (the string literal).
|
|
81
|
+
2. **Input schema** — rename the exported `…InputSchema` only if its name encodes the old tool name;
|
|
82
|
+
the schema *shape* is unchanged.
|
|
83
|
+
3. **Manifest** — `src/mcp/mcp-tool-manifest.ts` `entry({ name: '<new_name>', … })`.
|
|
84
|
+
4. **Executor** — `src/mcp/http-mcp-tool-executor.ts` method name only if it mirrors the tool name; the
|
|
85
|
+
**REST route string is unchanged** (rename is MCP-surface only, not the backend).
|
|
86
|
+
5. **Formatter** — `format<Tool>` function name optional; wiring in `registerTool` handler updated.
|
|
87
|
+
6. **Catalog** — update `mcp_name` in `docs/tool-catalog-spec.md` §3.2/§9 and the seed.
|
|
88
|
+
7. **Lint/manifest generators** — re-run `npm run mcp:manifest` and `npm run mcp:lint-tools` to
|
|
89
|
+
regenerate `docs/mcp-tool-manifest.generated.*`.
|
|
90
|
+
|
|
91
|
+
### B.3 Optional backward-compat (recommended for 1–2 releases)
|
|
92
|
+
Register the **old name as a hidden alias** pointing at the same handler, so in-flight callers don't
|
|
93
|
+
hard-break: `server.registerTool('<old_name>', { ...sameConfig, annotations: { deprecated: true } }, handler)`.
|
|
94
|
+
Remove after one release. If you'd rather hard-cut, fold it into the Part A breaking release.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Part C — Server-instructions block (the pre-fetch routing map)
|
|
99
|
+
|
|
100
|
+
This is the highest-leverage change: a short string loaded **at session start** (alongside tool names),
|
|
101
|
+
so the model routes correctly *before* fetching any schema.
|
|
102
|
+
|
|
103
|
+
### C.1 New file — `src/mcp/server-instructions.ts`
|
|
104
|
+
```ts
|
|
105
|
+
export const SERVER_INSTRUCTIONS = `
|
|
106
|
+
This server scrapes and analyzes web, social, search, maps, and site data. Pick a tool by NAME using
|
|
107
|
+
this routing map; load its schema before calling.
|
|
108
|
+
|
|
109
|
+
ROUTING
|
|
110
|
+
- One web page → extract_url. Whole site (crawl + SEO) → extract_site. Just the URL list → map_site_urls.
|
|
111
|
+
- Web search (SERP) → search_serp. Full SERP + People-Also-Ask detail → harvest_paa.
|
|
112
|
+
- Google Maps: find places → maps_search; one place deep-dive + reviews → maps_place_intel.
|
|
113
|
+
- YouTube: find/list videos → youtube_harvest; transcribe one → youtube_transcribe.
|
|
114
|
+
- Facebook: find ads → facebook_ad_search; transcribe an ad → facebook_ad_transcribe; transcribe a
|
|
115
|
+
video → facebook_video_transcribe; page profile → facebook_page_intel.
|
|
116
|
+
- Instagram: profile inventory → instagram_profile_content; one post/reel → instagram_media_download.
|
|
117
|
+
- Workflows (multi-step): local directory → directory_workflow; rank blueprint → rank_tracker_workflow;
|
|
118
|
+
AI-answer citation fan-out (AEO) → query_fanout_workflow; deep research → deep_research_workflow.
|
|
119
|
+
- Anything without a dedicated tool (e.g. Reddit, arbitrary logged-in sites) → the browser_* agent
|
|
120
|
+
(browser_open then navigate/read), and capture profiles via browser_profile_*.
|
|
121
|
+
|
|
122
|
+
NOTES
|
|
123
|
+
- Bulk/full-site crawls: call extract_site with rotateProxies:true for blocked/rate-limited sites; it
|
|
124
|
+
discovers URLs and returns a saved folder/artifact, not the full content inline.
|
|
125
|
+
- Tools that open a browser session or transcribe media cost credits and take longer — prefer the
|
|
126
|
+
cheapest tool that answers the question (plain search/extract before browser agents).
|
|
127
|
+
- Results may save large output to disk/artifact and return a summary + path; read the path for detail.
|
|
128
|
+
`.trim()
|
|
129
|
+
```
|
|
130
|
+
> Keep it **cross-tool only** — no per-tool parameter detail (that's the fetched schema's job). Use
|
|
131
|
+
> neutral capability language; never name the underlying browser/proxy vendor (concealment policy).
|
|
132
|
+
|
|
133
|
+
### C.2 Wiring (both server construction sites)
|
|
134
|
+
- `src/mcp/paa-mcp-server.ts:152`:
|
|
135
|
+
`new McpServer({ name: 'scraper', version: PACKAGE_VERSION }, { instructions: SERVER_INSTRUCTIONS })`
|
|
136
|
+
- `bin/mcp-scraper-combined-stdio-server.ts:61`: same second argument.
|
|
137
|
+
- Import `SERVER_INSTRUCTIONS` from `../src/mcp/server-instructions.js` (and `./server-instructions.js`).
|
|
138
|
+
|
|
139
|
+
### C.3 The insight, enforced
|
|
140
|
+
Per-tool descriptions must **NOT** be the only home for "which tool for which job." That guidance moves
|
|
141
|
+
to `SERVER_INSTRUCTIONS` (always loaded). Per-tool descriptions keep only a **single** negative-space
|
|
142
|
+
line for post-fetch tie-breaking (see Part D). This prevents the failure where the routing hint lives in
|
|
143
|
+
a description the model never fetched.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Part D — Tool-description optimization pass
|
|
148
|
+
|
|
149
|
+
For every tool in `mcp-tool-schemas.ts` / `paa-mcp-server.ts` `registerTool` descriptions, rewrite to
|
|
150
|
+
this shape (apply the `docs/mcp-tool-design-guide.md` checklist):
|
|
151
|
+
|
|
152
|
+
1. **Line 1 = when-to-use**, in the user's words. ("Transcribe one YouTube video to text…")
|
|
153
|
+
2. **Line 2 = side-effects/cost** if any. ("Opens a browser session; costs credits per minute.")
|
|
154
|
+
3. **Params**: each param's `.describe()` states intent + bounds + default, mirroring the Zod schema.
|
|
155
|
+
4. **One** negative-space line max. ("For a whole site use extract_site.") — the *broad* map lives in
|
|
156
|
+
§C, so do not enumerate the full tool family here.
|
|
157
|
+
5. **Delete** redundant cross-tool prose and marketing. Shorter descriptions load faster and rank cleaner.
|
|
158
|
+
|
|
159
|
+
Concrete trims to make now:
|
|
160
|
+
- `extract_url` / `extract_site` / `map_site_urls`: each currently restates the others; cut to one
|
|
161
|
+
negative-space line each (the trio map is in §C).
|
|
162
|
+
- `search_serp` vs `harvest_paa`: both hit `/harvest/sync`; clarify in one line each that
|
|
163
|
+
search_serp = organic results only, harvest_paa = + PAA/AI-Overview/SERP features.
|
|
164
|
+
- Renamed workflow tools: rewrite line 1 to lead with the keyword now in the name.
|
|
165
|
+
|
|
166
|
+
Re-run `npm run mcp:lint-tools` (the craft linter) after edits; fix any flagged tools.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Part E — Catalog integration
|
|
171
|
+
|
|
172
|
+
Reconcile `docs/tool-catalog-spec.md` with the renames, and ship the **static v1** (no DB) first:
|
|
173
|
+
|
|
174
|
+
- Update catalog `mcp_name` values: `rank_tracker_blueprint→rank_tracker_workflow`,
|
|
175
|
+
`browser_capture_fanout→query_fanout_workflow`; add `deep_research_workflow`.
|
|
176
|
+
- Categories stay **platform-first** (YouTube / Facebook / Instagram / Google Search / Google Maps /
|
|
177
|
+
Websites / Browser / Workflows), with **Reddit** served by the Browser category (no dedicated tool) —
|
|
178
|
+
or add a `reddit_*` named wrapper later if AI routing for Reddit is wanted (per the design guide).
|
|
179
|
+
- **v1 implementation = static**: `src/api/catalog-seed.ts` exports `CATALOG`; `GET /catalog` in
|
|
180
|
+
`server.ts` returns it. Defer the 6-table DB schema (§2 of the catalog spec) to v2 (runtime editing).
|
|
181
|
+
- Catalog params must mirror the live Zod schemas (field names/bounds) so the frontend posts valid bodies.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Part F — Build order
|
|
186
|
+
1. **Part C** (server instructions) — highest leverage, lowest risk, non-breaking. Ship first.
|
|
187
|
+
2. **Part D** (description optimization) — non-breaking; pairs with C (move routing out → into C).
|
|
188
|
+
3. **Part B** (workflow renames) — with hidden aliases (B.3) so it's non-breaking.
|
|
189
|
+
4. **Part A** (server rename) — the one breaking change; batch into a single versioned release with a
|
|
190
|
+
changelog migration note. Run after B/C/D so users re-onboard once.
|
|
191
|
+
5. **Part E** static catalog — independent; any time.
|
|
192
|
+
6. Regenerate `docs/mcp-tool-manifest.generated.*`; run `npm run mcp:lint-tools`; full test + live tests;
|
|
193
|
+
then the release flow (3 version files, Vercel prod first, npm publish, rebuild `.mcpb`).
|
|
194
|
+
|
|
195
|
+
## Part G — Verification checklist
|
|
196
|
+
- [ ] `grep -rn "mcp-scraper'" src bin scripts` → only intentional (bundle filenames), server `name` = `scraper`.
|
|
197
|
+
- [ ] No tool identifier appears in two namespaces; `npm run mcp:manifest` regenerated.
|
|
198
|
+
- [ ] `SERVER_INSTRUCTIONS` loads on `initialize` (verify via an MCP inspector / the live protocol test).
|
|
199
|
+
- [ ] Every renamed tool: registration + manifest + catalog `mcp_name` updated; old name aliased or cut.
|
|
200
|
+
- [ ] No per-tool description contains the full cross-tool map (only one negative-space line).
|
|
201
|
+
- [ ] No vendor name (browser/proxy provider) in instructions or any description.
|
|
202
|
+
- [ ] `npm run mcp:lint-tools` clean; `vitest run tests/unit tests/contract` green.
|
|
203
|
+
|
|
204
|
+
## Non-goals (this spec)
|
|
205
|
+
DB-backed catalog (v2), the `reddit_*` / `deep_research_workflow` *implementations* beyond thin
|
|
206
|
+
registration, per-plan tool gating, and the frontend rendering itself.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Designing MCP Tools an AI Can Actually Find and Use
|
|
2
|
+
|
|
3
|
+
A practical guide to building MCP servers whose tools an LLM (Claude, but the mechanics generalize)
|
|
4
|
+
will **discover, select correctly, and invoke well**. It is written from how the model *actually*
|
|
5
|
+
perceives tools at runtime — not how a human reads your docs page.
|
|
6
|
+
|
|
7
|
+
The single thesis: **the model routes on what it can see, and it can see almost nothing until it asks.**
|
|
8
|
+
Everything below follows from that.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. How the model actually sees your tools (the mechanic that governs everything)
|
|
13
|
+
|
|
14
|
+
Modern MCP clients **defer** tool schemas to save context. The lifecycle in a session:
|
|
15
|
+
|
|
16
|
+
1. **At rest — names only.** When the server connects, the model gets a flat list of tool
|
|
17
|
+
**identifiers** (`mcp__<server>__<tool>`) — *names, nothing else*. No parameters, no descriptions,
|
|
18
|
+
no input/output shapes. For ~50 tools that's a cheap, scannable block.
|
|
19
|
+
2. **Server-instructions block — the one thing loaded upfront.** An MCP server may ship a top-level
|
|
20
|
+
`instructions` string. This **is** in context from the start, alongside the names. It's the only
|
|
21
|
+
place you can influence routing *before* the model fetches anything.
|
|
22
|
+
3. **Discovery — the model fetches a schema.** To use a tool, the model runs a tool-search step
|
|
23
|
+
(exact `select:<name>` or a keyword query) that loads that tool's **full definition** — description
|
|
24
|
+
prose + parameter JSONSchema — into context. *This is the first moment the model sees how the tool works.*
|
|
25
|
+
4. **Call → result.** The model invokes with arguments; your formatted result returns into context.
|
|
26
|
+
|
|
27
|
+
Two consequences that drive the whole guide:
|
|
28
|
+
|
|
29
|
+
- **Names do the *pre-fetch* routing.** The decision "which tool do I even look at?" is made from names
|
|
30
|
+
(+ the server-instructions block). A name that doesn't signal intent never gets fetched.
|
|
31
|
+
- **Descriptions do the *post-fetch* disambiguation.** They guide correct parameter choice and break
|
|
32
|
+
ties between near-neighbors — but only *after* the model already chose to fetch. They can't rescue a
|
|
33
|
+
tool the model never looked at.
|
|
34
|
+
|
|
35
|
+
> Mental model: the **name** is the search keyword; the **description** is the manual you read after you
|
|
36
|
+
> already pulled the book off the shelf; the **server-instructions block** is the sign on the shelf.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 2. Principle 1 — The name is the routing surface
|
|
41
|
+
|
|
42
|
+
Because names carry pre-fetch routing, treat them as the most important UX you ship.
|
|
43
|
+
|
|
44
|
+
- **Front-load the discriminator.** Put the distinguishing token first: `youtube_transcribe`,
|
|
45
|
+
`facebook_video_transcribe`. The platform/object is what the model scans for.
|
|
46
|
+
- **`platform_object_action` (or `object_action`).** A consistent scheme lets the model *predict*
|
|
47
|
+
names it hasn't seen and disambiguate neighbors.
|
|
48
|
+
- **Never ship a bare generic verb.** `search` is a trap — search *what*? `search_serp`,
|
|
49
|
+
`maps_search`, `facebook_ad_search`. The qualifier prevents cross-fetch and sharpens keyword ranking.
|
|
50
|
+
- **Disambiguate near-neighbors on their real axis.** `extract_url` vs `extract_site` vs
|
|
51
|
+
`map_site_urls` work because the names encode *single page / whole site / URLs-only*. If those blur,
|
|
52
|
+
the model picks the wrong granularity.
|
|
53
|
+
- **Match the user's vocabulary, or bridge it.** Users say "scrape a page"; if the tool is
|
|
54
|
+
`extract_url`, that gap is only bridged *after* fetch (via description/`symptomTriggers`). Where a
|
|
55
|
+
phrasing is common and high-value, make the name or its triggers reflect it.
|
|
56
|
+
- **Mind the namespace stutter.** The client prepends `mcp__`. Naming the server `mcp-scraper` yields
|
|
57
|
+
`mcp__mcp-scraper__…`. Name it `scraper` → `mcp__scraper__…`. Cosmetic, but it shows in permission
|
|
58
|
+
prompts/logs, and renaming later is breaking — do it early.
|
|
59
|
+
|
|
60
|
+
### The decisive test: named tool vs. generic engine
|
|
61
|
+
A user says *"collect ICP info from Reddit."*
|
|
62
|
+
- A **`reddit_research`** tool: the word "reddit" hits the name directly → fetched immediately, one hop.
|
|
63
|
+
- The generic **`browser_*`** agent: matches *none* of the user's words. To land there the model must
|
|
64
|
+
*infer* "no reddit tool → reddit is a website → drive a browser → navigate → search → read." Several
|
|
65
|
+
hops, each a chance to mis-route (e.g. grab `search_serp` and Google it instead).
|
|
66
|
+
|
|
67
|
+
**Lesson:** if there's a phrase users will say that names a platform or intent, you want a **named tool
|
|
68
|
+
catching it** — even if the implementation is a thin wrapper over a generic engine. *The name triggers;
|
|
69
|
+
the engine executes.* A category that merely "points at the browser" serves a human clicking a button;
|
|
70
|
+
it does **not** serve the model that has to *find* the capability from words.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 3. Principle 2 — Split by capability, parameterize the variations
|
|
75
|
+
|
|
76
|
+
It's tempting to think "names are cheap, so ship hundreds of hyper-specific tools." Don't. Three reasons:
|
|
77
|
+
|
|
78
|
+
1. **The name list isn't free at scale.** All names load at rest. 50 is fine; 300 is a wall the model
|
|
79
|
+
must scan and a real token cost. There's a ceiling.
|
|
80
|
+
2. **Search ranking *degrades* with volume.** More near-neighbors → more ambiguous keyword ranking →
|
|
81
|
+
*higher* wrong-fetch rate. Over-splitting makes the exact failure you're trying to avoid worse.
|
|
82
|
+
3. **It's the combinatorial trap.** `extract_url_screenshot`, `extract_url_branding`,
|
|
83
|
+
`extract_url_mobile`… is N×M×K variants of one capability. Parameters exist to prevent this.
|
|
84
|
+
|
|
85
|
+
**The rule:** one tool per *distinct thing a user wants to do*; **parameters** for *variations* of that thing.
|
|
86
|
+
|
|
87
|
+
The split test:
|
|
88
|
+
> Would a user think of these as **different things to do**, or the **same thing with options**?
|
|
89
|
+
|
|
90
|
+
- Different platform/object → different tool (`youtube_transcribe` vs `facebook_video_transcribe`).
|
|
91
|
+
- Different granularity / mental model → different tool (`extract_url` vs `extract_site`).
|
|
92
|
+
- A knob on the same action → **parameter** (`screenshot`, `formats`, `maxPages`, `rotateProxies`).
|
|
93
|
+
|
|
94
|
+
Post-fetch param-learning is cheap and reliable for the model, so you lose almost nothing by using
|
|
95
|
+
params for variations — and you keep the name list scannable and the ranking sharp.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 4. Principle 3 — Descriptions are the post-fetch safety net
|
|
100
|
+
|
|
101
|
+
Loaded on fetch, the description's job is **correct usage + tie-breaking**, not discovery. Make it earn that:
|
|
102
|
+
|
|
103
|
+
- **Lead with when-to-use and when-NOT-to-use.** Negative space ("use `extract_url` for one page; use
|
|
104
|
+
`extract_site` for a whole site") is what separates near-neighbors once both are fetched.
|
|
105
|
+
- **State side effects and cost plainly.** Especially for tools that spend money, open sessions, or
|
|
106
|
+
post — the model should be able to refuse a wrong pick *before* executing.
|
|
107
|
+
- **Encode parameter intent, bounds, and defaults** that mirror your actual schema, so the model fills
|
|
108
|
+
the form correctly the first time.
|
|
109
|
+
- **Carry symptom triggers / sample prompts.** Phrases that should route here. These help both the
|
|
110
|
+
keyword search ranking and the post-fetch confirmation.
|
|
111
|
+
|
|
112
|
+
Where the net fails (design against this): two tools with **similar names AND overlapping
|
|
113
|
+
descriptions** — the wrong one reads "plausible enough" and the model proceeds. Distinct names +
|
|
114
|
+
explicit negative space prevent it.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 5. Principle 4 — Use the server-instructions block as the routing map
|
|
119
|
+
|
|
120
|
+
It's the only content loaded *before* fetch besides names, so it's your lever on pre-fetch routing.
|
|
121
|
+
Keep it short and put **cross-tool routing**, not per-tool detail, there:
|
|
122
|
+
|
|
123
|
+
- A decision map: "single page → `extract_url`; whole site → `extract_site`; just URLs → `map_site_urls`."
|
|
124
|
+
- Setup/auth prerequisites that span tools.
|
|
125
|
+
- How to batch related fetches (clients often let the model load several schemas in one search call —
|
|
126
|
+
tell it which tools group together).
|
|
127
|
+
|
|
128
|
+
This is where you catch granularity and family mistakes *before* the model ever fetches the wrong one.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 6. Principle 5 — Two surfaces: human categories vs. AI tools
|
|
133
|
+
|
|
134
|
+
Your frontend and your MCP tool list are **different products with different optimal shapes.**
|
|
135
|
+
|
|
136
|
+
- **Humans** benefit from *consolidation*: one "Search" card with a source dropdown
|
|
137
|
+
(Web / Maps / Ads / YouTube). Fewer, richer entries; group by platform or verb as fits the UI.
|
|
138
|
+
- **The model** benefits from *distinction*: separate, well-named tools (`search_serp`, `maps_search`,
|
|
139
|
+
`facebook_ad_search`). A single `search(source=…)` tool forces the model to fetch it just to learn the
|
|
140
|
+
source enum, and the name `search` is ambiguous at discovery.
|
|
141
|
+
|
|
142
|
+
**Do not let the human IA pressure you into merging MCP tool names.** Map *one frontend card → many
|
|
143
|
+
distinct MCP tools*. Two surfaces, one backend.
|
|
144
|
+
|
|
145
|
+
Corollary — **a category can exist without a tool, but the AI path can't.** A "Reddit" card can be a
|
|
146
|
+
guided browser flow with no `reddit_*` tool; a human clicks it fine. But the model gets a clean path
|
|
147
|
+
*only* if a named tool exists (see §2). Decide per capability: human-only/occasional → category over the
|
|
148
|
+
generic engine; want strong AI routing → ship the named wrapper.
|
|
149
|
+
|
|
150
|
+
Platform-first vs verb-first grouping: for a multi-platform tool, **platform-first**
|
|
151
|
+
(YouTube / Facebook / Google Maps / Websites / Browser / Workflows) often wins — it matches how users
|
|
152
|
+
think *and* mirrors `platform_verb` naming. The category is the umbrella (covers search *and*
|
|
153
|
+
transcribe *and* …); the tools inside are the named verbs.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 7. Principle 6 — Tool *results* are context too; keep them lean
|
|
158
|
+
|
|
159
|
+
Discovery isn't the only context cost — **what you return floods the window if you let it.**
|
|
160
|
+
|
|
161
|
+
- **Summarize inline, persist the bulk, hand back a reference.** For large outputs, return a concise
|
|
162
|
+
summary + a saved file / artifact / URL the model can read on demand, not the whole payload.
|
|
163
|
+
- **Truncate with a signpost.** "showing first N of M; full data at `<path>`" beats silently dumping or
|
|
164
|
+
silently dropping.
|
|
165
|
+
- **Put the full-fidelity data somewhere the model can fetch** (disk file, object storage URL), so the
|
|
166
|
+
model gets the gist now and the detail only if needed.
|
|
167
|
+
- **Structured + human-readable.** Return both a structured object (for programmatic chaining) and a
|
|
168
|
+
short readable block (for the model to reason over) — don't make it parse a wall of text.
|
|
169
|
+
|
|
170
|
+
A tool that returns 200 KB of HTML per call will blow out a multi-step task; one that returns a 2 KB
|
|
171
|
+
summary + a file path scales.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 8. Failure modes → the fix
|
|
176
|
+
|
|
177
|
+
| Failure | Cause | Fix |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
| **Tool never found** | Name doesn't match user vocabulary | Front-loaded, intent-matching name; `symptomTriggers` |
|
|
180
|
+
| **Wrong tool fetched & believed** | Similar names + overlapping descriptions | Distinct names; explicit negative space; bounds in description |
|
|
181
|
+
| **Routed to generic engine the hard way** | Capability has no named tool | Thin named wrapper over the engine (§2 Reddit) |
|
|
182
|
+
| **Wrong granularity** | `*_url` / `*_site` / `*_map` blur | Encode the axis in the name; routing map in server instructions |
|
|
183
|
+
| **Picked god-tool, wrong sub-mode** | One tool, mega-param surface | Split distinct capabilities into separate tools |
|
|
184
|
+
| **Context blown out** | Tool returns full payload | Summarize + persist + reference (§7) |
|
|
185
|
+
| **Expensive wrong call** | Side-effecting tool, ambiguous name/desc | Clear name; state cost/side-effects up top; confirm before execute |
|
|
186
|
+
| **Namespace stutter / churn** | Server renamed late | Pick the clean server name on day one |
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 9. Naming checklist (per tool)
|
|
191
|
+
|
|
192
|
+
- [ ] Discriminator (platform/object) is **first** in the name.
|
|
193
|
+
- [ ] Follows a consistent `platform_object_action` scheme used across the server.
|
|
194
|
+
- [ ] No bare generic verb; qualifier present.
|
|
195
|
+
- [ ] Distinct from every neighbor on a real axis (object / granularity / platform).
|
|
196
|
+
- [ ] Matches at least one phrase a real user would say (or `symptomTriggers` covers it).
|
|
197
|
+
- [ ] It's a *distinct capability*, not a variation that should be a parameter.
|
|
198
|
+
- [ ] Side effects / cost surfaced in the first lines of the description.
|
|
199
|
+
- [ ] Description leads with when-to-use **and** when-not-to-use (negative space).
|
|
200
|
+
- [ ] Params mirror the real schema (names, bounds, enums, defaults).
|
|
201
|
+
- [ ] Result is lean: summary + reference, not a raw dump.
|
|
202
|
+
|
|
203
|
+
## 10. Server-level checklist
|
|
204
|
+
|
|
205
|
+
- [ ] Clean server name (no `mcp__mcp-…` stutter), chosen before launch.
|
|
206
|
+
- [ ] `instructions` block carries a short **cross-tool routing map**, not per-tool detail.
|
|
207
|
+
- [ ] Tool count is at the *capability* grain — not exploded into variants.
|
|
208
|
+
- [ ] Frontend categories map to *multiple distinct* MCP tools (two surfaces).
|
|
209
|
+
- [ ] High-value, user-named capabilities have a **named tool**, even if engine-backed.
|
|
210
|
+
- [ ] Related tools are documented as a batch the model can fetch together.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 11. The one-paragraph version
|
|
215
|
+
|
|
216
|
+
The model sees only tool **names** until it fetches a schema, so **names carry routing** — front-load
|
|
217
|
+
the discriminator, never ship a bare verb, and give every user-named capability its own named tool
|
|
218
|
+
(even a thin wrapper over a generic engine), because a name that matches the user's words is a one-hop
|
|
219
|
+
hit while a generic engine is a multi-hop inference. Split tools by **distinct capability** and use
|
|
220
|
+
**parameters** for variations — more tools past a point *degrades* selection, it doesn't help.
|
|
221
|
+
**Descriptions** are the post-fetch safety net (when-to-use + when-not, cost, bounds); the
|
|
222
|
+
**server-instructions block** is your only pre-fetch routing lever (put a cross-tool decision map
|
|
223
|
+
there). Keep the **human frontend** (consolidated cards) and the **AI tool list** (distinct names)
|
|
224
|
+
as separate surfaces over one backend. And remember tool **results are context** — return a summary
|
|
225
|
+
plus a reference, never a raw dump.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-06-
|
|
2
|
+
"generatedAt": "2026-06-26T17:22:50.447Z",
|
|
3
3
|
"counts": {
|
|
4
|
-
"main_stdio":
|
|
4
|
+
"main_stdio": 25,
|
|
5
5
|
"browser_agent_stdio": 20,
|
|
6
|
-
"combined_stdio":
|
|
7
|
-
"hosted_http":
|
|
6
|
+
"combined_stdio": 45,
|
|
7
|
+
"hosted_http": 27,
|
|
8
8
|
"hosted_only": 2,
|
|
9
|
-
"unique_public":
|
|
9
|
+
"unique_public": 47
|
|
10
10
|
},
|
|
11
11
|
"surfaces": {
|
|
12
12
|
"main_stdio": [
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"extract_url",
|
|
16
16
|
"map_site_urls",
|
|
17
17
|
"extract_site",
|
|
18
|
+
"audit_site",
|
|
18
19
|
"youtube_harvest",
|
|
19
20
|
"youtube_transcribe",
|
|
20
21
|
"facebook_page_intel",
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
"workflow_step",
|
|
33
34
|
"workflow_status",
|
|
34
35
|
"workflow_artifact_read",
|
|
35
|
-
"
|
|
36
|
+
"rank_tracker_workflow",
|
|
36
37
|
"credits_info"
|
|
37
38
|
],
|
|
38
39
|
"browser_agent_stdio": [
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
"browser_replay_annotate",
|
|
56
57
|
"browser_close",
|
|
57
58
|
"browser_list_sessions",
|
|
58
|
-
"
|
|
59
|
+
"query_fanout_workflow"
|
|
59
60
|
],
|
|
60
61
|
"combined_stdio": [
|
|
61
62
|
"harvest_paa",
|
|
@@ -63,6 +64,7 @@
|
|
|
63
64
|
"extract_url",
|
|
64
65
|
"map_site_urls",
|
|
65
66
|
"extract_site",
|
|
67
|
+
"audit_site",
|
|
66
68
|
"youtube_harvest",
|
|
67
69
|
"youtube_transcribe",
|
|
68
70
|
"facebook_page_intel",
|
|
@@ -80,7 +82,7 @@
|
|
|
80
82
|
"workflow_step",
|
|
81
83
|
"workflow_status",
|
|
82
84
|
"workflow_artifact_read",
|
|
83
|
-
"
|
|
85
|
+
"rank_tracker_workflow",
|
|
84
86
|
"credits_info",
|
|
85
87
|
"browser_open",
|
|
86
88
|
"browser_profile_onboard",
|
|
@@ -101,7 +103,7 @@
|
|
|
101
103
|
"browser_replay_annotate",
|
|
102
104
|
"browser_close",
|
|
103
105
|
"browser_list_sessions",
|
|
104
|
-
"
|
|
106
|
+
"query_fanout_workflow"
|
|
105
107
|
],
|
|
106
108
|
"hosted_http": [
|
|
107
109
|
"harvest_paa",
|
|
@@ -109,6 +111,7 @@
|
|
|
109
111
|
"extract_url",
|
|
110
112
|
"map_site_urls",
|
|
111
113
|
"extract_site",
|
|
114
|
+
"audit_site",
|
|
112
115
|
"youtube_harvest",
|
|
113
116
|
"youtube_transcribe",
|
|
114
117
|
"facebook_page_intel",
|
|
@@ -126,7 +129,7 @@
|
|
|
126
129
|
"workflow_step",
|
|
127
130
|
"workflow_status",
|
|
128
131
|
"workflow_artifact_read",
|
|
129
|
-
"
|
|
132
|
+
"rank_tracker_workflow",
|
|
130
133
|
"credits_info",
|
|
131
134
|
"capture_serp_snapshot",
|
|
132
135
|
"capture_serp_page_snapshots"
|
|
@@ -141,6 +144,7 @@
|
|
|
141
144
|
"extract_url",
|
|
142
145
|
"map_site_urls",
|
|
143
146
|
"extract_site",
|
|
147
|
+
"audit_site",
|
|
144
148
|
"youtube_harvest",
|
|
145
149
|
"youtube_transcribe",
|
|
146
150
|
"facebook_page_intel",
|
|
@@ -158,7 +162,7 @@
|
|
|
158
162
|
"workflow_step",
|
|
159
163
|
"workflow_status",
|
|
160
164
|
"workflow_artifact_read",
|
|
161
|
-
"
|
|
165
|
+
"rank_tracker_workflow",
|
|
162
166
|
"credits_info",
|
|
163
167
|
"browser_open",
|
|
164
168
|
"browser_profile_onboard",
|
|
@@ -179,7 +183,7 @@
|
|
|
179
183
|
"browser_replay_annotate",
|
|
180
184
|
"browser_close",
|
|
181
185
|
"browser_list_sessions",
|
|
182
|
-
"
|
|
186
|
+
"query_fanout_workflow",
|
|
183
187
|
"capture_serp_snapshot",
|
|
184
188
|
"capture_serp_page_snapshots"
|
|
185
189
|
]
|
|
@@ -299,7 +303,7 @@
|
|
|
299
303
|
],
|
|
300
304
|
"formatExamples": [
|
|
301
305
|
"url=\"https://example.com\"",
|
|
302
|
-
"maxUrls=100 for normal maps, maxUrls=
|
|
306
|
+
"maxUrls=100 for normal maps, up to maxUrls=10000 for a full inventory (maps over 500 URLs write the complete list to a CSV file and return a summary plus the path)"
|
|
303
307
|
],
|
|
304
308
|
"negativeSpace": "Use extract_url for one known page; use extract_site after mapping when the user wants page content.",
|
|
305
309
|
"producesHandlesFor": [
|
|
@@ -322,18 +326,49 @@
|
|
|
322
326
|
"hasStructuredContent": true,
|
|
323
327
|
"writesLocalFiles": true,
|
|
324
328
|
"symptomTriggers": [
|
|
325
|
-
"the user wants
|
|
329
|
+
"the user wants the page content/text of multiple pages on a site (a content crawl or scrape)"
|
|
326
330
|
],
|
|
327
331
|
"samplePrompts": [
|
|
328
|
-
"
|
|
329
|
-
"Crawl the top pages of this competitor",
|
|
330
|
-
"Get
|
|
332
|
+
"Scrape the content of these pages",
|
|
333
|
+
"Crawl the top pages of this competitor for their copy",
|
|
334
|
+
"Get the page content across the site"
|
|
331
335
|
],
|
|
332
336
|
"formatExamples": [
|
|
333
337
|
"url=\"https://example.com\"",
|
|
334
|
-
"maxPages=50 for the
|
|
338
|
+
"maxPages=50 for a normal crawl, up to maxPages=10000 for a full-site bulk scrape (crawls over 25 pages switch to folder mode: each page saved as its own file, response returns a summary plus the folder path)"
|
|
335
339
|
],
|
|
336
|
-
"negativeSpace": "Use map_site_urls first when the user needs URL selection; use extract_url for one page.",
|
|
340
|
+
"negativeSpace": "Returns content only. Use audit_site for a technical SEO audit (issues, link graph, indexability, image weights); use map_site_urls first when the user needs URL selection; use extract_url for one page.",
|
|
341
|
+
"producesHandlesFor": [
|
|
342
|
+
"saved-report"
|
|
343
|
+
],
|
|
344
|
+
"consumesHandles": [
|
|
345
|
+
"public-site-url"
|
|
346
|
+
]
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
"name": "audit_site",
|
|
350
|
+
"surfaces": [
|
|
351
|
+
"main_stdio",
|
|
352
|
+
"combined_stdio",
|
|
353
|
+
"hosted_http"
|
|
354
|
+
],
|
|
355
|
+
"tokenBudgetClass": "core",
|
|
356
|
+
"hasOutputSchema": true,
|
|
357
|
+
"hasStructuredContent": true,
|
|
358
|
+
"writesLocalFiles": true,
|
|
359
|
+
"symptomTriggers": [
|
|
360
|
+
"the user wants a technical SEO audit, site health check, or Screaming-Frog-style crawl with issues, internal link graph, indexability, heading breakdown, and image sizes/formats"
|
|
361
|
+
],
|
|
362
|
+
"samplePrompts": [
|
|
363
|
+
"Run a technical SEO audit on this site",
|
|
364
|
+
"Do a Screaming Frog style crawl of example.com",
|
|
365
|
+
"Audit this site for SEO issues and oversized images"
|
|
366
|
+
],
|
|
367
|
+
"formatExamples": [
|
|
368
|
+
"url=\"https://example.com\"",
|
|
369
|
+
"maxPages=50 for a normal audit, up to 10000 for a full-site audit; always writes a folder of analysis files (report.md, issues.json, pages.jsonl, links.jsonl, link-metrics.jsonl, images.jsonl) plus per-page content and returns a summary plus the folder path"
|
|
370
|
+
],
|
|
371
|
+
"negativeSpace": "Use extract_site when the user only wants page content; use map_site_urls to just list URLs.",
|
|
337
372
|
"producesHandlesFor": [
|
|
338
373
|
"saved-report"
|
|
339
374
|
],
|
|
@@ -861,7 +896,7 @@
|
|
|
861
896
|
]
|
|
862
897
|
},
|
|
863
898
|
{
|
|
864
|
-
"name": "
|
|
899
|
+
"name": "rank_tracker_workflow",
|
|
865
900
|
"surfaces": [
|
|
866
901
|
"main_stdio",
|
|
867
902
|
"combined_stdio",
|
|
@@ -941,7 +976,7 @@
|
|
|
941
976
|
"browser_screenshot",
|
|
942
977
|
"browser_read",
|
|
943
978
|
"browser_close",
|
|
944
|
-
"
|
|
979
|
+
"query_fanout_workflow"
|
|
945
980
|
],
|
|
946
981
|
"consumesHandles": []
|
|
947
982
|
},
|
|
@@ -1489,7 +1524,7 @@
|
|
|
1489
1524
|
"consumesHandles": []
|
|
1490
1525
|
},
|
|
1491
1526
|
{
|
|
1492
|
-
"name": "
|
|
1527
|
+
"name": "query_fanout_workflow",
|
|
1493
1528
|
"surfaces": [
|
|
1494
1529
|
"browser_agent_stdio",
|
|
1495
1530
|
"combined_stdio"
|