@arcnow/mcp 0.1.1 → 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.
Files changed (2) hide show
  1. package/README.md +152 -207
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -22,8 +22,45 @@ npx -y @arcnow/mcp # read-only. The default, and the useful par
22
22
  npx -y @arcnow/mcp --allow-writes # can spend, if ARCNOW_PRIVATE_KEY is in the environment.
23
23
  ```
24
24
 
25
- Or tell an MCP client to. Claude Desktop, Cursor, Claude Code and the like all
26
- take this shape in their MCP server configuration:
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
38
+
39
+ ```sh
40
+ claude mcp add --env ARCNOW_MCP_NETWORK=arc-mainnet --scope user arcnow -- npx -y @arcnow/mcp
41
+ ```
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
+ }
57
+ ```
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`:
27
64
 
28
65
  ```json
29
66
  {
@@ -31,17 +68,89 @@ take this shape in their MCP server configuration:
31
68
  "arcnow": {
32
69
  "command": "npx",
33
70
  "args": ["-y", "@arcnow/mcp"],
34
- "env": { "ARCNOW_MCP_NETWORK": "arc-testnet" }
71
+ "env": { "ARCNOW_MCP_NETWORK": "arc-mainnet" }
35
72
  }
36
73
  }
37
74
  }
38
75
  ```
39
76
 
40
- A writes-enabled client adds `"--allow-writes"` to `args` and puts the key
41
- **in a file**, never in the config: see [`examples/`](examples) for both shapes
42
- and [Configuration](#configuration) for every variable. To run from source
43
- instead, clone
44
- [arcnow-io/arcnow-io-mcp](https://github.com/arcnow-io/arcnow-io-mcp), then
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
45
154
  `npm ci && npm run build && node dist/index.js`; the SDK comes from npm.
46
155
 
47
156
  ---
@@ -417,214 +526,50 @@ surface**: every module, the generated ABIs that encode every call, the
417
526
  published this file hashed its sources file by file and the gate recompiled its
418
527
  `dist/`; the integrity replaces all of that.)
419
528
 
420
- `scripts/check-pins.sh` (run straight after the install by `scripts/preflight.sh`)
421
- enforces four things:
422
-
423
- 1. `package.json` depends on the SDK at **exactly** `sdk.version`. A range, a
424
- `file:` path or another number fails, by name.
425
- 2. `package-lock.json`'s entry for it carries that version, resolves to the npm
426
- registry, and its `integrity` **is** `sdk.integrity` — the link between the
427
- version and the bytes; npm refuses to install a tarball that does not hash to
428
- it.
429
- 3. `node_modules/@arcnow/sdk/package.json` is that version — what the server
430
- actually compiles against and imports.
431
- 4. The registry has that version and serves it with that `dist.integrity`, so
432
- the pinned bytes are the published bytes. This needs the network: unreachable
433
- is a **warning** (a laptop on a train is not a broken pin) unless
434
- `ARCNOW_REQUIRE_REGISTRY=1`, which CI sets; a 404 is always a failure.
435
-
436
- Together: the manifest asks for one version, the lockfile binds it to one
437
- tarball, `node_modules` holds it, and the registry says those are the bytes it
438
- serves under that number. `test/unit/check-pins.test.ts` builds real roots with
439
- a fake `npm` on `PATH` and breaks each link on purpose — a range, a path, a
440
- foreign tarball, a stale install, a version the registry never had, a registry
441
- that cannot be reached.
442
-
443
- Moving the pin is a commit of its own: `npm install @arcnow/sdk@X.Y.Z
444
- --save-exact`, `npm ci`, `scripts/check-pins.sh --record` — which records the
445
- version, the lockfile's integrity and the tag, and refuses a range or a version
446
- the registry does not have — update `sdk.why`, run preflight, and say what
447
- changed in the SDK surface and what it meant for the tools. A tool description
448
- that still promises what an older SDK did is a model quoting a wrong price.
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.
449
532
 
450
533
  ---
451
534
 
452
- ## Running the gates
535
+ ## Testing
453
536
 
454
537
  ```bash
455
- ./scripts/preflight.sh # install, pins, lint, typecheck, build, tests, fork proof
456
- ./scripts/preflight.sh --no-install
457
- ./scripts/preflight.sh --no-chain # everything but the fork proof
458
- ./scripts/check-pins.sh # just the pin
459
- npm test # just the unit suite — no chain, no container, no key
460
- npm run test:fork # just the fork proof — needs Docker
538
+ npm test # the unit suite: no chain, no container, no key
461
539
  ```
462
540
 
463
- `.github/workflows/ci.yml` is `workflow_dispatch:` only, the same decision every
464
- repository in this org has made. **`scripts/preflight.sh` is the gate**; the
465
- workflow is a transcription of it, kept because a clean-checkout run is the one
466
- thing a local run cannot prove.
467
-
468
- Everything but the fork proof needs nothing beyond `npm ci`: the SDK comes from
469
- npm. The fork proof is the maintainers' **private infrastructure**: it starts one
470
- container, an anvil fork of Arc testnet from the Foundry image `pins.json` pins,
471
- labelled `io.arcnow.mcp.test`, and deploys arcnow.io's 3.x multi-quote contracts
472
- onto it with the private `arcnow-io/sdk` checkout's `scripts/fork-deploy-stack.sh`
473
- (`ARCNOW_SDK_DIR`, default `../sdk`). Arc testnet now runs a 3.x stack of its own,
474
- deployed on 2026-09-15, but the fork is taken at a pinned, already-cached block
475
- from before that, where the live contracts are the 2.x ones the SDK refuses — and
476
- a proof that deploys its own stack does not depend on what happens to be live
477
- anyway. That also needs `ARCNOW_CONTRACTS_DIR`, a checkout
478
- of arcnow-io/contracts at the commit that SDK checkout's `pins.json` names, and
479
- forge at the release it names. Its harness —
480
- copied from `arcnow-io/sdk`'s, not imported, because that repository's tests are
481
- not part of the pinned surface — removes it on every exit path, sweeps only its
482
- own label, and touches nothing else on a shared daemon.
483
-
484
- ### 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:
485
543
 
486
544
  - **A key is never an argument.** Every published schema is walked for
487
- credential-shaped field names and for descriptions that ask for one; every
488
- schema is asserted strict; and passing `privateKey` or `mnemonic` anyway is
489
- asserted to be refused, named, answered with "rotate it", and to leave the
490
- port untouched.
491
- - **A write refuses without the opt-in.** Every write tool, called on a
492
- read-only server, is asserted to error, to name `--allow-writes` and
493
- `ARCNOW_PRIVATE_KEY`, to tell the model not to ask a user for a key — and,
494
- the assertion that carries the weight, **to have sent nothing**.
495
- - **Both spend ceilings stop a transaction rather than annotate one**, on a
496
- curve and in a pool alike, **per quote token**: a USDC-only configuration
497
- refuses a EURC buy and launch; an EURC cap allows up to it and refuses a raw
498
- unit above; a typo'd cap variable refuses to start; a USDC cap does not apply
499
- to EURC or the reverse; a quote the network does not list is refused.
500
- - **Every amount is labelled with its own quote** native, a 6-decimal and an
501
- 18-decimal ERC-20 a pool's quote is the SDK's in either currency order, an
502
- input with more decimals than its quote is refused, and an ERC-20 spend's
503
- approve is reported whether it was sent or not.
504
- - **The gas trap is handled**, for a buy and for a launch whose initial buy
505
- graduates the curve.
506
- - **The venues are told apart.** A migrated token is quoted and traded in its
507
- pool through `client.trade`; a stranded one is refused everywhere with
508
- `arcnow_migrate` named; a curve-only parameter in a pool and a pool-only one
509
- on a curve are refused with nothing sent.
510
- - **The sell approval is gated and disclosed.** Never sent without
511
- `approveRouter: true`, never for more than the amount sold, not sent when the
512
- allowance already covers the sale, reported in full, and still reported when
513
- the sell after it fails.
514
- - **The reports say true things.** Fees broken out, a pool's two charges apart,
515
- an absent referrer's share named as going to the platform, the average fill
516
- price distinguished from the spot price, graduated distinguished from
517
- migrated, an undecodable pool revert explained cautiously rather than bare.
518
- - **The pin gate fails when it should.** See above.
519
- - **The wire works.** A real MCP client against a real MCP server over an
520
- in-memory transport, and — in the fork proof — over stdio against the built
521
- server.
522
- - **Against the 3.x contracts deployed onto a fork of Arc testnet**
523
- (`test/fork/mcp.fork.test.ts`), the built server — started with
524
- `ARCNOW_MCP_NETWORK_FILE` naming the deployed stack — driven by a real MCP
525
- client lists the quote tokens from the deployed registry in at most three
526
- `eth_call`s with each one's cap, launches a native USDC
527
- token on the launchpad, reads it
528
- back with its quote token, `r0Wad` and `y0Wad`, buys it on its curve,
529
- watches an impersonated Arc account graduate it, then quotes, buys and sells it
530
- in its Uniswap v4 pool through the live router — and sells a second token back
531
- to its curve — and launches a token in EURC, with its exact approve and exactly
532
- the total pulled, buys it in EURC, and has a buy above the 50 EURC cap refused
533
- with nothing sent. Every figure is checked against the chain with viem, not through
534
- the server: every quote equals its fill, to the wei; exactly the amount and
535
- the gas left the signer; the sell's approval is one `Approval` log to the
536
- router for exactly the amount sold, used up by the sale; the recipient is paid
537
- exactly what the report says; a swap's fee payout matches the hook's
538
- `FeesDistributed` log; the refusals leave the signer's nonce where it was; and
539
- the USDC ERC-20 predeploy comes back as `AddressIsNotACurve`. The server's RPC
540
- goes through a counting proxy, and the per-call request counts are printed.
541
- - **One curve, and nothing else priced.** Against the fake, whose version
542
- checks are the SDK's own `assertCurveVersion` and `assertPlatformVersion`: no
543
- report names a curve kind or a stack, an `@1` curve, platform or fee hook is
544
- refused by name with nothing quoted or sent, and the launch scan reads the one
545
- launchpad from its deployment block.
546
-
547
- ### What they do not prove
548
-
549
- - **Not Arc's own execution semantics.** A fork re-executes locally with
550
- anvil's EVM and disagrees with Arc about blocklisted transfers, EIP-1153, the
551
- EIP-7708 system emitter and burn-to-zero without saying so.
552
- - **Not a EURC pool against a chain.** The fork proof launches a EURC token,
553
- buys it on its curve with the exact approve, and holds the 50 EURC cap; a
554
- EURC token's pool is unit-tested against the fake.
555
- - **Not every path against a chain.** The fork proof covers a launch, curve
556
- trades, graduation and pool trades. A
557
- migration through `arcnow_migrate`, a graduating buy through `arcnow_buy` (the
558
- proof graduates its token through the SDK, so that nothing credits the
559
- server's signer), a token that graduated but never migrated — there is none on
560
- Arc testnet to fork — and a curve, platform or hook whose version the SDK
561
- refuses are
562
- unit-tested here against the fake, and proved against a chain, where they are
563
- at all, by the SDK's own fork suite. If you change how a tool calls the SDK,
564
- run the SDK's preflight too.
565
- - **Not that the tool descriptions are true.** Those are prose, read by a model
566
- deciding whether to spend somebody's money. When the pin moves, that is a
567
- person's job.
568
- - **Not that a model behaves.** Every guard in this server is a bound on
569
- damage, not a guarantee of judgement.
570
-
571
- ---
572
-
573
- ## What made a clean surface awkward
574
-
575
- Five things, all worth fixing upstream rather than here — and one of them now is.
576
-
577
- **The SDK cannot enumerate tokens.** There is no `recentLaunches()` anywhere in
578
- `@arcnow/sdk`, and its `networks.json` explains why a preset cannot carry one: a
579
- token and its curve come from the `Launched` log, one pair per launch, and there
580
- are as many as there have been launches. But "show me the recent tokens" is the
581
- first thing anybody asks an assistant, so `arcnow_list_tokens` reads that log
582
- directly — with the SDK's own pinned ABI, through the SDK's own configured
583
- client, at the SDK's own launchpad address, adding nothing but the scan. It is
584
- the only place in this repository that talks to the chain outside an SDK method,
585
- and it is in `src/sdk-port.ts` with a comment saying so. A
586
- `launchpad.recentLaunches()` belongs in the SDK, where the forked-chain suite
587
- could test it against a real chain; here it is tested against a fake.
588
-
589
- Because the scan is a bounded walk backwards from the tip rather than an index,
590
- `arcnow_list_tokens` reports the block window it covered and says plainly when
591
- it stopped on its budget with history unread. An assistant must not conclude a
592
- token does not exist from a tool that only ever saw a window.
593
-
594
- **The SDK's handles are classes with private fields.** `Curve`, `Token`,
595
- `Launchpad` and `PlatformRegistry` are nominally typed, so nothing can be
596
- assigned to them — a test cannot construct a stand-in, and there is no seam to
597
- substitute one. That is why `src/sdk-port.ts` exists: a set of interfaces the
598
- SDK's handles already satisfy, wrapping nothing and computing nothing, so that a
599
- fake can drive every tool through the states that matter and are hardest to
600
- reach on a real chain (a buy that graduates, a curve that graduated and never
601
- migrated, a cost that moved between the quote and the order). If the SDK exposed
602
- interfaces alongside its classes, that file would be a re-export.
603
-
604
- **The SDK has no reader for a pool's price.** A pool quote is the real fill, but
605
- "how far is that from the pool's price" needs the pool's price, and there is no
606
- `slot0`/`sqrtPriceX96` read anywhere in `@arcnow/sdk`. Reading the PoolManager's
607
- storage here would be the second, unpinned copy of chain code this server exists
608
- not to have. So the spot price is the SDK's own quote of a tiny probe buy with
609
- both fees taken out — accurate to far below the printed digits, one extra
610
- `eth_call`, and labelled as what it is. A `pool.spotPrice()` belongs in the SDK.
611
- The pool's LP fee is likewise not in a quote; it is read from `pool.key()`.
612
-
613
- **Fixed upstream: a revert inside the pool.** A revert from inside the fee hook,
614
- or a failed transfer out of the PoolManager, arrives wrapped in Uniswap v4's
615
- `WrappedError`. It used to surface as a bare `UnknownRevert`. Since sdk#7 the SDK
616
- unwraps it and names the failed transfer (`NativeTransferFailed` /
617
- `ERC20TransferFailed`), so this server only shows what the SDK decoded.
618
-
619
- **A pool sell quote needs a real holder.** The SDK overrides the router
620
- allowance for a sell simulation but deliberately not the balance, so
621
- `arcnow_quote_sell` on a pool needs `holder` on a read-only server. That is the
622
- right call, and it is why that argument exists.
623
-
624
- Nothing else got in the way. In particular, the SDK's four amount types, its
625
- refusal to default a slippage floor, its explicit `gasLimit`, its
626
- `migratedInThisTransaction` flag and its by-selector error decoding are each the
627
- 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.
628
573
 
629
574
  ---
630
575
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcnow/mcp",
3
- "version": "0.1.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",