@apify/mcpc 0.1.11-beta.1 → 0.1.11-beta.3

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 (68) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +137 -48
  3. package/dist/bridge/index.js +78 -4
  4. package/dist/bridge/index.js.map +1 -1
  5. package/dist/cli/commands/index.d.ts +1 -0
  6. package/dist/cli/commands/index.d.ts.map +1 -1
  7. package/dist/cli/commands/index.js +1 -0
  8. package/dist/cli/commands/index.js.map +1 -1
  9. package/dist/cli/commands/sessions.d.ts +1 -0
  10. package/dist/cli/commands/sessions.d.ts.map +1 -1
  11. package/dist/cli/commands/sessions.js +15 -0
  12. package/dist/cli/commands/sessions.js.map +1 -1
  13. package/dist/cli/commands/x402.d.ts +2 -0
  14. package/dist/cli/commands/x402.d.ts.map +1 -0
  15. package/dist/cli/commands/x402.js +203 -0
  16. package/dist/cli/commands/x402.js.map +1 -0
  17. package/dist/cli/helpers.d.ts +1 -0
  18. package/dist/cli/helpers.d.ts.map +1 -1
  19. package/dist/cli/helpers.js +12 -0
  20. package/dist/cli/helpers.js.map +1 -1
  21. package/dist/cli/index.js +18 -0
  22. package/dist/cli/index.js.map +1 -1
  23. package/dist/cli/parser.d.ts +1 -0
  24. package/dist/cli/parser.d.ts.map +1 -1
  25. package/dist/cli/parser.js +4 -0
  26. package/dist/cli/parser.js.map +1 -1
  27. package/dist/core/factory.d.ts +2 -0
  28. package/dist/core/factory.d.ts.map +1 -1
  29. package/dist/core/factory.js +4 -0
  30. package/dist/core/factory.js.map +1 -1
  31. package/dist/core/transports.d.ts +3 -2
  32. package/dist/core/transports.d.ts.map +1 -1
  33. package/dist/core/transports.js +4 -0
  34. package/dist/core/transports.js.map +1 -1
  35. package/dist/lib/auth/keychain.d.ts.map +1 -1
  36. package/dist/lib/auth/keychain.js +85 -67
  37. package/dist/lib/auth/keychain.js.map +1 -1
  38. package/dist/lib/auth/profiles.d.ts.map +1 -1
  39. package/dist/lib/auth/profiles.js.map +1 -1
  40. package/dist/lib/bridge-client.d.ts +2 -1
  41. package/dist/lib/bridge-client.d.ts.map +1 -1
  42. package/dist/lib/bridge-client.js +6 -0
  43. package/dist/lib/bridge-client.js.map +1 -1
  44. package/dist/lib/bridge-manager.d.ts +1 -0
  45. package/dist/lib/bridge-manager.d.ts.map +1 -1
  46. package/dist/lib/bridge-manager.js +33 -1
  47. package/dist/lib/bridge-manager.js.map +1 -1
  48. package/dist/lib/sessions.d.ts.map +1 -1
  49. package/dist/lib/sessions.js.map +1 -1
  50. package/dist/lib/types.d.ts +16 -1
  51. package/dist/lib/types.d.ts.map +1 -1
  52. package/dist/lib/utils.d.ts +1 -0
  53. package/dist/lib/utils.d.ts.map +1 -1
  54. package/dist/lib/utils.js +3 -0
  55. package/dist/lib/utils.js.map +1 -1
  56. package/dist/lib/wallets.d.ts +5 -0
  57. package/dist/lib/wallets.d.ts.map +1 -0
  58. package/dist/lib/wallets.js +66 -0
  59. package/dist/lib/wallets.js.map +1 -0
  60. package/dist/lib/x402/fetch-middleware.d.ts +9 -0
  61. package/dist/lib/x402/fetch-middleware.d.ts.map +1 -0
  62. package/dist/lib/x402/fetch-middleware.js +133 -0
  63. package/dist/lib/x402/fetch-middleware.js.map +1 -0
  64. package/dist/lib/x402/signer.d.ts +48 -0
  65. package/dist/lib/x402/signer.d.ts.map +1 -0
  66. package/dist/lib/x402/signer.js +139 -0
  67. package/dist/lib/x402/signer.js.map +1 -0
  68. package/package.json +5 -3
package/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+ - E2E tests now run under the Bun runtime (in addition to Node.js); use `./test/e2e/run.sh --runtime bun` or `npm run test:e2e:bun`
12
+
13
+ ### Changed
14
+ - OS keychain now falls back to `~/.mcpc/credentials.json` (mode 0600) when no keyring daemon is available (e.g. headless Linux servers, containers)
15
+
10
16
  ## [0.1.10] - 2026-03-01
11
17
 
12
18
  ### Added
package/README.md CHANGED
@@ -10,6 +10,7 @@ After all, UNIX-compatible shell script is THE most universal coding language.
10
10
  ![mcpc screenshot](https://raw.githubusercontent.com/apify/mcpc/main/docs/images/mcpc-demo.gif)
11
11
 
12
12
  **Key features:**
13
+
13
14
  - 🌎 **Compatible** - Works with any MCP server over Streamable HTTP or stdio.
14
15
  - 🔄 **Persistent sessions** - Keep multiple server connections alive simultaneously.
15
16
  - 🔧 **Strong MCP support** - Instructions, tools, resources, prompts, dynamic discovery.
@@ -17,7 +18,7 @@ After all, UNIX-compatible shell script is THE most universal coding language.
17
18
  - 🤖 **AI sandboxing** - MCP proxy server to securely access authenticated sessions from AI-generated code.
18
19
  - 🔒 **Secure** - Full OAuth 2.1 support, OS keychain for credentials storage.
19
20
  - 🪶 **Lightweight** - Minimal dependencies, works on Mac/Win/Linux, doesn't use LLMs on its own.
20
-
21
+ - 💸 **[Agentic payments (x402)](#agentic-payments-x402)** - Experimental support for the [x402](https://www.x402.org/) payment protocol, enabling AI agents to pay for MCP tool calls with USDC on [Base](https://www.base.org/).
21
22
 
22
23
  ## Table of contents
23
24
 
@@ -31,6 +32,7 @@ After all, UNIX-compatible shell script is THE most universal coding language.
31
32
  - [Authentication](#authentication)
32
33
  - [MCP proxy](#mcp-proxy)
33
34
  - [AI agents](#ai-agents)
35
+ - [Agentic payments (x402)](#agentic-payments-x402)
34
36
  - [MCP support](#mcp-support)
35
37
  - [Configuration](#configuration)
36
38
  - [Security](#security)
@@ -45,32 +47,35 @@ After all, UNIX-compatible shell script is THE most universal coding language.
45
47
 
46
48
  ```bash
47
49
  npm install -g @apify/mcpc
50
+
51
+ # Or with Bun
52
+ bun install -g @apify/mcpc
48
53
  ```
49
54
 
50
55
  **Linux users:** `mcpc` uses the OS keychain for secure credential storage via the
51
- [Secret Service API](https://specifications.freedesktop.org/secret-service/). Two things are required:
56
+ [Secret Service API](https://specifications.freedesktop.org/secret-service/).
57
+ On desktop systems (GNOME, KDE) this works out of the box. On headless/server/CI environments
58
+ without a keyring daemon, `mcpc` automatically falls back to a file-based credential store
59
+ (`~/.mcpc/credentials`, mode `0600`).
60
+
61
+ To use the OS keychain on a headless system, install `libsecret` and a secret service daemon:
52
62
 
53
- 1. **`libsecret`** — the shared library (client side):
54
- ```bash
55
- # Debian/Ubuntu
56
- sudo apt-get install libsecret-1-0
63
+ ```bash
64
+ # Debian/Ubuntu
65
+ sudo apt-get install libsecret-1-0 gnome-keyring
57
66
 
58
- # Fedora/RHEL/CentOS
59
- sudo dnf install libsecret
67
+ # Fedora/RHEL/CentOS
68
+ sudo dnf install libsecret gnome-keyring
60
69
 
61
- # Arch Linux
62
- sudo pacman -S libsecret
63
- ```
70
+ # Arch Linux
71
+ sudo pacman -S libsecret gnome-keyring
72
+ ```
64
73
 
65
- 2. **A running secret service daemon** — on desktop systems (GNOME, KDE) this is already provided
66
- by gnome-keyring or KWallet. On headless/server/CI environments you need to install and start one:
67
- ```bash
68
- # Debian/Ubuntu
69
- sudo apt-get install gnome-keyring
74
+ And then run `mcpc` as follows:
70
75
 
71
- # Then start it (e.g. in CI):
72
- dbus-run-session -- bash -c "echo -n 'password' | gnome-keyring-daemon --unlock && your-command"
73
- ```
76
+ ```
77
+ dbus-run-session -- bash -c "echo -n 'password' | gnome-keyring-daemon --unlock && mcpc ..."
78
+ ```
74
79
 
75
80
  ## Quickstart
76
81
 
@@ -117,6 +122,7 @@ Options:
117
122
  --timeout <seconds> Request timeout in seconds (default: 300)
118
123
  --proxy <[host:]port> Start proxy MCP server for session (with "connect" command)
119
124
  --proxy-bearer-token <token> Require authentication for access to proxy server
125
+ --x402 Enable x402 auto-payment using the configured wallet
120
126
  --clean[=types] Clean up mcpc data (types: sessions, logs, profiles, all)
121
127
  -h, --help Display general help
122
128
 
@@ -148,7 +154,14 @@ MCP server commands:
148
154
  resources-templates-list
149
155
  logging-set-level <level>
150
156
  ping
151
-
157
+
158
+ x402 payment commands (no target needed):
159
+ x402 init Create a new x402 wallet
160
+ x402 import <key> Import wallet from private key
161
+ x402 info Show wallet info
162
+ x402 sign -r <base64> Sign payment from PAYMENT-REQUIRED header
163
+ x402 remove Remove the wallet
164
+
152
165
  Run "mcpc" without <target> to show available sessions and profiles.
153
166
  ```
154
167
 
@@ -181,6 +194,7 @@ To connect and interact with an MCP server, you need to specify a `<target>`, wh
181
194
  connects, and enables you to interact with it.
182
195
 
183
196
  **URL handling:**
197
+
184
198
  - URLs without a scheme (e.g. `mcp.apify.com`) default to `https://`
185
199
  - `localhost` and `127.0.0.1` addresses without a scheme default to `http://` (for local dev/proxy servers)
186
200
  - To override the default, specify the scheme explicitly (e.g. `http://example.com`)
@@ -229,6 +243,7 @@ cat args.json | mcpc <target> tools-call <tool-name>
229
243
  ```
230
244
 
231
245
  **Rules:**
246
+
232
247
  - All arguments use `:=` syntax: `key:=value`
233
248
  - Values are auto-parsed: valid JSON becomes that type, otherwise treated as string
234
249
  - `count:=10` → number `10`
@@ -257,6 +272,7 @@ echo "{\"query\": \"${QUERY}\", \"limit\": 10}" | mcpc @server tools-call search
257
272
  ```
258
273
 
259
274
  **Common pitfall:** Don't put spaces around `:=` - it won't work:
275
+
260
276
  ```bash
261
277
  # Wrong - spaces around :=
262
278
  mcpc @server tools-call search query := "hello world"
@@ -329,7 +345,7 @@ Still, sessions can fail due to network disconnects, bridge process crash, or se
329
345
  **Session states:**
330
346
 
331
347
  | State | Meaning |
332
- |------------------|-----------------------------------------------------------------------------------------------|
348
+ | ---------------- | --------------------------------------------------------------------------------------------- |
333
349
  | 🟢 **`live`** | Bridge process is running; server might or might not be operational |
334
350
  | 🟡 **`crashed`** | Bridge process crashed or was killed; will auto-restart on next use |
335
351
  | 🔴 **`expired`** | Server rejected the session (auth failed, session ID invalid); requires `close` and reconnect |
@@ -365,7 +381,6 @@ and opens new connection with new `MCP-Session-Id`, by running:
365
381
  mcpc @apify restart
366
382
  ```
367
383
 
368
-
369
384
  ## Authentication
370
385
 
371
386
  `mcpc` supports all standard [MCP authorization methods](https://modelcontextprotocol.io/specification/latest/basic/authorization).
@@ -404,8 +419,8 @@ mcpc @apify tools-list
404
419
 
405
420
  ### OAuth profiles
406
421
 
407
- For OAuth-enabled remote MCP servers, `mcpc` implements the full OAuth 2.1 flow with PKCE,
408
- including `WWW-Authenticate` header discovery, server metadata discovery, client ID metadata documents,
422
+ For OAuth-enabled remote MCP servers, `mcpc` implements the full OAuth 2.1 flow with PKCE,
423
+ including `WWW-Authenticate` header discovery, server metadata discovery, client ID metadata documents,
409
424
  dynamic client registration, and automatic token refresh.
410
425
 
411
426
  The OAuth authentication **always** needs to be initiated by the user calling the `login` command,
@@ -413,11 +428,13 @@ which opens a web browser with login screen. `mcpc` never opens the web browser
413
428
 
414
429
  The OAuth credentials to specific servers are securely stored as **authentication profiles** - reusable
415
430
  credentials that allow you to:
431
+
416
432
  - Authenticate once, use credentials across multiple commands or sessions
417
433
  - Use different accounts (profiles) with the same server
418
434
  - Manage credentials independently from sessions
419
435
 
420
436
  Key concepts:
437
+
421
438
  - **Authentication profile**: Named set of OAuth credentials for a specific server (stored in `~/.mcpc/profiles.json` + OS keychain)
422
439
  - **Session**: Active connection to a server that may reference an authentication profile (stored in `~/.mcpc/sessions.json`)
423
440
  - **Default profile**: When `--profile` is not specified, `mcpc` uses the authentication profile named `default`
@@ -456,7 +473,6 @@ When multiple authentication methods are available, `mcpc` uses this precedence
456
473
  3. **Config file headers** - Headers from `--config` file for the server
457
474
  4. **No authentication** - Attempts unauthenticated connection
458
475
 
459
-
460
476
  `mcpc` automatically handles authentication based on whether you specify a profile:
461
477
 
462
478
  **When `--profile <name>` is specified:**
@@ -478,6 +494,7 @@ When multiple authentication methods are available, `mcpc` uses this precedence
478
494
  On failure, the error message includes instructions on how to login and save the profile, so you know what to do.
479
495
 
480
496
  This flow ensures:
497
+
481
498
  - You only authenticate when necessary
482
499
  - Credentials are never silently mixed up (personal → work) or downgraded (authenticated → unauthenticated)
483
500
  - You can mix authenticated sessions (with named profiles) and public access on the same server
@@ -500,7 +517,6 @@ mcpc mcp.apify.com connect @apify-personal
500
517
  mcpc mcp.apify.com\?tools=docs tools-list
501
518
  ```
502
519
 
503
-
504
520
  ## MCP proxy
505
521
 
506
522
  For stronger isolation, `mcpc` can expose an MCP session under a new local proxy MCP server using the `--proxy` option.
@@ -534,7 +550,7 @@ mcpc localhost:8081 connect @sandboxed2 --header "Authorization: Bearer secret12
534
550
  **Proxy options for `connect` command:**
535
551
 
536
552
  | Option | Description |
537
- |--------------------------------|--------------------------------------------------------------------------------|
553
+ | ------------------------------ | ------------------------------------------------------------------------------ |
538
554
  | `--proxy [host:]port` | Start proxy MCP server. Default host: `127.0.0.1` (localhost only) |
539
555
  | `--proxy-bearer-token <token>` | Requires `Authorization: Bearer <token>` header to access the proxy MCP server |
540
556
 
@@ -565,7 +581,6 @@ mcpc
565
581
  # @relay → https://mcp.apify.com (HTTP, OAuth: default) [proxy: 127.0.0.1:8080]
566
582
  ```
567
583
 
568
-
569
584
  ## AI agents
570
585
 
571
586
  `mcpc` is designed for CLI-enabled AI agents like Claude Code or Codex CLI, supporting both
@@ -635,6 +650,7 @@ mcpc @apify tools-call search-actors --schema expected.json keywords:="test"
635
650
  ```
636
651
 
637
652
  Available schema validation modes (`--schema-mode`):
653
+
638
654
  - `compatible` (default)
639
655
  - Input schema: new optional fields OK, required fields must have the same type.
640
656
  - Output schema: new fields OK, removed required fields cause error.
@@ -668,6 +684,73 @@ To help Claude Code use `mcpc`, you can install this [Claude skill](./docs/claud
668
684
 
669
685
  <!-- TODO: Add also AGENTS.md, GitHub skills etc. -->
670
686
 
687
+ ## Agentic payments (x402)
688
+
689
+ > ⚠️ **Experimental.** This feature is under active development and may change.
690
+
691
+ `mcpc` has experimental support for the [x402 payment protocol](https://www.x402.org/),
692
+ which enables AI agents to autonomously pay for MCP tool calls using cryptocurrency.
693
+ When an MCP server charges for a tool call (HTTP 402), `mcpc` automatically signs a USDC payment
694
+ on the [Base](https://base.org/) blockchain and retries the request — no human intervention needed.
695
+
696
+ This is entirely **opt-in**: existing functionality is unaffected unless you explicitly pass the `--x402` flag.
697
+
698
+ ### How it works
699
+
700
+ 1. **Server returns HTTP 402** with a `PAYMENT-REQUIRED` header describing the price and payment details.
701
+ 2. `mcpc` parses the header, signs an [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) `TransferWithAuthorization` using your local wallet.
702
+ 3. `mcpc` retries the request with a `PAYMENT-SIGNATURE` header containing the signed payment.
703
+ 4. The server verifies the signature and fulfills the request.
704
+
705
+ For tools that advertise pricing in their `_meta.x402` metadata, `mcpc` can **proactively sign** payments
706
+ on the first request, avoiding the 402 round-trip entirely.
707
+
708
+ ### Wallet setup
709
+
710
+ `mcpc` stores a single wallet in `~/.mcpc/wallets.json` (file permissions `0600`).
711
+ You need to create or import a wallet before using x402 payments.
712
+
713
+ ```bash
714
+ # Create a new wallet (generates a random private key)
715
+ mcpc x402 init
716
+
717
+ # Or import an existing wallet from a private key
718
+ mcpc x402 import <private-key>
719
+
720
+ # Show wallet address and creation date
721
+ mcpc x402 info
722
+
723
+ # Remove the wallet
724
+ mcpc x402 remove
725
+ ```
726
+
727
+ After creating a wallet, **fund it with USDC on Base** (mainnet or Sepolia testnet) to enable payments.
728
+
729
+ ### Using x402 with MCP servers
730
+
731
+ Pass the `--x402` flag when connecting to a session or running direct commands:
732
+
733
+ ```bash
734
+ # Create a session with x402 payment support
735
+ mcpc mcp.apify.com connect @apify --x402
736
+
737
+ # The session now automatically handles 402 responses
738
+ mcpc @apify tools-call expensive-tool query:="hello"
739
+
740
+ # Restart a session with x402 enabled
741
+ mcpc @apify restart --x402
742
+ ```
743
+
744
+ When `--x402` is active, a fetch middleware wraps all HTTP requests to the MCP server.
745
+ If any request returns HTTP 402, the middleware transparently signs and retries.
746
+
747
+ ### Supported networks
748
+
749
+ | Network | Status |
750
+ | -------------------- | ------------ |
751
+ | Base Mainnet | ✅ Supported |
752
+ | Base Sepolia testnet | ✅ Supported |
753
+
671
754
  ## MCP support
672
755
 
673
756
  `mcpc` is built on the official [MCP SDK for TypeScript](https://github.com/modelcontextprotocol/typescript-sdk) and supports most [MCP protocol features](https://modelcontextprotocol.io/specification/latest).
@@ -688,6 +771,7 @@ To help Claude Code use `mcpc`, you can install this [Claude skill](./docs/claud
688
771
  ### MCP session
689
772
 
690
773
  The bridge process manages the full MCP session lifecycle:
774
+
691
775
  - Performs initialization handshake (`initialize` → `initialized`)
692
776
  - Negotiates protocol version and capabilities
693
777
  - Fetches server-provided `instructions`
@@ -698,21 +782,21 @@ The bridge process manages the full MCP session lifecycle:
698
782
 
699
783
  ### MCP feature support
700
784
 
701
- | **Feature** | **Status** |
702
- |:---------------------------------------------------|:-----------------------------------|
703
- | 📖 [**Instructions**](#server-instructions) | ✅ Supported |
704
- | 🔧 [**Tools**](#tools) | ✅ Supported |
705
- | 💬 [**Prompts**](#prompts) | ✅ Supported |
706
- | 📦 [**Resources**](#resources) | ✅ Supported |
707
- | 📝 [**Logging**](#server-logs) | ✅ Supported |
708
- | 🔔 [**Notifications**](#list-change-notifications) | ✅ Supported |
709
- | 📄 [**Pagination**](#pagination) | ✅ Supported |
710
- | 🏓 [**Ping**](#ping) | ✅ Supported |
711
- | ⏳ **Async tasks** | 🚧 Planned |
712
- | 📁 **Roots** | 🚧 Planned |
713
- | ❓ **Elicitation** | 🚧 Planned |
714
- | 🔤 **Completion** | 🚧 Planned |
715
- | 🤖 **Sampling** | ❌ Not applicable (no LLM access) |
785
+ | **Feature** | **Status** |
786
+ | :------------------------------------------------- | :-------------------------------- |
787
+ | 📖 [**Instructions**](#server-instructions) | ✅ Supported |
788
+ | 🔧 [**Tools**](#tools) | ✅ Supported |
789
+ | 💬 [**Prompts**](#prompts) | ✅ Supported |
790
+ | 📦 [**Resources**](#resources) | ✅ Supported |
791
+ | 📝 [**Logging**](#server-logs) | ✅ Supported |
792
+ | 🔔 [**Notifications**](#list-change-notifications) | ✅ Supported |
793
+ | 📄 [**Pagination**](#pagination) | ✅ Supported |
794
+ | 🏓 [**Ping**](#ping) | ✅ Supported |
795
+ | ⏳ **Async tasks** | 🚧 Planned |
796
+ | 📁 **Roots** | 🚧 Planned |
797
+ | ❓ **Elicitation** | 🚧 Planned |
798
+ | 🔤 **Completion** | 🚧 Planned |
799
+ | 🤖 **Sampling** | ❌ Not applicable (no LLM access) |
716
800
 
717
801
  #### Server instructions
718
802
 
@@ -865,6 +949,7 @@ mcpc @apify ping --json
865
949
  You can configure `mcpc` using a config file, environment variables, or command-line flags.
866
950
 
867
951
  **Precedence** (highest to lowest):
952
+
868
953
  1. Command-line flags (including `--config` option)
869
954
  2. Environment variables
870
955
  3. Built-in defaults
@@ -912,11 +997,13 @@ mcpc --config .vscode/mcp.json apify connect @my-apify
912
997
  **Server configuration properties:**
913
998
 
914
999
  For **Streamable HTTP servers:**
1000
+
915
1001
  - `url` (required) - MCP server endpoint URL
916
1002
  - `headers` (optional) - HTTP headers to include with requests
917
1003
  - `timeout` (optional) - Request timeout in seconds
918
1004
 
919
1005
  For **stdio servers:**
1006
+
920
1007
  - `command` (required) - Command to execute (e.g., `node`, `npx`, `python`)
921
1008
  - `args` (optional) - Array of command arguments
922
1009
  - `env` (optional) - Environment variables for the process
@@ -944,7 +1031,7 @@ Config files support environment variable substitution using `${VAR_NAME}` synta
944
1031
  "secure-server": {
945
1032
  "url": "https://mcp.apify.com",
946
1033
  "headers": {
947
- "Authorization": "Bearer ${API_TOKEN}",
1034
+ "Authorization": "Bearer ${APIFY_TOKEN}",
948
1035
  "X-User-ID": "${USER_ID}"
949
1036
  }
950
1037
  }
@@ -958,6 +1045,7 @@ Config files support environment variable substitution using `${VAR_NAME}` synta
958
1045
 
959
1046
  - `~/.mcpc/sessions.json` - Active sessions with references to authentication profiles (file-locked for concurrent access)
960
1047
  - `~/.mcpc/profiles.json` - Authentication profiles (OAuth metadata, scopes, expiry)
1048
+ - `~/.mcpc/wallets.json` - x402 wallet data (file permissions `0600`)
961
1049
  - `~/.mcpc/bridges/` - Unix domain socket files for each bridge process
962
1050
  - `~/.mcpc/logs/bridge-*.log` - Log files for each bridge process
963
1051
  - OS keychain - Sensitive credentials (OAuth tokens, bearer tokens, client secrets)
@@ -1000,11 +1088,12 @@ MCP enables arbitrary tool execution and data access - treat servers like you tr
1000
1088
  ### Credential protection
1001
1089
 
1002
1090
  | What | How |
1003
- |------------------------|-------------------------------------------------|
1091
+ | ---------------------- | ----------------------------------------------- |
1004
1092
  | **OAuth tokens** | Stored in OS keychain, never on disk |
1005
1093
  | **HTTP headers** | Stored in OS keychain per-session |
1006
1094
  | **Bridge credentials** | Passed via Unix socket IPC, kept in memory only |
1007
1095
  | **Process arguments** | No secrets visible in `ps aux` |
1096
+ | **x402 private key** | Stored in `wallets.json` (`0600` permissions) |
1008
1097
  | **Config files** | Contain only metadata, never tokens |
1009
1098
  | **File permissions** | `0600` (user-only) for all config files |
1010
1099
 
@@ -1055,20 +1144,22 @@ The main `mcpc` process doesn't save log files, but supports [verbose mode](#ver
1055
1144
  ### Troubleshooting
1056
1145
 
1057
1146
  **"Cannot connect to bridge"**
1147
+
1058
1148
  - Bridge may have crashed. Try: `mcpc @<session-name> tools-list` to restart the bridge
1059
1149
  - Check bridge is running: `ps aux | grep -e 'mcpc-bridge' -e '[m]cpc/dist/bridge'`
1060
1150
  - Check socket exists: `ls ~/.mcpc/bridges/`
1061
1151
 
1062
1152
  **"Session not found"**
1153
+
1063
1154
  - List existing sessions: `mcpc`
1064
1155
  - Create new session if expired: `mcpc @<session-name> close` and `mcpc <target> connect @<session-name>`
1065
1156
 
1066
1157
  **"Authentication failed"**
1158
+
1067
1159
  - List saved OAuth profiles: `mcpc`
1068
1160
  - Re-authenticate: `mcpc <server> login [--profile <name>]`
1069
1161
  - For bearer tokens: provide `--header "Authorization: Bearer ${TOKEN}"` again
1070
1162
 
1071
-
1072
1163
  ## Development
1073
1164
 
1074
1165
  The initial version of `mcpc` was developed and [launched by Jan Curn](https://x.com/jancurn/status/2007144080959291756) of [Apify](https://apify.com)
@@ -1099,8 +1190,6 @@ See [CONTRIBUTING](./CONTRIBUTING.md) for development setup, architecture overvi
1099
1190
  - Other
1100
1191
  - https://github.com/TeamSparkAI/mcpGraph
1101
1192
 
1102
-
1103
1193
  ## License
1104
1194
 
1105
1195
  Apache-2.0 - see [LICENSE](./LICENSE) for details.
1106
-
@@ -15,6 +15,7 @@ import { readKeychainProxyBearerToken } from '../lib/auth/keychain.js';
15
15
  import { createRequire } from 'module';
16
16
  const { version: mcpcVersion } = createRequire(import.meta.url)('../../package.json');
17
17
  import { ProxyServer } from './proxy-server.js';
18
+ import { createX402FetchMiddleware } from '../lib/x402/fetch-middleware.js';
18
19
  setGlobalDispatcher(new EnvHttpProxyAgent());
19
20
  const KEEPALIVE_INTERVAL_MS = 30_000;
20
21
  const logger = createLogger('bridge');
@@ -30,8 +31,12 @@ class BridgeProcess {
30
31
  tokenManager = null;
31
32
  authProvider = null;
32
33
  headers = null;
34
+ x402Wallet = null;
35
+ cachedTools = null;
33
36
  authCredentialsReceived = null;
34
37
  authCredentialsResolver = null;
38
+ x402WalletReceived = null;
39
+ x402WalletResolver = null;
35
40
  mcpClientReady;
36
41
  mcpClientReadyResolver;
37
42
  mcpClientReadyRejecter;
@@ -120,6 +125,18 @@ class BridgeProcess {
120
125
  this.authCredentialsResolver = null;
121
126
  }
122
127
  }
128
+ setX402Wallet(credentials) {
129
+ logger.info(`Received x402 wallet: ${credentials.address}`);
130
+ this.x402Wallet = {
131
+ privateKey: credentials.privateKey,
132
+ address: credentials.address,
133
+ };
134
+ logger.debug('x402 wallet stored in memory');
135
+ if (this.x402WalletResolver) {
136
+ this.x402WalletResolver();
137
+ this.x402WalletResolver = null;
138
+ }
139
+ }
123
140
  async updateTransportAuth() {
124
141
  const config = { ...this.options.serverConfig };
125
142
  if (!config.url) {
@@ -177,16 +194,27 @@ class BridgeProcess {
177
194
  this.runOrphanedLogCleanup();
178
195
  try {
179
196
  await this.createSocketServer();
197
+ const ipcWaiters = [];
180
198
  if (this.options.profileName) {
181
199
  logger.debug(`Waiting for auth credentials (profile: ${this.options.profileName})...`);
182
200
  this.authCredentialsReceived = new Promise((resolve) => {
183
201
  this.authCredentialsResolver = resolve;
184
202
  });
203
+ ipcWaiters.push(this.authCredentialsReceived);
204
+ }
205
+ if (this.options.x402) {
206
+ logger.debug('Waiting for x402 wallet...');
207
+ this.x402WalletReceived = new Promise((resolve) => {
208
+ this.x402WalletResolver = resolve;
209
+ });
210
+ ipcWaiters.push(this.x402WalletReceived);
211
+ }
212
+ if (ipcWaiters.length > 0) {
185
213
  const timeout = new Promise((_, reject) => {
186
- setTimeout(() => reject(new Error('Timeout waiting for auth credentials')), 5000);
214
+ setTimeout(() => reject(new Error('Timeout waiting for IPC credentials')), 5000);
187
215
  });
188
- await Promise.race([this.authCredentialsReceived, timeout]);
189
- logger.debug('Auth credentials received, proceeding with MCP connection');
216
+ await Promise.race([Promise.all(ipcWaiters), timeout]);
217
+ logger.debug('IPC credentials received, proceeding with MCP connection');
190
218
  }
191
219
  try {
192
220
  await this.connectToMcp();
@@ -270,9 +298,19 @@ class BridgeProcess {
270
298
  }
271
299
  logger.debug('Building MCP client config...');
272
300
  logger.debug(` this.authProvider is set: ${!!this.authProvider}`);
301
+ logger.debug(` this.x402Wallet is set: ${!!this.x402Wallet}`);
273
302
  if (this.authProvider) {
274
303
  logger.debug(` authProvider type: ${this.authProvider.constructor.name}`);
275
304
  }
305
+ let customFetch;
306
+ if (this.x402Wallet && serverConfig.url) {
307
+ logger.debug('Creating x402 fetch middleware for payment signing');
308
+ const wallet = this.x402Wallet;
309
+ const getToolByName = (name) => {
310
+ return this.cachedTools?.find((t) => t.name === name);
311
+ };
312
+ customFetch = createX402FetchMiddleware(fetch, { wallet, getToolByName });
313
+ }
276
314
  const clientConfig = {
277
315
  clientInfo: { name: 'mcpc', version: mcpcVersion },
278
316
  serverConfig,
@@ -282,11 +320,16 @@ class BridgeProcess {
282
320
  },
283
321
  ...(this.authProvider && { authProvider: this.authProvider }),
284
322
  ...(this.options.mcpSessionId && { mcpSessionId: this.options.mcpSessionId }),
323
+ ...(customFetch && { customFetch }),
285
324
  listChanged: {
286
325
  tools: {
287
326
  autoRefresh: true,
288
327
  onChanged: (error, tools) => {
289
328
  logger.debug('Tools list changed', { error, count: tools?.length });
329
+ if (tools) {
330
+ this.cachedTools = tools;
331
+ logger.debug(`Updated cached tools list (${tools.length} tools)`);
332
+ }
290
333
  this.broadcastNotification('tools/list_changed');
291
334
  this.updateNotificationTimestamp('tools').catch((err) => {
292
335
  logger.warn('Failed to update tools notification timestamp:', err);
@@ -340,6 +383,18 @@ class BridgeProcess {
340
383
  logger.info(`MCP-Session-Id saved for resumption: ${newMcpSessionId}`);
341
384
  }
342
385
  await updateSession(this.options.sessionName, sessionUpdate);
386
+ if (this.x402Wallet) {
387
+ try {
388
+ const toolsResult = await this.client.listTools();
389
+ if (toolsResult.tools) {
390
+ this.cachedTools = toolsResult.tools;
391
+ logger.debug(`Pre-populated tools cache (${this.cachedTools.length} tools) for x402`);
392
+ }
393
+ }
394
+ catch (error) {
395
+ logger.warn('Failed to pre-populate tools cache for x402:', error);
396
+ }
397
+ }
343
398
  }
344
399
  async startProxyServer() {
345
400
  if (!this.options.proxyConfig || !this.client) {
@@ -499,6 +554,21 @@ class BridgeProcess {
499
554
  throw new ClientError('Missing authCredentials in set-auth-credentials message');
500
555
  }
501
556
  break;
557
+ case 'set-x402-wallet':
558
+ if (message.x402Wallet) {
559
+ this.setX402Wallet(message.x402Wallet);
560
+ if (message.id) {
561
+ this.sendResponse(socket, {
562
+ type: 'response',
563
+ id: message.id,
564
+ result: { success: true },
565
+ });
566
+ }
567
+ }
568
+ else {
569
+ throw new ClientError('Missing x402Wallet in set-x402-wallet message');
570
+ }
571
+ break;
502
572
  default:
503
573
  throw new ClientError(`Unknown message type: ${message.type}`);
504
574
  }
@@ -696,7 +766,7 @@ class BridgeProcess {
696
766
  async function main() {
697
767
  const args = process.argv.slice(2);
698
768
  if (args.length < 2) {
699
- console.error('Usage: mcpc-bridge <sessionName> <transportConfigJson> [--verbose] [--profile <name>] [--proxy-host <host>] [--proxy-port <port>] [--mcp-session-id <id>]');
769
+ console.error('Usage: mcpc-bridge <sessionName> <transportConfigJson> [--verbose] [--profile <name>] [--proxy-host <host>] [--proxy-port <port>] [--mcp-session-id <id>] [--x402]');
700
770
  process.exit(1);
701
771
  }
702
772
  const sessionName = args[0];
@@ -722,6 +792,7 @@ async function main() {
722
792
  if (mcpSessionIdIndex !== -1 && args[mcpSessionIdIndex + 1]) {
723
793
  mcpSessionId = args[mcpSessionIdIndex + 1];
724
794
  }
795
+ const x402 = args.includes('--x402');
725
796
  try {
726
797
  const bridgeOptions = {
727
798
  sessionName,
@@ -737,6 +808,9 @@ async function main() {
737
808
  if (mcpSessionId) {
738
809
  bridgeOptions.mcpSessionId = mcpSessionId;
739
810
  }
811
+ if (x402) {
812
+ bridgeOptions.x402 = true;
813
+ }
740
814
  const bridge = new BridgeProcess(bridgeOptions);
741
815
  await bridge.start();
742
816
  }