@402md/mcp 0.1.0

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 ADDED
@@ -0,0 +1,635 @@
1
+ # @402md/mcp
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@402md/mcp)](https://www.npmjs.com/package/@402md/mcp)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](https://www.typescriptlang.org/)
7
+ [![MCP](https://img.shields.io/badge/MCP-compatible-purple)](https://modelcontextprotocol.io)
8
+
9
+ MCP server that transforms [SKILL.md](https://402.md) files into executable tools for AI agents. Point to any skill — URL, local file, or marketplace name — and the server parses it, auto-pays via x402, and returns the result.
10
+
11
+ ## Table of Contents
12
+
13
+ - [Quick Start](#quick-start)
14
+ - [Networks](#networks)
15
+ - [Wallet Setup](#wallet-setup)
16
+ - [How Payments Work](#how-payments-work)
17
+ - [Claude Desktop Configuration](#claude-desktop-configuration)
18
+ - [Environment Variables](#environment-variables)
19
+ - [Wallet File](#wallet-file)
20
+ - [Budget & Spending Limits](#budget--spending-limits)
21
+ - [Tools](#tools)
22
+ - [Modes](#modes)
23
+ - [Skill Resolution](#skill-resolution)
24
+ - [Architecture](#architecture)
25
+ - [Examples](#examples)
26
+ - [Development](#development)
27
+ - [License](#license)
28
+
29
+ ## Quick Start
30
+
31
+ ```bash
32
+ npx @402md/mcp
33
+ ```
34
+
35
+ Or install globally:
36
+
37
+ ```bash
38
+ npm install -g @402md/mcp
39
+ 402md-mcp
40
+ ```
41
+
42
+ The server starts in **read-only mode** by default. You can browse and inspect skills immediately. To execute paid endpoints, configure a wallet (see [Wallet Setup](#wallet-setup)).
43
+
44
+ ## Networks
45
+
46
+ The server supports four networks across two blockchain ecosystems:
47
+
48
+ ### Stellar
49
+
50
+ | Network | ID | Use Case | USDC Contract |
51
+ |---------|-----|----------|---------------|
52
+ | **Stellar Mainnet** | `stellar` | Production payments with real USDC | Native Stellar USDC (Centre) |
53
+ | **Stellar Testnet** | `stellar-testnet` | Development & testing with free testnet USDC | Testnet USDC |
54
+
55
+ **Stellar** is the default and preferred network. It offers sub-second finality, near-zero fees (~0.00001 XLM per tx), and native USDC support.
56
+
57
+ - **Testnet faucet**: Use [Stellar Laboratory](https://laboratory.stellar.org/#account-creator?network=test) to create and fund testnet accounts.
58
+ - **Mainnet**: Fund your account via any Stellar DEX, exchange, or on-ramp that supports USDC on Stellar.
59
+
60
+ ### EVM (Base)
61
+
62
+ | Network | ID | Use Case | USDC Contract |
63
+ |---------|-----|----------|---------------|
64
+ | **Base Mainnet** | `base` | Production payments on Base L2 | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
65
+ | **Base Sepolia** | `base-sepolia` | Development & testing on Base testnet | Sepolia USDC |
66
+
67
+ **Base** is an Ethereum L2 with low gas fees and fast confirmations.
68
+
69
+ - **Sepolia faucet**: Get testnet ETH from [Alchemy Faucet](https://www.alchemy.com/faucets/base-sepolia) or [Coinbase Faucet](https://faucet.quicknode.com/base/sepolia) (needed for gas). Then bridge or mint testnet USDC.
70
+ - **Mainnet**: Bridge USDC to Base from Ethereum, or buy directly on Base via Coinbase or any supported on-ramp.
71
+
72
+ ### Choosing a Network
73
+
74
+ - **Just getting started?** Use `stellar-testnet` — no real money, instant setup with `create_wallet`.
75
+ - **Testing EVM skills?** Use `base-sepolia` — free testnet, good for EVM-specific endpoints.
76
+ - **Production?** Use `stellar` (lower fees) or `base` depending on what the skill accepts.
77
+
78
+ The server automatically selects the best compatible network when calling a skill. If a skill supports multiple networks and your wallet has both keys configured, Stellar is preferred.
79
+
80
+ ## Wallet Setup
81
+
82
+ There are three ways to configure a wallet, listed by priority (highest first):
83
+
84
+ ### Option 1: Environment Variables (recommended for production)
85
+
86
+ ```bash
87
+ # Stellar
88
+ export STELLAR_SECRET="SCZANGBA5YHTNYVVV3C7CAZMCLXPILHSE6PGYV2FHHUQ5DGQJWRZ4GXT"
89
+ export NETWORK="stellar-testnet"
90
+
91
+ # EVM (Base)
92
+ export EVM_PRIVATE_KEY="0x4c0883a69102937d6231471b5dbb6204fe512961708279f23efb3c0c90..."
93
+ export NETWORK="base-sepolia"
94
+
95
+ # Both (FULL mode)
96
+ export STELLAR_SECRET="S..."
97
+ export EVM_PRIVATE_KEY="0x..."
98
+ export NETWORK="stellar" # default network when both are available
99
+ ```
100
+
101
+ ### Option 2: `create_wallet` Tool (recommended for development)
102
+
103
+ If no wallet is configured, ask your AI agent to use the `create_wallet` tool:
104
+
105
+ ```
106
+ "Create a new wallet on stellar-testnet"
107
+ ```
108
+
109
+ This generates a keypair and saves it to `~/.402md/wallet.json`. The server reloads automatically.
110
+
111
+ **Important**: `create_wallet` refuses to run if a wallet is already configured. To replace an existing wallet, delete `~/.402md/wallet.json` manually first.
112
+
113
+ ### Option 3: Wallet File (manual)
114
+
115
+ Create `~/.402md/wallet.json` manually:
116
+
117
+ ```json
118
+ {
119
+ "stellarSecret": "SCZANGBA5YHTNYVVV3C7CAZMCLXPILHSE6PGYV2FHHUQ5DGQJWRZ4GXT",
120
+ "evmPrivateKey": "0x4c0883a69102937d6231471b5dbb6204fe512961708279f23efb3c0c90...",
121
+ "network": "stellar-testnet",
122
+ "createdAt": "2026-01-15T10:30:00.000Z"
123
+ }
124
+ ```
125
+
126
+ The file is created with `0o600` permissions (owner read/write only). The directory `~/.402md/` is created with `0o700`.
127
+
128
+ ### Generating Keys Manually
129
+
130
+ **Stellar**:
131
+ ```bash
132
+ # Using stellar-sdk in Node.js
133
+ node -e "const { Keypair } = require('@stellar/stellar-sdk'); const kp = Keypair.random(); console.log('Secret:', kp.secret()); console.log('Public:', kp.publicKey())"
134
+ ```
135
+
136
+ **EVM**:
137
+ ```bash
138
+ # Using viem in Node.js
139
+ node -e "const { generatePrivateKey, privateKeyToAccount } = require('viem/accounts'); const pk = generatePrivateKey(); const acc = privateKeyToAccount(pk); console.log('Private Key:', pk); console.log('Address:', acc.address)"
140
+
141
+ # Or using openssl
142
+ openssl rand -hex 32 | sed 's/^/0x/'
143
+ ```
144
+
145
+ ## How Payments Work
146
+
147
+ The payment flow is handled automatically by the x402 protocol:
148
+
149
+ ```
150
+ Agent calls use_skill("my-skill", "/api/generate")
151
+
152
+ ├─ 1. Resolve skill → parse SKILL.md manifest
153
+ ├─ 2. Validate manifest (schema, required fields)
154
+ ├─ 3. Check budget limits (per-call & daily)
155
+ ├─ 4. Select compatible network (skill networks ∩ wallet networks)
156
+ ├─ 5. Create PaymentClient for that network
157
+ ├─ 6. client.fetch(url) → x402 auto-payment:
158
+ │ a. First request returns 402 Payment Required
159
+ │ b. Client signs USDC payment (on-chain)
160
+ │ c. Retries request with payment proof header
161
+ │ d. Server verifies payment, returns response
162
+ ├─ 7. Record spending (amount, skill, endpoint, network)
163
+ └─ 8. Return response to agent
164
+ ```
165
+
166
+ The agent never sees the payment mechanics — it just calls `use_skill` and gets a result. All USDC amounts use 6 decimal places (e.g., `"0.050000"`).
167
+
168
+ ### What Happens If the Endpoint Fails?
169
+
170
+ If payment succeeds but the endpoint returns an error (4xx/5xx), the spending is still recorded (the payment was already made on-chain) but the tool returns `isError: true` with a clear message:
171
+
172
+ ```
173
+ Endpoint returned 500. Payment was sent but the request failed.
174
+
175
+ {"error": "Internal server error"}
176
+ ```
177
+
178
+ This lets the agent (or user) know to contact the skill provider.
179
+
180
+ ## Claude Desktop Configuration
181
+
182
+ Add to your `claude_desktop_config.json`:
183
+
184
+ ### Stellar Testnet (getting started)
185
+
186
+ ```json
187
+ {
188
+ "mcpServers": {
189
+ "402md": {
190
+ "command": "npx",
191
+ "args": ["-y", "@402md/mcp"],
192
+ "env": {
193
+ "STELLAR_SECRET": "SCZANGBA5YHTNYVVV3C7CAZMCLXPILHSE6PGYV2FHHUQ5DGQJWRZ4GXT",
194
+ "NETWORK": "stellar-testnet",
195
+ "MAX_PER_CALL": "0.10",
196
+ "MAX_PER_DAY": "5.00"
197
+ }
198
+ }
199
+ }
200
+ }
201
+ ```
202
+
203
+ ### Base Sepolia (EVM testing)
204
+
205
+ ```json
206
+ {
207
+ "mcpServers": {
208
+ "402md": {
209
+ "command": "npx",
210
+ "args": ["-y", "@402md/mcp"],
211
+ "env": {
212
+ "EVM_PRIVATE_KEY": "0x4c0883a69102937d623147...",
213
+ "NETWORK": "base-sepolia",
214
+ "MAX_PER_CALL": "0.10",
215
+ "MAX_PER_DAY": "5.00"
216
+ }
217
+ }
218
+ }
219
+ }
220
+ ```
221
+
222
+ ### Production (both networks)
223
+
224
+ ```json
225
+ {
226
+ "mcpServers": {
227
+ "402md": {
228
+ "command": "npx",
229
+ "args": ["-y", "@402md/mcp"],
230
+ "env": {
231
+ "STELLAR_SECRET": "S...",
232
+ "EVM_PRIVATE_KEY": "0x...",
233
+ "NETWORK": "stellar",
234
+ "MAX_PER_CALL": "1.00",
235
+ "MAX_PER_DAY": "50.00"
236
+ }
237
+ }
238
+ }
239
+ }
240
+ ```
241
+
242
+ ### Read-only (no wallet)
243
+
244
+ ```json
245
+ {
246
+ "mcpServers": {
247
+ "402md": {
248
+ "command": "npx",
249
+ "args": ["-y", "@402md/mcp"]
250
+ }
251
+ }
252
+ }
253
+ ```
254
+
255
+ ## Environment Variables
256
+
257
+ | Variable | Default | Description |
258
+ |----------|---------|-------------|
259
+ | `STELLAR_SECRET` | — | Stellar secret key (starts with `S`). Enables Stellar payments. |
260
+ | `EVM_PRIVATE_KEY` | — | EVM private key (hex, starts with `0x`). Enables Base payments. |
261
+ | `NETWORK` | `stellar` | Default network: `stellar`, `stellar-testnet`, `base`, `base-sepolia` |
262
+ | `MAX_PER_CALL` | `0.10` | Maximum USDC allowed per individual skill call |
263
+ | `MAX_PER_DAY` | `20.00` | Maximum USDC allowed per calendar day |
264
+ | `REGISTRY_URL` | `https://api.402.md` | 402.md marketplace API base URL |
265
+
266
+ Environment variables always take priority over the wallet file (`~/.402md/wallet.json`).
267
+
268
+ ## Wallet File
269
+
270
+ Located at `~/.402md/wallet.json`. Created automatically by `create_wallet` or manually.
271
+
272
+ ```json
273
+ {
274
+ "stellarSecret": "S...",
275
+ "evmPrivateKey": "0x...",
276
+ "network": "stellar-testnet",
277
+ "createdAt": "2026-01-15T10:30:00.000Z"
278
+ }
279
+ ```
280
+
281
+ - **Permissions**: `0o600` (read/write owner only)
282
+ - **Directory**: `~/.402md/` with `0o700`
283
+ - **Merge behavior**: `saveWalletConfig` merges new fields with existing data, so adding an EVM key won't erase an existing Stellar key
284
+ - **Priority**: env vars > wallet file > defaults
285
+
286
+ ## Budget & Spending Limits
287
+
288
+ The server enforces two spending limits:
289
+
290
+ | Limit | Default | Env Variable | Description |
291
+ |-------|---------|--------------|-------------|
292
+ | **Per-call** | `0.10 USDC` | `MAX_PER_CALL` | Maximum for a single skill invocation |
293
+ | **Per-day** | `20.00 USDC` | `MAX_PER_DAY` | Maximum total across all calls in a calendar day |
294
+
295
+ Budget is checked **before** each payment. If a call would exceed either limit, the request is rejected with an error (no payment is made).
296
+
297
+ Use `spending_summary` to check current spending and configured limits:
298
+
299
+ ```json
300
+ {
301
+ "spentToday": "1.2500",
302
+ "spentSession": "0.3000",
303
+ "limits": {
304
+ "maxPerCall": "1.00",
305
+ "maxPerDay": "50.00"
306
+ },
307
+ "recentPayments": [
308
+ {
309
+ "skillName": "image-gen",
310
+ "endpoint": "/api/generate",
311
+ "amount": "0.15",
312
+ "network": "stellar-testnet",
313
+ "timestamp": "2026-01-15T14:30:00.000Z"
314
+ }
315
+ ]
316
+ }
317
+ ```
318
+
319
+ ## Tools
320
+
321
+ ### `use_skill`
322
+
323
+ Execute a paid SKILL.md endpoint. Resolves the skill, validates the manifest, auto-pays via x402, and returns the result.
324
+
325
+ | Parameter | Type | Required | Description |
326
+ |-----------|------|----------|-------------|
327
+ | `skill` | `string` | Yes | Skill source: URL, local file path, or marketplace name |
328
+ | `endpoint` | `string` | No | Endpoint path (defaults to first endpoint in manifest) |
329
+ | `method` | `string` | No | HTTP method: `GET`, `POST`, `PUT`, `DELETE`, `PATCH` (defaults to spec) |
330
+ | `body` | `string` | No | Request body as JSON string |
331
+ | `headers` | `object` | No | Additional request headers |
332
+
333
+ **Requires**: configured wallet (`STELLAR_ONLY`, `EVM_ONLY`, or `FULL` mode).
334
+
335
+ **Validation**: the manifest is validated before any payment is attempted. If the SKILL.md is invalid, the tool returns an error without spending.
336
+
337
+ **Examples**:
338
+
339
+ ```
340
+ # By marketplace name
341
+ use_skill({ skill: "image-gen", body: '{"prompt": "a sunset"}' })
342
+
343
+ # By URL
344
+ use_skill({ skill: "https://example.com/SKILL.md", endpoint: "/api/v2/generate" })
345
+
346
+ # By local file
347
+ use_skill({ skill: "./skills/my-skill/SKILL.md", method: "POST", body: '{"input": "hello"}' })
348
+
349
+ # With custom headers
350
+ use_skill({ skill: "translate", headers: { "X-Target-Lang": "pt-BR" }, body: '{"text": "hello"}' })
351
+ ```
352
+
353
+ ### `read_skill`
354
+
355
+ Read and parse a SKILL.md without executing it. Returns full manifest details, endpoints, pricing, and validation results. Works in any mode (including `READ_ONLY`).
356
+
357
+ | Parameter | Type | Required | Description |
358
+ |-----------|------|----------|-------------|
359
+ | `skill` | `string` | Yes | Skill source: URL, local file path, or marketplace name |
360
+
361
+ **Returns**: name, description, version, author, base URL, endpoints (with pricing and schemas), payment info (networks, asset, payTo), tags, category, validation result, and current wallet mode.
362
+
363
+ ### `check_balance`
364
+
365
+ Check the USDC balance and address for the configured wallet.
366
+
367
+ **No parameters.**
368
+
369
+ **Returns**:
370
+
371
+ ```json
372
+ {
373
+ "address": "GCXZ...",
374
+ "balance": "45.250000 USDC",
375
+ "network": "stellar-testnet",
376
+ "mode": "STELLAR_ONLY"
377
+ }
378
+ ```
379
+
380
+ ### `spending_summary`
381
+
382
+ View spending summary: amounts spent today and in the current session, budget limits, and the last 10 payments.
383
+
384
+ **No parameters.**
385
+
386
+ **Returns**: see [Budget & Spending Limits](#budget--spending-limits) for output format.
387
+
388
+ ### `create_wallet`
389
+
390
+ Generate a new wallet keypair and save it to `~/.402md/wallet.json`.
391
+
392
+ | Parameter | Type | Required | Description |
393
+ |-----------|------|----------|-------------|
394
+ | `network` | `string` | Yes | `stellar`, `stellar-testnet`, `base`, or `base-sepolia` |
395
+
396
+ **Network-to-wallet mapping**:
397
+ - `stellar` or `stellar-testnet` → generates a Stellar keypair (`Keypair.random()`)
398
+ - `base` or `base-sepolia` → generates an EVM keypair (`generatePrivateKey()`)
399
+
400
+ **Safety**:
401
+ - Refuses to run if a wallet is already configured (any mode except `READ_ONLY`).
402
+ - To replace an existing wallet, delete `~/.402md/wallet.json` first.
403
+ - New keys are merged with existing config — adding an EVM wallet won't erase a Stellar key.
404
+
405
+ **Returns**:
406
+
407
+ ```json
408
+ {
409
+ "type": "stellar",
410
+ "publicKey": "GCXZ...",
411
+ "network": "stellar-testnet",
412
+ "savedTo": "/Users/you/.402md/wallet.json",
413
+ "note": "Fund this address on the Stellar testnet faucet before use."
414
+ }
415
+ ```
416
+
417
+ ### `search_skills`
418
+
419
+ Search the 402.md marketplace for available skills.
420
+
421
+ | Parameter | Type | Required | Description |
422
+ |-----------|------|----------|-------------|
423
+ | `query` | `string` | Yes | Search keywords |
424
+ | `maxPrice` | `string` | No | Max price per call in USDC (e.g., `"0.50"`) |
425
+ | `category` | `string` | No | Filter by category |
426
+
427
+ **Returns**: up to 20 results with name, description, min price, and supported networks.
428
+
429
+ ## Modes
430
+
431
+ The server operates in one of four modes based on which keys are available:
432
+
433
+ | Mode | Condition | Available Tools |
434
+ |------|-----------|-----------------|
435
+ | `READ_ONLY` | No keys configured | `read_skill`, `search_skills`, `create_wallet` |
436
+ | `STELLAR_ONLY` | `STELLAR_SECRET` set | All tools — pays via Stellar |
437
+ | `EVM_ONLY` | `EVM_PRIVATE_KEY` set | All tools — pays via Base |
438
+ | `FULL` | Both keys set | All tools — pays via best available network |
439
+
440
+ In `FULL` mode, when a skill supports both Stellar and EVM networks, Stellar is preferred (lower fees and faster finality).
441
+
442
+ ## Skill Resolution
443
+
444
+ The `skill` parameter in `use_skill` and `read_skill` accepts three formats:
445
+
446
+ | Format | Example | How It Resolves |
447
+ |--------|---------|-----------------|
448
+ | **URL** | `https://example.com/SKILL.md` | Fetches directly via HTTP |
449
+ | **Local file** | `./skills/my-skill/SKILL.md` | Reads from filesystem |
450
+ | **Marketplace name** | `image-gen` | Queries `{REGISTRY_URL}/api/v1/discovery/skills/{name}/skill-md` |
451
+
452
+ Local file detection: any source starting with `/`, `./`, `../`, or ending with `.md` is treated as a file path.
453
+
454
+ ## Architecture
455
+
456
+ ```
457
+ ┌──────────────────────────────────────────────────────┐
458
+ │ AI Agent (Claude, etc.) │
459
+ │ Calls MCP tools: use_skill, check_balance, etc. │
460
+ └──────────────┬───────────────────────────────────────┘
461
+ │ stdio (MCP protocol)
462
+ ┌──────────────▼───────────────────────────────────────┐
463
+ │ @402md/mcp Server │
464
+ │ │
465
+ │ ┌─────────────┐ ┌────────────┐ ┌───────────────┐ │
466
+ │ │ Use Tools │ │ Wallet │ │ Registry │ │
467
+ │ │ use_skill │ │ check_bal │ │ search_skills │ │
468
+ │ │ read_skill │ │ spending │ │ │ │
469
+ │ │ │ │ create_wal │ │ │ │
470
+ │ └──────┬──────┘ └─────┬──────┘ └───────┬───────┘ │
471
+ │ │ │ │ │
472
+ │ ┌──────▼──────┐ ┌─────▼──────┐ ┌───────▼───────┐ │
473
+ │ │ resolveSkill│ │ Spending │ │ fetch() │ │
474
+ │ │ validateSkil│ │ Tracker │ │ → Registry API│ │
475
+ │ │ selectNetwrk│ │ (budget) │ │ │ │
476
+ │ └──────┬──────┘ └────────────┘ └───────────────┘ │
477
+ │ │ │
478
+ │ ┌──────▼──────────────────────────────────────────┐ │
479
+ │ │ ClientCache → PaymentClient (@402md/x402) │ │
480
+ │ │ ├─ StellarClient (@stellar/stellar-sdk) │ │
481
+ │ │ └─ EvmClient (viem) │ │
482
+ │ └──────┬──────────────────────────────────────────┘ │
483
+ └─────────┼────────────────────────────────────────────┘
484
+ │ x402 payment protocol
485
+ ┌─────────▼────────────────────────────────────────────┐
486
+ │ Skill Provider (HTTP server) │
487
+ │ Returns 402 → receives payment proof → returns data │
488
+ └──────────────────────────────────────────────────────┘
489
+ ```
490
+
491
+ **Key patterns**:
492
+ - **Lazy client loading**: `ClientCache` creates `PaymentClient` instances on first use per network
493
+ - **Budget enforcement**: `SpendingTracker` wraps `BudgetTracker` from `@402md/x402` — checked before, recorded after
494
+ - **Smart network selection**: intersects skill's supported networks with wallet capabilities, prefers Stellar
495
+ - **Config reload**: `create_wallet` triggers `config.reload()` so new keys take effect immediately
496
+ - **Optional deps**: `@stellar/stellar-sdk` and `viem` are optional — only loaded when the corresponding network is used
497
+
498
+ ## Examples
499
+
500
+ ### End-to-end: first-time setup to skill execution
501
+
502
+ ```
503
+ User: "Search for image generation skills"
504
+ → Agent calls search_skills({ query: "image generation" })
505
+ → Returns list of skills with pricing
506
+
507
+ User: "Create a wallet so we can use one"
508
+ → Agent calls create_wallet({ network: "stellar-testnet" })
509
+ → Returns public key + "fund on testnet faucet"
510
+
511
+ User: "Use the image-gen skill to create a sunset"
512
+ → Agent calls use_skill({ skill: "image-gen", body: '{"prompt":"a sunset over mountains"}' })
513
+ → Server resolves skill → validates → checks budget → pays → returns image URL
514
+
515
+ User: "How much have we spent?"
516
+ → Agent calls spending_summary()
517
+ → Returns { spentToday: "0.0500", spentSession: "0.0500", limits: { maxPerCall: "0.10", maxPerDay: "20.00" }, ... }
518
+ ```
519
+
520
+ ### Using a local SKILL.md during development
521
+
522
+ ```
523
+ User: "Test my local skill"
524
+ → Agent calls read_skill({ skill: "./my-skill/SKILL.md" })
525
+ → Returns parsed manifest with validation errors/warnings
526
+
527
+ → Agent calls use_skill({ skill: "./my-skill/SKILL.md", endpoint: "/api/test" })
528
+ → Executes against your local server
529
+ ```
530
+
531
+ ### Budget rejection
532
+
533
+ ```
534
+ → Agent calls use_skill for a skill priced at 5.00 USDC
535
+ → MAX_PER_CALL is 0.10
536
+ → Error: "Exceeds per-call limit (0.10 USDC)"
537
+ → No payment is made
538
+ ```
539
+
540
+ ## Development
541
+
542
+ ### Prerequisites
543
+
544
+ - Node.js >= 18
545
+ - npm
546
+
547
+ ### Setup
548
+
549
+ ```bash
550
+ git clone https://github.com/402md/mcp.git
551
+ cd mcp
552
+ npm install
553
+ ```
554
+
555
+ ### Scripts
556
+
557
+ | Command | Description |
558
+ |---------|-------------|
559
+ | `npm run build` | Build with tsup (ESM, shebang included) |
560
+ | `npm run dev` | Build in watch mode |
561
+ | `npm run typecheck` | Run TypeScript type checking (`tsc --noEmit`) |
562
+ | `npm run lint` | Lint source files with ESLint |
563
+ | `npm run lint:fix` | Lint and auto-fix |
564
+ | `npm run format` | Format with Prettier |
565
+ | `npm run format:check` | Check formatting without writing |
566
+ | `npm test` | Run tests with Vitest |
567
+ | `npm run test:watch` | Run tests in watch mode |
568
+
569
+ ### Project Structure
570
+
571
+ ```
572
+ mcp/
573
+ ├── src/
574
+ │ ├── index.ts # CLI entry point (stdio transport)
575
+ │ ├── server.ts # MCP server setup, tool registration
576
+ │ ├── config.ts # Env vars + wallet file → McpConfig
577
+ │ ├── types.ts # McpConfig, SpendingRecord, WalletFileConfig
578
+ │ ├── clients.ts # ClientCache + network selection logic
579
+ │ ├── resolve.ts # Skill resolution (URL / file / registry)
580
+ │ ├── spending.ts # SpendingTracker (budget enforcement)
581
+ │ ├── wallet-store.ts # Read/write ~/.402md/wallet.json
582
+ │ └── tools/
583
+ │ ├── use.ts # use_skill, read_skill
584
+ │ ├── wallet.ts # check_balance, spending_summary, create_wallet
585
+ │ └── registry.ts # search_skills
586
+ ├── __tests__/
587
+ │ ├── config.test.ts
588
+ │ ├── spending.test.ts
589
+ │ ├── resolve.test.ts
590
+ │ └── wallet-store.test.ts
591
+ ├── tsup.config.ts # Build config (ESM, shebang, sourcemaps)
592
+ ├── tsconfig.json
593
+ ├── eslint.config.mjs
594
+ └── .prettierrc
595
+ ```
596
+
597
+ ### Code Conventions
598
+
599
+ - **No semicolons**, single quotes, no trailing commas (Prettier)
600
+ - ESM only (`"type": "module"`, `.js` extensions in imports)
601
+ - Strict TypeScript (`strict: true`)
602
+ - Tools are thin handlers — business logic lives in modules (`spending.ts`, `clients.ts`, `resolve.ts`)
603
+ - `@stellar/stellar-sdk` and `viem` are optional deps, dynamically imported only when the matching network is used
604
+
605
+ ### Adding a New Tool
606
+
607
+ 1. Create or edit a file in `src/tools/`
608
+ 2. Export a `register*Tools(server, config, spending, clientCache)` function
609
+ 3. Call it from `src/server.ts`
610
+ 4. Add tests in `__tests__/`
611
+ 5. Run `npm run typecheck && npm run lint && npm test`
612
+
613
+ ### Running Locally
614
+
615
+ ```bash
616
+ # Build and run
617
+ npm run build
618
+ node dist/index.js
619
+
620
+ # Or in dev mode (rebuilds on change)
621
+ npm run dev
622
+
623
+ # In another terminal, test with MCP inspector
624
+ npx @modelcontextprotocol/inspector node dist/index.js
625
+ ```
626
+
627
+ To test with environment variables:
628
+
629
+ ```bash
630
+ STELLAR_SECRET="S..." NETWORK="stellar-testnet" node dist/index.js
631
+ ```
632
+
633
+ ## License
634
+
635
+ MIT