@arcnow/mcp 0.1.0 → 0.1.2
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 +200 -246
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
> **This repository is a published mirror.** Every release of the arcnow.io MCP
|
|
2
|
-
> server lands here as one commit, tagged `vX.Y.Z`, with the `npm pack` tarball
|
|
3
|
-
> under [Releases](https://github.com/arcnow-io/arcnow-io-mcp/releases). Issues and pull requests are welcome
|
|
4
|
-
> here. The maintainers' tooling (the pin gate against the SDK's history, the
|
|
5
|
-
> fork proof against the contracts) is not part of the mirror, so `scripts/`
|
|
6
|
-
> referred to below is absent; `@arcnow/sdk` is a path dependency, so clone
|
|
7
|
-
> [arcnow-io/arcnow-io-sdk](https://github.com/arcnow-io/arcnow-io-sdk) as a
|
|
8
|
-
> sibling directory named `sdk` and build `sdk/typescript` first.
|
|
9
|
-
> Site: [www.arcnow.io](https://www.arcnow.io) - docs:
|
|
10
|
-
> [docs.arcnow.io](https://docs.arcnow.io).
|
|
11
|
-
|
|
12
1
|
# `@arcnow/mcp`
|
|
13
2
|
|
|
14
3
|
An [MCP](https://modelcontextprotocol.io) server that lets an AI assistant use
|
|
@@ -19,14 +8,151 @@ graduating, and, **only if the operator explicitly turns it on**, launch, buy,
|
|
|
19
8
|
sell and rescue a stranded migration.
|
|
20
9
|
|
|
21
10
|
Every chain interaction goes through
|
|
22
|
-
[`@arcnow/sdk`](https://
|
|
23
|
-
[`pins.json`](pins.json). Nothing here encodes a call or
|
|
11
|
+
[`@arcnow/sdk`](https://www.npmjs.com/package/@arcnow/sdk), pinned to an exact
|
|
12
|
+
published version in [`pins.json`](pins.json). Nothing here encodes a call or
|
|
13
|
+
computes a curve.
|
|
14
|
+
|
|
15
|
+
## Install and run
|
|
16
|
+
|
|
17
|
+
The server is on npm as [`@arcnow/mcp`](https://www.npmjs.com/package/@arcnow/mcp)
|
|
18
|
+
(Node 22.12 or newer). Run it straight from the registry:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npx -y @arcnow/mcp # read-only. The default, and the useful part.
|
|
22
|
+
npx -y @arcnow/mcp --allow-writes # can spend, if ARCNOW_PRIVATE_KEY is in the environment.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Every client below runs that same command with `ARCNOW_MCP_NETWORK` set. The
|
|
26
|
+
examples say `arc-mainnet`, where [arcnow.io](https://arcnow.io) is live.
|
|
27
|
+
*The currently published server still ships the previous, testnet-only preset
|
|
28
|
+
and refuses `arc-mainnet` by name; mainnet lands in the next release — until
|
|
29
|
+
then use `arc-testnet`.*
|
|
30
|
+
|
|
31
|
+
**Write mode, in every client:** add `--allow-writes` to the arguments and put
|
|
32
|
+
the signing key **in a file** the server reads through `ARCNOW_PRIVATE_KEY_FILE`
|
|
33
|
+
— never the key itself in a client config, which gets committed and
|
|
34
|
+
screenshotted. Read-only needs no key at all. Every variable is in
|
|
35
|
+
[Configuration](#configuration); [`examples/`](examples) has both shapes.
|
|
36
|
+
|
|
37
|
+
### Claude Code
|
|
24
38
|
|
|
39
|
+
```sh
|
|
40
|
+
claude mcp add --env ARCNOW_MCP_NETWORK=arc-mainnet --scope user arcnow -- npx -y @arcnow/mcp
|
|
25
41
|
```
|
|
26
|
-
|
|
27
|
-
|
|
42
|
+
|
|
43
|
+
`--scope` is `local` (this project, you only; the default), `project` (checked
|
|
44
|
+
into `.mcp.json` at the project root, shared with the team) or `user` (every
|
|
45
|
+
project). The project-file form:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"mcpServers": {
|
|
50
|
+
"arcnow": {
|
|
51
|
+
"command": "npx",
|
|
52
|
+
"args": ["-y", "@arcnow/mcp"],
|
|
53
|
+
"env": { "ARCNOW_MCP_NETWORK": "arc-mainnet" }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
28
57
|
```
|
|
29
58
|
|
|
59
|
+
### Claude Desktop
|
|
60
|
+
|
|
61
|
+
`claude_desktop_config.json` — macOS
|
|
62
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json`, Windows
|
|
63
|
+
`%APPDATA%\Claude\claude_desktop_config.json`:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"arcnow": {
|
|
69
|
+
"command": "npx",
|
|
70
|
+
"args": ["-y", "@arcnow/mcp"],
|
|
71
|
+
"env": { "ARCNOW_MCP_NETWORK": "arc-mainnet" }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Codex
|
|
78
|
+
|
|
79
|
+
`~/.codex/config.toml` (or `.codex/config.toml` in a project):
|
|
80
|
+
|
|
81
|
+
```toml
|
|
82
|
+
[mcp_servers.arcnow]
|
|
83
|
+
command = "npx"
|
|
84
|
+
args = ["-y", "@arcnow/mcp"]
|
|
85
|
+
|
|
86
|
+
[mcp_servers.arcnow.env]
|
|
87
|
+
ARCNOW_MCP_NETWORK = "arc-mainnet"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Or from the CLI: `codex mcp add arcnow --env ARCNOW_MCP_NETWORK=arc-mainnet -- npx -y @arcnow/mcp`.
|
|
91
|
+
|
|
92
|
+
### Cursor
|
|
93
|
+
|
|
94
|
+
`.cursor/mcp.json` in the project (or `~/.cursor/mcp.json` for every project):
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"mcpServers": {
|
|
99
|
+
"arcnow": {
|
|
100
|
+
"command": "npx",
|
|
101
|
+
"args": ["-y", "@arcnow/mcp"],
|
|
102
|
+
"env": { "ARCNOW_MCP_NETWORK": "arc-mainnet" }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### VS Code (Copilot agent mode)
|
|
109
|
+
|
|
110
|
+
`.vscode/mcp.json` — note the key is `servers`, not `mcpServers`:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"servers": {
|
|
115
|
+
"arcnow": {
|
|
116
|
+
"type": "stdio",
|
|
117
|
+
"command": "npx",
|
|
118
|
+
"args": ["-y", "@arcnow/mcp"],
|
|
119
|
+
"env": { "ARCNOW_MCP_NETWORK": "arc-mainnet" }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
For write mode, VS Code's `inputs` can prompt for the key-file path instead of
|
|
126
|
+
writing it into the file; see its MCP documentation for the `${input:…}` form.
|
|
127
|
+
|
|
128
|
+
### Gemini CLI
|
|
129
|
+
|
|
130
|
+
`~/.gemini/settings.json` (or `.gemini/settings.json` in a project):
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"mcpServers": {
|
|
135
|
+
"arcnow": {
|
|
136
|
+
"command": "npx",
|
|
137
|
+
"args": ["-y", "@arcnow/mcp"],
|
|
138
|
+
"env": { "ARCNOW_MCP_NETWORK": "arc-mainnet" }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Or `gemini mcp add -e ARCNOW_MCP_NETWORK=arc-mainnet arcnow npx -y @arcnow/mcp`.
|
|
145
|
+
|
|
146
|
+
### Windsurf, Cline
|
|
147
|
+
|
|
148
|
+
Both take the generic `mcpServers` JSON above — the same `command`, `args` and
|
|
149
|
+
`env` — in their MCP settings file.
|
|
150
|
+
|
|
151
|
+
### From source
|
|
152
|
+
|
|
153
|
+
Clone [arcnow-io/arcnow-io-mcp](https://github.com/arcnow-io/arcnow-io-mcp), then
|
|
154
|
+
`npm ci && npm run build && node dist/index.js`; the SDK comes from npm.
|
|
155
|
+
|
|
30
156
|
---
|
|
31
157
|
|
|
32
158
|
## The two rules this server is built around
|
|
@@ -373,249 +499,77 @@ token. Gas is always native USDC, whatever a token's quote.
|
|
|
373
499
|
|
|
374
500
|
## The SDK, and how the pin works
|
|
375
501
|
|
|
376
|
-
`@arcnow/sdk`
|
|
377
|
-
|
|
378
|
-
|
|
502
|
+
`@arcnow/sdk` is published to npm from
|
|
503
|
+
[arcnow-io/arcnow-io-sdk](https://github.com/arcnow-io/arcnow-io-sdk), one tagged
|
|
504
|
+
release per version, and this server depends on it at an **exact version** —
|
|
505
|
+
`"@arcnow/sdk": "0.1.3"`, never `^0.1.3`. The tool descriptions promise what one
|
|
506
|
+
known SDK does; a range would let `npm install` move the code under them with no
|
|
507
|
+
commit here saying so. Moving the pin is a pull request.
|
|
379
508
|
|
|
509
|
+
[`pins.json`](pins.json) records it:
|
|
510
|
+
|
|
511
|
+
```json
|
|
512
|
+
"sdk": {
|
|
513
|
+
"package": "@arcnow/sdk",
|
|
514
|
+
"version": "0.1.3",
|
|
515
|
+
"integrity": "sha512-…",
|
|
516
|
+
"public_repo": "arcnow-io/arcnow-io-sdk",
|
|
517
|
+
"tag": "v0.1.3",
|
|
518
|
+
"why": ["what this server uses from that SDK, in prose"]
|
|
519
|
+
}
|
|
380
520
|
```
|
|
381
|
-
project/
|
|
382
|
-
sdk/ git clone https://github.com/arcnow-io/arcnow-io-sdk.git sdk
|
|
383
|
-
mcp/ this repository
|
|
384
|
-
```
|
|
385
521
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
0. `sdk.commit` is a **full 40-hex commit**, never a ref that resolves to
|
|
398
|
-
whatever it names today.
|
|
399
|
-
1. `package.json` still depends on the SDK **by path**. A `file:` specifier that
|
|
400
|
-
became a version range would resolve through the public registry, under a
|
|
401
|
-
name nobody in this project has claimed.
|
|
402
|
-
2. `node_modules/@arcnow/sdk` really is that sibling directory — linked, not
|
|
403
|
-
copied — carries the pinned version, and its `dist/` is **exactly what its
|
|
404
|
-
sources compile to**. The gate compiles the SDK again, with the SDK's own
|
|
405
|
-
TypeScript, into a temporary directory and compares every emitted file. This
|
|
406
|
-
server imports `dist/`, not `src/`, so a `dist/` left over from an older
|
|
407
|
-
checkout runs old code under new hashes — which is the state the sibling
|
|
408
|
-
checkout was found in when the pin moved off `ac52eaa`, with no `dist/pool.js`
|
|
409
|
-
at all, and the old gate passed it.
|
|
410
|
-
3. The sibling's **working tree** hashes to `surface_sha256`, with **no surface
|
|
411
|
-
file unpinned**. The linked directory *is* the working tree, so an
|
|
412
|
-
uncommitted edit — or an uncommitted new module — is code this server runs
|
|
413
|
-
under no git ref at all.
|
|
414
|
-
4. The sibling repository has `sdk.commit`, **it is on `sdk.branch`**, and it
|
|
415
|
-
carries exactly the pinned files with the pinned bytes — the only check that
|
|
416
|
-
can see that the recorded hashes belong to the commit named. It also
|
|
417
|
-
**reports, without failing**, when the SDK has moved past the pin and whether
|
|
418
|
-
the surface changed on the way. A pin is supposed to lag.
|
|
419
|
-
|
|
420
|
-
Together: `dist/` is the compilation of the working tree, the working tree is the
|
|
421
|
-
pinned bytes, and the pinned bytes are the commit on the branch.
|
|
422
|
-
|
|
423
|
-
**What is pinned is a rule, not a list:** `networks.json`,
|
|
424
|
-
`typescript/package.json`, and every file under `typescript/src` except
|
|
425
|
-
`typescript/src/errors/`. A module the SDK adds is surface the moment it exists.
|
|
426
|
-
The generated ABIs are inside the rule because they encode every call, and the
|
|
427
|
-
generated `networks.json` because it is the copy the SDK compiles its
|
|
428
|
-
**addresses** from. `errors/` is outside it: message prose behind a surface
|
|
429
|
-
`index.ts` already pins, where pinning would make every improved error message
|
|
430
|
-
a false alarm. `test/unit/check-pins.test.ts` builds real repositories and
|
|
431
|
-
breaks each link on purpose — a stale build, an unpinned module, an uncommitted
|
|
432
|
-
edit, a ref for a commit, a commit off the branch.
|
|
433
|
-
|
|
434
|
-
Moving the pin is a commit of its own: check the SDK out at the commit and build
|
|
435
|
-
it, run `scripts/check-pins.sh --record` — which records `sdk.commit` and every
|
|
436
|
-
surface hash from `HEAD`, and refuses an edit no commit carries — update
|
|
437
|
-
`sdk.why`, run preflight, and say what changed in the SDK surface and what it
|
|
438
|
-
meant for the tools. A tool description that still promises what an older SDK
|
|
439
|
-
did is a model quoting a wrong price.
|
|
440
|
-
|
|
441
|
-
Point the gate at a checkout somewhere else with `ARCNOW_SDK_DIR`.
|
|
522
|
+
`integrity` is the sha512 of that version's tarball, as npm records it in
|
|
523
|
+
`package-lock.json` and serves it as `dist.integrity`. **The tarball is the
|
|
524
|
+
surface**: every module, the generated ABIs that encode every call, the
|
|
525
|
+
`networks.json` every address comes from — one hash. (Until the SDK was
|
|
526
|
+
published this file hashed its sources file by file and the gate recompiled its
|
|
527
|
+
`dist/`; the integrity replaces all of that.)
|
|
528
|
+
|
|
529
|
+
Before every release the maintainers verify that the manifest, the lockfile, the
|
|
530
|
+
installed package and the registry all name that one tarball; moving the pin is a
|
|
531
|
+
pull request that reads the SDK's changelog and re-reads every tool description.
|
|
442
532
|
|
|
443
533
|
---
|
|
444
534
|
|
|
445
|
-
##
|
|
535
|
+
## Testing
|
|
446
536
|
|
|
447
537
|
```bash
|
|
448
|
-
|
|
449
|
-
./scripts/preflight.sh --no-install
|
|
450
|
-
./scripts/preflight.sh --no-chain # everything but the fork proof
|
|
451
|
-
./scripts/check-pins.sh # just the pin
|
|
452
|
-
npm test # just the unit suite — no chain, no container, no key
|
|
453
|
-
npm run test:fork # just the fork proof — needs Docker
|
|
538
|
+
npm test # the unit suite: no chain, no container, no key
|
|
454
539
|
```
|
|
455
540
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
workflow is a transcription of it, kept because a clean-checkout run is the one
|
|
459
|
-
thing a local run cannot prove.
|
|
460
|
-
|
|
461
|
-
The fork proof starts one container, an anvil fork of Arc testnet from the
|
|
462
|
-
Foundry image `pins.json` pins, labelled `io.arcnow.mcp.test`, and deploys
|
|
463
|
-
arcnow.io's 3.x multi-quote contracts onto it with the pinned SDK's
|
|
464
|
-
`scripts/fork-deploy-stack.sh`. Arc testnet now runs a 3.x stack of its own,
|
|
465
|
-
deployed on 2026-09-15, but the fork is taken at a pinned, already-cached block
|
|
466
|
-
from before that, where the live contracts are the 2.x ones the SDK refuses — and
|
|
467
|
-
a proof that deploys its own stack does not depend on what happens to be live
|
|
468
|
-
anyway. That needs `ARCNOW_CONTRACTS_DIR`, a checkout
|
|
469
|
-
of arcnow-io/contracts at the commit the SDK's `pins.json` names, and forge at
|
|
470
|
-
the release it names. Its harness —
|
|
471
|
-
copied from `arcnow-io/sdk`'s, not imported, because that repository's tests are
|
|
472
|
-
not part of the pinned surface — removes it on every exit path, sweeps only its
|
|
473
|
-
own label, and touches nothing else on a shared daemon.
|
|
474
|
-
|
|
475
|
-
### What the tests prove
|
|
541
|
+
It runs against a fake chain that does exactly what each test says, and proves
|
|
542
|
+
what can be proved offline:
|
|
476
543
|
|
|
477
544
|
- **A key is never an argument.** Every published schema is walked for
|
|
478
|
-
credential-shaped field names and
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
- **
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
an absent referrer's share named as going to the platform, the average fill
|
|
507
|
-
price distinguished from the spot price, graduated distinguished from
|
|
508
|
-
migrated, an undecodable pool revert explained cautiously rather than bare.
|
|
509
|
-
- **The pin gate fails when it should.** See above.
|
|
510
|
-
- **The wire works.** A real MCP client against a real MCP server over an
|
|
511
|
-
in-memory transport, and — in the fork proof — over stdio against the built
|
|
512
|
-
server.
|
|
513
|
-
- **Against the 3.x contracts deployed onto a fork of Arc testnet**
|
|
514
|
-
(`test/fork/mcp.fork.test.ts`), the built server — started with
|
|
515
|
-
`ARCNOW_MCP_NETWORK_FILE` naming the deployed stack — driven by a real MCP
|
|
516
|
-
client lists the quote tokens from the deployed registry in at most three
|
|
517
|
-
`eth_call`s with each one's cap, launches a native USDC
|
|
518
|
-
token on the launchpad, reads it
|
|
519
|
-
back with its quote token, `r0Wad` and `y0Wad`, buys it on its curve,
|
|
520
|
-
watches an impersonated Arc account graduate it, then quotes, buys and sells it
|
|
521
|
-
in its Uniswap v4 pool through the live router — and sells a second token back
|
|
522
|
-
to its curve — and launches a token in EURC, with its exact approve and exactly
|
|
523
|
-
the total pulled, buys it in EURC, and has a buy above the 50 EURC cap refused
|
|
524
|
-
with nothing sent. Every figure is checked against the chain with viem, not through
|
|
525
|
-
the server: every quote equals its fill, to the wei; exactly the amount and
|
|
526
|
-
the gas left the signer; the sell's approval is one `Approval` log to the
|
|
527
|
-
router for exactly the amount sold, used up by the sale; the recipient is paid
|
|
528
|
-
exactly what the report says; a swap's fee payout matches the hook's
|
|
529
|
-
`FeesDistributed` log; the refusals leave the signer's nonce where it was; and
|
|
530
|
-
the USDC ERC-20 predeploy comes back as `AddressIsNotACurve`. The server's RPC
|
|
531
|
-
goes through a counting proxy, and the per-call request counts are printed.
|
|
532
|
-
- **One curve, and nothing else priced.** Against the fake, whose version
|
|
533
|
-
checks are the SDK's own `assertCurveVersion` and `assertPlatformVersion`: no
|
|
534
|
-
report names a curve kind or a stack, an `@1` curve, platform or fee hook is
|
|
535
|
-
refused by name with nothing quoted or sent, and the launch scan reads the one
|
|
536
|
-
launchpad from its deployment block.
|
|
537
|
-
|
|
538
|
-
### What they do not prove
|
|
539
|
-
|
|
540
|
-
- **Not Arc's own execution semantics.** A fork re-executes locally with
|
|
541
|
-
anvil's EVM and disagrees with Arc about blocklisted transfers, EIP-1153, the
|
|
542
|
-
EIP-7708 system emitter and burn-to-zero without saying so.
|
|
543
|
-
- **Not a EURC pool against a chain.** The fork proof launches a EURC token,
|
|
544
|
-
buys it on its curve with the exact approve, and holds the 50 EURC cap; a
|
|
545
|
-
EURC token's pool is unit-tested against the fake.
|
|
546
|
-
- **Not every path against a chain.** The fork proof covers a launch, curve
|
|
547
|
-
trades, graduation and pool trades. A
|
|
548
|
-
migration through `arcnow_migrate`, a graduating buy through `arcnow_buy` (the
|
|
549
|
-
proof graduates its token through the SDK, so that nothing credits the
|
|
550
|
-
server's signer), a token that graduated but never migrated — there is none on
|
|
551
|
-
Arc testnet to fork — and a curve, platform or hook whose version the SDK
|
|
552
|
-
refuses are
|
|
553
|
-
unit-tested here against the fake, and proved against a chain, where they are
|
|
554
|
-
at all, by the SDK's own fork suite. If you change how a tool calls the SDK,
|
|
555
|
-
run the SDK's preflight too.
|
|
556
|
-
- **Not that the tool descriptions are true.** Those are prose, read by a model
|
|
557
|
-
deciding whether to spend somebody's money. When the pin moves, that is a
|
|
558
|
-
person's job.
|
|
559
|
-
- **Not that a model behaves.** Every guard in this server is a bound on
|
|
560
|
-
damage, not a guarantee of judgement.
|
|
561
|
-
|
|
562
|
-
---
|
|
563
|
-
|
|
564
|
-
## What made a clean surface awkward
|
|
565
|
-
|
|
566
|
-
Five things, all worth fixing upstream rather than here — and one of them now is.
|
|
567
|
-
|
|
568
|
-
**The SDK cannot enumerate tokens.** There is no `recentLaunches()` anywhere in
|
|
569
|
-
`@arcnow/sdk`, and its `networks.json` explains why a preset cannot carry one: a
|
|
570
|
-
token and its curve come from the `Launched` log, one pair per launch, and there
|
|
571
|
-
are as many as there have been launches. But "show me the recent tokens" is the
|
|
572
|
-
first thing anybody asks an assistant, so `arcnow_list_tokens` reads that log
|
|
573
|
-
directly — with the SDK's own pinned ABI, through the SDK's own configured
|
|
574
|
-
client, at the SDK's own launchpad address, adding nothing but the scan. It is
|
|
575
|
-
the only place in this repository that talks to the chain outside an SDK method,
|
|
576
|
-
and it is in `src/sdk-port.ts` with a comment saying so. A
|
|
577
|
-
`launchpad.recentLaunches()` belongs in the SDK, where the forked-chain suite
|
|
578
|
-
could test it against a real chain; here it is tested against a fake.
|
|
579
|
-
|
|
580
|
-
Because the scan is a bounded walk backwards from the tip rather than an index,
|
|
581
|
-
`arcnow_list_tokens` reports the block window it covered and says plainly when
|
|
582
|
-
it stopped on its budget with history unread. An assistant must not conclude a
|
|
583
|
-
token does not exist from a tool that only ever saw a window.
|
|
584
|
-
|
|
585
|
-
**The SDK's handles are classes with private fields.** `Curve`, `Token`,
|
|
586
|
-
`Launchpad` and `PlatformRegistry` are nominally typed, so nothing can be
|
|
587
|
-
assigned to them — a test cannot construct a stand-in, and there is no seam to
|
|
588
|
-
substitute one. That is why `src/sdk-port.ts` exists: a set of interfaces the
|
|
589
|
-
SDK's handles already satisfy, wrapping nothing and computing nothing, so that a
|
|
590
|
-
fake can drive every tool through the states that matter and are hardest to
|
|
591
|
-
reach on a real chain (a buy that graduates, a curve that graduated and never
|
|
592
|
-
migrated, a cost that moved between the quote and the order). If the SDK exposed
|
|
593
|
-
interfaces alongside its classes, that file would be a re-export.
|
|
594
|
-
|
|
595
|
-
**The SDK has no reader for a pool's price.** A pool quote is the real fill, but
|
|
596
|
-
"how far is that from the pool's price" needs the pool's price, and there is no
|
|
597
|
-
`slot0`/`sqrtPriceX96` read anywhere in `@arcnow/sdk`. Reading the PoolManager's
|
|
598
|
-
storage here would be the second, unpinned copy of chain code this server exists
|
|
599
|
-
not to have. So the spot price is the SDK's own quote of a tiny probe buy with
|
|
600
|
-
both fees taken out — accurate to far below the printed digits, one extra
|
|
601
|
-
`eth_call`, and labelled as what it is. A `pool.spotPrice()` belongs in the SDK.
|
|
602
|
-
The pool's LP fee is likewise not in a quote; it is read from `pool.key()`.
|
|
603
|
-
|
|
604
|
-
**Fixed upstream: a revert inside the pool.** A revert from inside the fee hook,
|
|
605
|
-
or a failed transfer out of the PoolManager, arrives wrapped in Uniswap v4's
|
|
606
|
-
`WrappedError`. It used to surface as a bare `UnknownRevert`. Since sdk#7 the SDK
|
|
607
|
-
unwraps it and names the failed transfer (`NativeTransferFailed` /
|
|
608
|
-
`ERC20TransferFailed`), so this server only shows what the SDK decoded.
|
|
609
|
-
|
|
610
|
-
**A pool sell quote needs a real holder.** The SDK overrides the router
|
|
611
|
-
allowance for a sell simulation but deliberately not the balance, so
|
|
612
|
-
`arcnow_quote_sell` on a pool needs `holder` on a read-only server. That is the
|
|
613
|
-
right call, and it is why that argument exists.
|
|
614
|
-
|
|
615
|
-
Nothing else got in the way. In particular, the SDK's four amount types, its
|
|
616
|
-
refusal to default a slippage floor, its explicit `gasLimit`, its
|
|
617
|
-
`migratedInThisTransaction` flag and its by-selector error decoding are each the
|
|
618
|
-
reason a corresponding class of mistake is not reachable from here.
|
|
545
|
+
credential-shaped field names and asserted strict; passing `privateKey` or
|
|
546
|
+
`mnemonic` anyway is refused, named, answered with "rotate it", and sends
|
|
547
|
+
nothing.
|
|
548
|
+
- **A write refuses without the opt-in**, names `--allow-writes` and
|
|
549
|
+
`ARCNOW_PRIVATE_KEY`, and has sent nothing.
|
|
550
|
+
- **Both spend ceilings stop a transaction**, on a curve and in a pool, per
|
|
551
|
+
quote token; a typo'd cap variable refuses to start; a USDC cap does not apply
|
|
552
|
+
to EURC or the reverse.
|
|
553
|
+
- **Every amount is labelled with its own quote**, an input with more decimals
|
|
554
|
+
than its quote is refused, and an ERC-20 approve is reported whether it was
|
|
555
|
+
sent or not.
|
|
556
|
+
- **The gas trap is handled**, the venues are told apart (a stranded token is
|
|
557
|
+
refused everywhere with `arcnow_migrate` named), the sell approval is never
|
|
558
|
+
sent without `approveRouter: true` and never for more than the amount sold,
|
|
559
|
+
and the reports say true things: fees broken out, average fill price apart
|
|
560
|
+
from spot, graduated apart from migrated.
|
|
561
|
+
- **The wire works**: a real MCP client against a real MCP server over an
|
|
562
|
+
in-memory transport.
|
|
563
|
+
|
|
564
|
+
What the unit suite cannot prove — that the built server, driven by a real MCP
|
|
565
|
+
client over stdio, lists the quote tokens, launches, buys and sells a token on its
|
|
566
|
+
curve and in its Uniswap v4 pool, and launches and buys a token priced in EURC
|
|
567
|
+
under the EURC cap, against arcnow.io's real contracts, every quote equal to its
|
|
568
|
+
fill to the wei — the maintainers prove before every release, on an anvil fork of
|
|
569
|
+
Arc testnet with those contracts deployed onto it. A fork re-executes with anvil's
|
|
570
|
+
EVM, so Arc's own execution semantics are outside even that; and no test can
|
|
571
|
+
prove that a tool description is true or that a model behaves — every guard here
|
|
572
|
+
is a bound on damage, not a guarantee of judgement.
|
|
619
573
|
|
|
620
574
|
---
|
|
621
575
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcnow/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "An MCP server for arcnow.io on Arc: read the curves, quote a trade, and — only when the operator opts in — launch, buy and sell.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"start": "node dist/index.js"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@arcnow/sdk": "
|
|
33
|
+
"@arcnow/sdk": "0.1.3",
|
|
34
34
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
35
35
|
"viem": "^2.56.3",
|
|
36
36
|
"zod": "^4.6.2"
|