@fangorn-network/westmarch 2026.11.3 → 2026.11.4-dev
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 +1 -1
- package/docs/app-to-agent.md +12 -80
- package/package.json +5 -8
- package/site/worker.js +4 -1
- package/src/agent/mcp.js +134 -386
- package/src/agent/tools.js +2 -1
- package/src/agent/view-tools.js +4 -2
- package/src/agent/x402.js +16 -18
- package/src/publish/devnet.js +4 -2
- package/src/publish/init.js +1 -1
- package/src/publish/ship.js +6 -5
- package/src/publish/view.js +7 -6
- package/template/README.md +7 -9
- package/plugin/scripts/fx.mjs +0 -266
- package/src/discover/explore.js +0 -227
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ src/
|
|
|
60
60
|
envelope.js · manifest.js · cli.js · view.js · ship.js · eval.ts
|
|
61
61
|
lint.js …and whether any of that WORKED
|
|
62
62
|
site/ the stock page every `westmarch-ship` app deploys
|
|
63
|
-
plugin/ Claude Code plugin fangorn-index: its skill
|
|
63
|
+
plugin/ Claude Code plugin fangorn-index: its skill and the fangorn MCP server
|
|
64
64
|
plugin-builder/ Claude Code plugin fangorn-builder: fangorn-app, fangorn-improve
|
|
65
65
|
examples/ demo/ (a second consumer + fixtures) · publisher/ (the smallest publisher)
|
|
66
66
|
scripts/ test.js (`npm test`) · build-bin.js (single-file fangorn-mcp)
|
package/docs/app-to-agent.md
CHANGED
|
@@ -12,7 +12,8 @@ browser and any agent can find, verify and query. By the end you have:
|
|
|
12
12
|
- **a site**: the view, a page for people, and an MCP endpoint for agents (one Cloudflare
|
|
13
13
|
Worker, deployed by `westmarch-ship`);
|
|
14
14
|
- **an agent card** bound to the app on chain (the repo's `@card` record), and registered as an ERC-8004 agent;
|
|
15
|
-
- **agents
|
|
15
|
+
- **agents finding it:** the Fangorn explorer indexes every publisher, so `fangorn-mcp`'s
|
|
16
|
+
`search` finds your records and names you as their publisher.
|
|
16
17
|
|
|
17
18
|
Where the data comes from is up to you. The guide starts from a JSON array of objects,
|
|
18
19
|
which anything can produce: a scraper, a database export, a converted spreadsheet.
|
|
@@ -27,7 +28,7 @@ which anything can produce: a scraper, a database export, a converted spreadshee
|
|
|
27
28
|
westmarch-ship ─► card at <site>/.well-known/agent-card.json
|
|
28
29
|
└─► fangorn card <url> --repo <app> ─► the repo's @card = card URL, + ERC-8004 agent
|
|
29
30
|
│
|
|
30
|
-
|
|
31
|
+
the explorer (indexes every publisher) ◄── fangorn-mcp: search, publishers, get, buy
|
|
31
32
|
```
|
|
32
33
|
|
|
33
34
|
Everything runs on Base Sepolia: `"network": "baseSepolia"` in `app.json`.
|
|
@@ -110,8 +111,7 @@ and commit `.ship/state.json`.
|
|
|
110
111
|
- **Serving.** `/view/*` (but `cdn/catalog`) and every MCP tool but `describe` need access: a pass
|
|
111
112
|
(`POST /pass`, x402 v2, a bearer token good for `days`) or one x402 payment per tool call over
|
|
112
113
|
x402's MCP transport (`_meta["x402/payment"]`). A call that fails or misses is not charged.
|
|
113
|
-
|
|
114
|
-
`FANGORN_MCP_MAX_PASS`, else per call at or under `FANGORN_MCP_MAX_PRICE`.
|
|
114
|
+
An agent pays with any x402 client pointed at the app's `/mcp`.
|
|
115
115
|
- **Discovery.** The card's Fangorn extension carries `access` (pass URL and prices), and its
|
|
116
116
|
skills are tagged `x402`, so the ERC-8004 registration says `x402Support: true`.
|
|
117
117
|
`/openapi.json` and `/.well-known/x402` list `POST /pass` for x402scan; the facilitator's Bazaar
|
|
@@ -177,75 +177,16 @@ or a PR can change the gate along with what it gates.
|
|
|
177
177
|
What is not graded yet: a change to how a source *parses*. `--local` reads what the
|
|
178
178
|
sources last staged; it does not replay their parsing over the raw documents.
|
|
179
179
|
|
|
180
|
-
##
|
|
180
|
+
## How agents find your data
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
Through the **Fangorn explorer** (`https://explorer.fangorn.network`), the app that indexes
|
|
183
|
+
every publisher. Once your records are pushed, its next index run (every few minutes) picks
|
|
184
|
+
them up, and anyone running `fangorn-mcp` finds them with `search`, sees your repos under
|
|
185
|
+
`publishers`, and buys what you sell with `buy`. You register nothing for this: being a
|
|
186
|
+
publisher is enough. [docs/mcp.md](mcp.md) has the setup.
|
|
184
187
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
App owners add `fangorn-builder@fangorn` (the `fangorn-app` and `fangorn-improve` skills).
|
|
190
|
-
Without the plugin, install it through npm (node ≥ 20):
|
|
191
|
-
|
|
192
|
-
```sh
|
|
193
|
-
claude mcp add fangorn -- \
|
|
194
|
-
npx -y -p @fangorn-network/westmarch -p @huggingface/transformers fangorn-mcp
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
Without `-p @huggingface/transformers`, the install is ~500 MB lighter and `search` ranks by
|
|
198
|
-
words, not meaning. For private apps, `FANGORN_MCP_WALLET_KEY` lets it pay
|
|
199
|
-
(see [A private app](#a-private-app-the-data-is-the-product)); the plugin reads it from your
|
|
200
|
-
environment, while `-e` stores it in `~/.claude.json` in plain text.
|
|
201
|
-
|
|
202
|
-
[docs/mcp.md](mcp.md) has the full setup: other MCP clients, every setting, running from
|
|
203
|
-
source, and troubleshooting. A single-file binary (`bun build-bin.js`) exists but is not
|
|
204
|
-
published yet.
|
|
205
|
-
|
|
206
|
-
It starts with three tools:
|
|
207
|
-
- **`list-apps`** reads the apps off the chain and keeps the ones whose cards verify.
|
|
208
|
-
- **`open-app <name>`** reads that app's `@card` (one call), verifies its card, and adds the
|
|
209
|
-
app's tools as `<name>__<tool>`.
|
|
210
|
-
- **`call-app-tool`** reaches the same tools, for clients that don't refresh their tool list.
|
|
211
|
-
|
|
212
|
-
**Two kinds of tools.**
|
|
213
|
-
- **Data tools:** an app whose card names `views` gets these at once, with no browser:
|
|
214
|
-
`describe`, `search`, `get`, `similar`, `count`, `browse`. westmarch runs them over the
|
|
215
|
-
app's shards, checked against their manifests' sha256. `search` reads the 3 views whose
|
|
216
|
-
coverage sketch best matches the query and names the next candidates.
|
|
217
|
-
- **Page tools:** `open-app` with `page: true` also opens `card.url` in a browser for the
|
|
218
|
-
page's own WebMCP tools. An app with no views gets only these.
|
|
219
|
-
|
|
220
|
-
**How fast** (measured on a 60,760-record app in 23 views):
|
|
221
|
-
|
|
222
|
-
| | time |
|
|
223
|
-
|---|---|
|
|
224
|
-
| `open-app` | 0.4–2 s |
|
|
225
|
-
| `describe` | under 5 ms |
|
|
226
|
-
| first `search`, caches warm | 0.4–1.8 s |
|
|
227
|
-
| later searches | ~0.2–0.5 s |
|
|
228
|
-
| first `search` from an empty machine (single file) | ~3.5 s, by words while the model downloads |
|
|
229
|
-
| page tools (browser) | 5–15 s to open, ~2 GB of memory |
|
|
230
|
-
|
|
231
|
-
On a new machine, the embedding model (131 MB, kept in `~/.cache/fangorn-mcp/models`)
|
|
232
|
-
downloads in the background. Until it lands, `search` ranks by words and says so in
|
|
233
|
-
`ranked_by`; then it switches to meaning. Shards are downloaded once and kept by content
|
|
234
|
-
hash.
|
|
235
|
-
|
|
236
|
-
**Where the browser comes from (page tools only).** Anything that speaks the Chrome DevTools
|
|
237
|
-
Protocol and has WebMCP will do: a headless Chromium service, a hosted browser, or the local
|
|
238
|
-
Chrome.
|
|
239
|
-
|
|
240
|
-
| setting | browser |
|
|
241
|
-
|---|---|
|
|
242
|
-
| `FANGORN_MCP_CDP=http://host:9222` (or `--cdp`) | a running browser's DevTools address |
|
|
243
|
-
| `FANGORN_MCP_CDP=ws://host:9222/devtools/browser/…` | a browser WebSocket endpoint, which is what hosted services hand out |
|
|
244
|
-
| neither | the local Chrome 150+ (`CHROME=/path` if it isn't `google-chrome`), headless (`--headed` to watch) |
|
|
245
|
-
|
|
246
|
-
A remote browser is never closed; only the tabs the server opened are. A local Chrome is
|
|
247
|
-
shared by every session on the machine and closes when the last one exits. Its profile is
|
|
248
|
-
`~/.cache/westmarch-mcp/`. `--from-block <n>` points the server at another deployment.
|
|
188
|
+
Your app's own Worker still serves its page and its own MCP endpoint at `/mcp`, with your
|
|
189
|
+
schema and your tools, for agents that come to your app directly.
|
|
249
190
|
|
|
250
191
|
## Discovering apps in code
|
|
251
192
|
|
|
@@ -266,14 +207,6 @@ const fangorn = Fangorn.create({ privateKey: generatePrivateKey() });
|
|
|
266
207
|
const { card, publisher, repo } = await fangorn.discoverCard(cardUrl);
|
|
267
208
|
```
|
|
268
209
|
|
|
269
|
-
**From the chain** (every Fangorn app; no index, no server):
|
|
270
|
-
|
|
271
|
-
```js
|
|
272
|
-
import { listApps } from "@fangorn-network/westmarch/apps";
|
|
273
|
-
const { apps, rejected } = await listApps(fangorn);
|
|
274
|
-
// apps: one per repo whose card verifies; rejected: [{ card, why }], reported rather than dropped
|
|
275
|
-
```
|
|
276
|
-
|
|
277
210
|
**From ERC-8004** (for general-purpose agents that know nothing about Fangorn):
|
|
278
211
|
|
|
279
212
|
```js
|
|
@@ -307,7 +240,6 @@ const { contents } = await fangorn.readRepo(publisher, repo);
|
|
|
307
240
|
| card rejected as not bound | URLs must match exactly. A trailing slash, `http` vs `https`, or a different path counts as a different card. |
|
|
308
241
|
| card rejected after a rebuild | the build dropped the `fangorn` block or changed `fromBlock`/`url`; keep them fixed |
|
|
309
242
|
| discovery works in Node, fails in a browser | no `Access-Control-Allow-Origin` on the card |
|
|
310
|
-
| `listApps` takes forever | `fromBlock` is too early; the scan is 1000 blocks per RPC call |
|
|
311
243
|
| `fangorn card`: "ERC-8004 registration pins the registration file to IPFS" | no Pinata JWT in `fangorn init` |
|
|
312
244
|
| `fangorn card` signs as the wrong wallet | `~/.fangorn/config.json` beats `ETH_PRIVATE_KEY` |
|
|
313
245
|
| `fangorn card`: "replacement transaction underpriced" | a nonce race after the mint; the agent exists, so rerun with `--skip-register` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fangorn-network/westmarch",
|
|
3
|
-
"version": "2026.11.
|
|
3
|
+
"version": "2026.11.4-dev",
|
|
4
4
|
"description": "all-in-one builder for fangorn apps",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"./embed": "./src/core/embed.js",
|
|
12
12
|
"./rank": "./src/core/rank.js",
|
|
13
13
|
"./apps": "./src/discover/apps.js",
|
|
14
|
-
"./explore": "./src/discover/explore.js",
|
|
15
14
|
"./agent-card": "./src/agent/agent-card.js",
|
|
16
15
|
"./view": "./src/publish/view.js",
|
|
17
16
|
"./roles": "./src/core/roles.js",
|
|
@@ -42,7 +41,7 @@
|
|
|
42
41
|
"@fangorn-network/markov": "^0.1.0"
|
|
43
42
|
},
|
|
44
43
|
"peerDependencies": {
|
|
45
|
-
"@fangorn-network/sdk": "^2026.11.
|
|
44
|
+
"@fangorn-network/sdk": "^2026.11.4-dev",
|
|
46
45
|
"@huggingface/transformers": "^4.2.0",
|
|
47
46
|
"@noble/curves": "^1.9.1",
|
|
48
47
|
"@semaphore-protocol/core": "^4.14.5",
|
|
@@ -57,7 +56,7 @@
|
|
|
57
56
|
}
|
|
58
57
|
},
|
|
59
58
|
"devDependencies": {
|
|
60
|
-
"@fangorn-network/sdk": "2026.11.
|
|
59
|
+
"@fangorn-network/sdk": "2026.11.4-dev",
|
|
61
60
|
"@huggingface/transformers": "^4.2.0",
|
|
62
61
|
"@noble/curves": "^1.9.1",
|
|
63
62
|
"@semaphore-protocol/core": "^4.14.5",
|
|
@@ -72,8 +71,7 @@
|
|
|
72
71
|
"westmarch-ship": "./src/publish/ship.js",
|
|
73
72
|
"westmarch-eval": "./dist/ts/eval.js",
|
|
74
73
|
"westmarch-lint": "./src/publish/lint.js",
|
|
75
|
-
"fangorn-mcp": "./src/agent/mcp.js"
|
|
76
|
-
"fangorn-fx": "./plugin/scripts/fx.mjs"
|
|
74
|
+
"fangorn-mcp": "./src/agent/mcp.js"
|
|
77
75
|
},
|
|
78
76
|
"files": [
|
|
79
77
|
"src",
|
|
@@ -85,8 +83,7 @@
|
|
|
85
83
|
"site/worker.js",
|
|
86
84
|
"site/no-model.js",
|
|
87
85
|
"site/store.js",
|
|
88
|
-
"dist/ts"
|
|
89
|
-
"plugin/scripts/fx.mjs"
|
|
86
|
+
"dist/ts"
|
|
90
87
|
],
|
|
91
88
|
"engines": {
|
|
92
89
|
"node": ">=20"
|
package/site/worker.js
CHANGED
|
@@ -374,8 +374,11 @@ if (typeof process !== "undefined" && import.meta.url === `file://${process.argv
|
|
|
374
374
|
for (const t of ["describe", "search", "get", "similar", "count", "browse"]) if (!names.includes(t)) throw new Error(`missing tool ${t}: ${names}`);
|
|
375
375
|
const described = JSON.parse((await call("tools/call", { name: "describe", arguments: {} })).result.content[0].text);
|
|
376
376
|
eq(described.views.map((v) => v.rows), [2], "describe reads the catalog from R2");
|
|
377
|
+
eq(described.views[0].repos.map((r) => [r.publisher, r.repo, r.rows]), [[PUB, "shop", 2]], "describe names each repo's publisher");
|
|
377
378
|
const found = JSON.parse((await call("tools/call", { name: "search", arguments: { query: "reading lamp", limit: 1 } })).result.content[0].text);
|
|
378
|
-
eq([found.ranked_by, found.hits[0].title], ["meaning", "Brass lamp"], "search ranks by meaning, the query embedded by the same runtime");
|
|
379
|
+
eq([found.ranked_by, found.hits[0].title, found.hits[0].publisher], ["meaning", "Brass lamp", PUB], "search ranks by meaning, the query embedded by the same runtime; a hit names its publisher");
|
|
380
|
+
const theirs = JSON.parse((await call("tools/call", { name: "search", arguments: { query: "lamp", where: { owner: OWNER } } })).result.content[0].text);
|
|
381
|
+
eq(theirs.hits.length, 0, "search narrows to one publisher with where.owner");
|
|
379
382
|
if (!calls.at(-1).text[0].startsWith(QUERY)) throw new Error("the query goes to the AI binding with the query prompt");
|
|
380
383
|
eq((await (await worker.fetch(new Request("https://shop.test/mcp", { method: "POST", body: '{"jsonrpc":"2.0","method":"notifications/initialized"}' }), env)).status), 202, "a notification is accepted, not answered");
|
|
381
384
|
|