@url2md-io/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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 url2md
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,392 @@
1
+ # @url2md-io/mcp
2
+
3
+ > **The official package is `@url2md-io/mcp` — with the `@url2md-io` scope.** The unscoped name
4
+ > `url2md-mcp` on npm is **someone else's package**, published by an unrelated person in May 2026.
5
+ > It is not ours, we have no control over it, and we cannot see what it does with what it is given.
6
+ > Running `npx url2md-mcp` runs their code — and this tool is set up by handing it a wallet private
7
+ > key. Give the key only to `@url2md-io/mcp`. If the name you are about to install has no scope in
8
+ > front of it, it is the wrong one.
9
+
10
+ An MCP server that gives your agent one tool: **turn any URL into clean Markdown**.
11
+
12
+ It handles ordinary pages, JavaScript-rendered pages (a headless browser is used only when the page
13
+ needs one) and PDFs. There is no account, no sign-up and no API key. Each call is paid for on-chain,
14
+ from a wallet you control, at **$0.005** — half a cent — in USDC on Base. The settlement receipt
15
+ comes back with the Markdown.
16
+
17
+ ---
18
+
19
+ ## What you need before you start
20
+
21
+ 1. **Node 22.18 or newer.** Check with `node --version`. Nothing else: `npx` fetches the package
22
+ and the published files are plain JavaScript, so there is nothing for you to build.
23
+ 2. **A wallet private key, holding USDC on Base.** This is the part that is different from a normal
24
+ MCP server, so it is worth being precise:
25
+ - It must be a raw private key: `0x` followed by 64 hex characters. Not a seed phrase.
26
+ - The wallet needs **USDC only — no ETH**. The payment facilitator submits the transfer and pays
27
+ the gas. A wallet with $2 of USDC and nothing else works.
28
+ - **Use a fresh wallet with a small balance.** Anything with this key can spend the USDC in it.
29
+ Do not use a wallet you keep anything else in.
30
+ - Get USDC on Base by bridging, or by buying on an exchange that withdraws to Base directly
31
+ (Coinbase does). The USDC contract on Base is `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`.
32
+
33
+ If you would rather try it without spending real money, see [Trying it on
34
+ testnet](#trying-it-on-testnet) below — the test network's USDC is free.
35
+
36
+ ---
37
+
38
+ ## Install
39
+
40
+ Nothing to install. Every example below runs the server with `npx`, which fetches it on first use
41
+ and caches it:
42
+
43
+ ```bash
44
+ npx @url2md-io/mcp
45
+ ```
46
+
47
+ Mind the scope — `@url2md-io/mcp`, never the unscoped `url2md-mcp`, which is someone else's
48
+ package. Run it once by hand like that and it will refuse to start, with `CONFIG_MISSING_KEY`.
49
+ That is the answer you want: the package is there and the only thing missing is the key, which the
50
+ next step sets up.
51
+
52
+ To pin a version, or to keep it off the network at start-up, install it instead:
53
+
54
+ ```bash
55
+ npm install -g @url2md-io/mcp # then the command is: url2md
56
+ ```
57
+
58
+ ### Put the key in a file, not in a config
59
+
60
+ You can pass the key in `URL2MD_PRIVATE_KEY`, and the examples below show both ways. Prefer the
61
+ file:
62
+
63
+ ```bash
64
+ umask 077
65
+ printf '0xYOUR_PRIVATE_KEY' > ~/.url2md-key # no trailing newline needed; one is fine
66
+ chmod 600 ~/.url2md-key
67
+ ```
68
+
69
+ Then set **`URL2MD_PRIVATE_KEY_FILE=/Users/you/.url2md-key`** instead of the key itself. A path is
70
+ not a secret. The key itself, put anywhere else, ends up somewhere you did not intend:
71
+
72
+ - `claude mcp add … --env URL2MD_PRIVATE_KEY=0x…` puts it in your shell history, and then in
73
+ `~/.claude.json` in plaintext.
74
+ - Any `-e URL2MD_PRIVATE_KEY=0x…` on a command line is visible to every process on the machine
75
+ through `ps aux` for as long as it runs.
76
+
77
+ `URL2MD_PRIVATE_KEY_FILE` wins if both are set.
78
+
79
+ ## Add it to your MCP client
80
+
81
+ ### Claude Code
82
+
83
+ ```bash
84
+ claude mcp add url2md \
85
+ --env URL2MD_PRIVATE_KEY_FILE=/Users/you/.url2md-key \
86
+ --env URL2MD_BASE_URL=https://url2md.io \
87
+ -- npx -y @url2md-io/mcp
88
+ ```
89
+
90
+ ### Claude Desktop, Cursor, Windsurf, and anything else that reads a JSON config
91
+
92
+ Add this to the `mcpServers` object of your client's config file (Claude Desktop:
93
+ `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS,
94
+ `%APPDATA%\Claude\claude_desktop_config.json` on Windows):
95
+
96
+ ```json
97
+ {
98
+ "mcpServers": {
99
+ "url2md": {
100
+ "command": "npx",
101
+ "args": ["-y", "@url2md-io/mcp"],
102
+ "env": {
103
+ "URL2MD_PRIVATE_KEY_FILE": "/Users/you/.url2md-key",
104
+ "URL2MD_BASE_URL": "https://url2md.io"
105
+ }
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ If the server never appears, the cause is almost always `PATH`. Desktop clients on macOS are
112
+ launched by the window manager with a minimal `PATH` that usually does not include a
113
+ version-managed Node, so a bare `"npx"` resolves to nothing. Use an absolute path for `command` —
114
+ `which npx` prints it — and keep `args` as they are. Restart the client after editing the file.
115
+
116
+ ### Check it before you wire it up
117
+
118
+ You can talk to the server yourself with the reference MCP inspector, which needs nothing installed:
119
+
120
+ ```bash
121
+ npx @modelcontextprotocol/inspector --cli npx -y @url2md-io/mcp \
122
+ -e URL2MD_PRIVATE_KEY_FILE=/Users/you/.url2md-key \
123
+ --method tools/list
124
+ ```
125
+
126
+ The server prints one line to stderr when it starts, naming the deployment, **the network and
127
+ whether it is real money**, the wallet address it will pay from, and the per-call limit:
128
+
129
+ ```
130
+ @url2md-io/mcp 0.1.0 → https://url2md.io; eip155:8453 (real money); paying from 0x0972…7a36, at most $0.10 per call and $1.00 in total
131
+ ```
132
+
133
+ Your private key is never printed, logged, or sent anywhere except as the signature on a payment.
134
+
135
+ ---
136
+
137
+ ## The tool
138
+
139
+ ### `fetch_markdown`
140
+
141
+ | Parameter | Required | Default | What it does |
142
+ |---|---|---|---|
143
+ | `url` | yes | — | The absolute `http(s)` URL to convert. |
144
+ | `render` | no | `auto` | `auto`: use a headless browser only if the page looks client-rendered. `static`: never render, take the HTML as served. `browser`: always render. |
145
+
146
+ `render=browser` costs the same but is slower, and can fail on pages a plain fetch would have
147
+ handled. `auto` is right almost always.
148
+
149
+ **What comes back** — the Markdown as the tool's text, and this as structured content:
150
+
151
+ ```json
152
+ {
153
+ "markdown": "# Example Domain\n\nThis domain is for use in documentation examples…",
154
+ "url": "https://example.com/",
155
+ "finalUrl": "https://example.com/",
156
+ "title": "Example Domain",
157
+ "source": "static",
158
+ "words": 24,
159
+ "tokensEstimate": 42,
160
+ "warnings": [],
161
+ "payment": {
162
+ "amountUsd": "0.005",
163
+ "amountAtomic": "5000",
164
+ "network": "eip155:8453",
165
+ "payTo": "0xa09aE66Ec8664475697FfC0eb45aa3Fe52Aa6030",
166
+ "settled": true,
167
+ "payer": "0x…your wallet",
168
+ "transaction": "0x3bd94a…",
169
+ "explorerUrl": "https://basescan.org/tx/0x3bd94a…",
170
+ "ledgerId": "e17a62c0-0801-4043-a58a-da135c1343ae"
171
+ },
172
+ "paymentRounds": 1,
173
+ "committedUsd": "0.005",
174
+ "signed": [
175
+ {
176
+ "amountUsd": "0.005",
177
+ "amountAtomic": "5000",
178
+ "network": "eip155:8453",
179
+ "payTo": "0xa09aE66Ec8664475697FfC0eb45aa3Fe52Aa6030",
180
+ "payer": "0x…your wallet",
181
+ "nonce": "0xdccc5af7…",
182
+ "validBefore": "2026-09-07T05:17:26.000Z"
183
+ }
184
+ ]
185
+ }
186
+ ```
187
+
188
+ `source` says how the Markdown was produced: `static`, `browser`, `pdf`, `origin-markdown` or
189
+ `text`. Fields that do not apply are left out rather than set to `null`.
190
+
191
+ **`payment` is there whenever you were charged**, even if the service answered without confirming
192
+ the settlement. The `amountUsd`, `network`, `payTo` and `payer` are always **what this client
193
+ signed** — never what the service's receipt claims. If the receipt disagrees with any of them,
194
+ `warnings` names both figures and the signed one stands. `settled: false` means no usable
195
+ settlement came back at all; `transaction` is then absent and `warnings` tells you to check the
196
+ wallet on-chain.
197
+
198
+ `committedUsd` is everything the call authorized, and `signed` lists each authorization with the
199
+ nonce that identifies it on-chain. On an ordinary call `committedUsd` equals `payment.amountUsd`.
200
+ On a **tolled** call it is higher: the first authorization is replaced by the quote, only the quote
201
+ is settled, and the service still holds the first one — `warnings` says so, with both numbers.
202
+
203
+ Read `warnings`. It carries the service's own notes too — a browser render that failed and fell
204
+ back to the static result, for instance. Anything you must not miss is also written **above the
205
+ Markdown in the tool's text**, because many MCP clients show a model only the text and drop the
206
+ structured content: an unconfirmed payment appears there as a `PAYMENT NOT CONFIRMED` line, and
207
+ every warning as a `NOTE:` line. A clean, settled call has no preamble at all.
208
+
209
+ ---
210
+
211
+ ## What a call costs
212
+
213
+ **$0.005** per call, whatever the page. The wallet is debited once, when the call succeeds — or, for
214
+ a page that needs a browser, just before the browser starts.
215
+
216
+ **Which failures are free, exactly.** A bad URL, an unsupported scheme and a blocked host are
217
+ refused on the *unpaid* request: nothing is signed and `details` is empty. Everything else —
218
+ `ROBOTS_DISALLOWED`, `ROBOTS_UNREACHABLE` (which is where a mistyped domain lands), a page that
219
+ times out, a render that fails — is discovered **after** the payment gate, so a signed
220
+ authorization has already left this process. url2md does not settle a payment for a call it
221
+ answered with an error, and the ledger row would say `needs_refund` if it did; but this client
222
+ cannot see the chain and does not claim the money is safe. Every such failure therefore comes back
223
+ with `details.committedUsd`, and `details.signed[]` carrying each authorization's amount,
224
+ recipient, EIP-3009 nonce and expiry — that nonce is what USDC emits as `AuthorizationUsed` when a
225
+ payment is actually redeemed, so you can check for yourself. An agent that retries a mistyped
226
+ domain in a loop signs $0.005 each time until `URL2MD_MAX_SPEND_USD` stops it.
227
+
228
+ **Pages that charge their own toll.** Some pages are themselves behind an x402 paywall. url2md can
229
+ pay them for you: it quotes you the base price plus the page's toll plus 25 %, you pay that, and it
230
+ pays the origin from its own wallet. Those calls come back with `toll` filled in and
231
+ `paymentRounds: 2`. Tolls above $0.05 are refused rather than passed on.
232
+
233
+ **Two ceilings.** One call will not commit more than **$0.10** in total, however many rounds it
234
+ takes — comfortably above the most a legitimate call can cost (base + the $0.05 toll cap + margin
235
+ ≈ $0.068), and low enough that a surprising price is refused instead of paid. And the whole process
236
+ will not commit more than **$1.00** for as long as it runs, which is what bounds an agent stuck in a
237
+ retry loop; restarting the server resets it. Raise either with `URL2MD_MAX_PRICE_USD` and
238
+ `URL2MD_MAX_SPEND_USD`. Neither check can be turned off, though setting the numbers absurdly high
239
+ has the same effect, so the server says so on startup if you do.
240
+
241
+ **What is checked before anything is signed.** A `402` is written by whoever answers your request,
242
+ so the terms in it are not evidence of anything on their own. Before signing, this client fetches
243
+ the service's own published terms from `GET /` (free, unpaid) and refuses the payment unless the
244
+ 402 matches them:
245
+
246
+ | Check | Refused with |
247
+ |---|---|
248
+ | The service publishes a `payTo`, an asset and a network at all | `UNPUBLISHED_TERMS` |
249
+ | The recipient is the `payTo` the service publishes | `PAYTO_MISMATCH` |
250
+ | The token is the asset the service publishes | `UNSUPPORTED_PAYMENT_TERMS` |
251
+ | The network is the one the service publishes | `UNSUPPORTED_PAYMENT_TERMS` |
252
+ | The scheme is `exact` | `UNSUPPORTED_PAYMENT_TERMS` |
253
+ | The authorization is redeemable for at most an hour | `UNSUPPORTED_PAYMENT_TERMS` |
254
+ | The **whole call** stays at or under your per-call ceiling | `PRICE_ABOVE_LIMIT` |
255
+ | This process has not spent its session budget | `SPEND_LIMIT_REACHED` |
256
+
257
+ These **fail closed**: a service that publishes no receiving address gets no signature, rather than
258
+ an unchecked one. The amount is not compared with the published price for the *toll* round, because
259
+ a quote is legitimately higher than the base price — but a **first** 402 above the published price
260
+ is reported in `warnings` even when it is inside your ceiling, since that is a service charging
261
+ something other than what it advertises.
262
+
263
+ **`URL2MD_BASE_URL` is the whole trust anchor.** Every check above compares the 402 with what *that
264
+ URL* published moments earlier. It stops a service contradicting itself; it cannot stop a service
265
+ that lies consistently, because an impostor writes both halves. So: point it at a deployment you
266
+ trust, and nothing else. It must be `https://` (only a loopback address may be plain `http`), and
267
+ the paid request is pinned to that origin — a redirect to another host is refused
268
+ (`REDIRECT_REFUSED`) rather than followed, because following it would hand your signed
269
+ authorization to whoever the redirect names.
270
+
271
+ The ceiling bounds the whole call, so a two-round tolled call cannot commit twice the number you
272
+ set — and what a call committed in total is always reported, as `committedUsd` on success and
273
+ `details.committedUsd` on failure.
274
+
275
+ **If a call fails after a payment was signed**, the error carries `details.signedButUnconfirmed`,
276
+ `details.committedUsd`, and `details.signed` — one entry per authorization, with the amount, the
277
+ recipient, the network, the EIP-3009 nonce and when it stops being redeemable. A signature that has
278
+ left this process is money you have committed whether or not the service ever redeems it, so it is
279
+ never omitted from a failure.
280
+
281
+ ---
282
+
283
+ ## Configuration
284
+
285
+ | Variable | Required | Default | What it is |
286
+ |---|---|---|---|
287
+ | `URL2MD_PRIVATE_KEY_FILE` | one of these two | — | Path to a file containing the key. **Preferred** — a path is not a secret. Wins if both are set. |
288
+ | `URL2MD_PRIVATE_KEY` | one of these two | — | The key itself: `0x` + 64 hex characters. |
289
+ | `URL2MD_MAX_PRICE_USD` | no | `0.10` | Most this server will commit for **one call**, in USD, across every round of it. |
290
+ | `URL2MD_MAX_SPEND_USD` | no | `1.00` | Most this server will commit **in total**, for as long as the process runs. Restarting resets it. |
291
+ | `URL2MD_BASE_URL` | no | `https://url2md.io` | The url2md deployment to call. |
292
+ | `URL2MD_TIMEOUT_MS` | no | `90000` | How long to wait for one conversion. |
293
+
294
+ ---
295
+
296
+ ## When something goes wrong
297
+
298
+ Failures come back as MCP error results (`isError: true`) whose text is a JSON object, so an agent
299
+ can branch on the code rather than read prose:
300
+
301
+ ```json
302
+ { "error": { "code": "BLOCKED_HOST", "message": "localhost is not a public host", "retryable": false, "httpStatus": 400 } }
303
+ ```
304
+
305
+ | Code | What happened | What to do |
306
+ |---|---|---|
307
+ | `CONFIG_MISSING_KEY`, `CONFIG_BAD_KEY`, `CONFIG_BAD_KEY_FILE` | No key, not a private key, or a key file that could not be read. The server refuses to start (exit 2). | Set `URL2MD_PRIVATE_KEY_FILE` to a readable file holding `0x` + 64 hex characters. |
308
+ | `CONFIG_BAD_LIMIT`, `CONFIG_BAD_BASE_URL`, `CONFIG_BAD_TIMEOUT` | A setting the server will not guess at — including a `URL2MD_BASE_URL` that is not `https://`. It refuses to start. | Fix the value named in the message. |
309
+ | `INVALID_URL`, `UNSUPPORTED_SCHEME` | The URL is not an absolute `http(s)` URL. | Fix the URL. |
310
+ | `BLOCKED_HOST` | Private, loopback, link-local or internal host. | Public URLs only — this is deliberate. |
311
+ | `ROBOTS_DISALLOWED` | The origin's robots.txt forbids it. Checked after the payment gate, so an authorization was signed — read `details.committedUsd`. | Nothing; url2md obeys robots.txt. |
312
+ | `PRICE_ABOVE_LIMIT` | The call, in total, would cost more than `URL2MD_MAX_PRICE_USD`. | Read `details.totalUsd`; raise the limit if it is reasonable. |
313
+ | `SPEND_LIMIT_REACHED` | This server has committed its whole `URL2MD_MAX_SPEND_USD` session budget. **Nothing was signed.** | Raise it and restart, or look at why the agent is spending. |
314
+ | `UNPUBLISHED_TERMS` | The service asks for payment but publishes no receiving address, asset or network to check its 402 against. **Nothing was signed.** | Check `URL2MD_BASE_URL`. A real url2md deployment publishes all three at `GET /`. |
315
+ | `PAYTO_MISMATCH` | The 402 asked you to pay an address the service does not publish. **Nothing was signed.** | Do not raise any limit. Check you are pointed at the right `URL2MD_BASE_URL`; if you are, stop and investigate. |
316
+ | `UNSUPPORTED_PAYMENT_TERMS` | The 402's network, asset or scheme is not the one the service publishes, or this client cannot pay it. Nothing was signed. | As above. |
317
+ | `PAYMENT_NOT_SIGNED` | The payment could not be signed — usually the x402 client's own spend controls refusing an unrecognised token. | Read the message; nothing was sent. |
318
+ | `ROBOTS_UNREACHABLE` | The origin's robots.txt could not be fetched. RFC 9309 requires treating that as disallow. Checked after the payment gate, so an authorization was signed — read `details.committedUsd`. | Retryable, but each retry signs another payment: a nonexistent domain lands here too, so check the URL before looping. |
319
+ | `SERVICE_UNREACHABLE`, `TIMEOUT` | url2md itself did not answer, or not in time. | Retryable. Check `URL2MD_BASE_URL`. |
320
+ | `BAD_RESPONSE`, `PAYMENT_NOT_ACCEPTED` | url2md answered something this client could not use, or kept asking for payment after two rounds. | Nothing further was signed. Report it. |
321
+ | `REDIRECT_REFUSED` | The service tried to redirect the request to another host. The request — and the signed authorization it carried — was not followed there. | Check `URL2MD_BASE_URL`. If it is right, stop and investigate: an honest deployment does not do this. |
322
+ | `TOO_MANY_REDIRECTS` | More than three redirects inside the service's own origin. | Check `URL2MD_BASE_URL`. |
323
+ | `UPSTREAM_PAYMENT_REQUIRED`, `UPSTREAM_PAYMENT_FAILED` | The page charges its own toll and url2md could not pass it through, or the origin refused url2md's payment. | If `details.callerCharged` is true you were charged; quote `details.ledgerId`. |
324
+ | `INTERNAL` | An unexpected failure inside this server. | Report it. |
325
+ | `UPSTREAM_TOLL_TOO_HIGH` | The page charges a toll above url2md's $0.05 cap. | Nothing; that page is not available through url2md. |
326
+ | `PAYMENT_REJECTED` | The service refused the payment when it tried to settle it. Usually an empty wallet. | Check the wallet has USDC on the network the startup line names. |
327
+ | `PAYMENT_REQUIRED` | The service asked for payment but does not publish x402 pricing, so this client will not guess at terms. | Check `URL2MD_BASE_URL`. |
328
+ | `RENDER_FAILED`, `UPSTREAM_STATUS`, `UPSTREAM_TIMEOUT` | The page itself failed. | `retryable` says whether trying again is worth it. |
329
+ | `RATE_LIMITED` | 60 calls a minute, 6 browser renders a minute. | Wait. |
330
+
331
+ url2md's own error codes are documented in full in the service's
332
+ [`FEATURES.md`](https://github.com/glassyeah/url2md/blob/main/FEATURES.md) and `openapi.json`; any
333
+ of them can appear here unchanged.
334
+
335
+ **If you were charged and got nothing**, the error carries `details.callerCharged: true` and
336
+ `details.ledgerId`. url2md keeps a row for every settled payment and reconciles it against the chain;
337
+ quote the ledger id to <hello@url2md.io>.
338
+
339
+ ---
340
+
341
+ ## Trying it on testnet
342
+
343
+ Base Sepolia USDC is free, so you can exercise the whole thing without spending anything. Note that
344
+ this changes the **payment network** to Base Sepolia (`eip155:84532`): a wallet funded with USDC on
345
+ Base mainnet cannot pay the testnet deployment, and vice versa. The startup line names the network
346
+ it will pay on, so you can check before you spend.
347
+
348
+ ```bash
349
+ npx @modelcontextprotocol/inspector --cli npx -y @url2md-io/mcp \
350
+ -e URL2MD_PRIVATE_KEY_FILE=/Users/you/.url2md-testnet-key \
351
+ -e URL2MD_BASE_URL=https://url2md-testnet.url2md.workers.dev \
352
+ --method tools/call --tool-name fetch_markdown --tool-arg url=https://example.com
353
+ ```
354
+
355
+ Fund a throwaway wallet from the [Coinbase Base Sepolia faucet](https://portal.cdp.coinbase.com/products/faucet).
356
+ The testnet deployment is the same code as production, on the test network. Set the same
357
+ `URL2MD_BASE_URL` in your client config to point it at testnet permanently.
358
+
359
+ ---
360
+
361
+ ## Why this is a local server and not a hosted one
362
+
363
+ Because a hosted server would have no wallet of yours to pay with. It would pay from its own and
364
+ then have to bill you some other way — an account, an API key, an invoice — which is the exact thing
365
+ x402 removes. Running it beside your client keeps the key on your machine and the payment yours.
366
+
367
+ The key is read once at startup and used for one thing: signing
368
+ [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) USDC transfer authorizations for the exact
369
+ amount the 402 asks for. It is never written to stdout (which is the MCP wire), to stderr, to an
370
+ error message, or into a tool result. The only thing derived from it that ever leaves the process is
371
+ the public address.
372
+
373
+ ---
374
+
375
+ ## Working on it
376
+
377
+ The source is the `mcp/` directory of the service repository. There is no build step — Node runs the
378
+ TypeScript directly, which is why `engines.node` is `>=22.18`.
379
+
380
+ ```bash
381
+ npm install # in mcp/
382
+ npm run typecheck # tsc
383
+ npm test # vitest
384
+ ```
385
+
386
+ ## About url2md
387
+
388
+ <https://url2md.io> — the service itself, with the same facts as JSON (`GET /`), Markdown
389
+ (`/llms.txt`) and OpenAPI (`/openapi.json`). Source: <https://github.com/glassyeah/url2md>.
390
+ Questions: <hello@url2md.io>.
391
+
392
+ MIT.