@shicodiez/crypto-regime-mcp 0.0.0-stage → 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 Shicodiez
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 CHANGED
@@ -1,3 +1,199 @@
1
- # Temporary Holding Version
1
+ # @shicodiez/crypto-regime-mcp
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Puente **MCP stdio** para [`signal-api`](https://signal-api.shicoars.workers.dev).
4
+ Conecta Claude Desktop, Claude Code o Cursor al endpoint remoto
5
+ `/mcp` y paga automáticamente las tools x402 con una wallet EVM local.
6
+
7
+ > **No es Signal Messenger.** `signal-api` es el nombre del servicio de señales
8
+ > cripto; este paquete no tiene relación con Signal Messenger ni con su app de
9
+ > mensajería.
10
+
11
+ - Tools gratis: `list_assets`, `get_catalog`.
12
+ - Tools de pago: `get_signal` (0,01 USDC), `get_trend` (0,005 USDC),
13
+ `get_regime` (0,005 USDC).
14
+ - Red por defecto: **Base mainnet** (`eip155:8453`), USDC.
15
+ - Límite de gasto por sesión: `MAX_SPEND_USDC` (por defecto **1 USDC**).
16
+
17
+ > **No publiques la clave privada.** Usa una wallet **dedicada**, con poco saldo,
18
+ > solo para este puente. La clave se guarda en la configuración de Claude/Cursor
19
+ > en texto plano y el puente la usa para firmar pagos x402.
20
+
21
+ ## Instalación
22
+
23
+ El paquete aún **no está publicado en npm**. Para probarlo:
24
+
25
+ ```powershell
26
+ cd mcp-bridge
27
+ npm install
28
+ node src/index.js
29
+ ```
30
+
31
+ Para usarlo como comando local:
32
+
33
+ ```powershell
34
+ npm link
35
+ crypto-regime-mcp
36
+ ```
37
+
38
+ La idea es publicarlo más adelante como `@shicodiez/crypto-regime-mcp`; entonces
39
+ valdrá `npx -y @shicodiez/crypto-regime-mcp`.
40
+
41
+ ## Variables de entorno
42
+
43
+ | Variable | Obligatoria | Default | Descripción |
44
+ |---|---|---|---|
45
+ | `EVM_PRIVATE_KEY` | Sí | — | Clave privada `0x...` de la wallet dedicada. Nunca la commitees. |
46
+ | `MAX_SPEND_USDC` | No | `1` | Gasto máximo acumulado por sesión, en USDC. |
47
+ | `SIGNAL_API_MCP_URL` | No | `https://signal-api.shicoars.workers.dev/mcp` | Endpoint MCP remoto. |
48
+ | `NETWORK` | No | `eip155:8453` | Red x402. Usa `eip155:84532` para testnet. |
49
+
50
+ El límite `MAX_SPEND_USDC` se aplica **antes** de cada pago: si el pago haría
51
+ superar el acumulado, se bloquea y la tool devuelve error. Se asume USDC de 6
52
+ decimales (el activo de signal-api). El contador es conservador: si un settlement
53
+ falla después de autorizar, el importe sigue contado en la sesión.
54
+
55
+ ## Claude Desktop
56
+
57
+ Edita:
58
+
59
+ - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
60
+ - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
61
+ - Linux: `~/.config/Claude/claude_desktop_config.json`
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "crypto-regime": {
67
+ "command": "npx",
68
+ "args": ["-y", "@shicodiez/crypto-regime-mcp"],
69
+ "env": {
70
+ "EVM_PRIVATE_KEY": "0x...",
71
+ "MAX_SPEND_USDC": "1"
72
+ }
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ Si aún no está publicado, apunta al script local:
79
+
80
+ ```json
81
+ {
82
+ "mcpServers": {
83
+ "crypto-regime": {
84
+ "command": "node",
85
+ "args": ["C:\\Users\\TU_USUARIO\\signal-bot\\mcp-bridge\\src\\index.js"],
86
+ "env": {
87
+ "EVM_PRIVATE_KEY": "0x...",
88
+ "MAX_SPEND_USDC": "1"
89
+ }
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ Reinicia Claude Desktop después de guardar.
96
+
97
+ ## Claude Code
98
+
99
+ Con el paquete publicado:
100
+
101
+ ```powershell
102
+ claude mcp add crypto-regime `
103
+ --env EVM_PRIVATE_KEY=0x... `
104
+ --env MAX_SPEND_USDC=1 `
105
+ -- npx -y @shicodiez/crypto-regime-mcp
106
+ ```
107
+
108
+ Con el script local:
109
+
110
+ ```powershell
111
+ claude mcp add crypto-regime `
112
+ --env EVM_PRIVATE_KEY=0x... `
113
+ --env MAX_SPEND_USDC=1 `
114
+ -- node C:\Users\TU_USUARIO\signal-bot\mcp-bridge\src\index.js
115
+ ```
116
+
117
+ Comprueba con `claude mcp list` y `claude mcp get crypto-regime`.
118
+
119
+ ## Cursor
120
+
121
+ Crea `.cursor/mcp.json` en el proyecto (o usa la configuración global de Cursor):
122
+
123
+ ```json
124
+ {
125
+ "mcpServers": {
126
+ "crypto-regime": {
127
+ "command": "npx",
128
+ "args": ["-y", "@shicodiez/crypto-regime-mcp"],
129
+ "env": {
130
+ "EVM_PRIVATE_KEY": "0x...",
131
+ "MAX_SPEND_USDC": "1"
132
+ }
133
+ }
134
+ }
135
+ }
136
+ ```
137
+
138
+ Para el script local, cambia `command` por `node` y `args` por la ruta absoluta
139
+ de `src/index.js`. Recarga la ventana de MCP de Cursor.
140
+
141
+ ## Cómo funciona
142
+
143
+ El puente:
144
+
145
+ 1. Abre el `/mcp` remoto con `StreamableHTTPClientTransport`.
146
+ 2. Lo envuelve con **`createx402MCPClient`** y `ExactEvmScheme` de `@x402/evm`.
147
+ 3. Expone un servidor MCP **stdio** con `ListTools`/`CallTool` que reenvía al
148
+ cliente remoto.
149
+ 4. Cuando una tool devuelve `PaymentRequired` (x402 v2), el cliente firma el
150
+ pago con `EVM_PRIVATE_KEY`, lo reintenta con `_meta["x402/payment"]` y
151
+ devuelve el resultado junto con `_meta["x402/payment-response"]`.
152
+
153
+ El servidor remoto verifica y liquida con su facilitator; el puente solo firma
154
+ el pago. No hay custodia de fondos.
155
+
156
+ ## Probar con MCP Inspector (stdio)
157
+
158
+ ```powershell
159
+ cd mcp-bridge
160
+ npm install
161
+ $env:EVM_PRIVATE_KEY = "0x..." # no lo imprimas
162
+ $env:MAX_SPEND_USDC = "1"
163
+
164
+ npx @modelcontextprotocol/inspector@latest --cli `
165
+ --transport stdio `
166
+ --method tools/list `
167
+ --format json `
168
+ -- node C:\Users\TU_USUARIO\signal-bot\mcp-bridge\src\index.js
169
+ ```
170
+
171
+ También puedes arrancar la UI web de Inspector sin `--cli` y elegir
172
+ `Standalone` → `stdio` → comando `node`, argumento `src/index.js`.
173
+
174
+ Para probar una tool gratis:
175
+
176
+ ```powershell
177
+ npx @modelcontextprotocol/inspector@latest --cli `
178
+ --transport stdio `
179
+ --method tools/call `
180
+ --tool-name list_assets `
181
+ --format json `
182
+ -- node C:\Users\TU_USUARIO\signal-bot\mcp-bridge\src\index.js
183
+ ```
184
+
185
+ Las tools de pago requieren saldo USDC en la wallet y consumen
186
+ `MAX_SPEND_USDC`. Para testnet usa `NETWORK=eip155:84532` y un endpoint local
187
+ `SIGNAL_API_MCP_URL=http://localhost:8787/mcp`.
188
+
189
+ ## Seguridad
190
+
191
+ - Usa una wallet **exclusiva** para este puente y mantén solo el saldo que estás
192
+ dispuesto a gastar.
193
+ - No pongas la clave en el repositorio, en `README.md`, en capturas ni en logs.
194
+ - `MAX_SPEND_USDC` reduce el daño de un agente descontrolado, pero no sustituye
195
+ a una wallet con poco saldo.
196
+ - El puente no puede retirar fondos: solo firma autorizaciones x402 del importe
197
+ que pide el servidor, sujeto al límite.
198
+ - Si ves un `payment blocked`, revisa `MAX_SPEND_USDC` y el acumulado de la
199
+ sesión; reinicia el cliente MCP para empezar una sesión nueva.
package/package.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
2
  "name": "@shicodiez/crypto-regime-mcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Stdio MCP bridge for crypto trend/regime tools with automatic x402 payments.",
5
+ "type": "module",
6
+ "bin": {
7
+ "crypto-regime-mcp": "src/index.js"
8
+ },
9
+ "files": [
10
+ "src",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "scripts": {
15
+ "start": "node src/index.js",
16
+ "test": "node --test"
17
+ },
18
+ "engines": {
19
+ "node": ">=20"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "x402",
24
+ "crypto",
25
+ "trend",
26
+ "regime",
27
+ "volatility",
28
+ "base",
29
+ "usdc"
30
+ ],
31
+ "license": "MIT",
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/Shicodiez/signal-bot.git",
38
+ "directory": "mcp-bridge"
39
+ },
40
+ "homepage": "https://signal-api.shicoars.workers.dev",
41
+ "dependencies": {
42
+ "@modelcontextprotocol/sdk": "1.32.2",
43
+ "@x402/core": "2.30.0",
44
+ "@x402/evm": "2.30.0",
45
+ "@x402/mcp": "2.30.0",
46
+ "viem": "^2.57.16"
47
+ }
48
+ }
package/src/index.js ADDED
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import {
8
+ CallToolRequestSchema,
9
+ ListToolsRequestSchema,
10
+ } from "@modelcontextprotocol/sdk/types.js";
11
+ import { ExactEvmScheme } from "@x402/evm/exact/client";
12
+ import { createx402MCPClient } from "@x402/mcp";
13
+ import { privateKeyToAccount } from "viem/accounts";
14
+
15
+ import { createSpendTracker, usdcFromAtomic } from "./spend.js";
16
+
17
+ const VERSION = "0.1.0";
18
+ const DEFAULT_MCP_URL = "https://signal-api.shicoars.workers.dev/mcp";
19
+ const DEFAULT_NETWORK = "eip155:8453";
20
+ const DEFAULT_MAX_SPEND_USDC = 1;
21
+
22
+ function log(message) {
23
+ // stdout is reserved for the MCP stdio protocol.
24
+ console.error(`[crypto-regime-mcp] ${message}`);
25
+ }
26
+
27
+ function parseMaxSpend() {
28
+ const raw = process.env.MAX_SPEND_USDC;
29
+ if (raw === undefined || raw.trim() === "") {
30
+ return DEFAULT_MAX_SPEND_USDC;
31
+ }
32
+ const value = Number(raw);
33
+ if (!Number.isFinite(value) || value < 0) {
34
+ throw new Error("MAX_SPEND_USDC must be a non-negative number");
35
+ }
36
+ return value;
37
+ }
38
+
39
+ function getPrivateKey() {
40
+ const raw = process.env.EVM_PRIVATE_KEY?.trim();
41
+ if (!raw || !/^0x[0-9a-fA-F]{64}$/.test(raw)) {
42
+ throw new Error("EVM_PRIVATE_KEY must be a 32-byte hex private key (0x...)");
43
+ }
44
+ return raw;
45
+ }
46
+
47
+ async function main() {
48
+ const mcpUrl = process.env.SIGNAL_API_MCP_URL ?? DEFAULT_MCP_URL;
49
+ const network = process.env.NETWORK ?? DEFAULT_NETWORK;
50
+ const maxSpendUsdc = parseMaxSpend();
51
+ const account = privateKeyToAccount(getPrivateKey());
52
+ const tracker = createSpendTracker(maxSpendUsdc);
53
+
54
+ const paymentClient = createx402MCPClient({
55
+ name: "crypto-regime-mcp",
56
+ version: VERSION,
57
+ schemes: [
58
+ {
59
+ network,
60
+ client: new ExactEvmScheme(account),
61
+ },
62
+ ],
63
+ spendControls:
64
+ maxSpendUsdc > 0
65
+ ? { maxAmountPerPayment: `$${maxSpendUsdc}` }
66
+ : false,
67
+ autoPayment: true,
68
+ onPaymentRequested: async ({ paymentRequired }) => {
69
+ const accept = paymentRequired.accepts?.[0];
70
+ if (!accept) {
71
+ log("payment blocked: no accepts entry");
72
+ return false;
73
+ }
74
+ const amountUsd = usdcFromAtomic(accept.amount);
75
+ if (amountUsd === null) {
76
+ log(`payment blocked: invalid amount ${accept.amount}`);
77
+ return false;
78
+ }
79
+ if (!tracker.canSpend(amountUsd)) {
80
+ log(
81
+ `payment blocked: $${amountUsd} would exceed MAX_SPEND_USDC=$${maxSpendUsdc} (spent $${tracker.spentUsdc})`,
82
+ );
83
+ return false;
84
+ }
85
+ tracker.record(amountUsd);
86
+ log(
87
+ `authorizing $${amountUsd} on ${accept.network} to ${accept.payTo} (spent $${tracker.spentUsdc}/$${maxSpendUsdc})`,
88
+ );
89
+ return true;
90
+ },
91
+ });
92
+
93
+ const transport = new StreamableHTTPClientTransport(new URL(mcpUrl));
94
+ await paymentClient.connect(transport);
95
+
96
+ const server = new Server(
97
+ { name: "crypto-regime-mcp", version: VERSION },
98
+ { capabilities: { tools: {} } },
99
+ );
100
+
101
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
102
+ const result = await paymentClient.listTools();
103
+ return { tools: result.tools };
104
+ });
105
+
106
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
107
+ const { name, arguments: args } = request.params;
108
+ try {
109
+ const result = await paymentClient.callTool(name, args ?? {});
110
+ const meta = { ...(result._meta ?? {}) };
111
+ if (result.paymentResponse) {
112
+ meta["x402/payment-response"] = result.paymentResponse;
113
+ }
114
+ return {
115
+ content: result.content,
116
+ isError: result.isError,
117
+ structuredContent: result.structuredContent,
118
+ _meta: Object.keys(meta).length > 0 ? meta : undefined,
119
+ };
120
+ } catch (error) {
121
+ const message = error instanceof Error ? error.message : String(error);
122
+ log(`tool ${name} failed: ${message}`);
123
+ return {
124
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }],
125
+ isError: true,
126
+ };
127
+ }
128
+ });
129
+
130
+ await server.connect(new StdioServerTransport());
131
+ log(
132
+ `connected to ${mcpUrl} as ${account.address}; MAX_SPEND_USDC=$${maxSpendUsdc}`,
133
+ );
134
+ }
135
+
136
+ main().catch((error) => {
137
+ log(error instanceof Error ? error.message : String(error));
138
+ process.exit(1);
139
+ });
package/src/spend.js ADDED
@@ -0,0 +1,40 @@
1
+ export function createSpendTracker(maxSpendUsdc) {
2
+ let spentUsdc = 0;
3
+ const epsilon = 1e-9;
4
+
5
+ return {
6
+ get maxUsdc() {
7
+ return maxSpendUsdc;
8
+ },
9
+ get spentUsdc() {
10
+ return spentUsdc;
11
+ },
12
+ remainingUsdc() {
13
+ return Math.max(
14
+ 0,
15
+ Math.round((maxSpendUsdc - spentUsdc) * 1e6) / 1e6,
16
+ );
17
+ },
18
+ canSpend(amountUsdc) {
19
+ return (
20
+ Number.isFinite(amountUsdc) &&
21
+ amountUsdc >= 0 &&
22
+ spentUsdc + amountUsdc <= maxSpendUsdc + epsilon
23
+ );
24
+ },
25
+ record(amountUsdc) {
26
+ if (!Number.isFinite(amountUsdc) || amountUsdc < 0) {
27
+ throw new Error("amountUsdc must be a non-negative finite number");
28
+ }
29
+ spentUsdc = Math.round((spentUsdc + amountUsdc) * 1e6) / 1e6;
30
+ },
31
+ };
32
+ }
33
+
34
+ export function usdcFromAtomic(amount, decimals = 6) {
35
+ const value = Number(amount);
36
+ if (!Number.isFinite(value) || value < 0) {
37
+ return null;
38
+ }
39
+ return value / 10 ** decimals;
40
+ }