@scriptmasterlabs/mcp-x402 2.0.1 → 2.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/.well-known/agentcard.json +34 -34
- package/.well-known/ai.txt +32 -0
- package/CONTRIBUTING.md +76 -76
- package/LICENSE +21 -21
- package/README.md +304 -304
- package/agents.json +81 -67
- package/ai/faq.json +74 -0
- package/ai/summary.json +157 -0
- package/dist/lib/chains/base.d.ts.map +1 -1
- package/dist/lib/chains/base.js +2 -0
- package/dist/lib/chains/base.js.map +1 -1
- package/dist/lib/credit/bureau.d.ts +7 -1
- package/dist/lib/credit/bureau.d.ts.map +1 -1
- package/dist/lib/credit/bureau.js +40 -10
- package/dist/lib/credit/bureau.js.map +1 -1
- package/dist/server/index.js +128 -5
- package/dist/server/index.js.map +1 -1
- package/llms.txt +170 -70
- package/package.json +78 -78
- package/server.json +52 -48
- package/.env.example +0 -35
- package/.github/workflows/ci.yml +0 -59
- package/.github/workflows/keepalive.yml +0 -31
- package/Dockerfile +0 -19
- package/docker-compose.yml +0 -50
- package/mcp-publisher.exe +0 -0
- package/render.yaml +0 -39
- package/sdk/mcp-x402-sdk/package.json +0 -18
- package/sdk/mcp-x402-sdk/src/index.ts +0 -118
- package/sdk/mcp-x402-sdk/tsconfig.json +0 -14
- package/services/backtest_service.py +0 -176
- package/src/lib/chains/base.ts +0 -77
- package/src/lib/chains/solana.ts +0 -59
- package/src/lib/chains/xrpl.ts +0 -63
- package/src/lib/credit/bureau.ts +0 -65
- package/src/lib/sml-api/agentcard.ts +0 -40
- package/src/lib/sml-api/backtest.ts +0 -47
- package/src/lib/sml-api/brokers.ts +0 -160
- package/src/lib/sml-api/copytrader.ts +0 -33
- package/src/lib/sml-api/crawl.ts +0 -44
- package/src/lib/sml-api/echo.ts +0 -28
- package/src/lib/sml-api/forge.ts +0 -33
- package/src/lib/sml-api/ftd.ts +0 -53
- package/src/lib/sml-api/ghost.ts +0 -35
- package/src/lib/sml-api/launchpad.ts +0 -43
- package/src/lib/sml-api/leviathan.ts +0 -49
- package/src/lib/sml-api/nexus.ts +0 -50
- package/src/lib/sml-api/proof402.ts +0 -27
- package/src/lib/sml-api/rails.ts +0 -34
- package/src/lib/sml-api/shadow.ts +0 -35
- package/src/lib/sml-api/squeezeos.ts +0 -95
- package/src/lib/sml-api/xdeo.ts +0 -40
- package/src/lib/sml-api/xmit.ts +0 -40
- package/src/server/health.ts +0 -52
- package/src/server/index.ts +0 -213
- package/src/server/payments/ap2.ts +0 -101
- package/src/server/payments/receipt.ts +0 -85
- package/src/server/payments/router.ts +0 -110
- package/src/server/payments/wallet.ts +0 -123
- package/src/server/payments/x402.ts +0 -177
- package/src/server/registry/catalog.ts +0 -61
- package/src/server/registry/discovery.ts +0 -39
- package/src/server/registry/pricing.ts +0 -133
- package/src/server/security/acl.ts +0 -42
- package/src/server/security/audit.ts +0 -94
- package/src/server/security/rate-limit.ts +0 -84
- package/src/server/security/sandbox.ts +0 -40
- package/src/server/tools/agentcard.ts +0 -134
- package/src/server/tools/backtest.ts +0 -119
- package/src/server/tools/brokers.ts +0 -250
- package/src/server/tools/copytrader.ts +0 -104
- package/src/server/tools/crawl.ts +0 -70
- package/src/server/tools/discovery.ts +0 -202
- package/src/server/tools/echo.ts +0 -58
- package/src/server/tools/forge.ts +0 -87
- package/src/server/tools/ftd.ts +0 -88
- package/src/server/tools/ghost.ts +0 -93
- package/src/server/tools/index.ts +0 -42
- package/src/server/tools/launchpad.ts +0 -173
- package/src/server/tools/leviathan.ts +0 -81
- package/src/server/tools/nexus.ts +0 -76
- package/src/server/tools/proof402.ts +0 -87
- package/src/server/tools/rails.ts +0 -92
- package/src/server/tools/shadow.ts +0 -128
- package/src/server/tools/squeezeos.ts +0 -312
- package/src/server/tools/xdeo.ts +0 -67
- package/src/server/tools/xmit.ts +0 -68
- package/tests/integration/e2e.test.ts +0 -51
- package/tests/unit/payments.test.ts +0 -49
- package/tests/unit/security.test.ts +0 -92
- package/tests/unit/tools.test.ts +0 -42
- package/tsconfig.json +0 -21
- package/vitest.config.ts +0 -20
package/server.json
CHANGED
|
@@ -1,48 +1,52 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
-
"name": "io.github.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.Timwal78/mcp-x402",
|
|
4
|
+
"title": "mcp-x402 — The x402 Amazon for AI Agents",
|
|
5
|
+
"description": "51 pay-per-call MCP tools: market intel, SEC, XRPL payments, copy-trading. USDC/RLUSD. No keys.",
|
|
6
|
+
"version": "2.1.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://github.com/timwal78/SML_Portfolio",
|
|
9
|
+
"source": "github"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://scriptmasterlabs.com",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"packages": [
|
|
14
|
+
{
|
|
15
|
+
"registryType": "npm",
|
|
16
|
+
"identifier": "@scriptmasterlabs/mcp-x402",
|
|
17
|
+
"version": "2.1.0",
|
|
18
|
+
"runtimeHint": "node",
|
|
19
|
+
"transport": {
|
|
20
|
+
"type": "stdio"
|
|
21
|
+
},
|
|
22
|
+
"environmentVariables": [
|
|
23
|
+
{
|
|
24
|
+
"name": "MCP_TRANSPORT",
|
|
25
|
+
"description": "Transport mode. Omit for stdio (default). Set to 'sse' for HTTP server mode.",
|
|
26
|
+
"isRequired": false
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "SML_PAYMENT_RECEIVER",
|
|
30
|
+
"description": "Optional USDC Base address to receive payments from agent tool calls.",
|
|
31
|
+
"isRequired": false
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "WALLET_SEED",
|
|
35
|
+
"description": "BIP-39 mnemonic for the server wallet. Auto-generated if not set.",
|
|
36
|
+
"isRequired": false
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"remotes": [
|
|
42
|
+
{
|
|
43
|
+
"type": "streamable-http",
|
|
44
|
+
"url": "https://mcp-x402.onrender.com/mcp"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"type": "sse",
|
|
48
|
+
"url": "https://mcp-x402.onrender.com/sse"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"keywords": ["x402", "micropayments", "usdc", "rlusd", "xrpl", "solana", "base", "squeeze", "options", "sec-filings", "ftd", "market-intelligence", "pay-per-call", "autonomous-agents"]
|
|
52
|
+
}
|
package/.env.example
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# === TRANSPORT ===
|
|
2
|
-
# stdio (default, for Claude Code) or sse (remote/Cursor)
|
|
3
|
-
MCP_TRANSPORT=stdio
|
|
4
|
-
MCP_SSE_PORT=3402
|
|
5
|
-
|
|
6
|
-
# === SML API ===
|
|
7
|
-
# Base URL for ScriptMasterLabs APIs
|
|
8
|
-
SML_API_BASE=https://api.scriptmasterlabs.com
|
|
9
|
-
SML_MTLS_CERT_PATH=./certs/client.crt
|
|
10
|
-
SML_MTLS_KEY_PATH=./certs/client.key
|
|
11
|
-
SML_MTLS_CA_PATH=./certs/sml-ca.crt
|
|
12
|
-
|
|
13
|
-
# === WALLET (stored in OS keychain — env only for CI/testnet) ===
|
|
14
|
-
# NEVER use in production — use OS keychain instead
|
|
15
|
-
# CI_WALLET_SEED=your-bip39-mnemonic-here
|
|
16
|
-
|
|
17
|
-
# === CHAINS ===
|
|
18
|
-
BASE_RPC_URL=https://mainnet.base.org
|
|
19
|
-
BASE_SEPOLIA_RPC_URL=https://sepolia.base.org
|
|
20
|
-
XRPL_RPC_URL=wss://xrplcluster.com
|
|
21
|
-
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
|
|
22
|
-
|
|
23
|
-
# === SPEND LIMITS ===
|
|
24
|
-
DAILY_SPEND_CAP_USD=50
|
|
25
|
-
AUTO_APPROVE_THRESHOLD_USD=1
|
|
26
|
-
PRICE_CACHE_TTL_MS=60000
|
|
27
|
-
|
|
28
|
-
# === AUDIT ===
|
|
29
|
-
AUDIT_LOG_PATH=./audit.log
|
|
30
|
-
|
|
31
|
-
# === CREDIT BUREAU ===
|
|
32
|
-
MIN_CREDIT_SCORE=300
|
|
33
|
-
|
|
34
|
-
# === TESTNET ===
|
|
35
|
-
TESTNET=false
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
name: mcp-x402 CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
paths:
|
|
6
|
-
- 'mcp-x402/**'
|
|
7
|
-
pull_request:
|
|
8
|
-
paths:
|
|
9
|
-
- 'mcp-x402/**'
|
|
10
|
-
|
|
11
|
-
defaults:
|
|
12
|
-
run:
|
|
13
|
-
working-directory: mcp-x402
|
|
14
|
-
|
|
15
|
-
jobs:
|
|
16
|
-
test:
|
|
17
|
-
runs-on: ubuntu-latest
|
|
18
|
-
steps:
|
|
19
|
-
- uses: actions/checkout@v4
|
|
20
|
-
|
|
21
|
-
- uses: actions/setup-node@v4
|
|
22
|
-
with:
|
|
23
|
-
node-version: '22'
|
|
24
|
-
cache: 'npm'
|
|
25
|
-
cache-dependency-path: mcp-x402/package-lock.json
|
|
26
|
-
|
|
27
|
-
- name: Install dependencies
|
|
28
|
-
run: npm ci --ignore-scripts
|
|
29
|
-
|
|
30
|
-
- name: Type check
|
|
31
|
-
run: npm run typecheck
|
|
32
|
-
|
|
33
|
-
- name: Unit tests
|
|
34
|
-
run: npm run test:unit
|
|
35
|
-
|
|
36
|
-
- name: Upload coverage
|
|
37
|
-
uses: codecov/codecov-action@v4
|
|
38
|
-
with:
|
|
39
|
-
directory: mcp-x402/coverage
|
|
40
|
-
flags: mcp-x402
|
|
41
|
-
continue-on-error: true
|
|
42
|
-
|
|
43
|
-
build:
|
|
44
|
-
runs-on: ubuntu-latest
|
|
45
|
-
steps:
|
|
46
|
-
- uses: actions/checkout@v4
|
|
47
|
-
|
|
48
|
-
- uses: actions/setup-node@v4
|
|
49
|
-
with:
|
|
50
|
-
node-version: '22'
|
|
51
|
-
|
|
52
|
-
- name: Install dependencies
|
|
53
|
-
run: npm ci --ignore-scripts
|
|
54
|
-
|
|
55
|
-
- name: Build
|
|
56
|
-
run: npm run build
|
|
57
|
-
|
|
58
|
-
- name: Docker build
|
|
59
|
-
run: docker build -t mcp-x402:ci .
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
name: mcp-x402 Keepalive
|
|
2
|
-
|
|
3
|
-
# Pings the SSE health endpoint every 14 minutes to prevent cold starts
|
|
4
|
-
# on free-tier Render instances (which sleep after 15 minutes of inactivity).
|
|
5
|
-
on:
|
|
6
|
-
schedule:
|
|
7
|
-
- cron: '*/14 * * * *' # every 14 minutes, 24/7
|
|
8
|
-
workflow_dispatch: # allow manual trigger
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
ping:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
timeout-minutes: 2
|
|
14
|
-
steps:
|
|
15
|
-
- name: Ping mcp-x402 health endpoint
|
|
16
|
-
env:
|
|
17
|
-
MCP_X402_URL: ${{ secrets.MCP_X402_URL }}
|
|
18
|
-
run: |
|
|
19
|
-
URL="${MCP_X402_URL:-https://mcp-x402.scriptmasterlabs.com}"
|
|
20
|
-
STATUS=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 "${URL}/health")
|
|
21
|
-
echo "Health check HTTP ${STATUS} — ${URL}/health"
|
|
22
|
-
if [ "$STATUS" != "200" ]; then
|
|
23
|
-
echo "::warning::Health check returned HTTP ${STATUS}. Service may be degraded."
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
- name: Ping SqueezeOS keepalive (ecosystem dependency)
|
|
27
|
-
run: |
|
|
28
|
-
curl -s --max-time 30 https://squeezeos-api.onrender.com/api/status > /dev/null || true
|
|
29
|
-
curl -s --max-time 30 https://four02proof.onrender.com/health > /dev/null || true
|
|
30
|
-
curl -s --max-time 30 https://ghost-layer.onrender.com/health > /dev/null || true
|
|
31
|
-
echo "Ecosystem keepalives sent."
|
package/Dockerfile
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
FROM node:22-alpine AS builder
|
|
2
|
-
WORKDIR /app
|
|
3
|
-
COPY package*.json ./
|
|
4
|
-
RUN npm ci --ignore-scripts
|
|
5
|
-
COPY tsconfig.json ./
|
|
6
|
-
COPY src/ ./src/
|
|
7
|
-
RUN npm run build
|
|
8
|
-
|
|
9
|
-
FROM node:22-alpine AS runner
|
|
10
|
-
WORKDIR /app
|
|
11
|
-
RUN apk add --no-cache dbus libsecret
|
|
12
|
-
COPY package*.json ./
|
|
13
|
-
RUN npm ci --omit=dev --ignore-scripts
|
|
14
|
-
COPY --from=builder /app/dist ./dist
|
|
15
|
-
COPY agents.json llms.txt .well-known/ ./
|
|
16
|
-
EXPOSE 3402
|
|
17
|
-
ENV NODE_ENV=production
|
|
18
|
-
USER node
|
|
19
|
-
CMD ["node", "dist/server/index.js"]
|
package/docker-compose.yml
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
version: '3.9'
|
|
2
|
-
|
|
3
|
-
services:
|
|
4
|
-
mcp-x402:
|
|
5
|
-
build:
|
|
6
|
-
context: .
|
|
7
|
-
dockerfile: Dockerfile
|
|
8
|
-
image: mcp-x402:latest
|
|
9
|
-
container_name: mcp-x402
|
|
10
|
-
restart: always # Auto-restart on crash, reboot, or OOM
|
|
11
|
-
environment:
|
|
12
|
-
NODE_ENV: production
|
|
13
|
-
MCP_TRANSPORT: sse
|
|
14
|
-
MCP_SSE_PORT: "3402"
|
|
15
|
-
DAILY_SPEND_CAP_USD: "50"
|
|
16
|
-
AUTO_APPROVE_THRESHOLD_USD: "1"
|
|
17
|
-
PRICE_CACHE_TTL_MS: "60000"
|
|
18
|
-
PROOF402_URL: https://four02proof.onrender.com
|
|
19
|
-
TESTNET: "false"
|
|
20
|
-
# Secrets — supply via .env file or secrets manager; NEVER hardcode
|
|
21
|
-
# SML_API_BASE:
|
|
22
|
-
# BASE_RPC_URL:
|
|
23
|
-
# XRPL_RPC_URL:
|
|
24
|
-
# SOLANA_RPC_URL:
|
|
25
|
-
ports:
|
|
26
|
-
- "3402:3402"
|
|
27
|
-
volumes:
|
|
28
|
-
- ./certs:/app/certs:ro # mTLS certs (read-only)
|
|
29
|
-
- audit_logs:/app/audit_logs # Persistent audit log volume
|
|
30
|
-
healthcheck:
|
|
31
|
-
test: ["CMD", "wget", "-qO-", "http://localhost:3402/health"]
|
|
32
|
-
interval: 30s
|
|
33
|
-
timeout: 10s
|
|
34
|
-
retries: 3
|
|
35
|
-
start_period: 20s
|
|
36
|
-
logging:
|
|
37
|
-
driver: json-file
|
|
38
|
-
options:
|
|
39
|
-
max-size: "50m"
|
|
40
|
-
max-file: "7"
|
|
41
|
-
deploy:
|
|
42
|
-
resources:
|
|
43
|
-
limits:
|
|
44
|
-
memory: 512m
|
|
45
|
-
reservations:
|
|
46
|
-
memory: 128m
|
|
47
|
-
|
|
48
|
-
volumes:
|
|
49
|
-
audit_logs:
|
|
50
|
-
driver: local
|
package/mcp-publisher.exe
DELETED
|
Binary file
|
package/render.yaml
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
services:
|
|
2
|
-
- type: web
|
|
3
|
-
name: mcp-x402
|
|
4
|
-
runtime: docker
|
|
5
|
-
dockerfilePath: ./mcp-x402/Dockerfile
|
|
6
|
-
dockerContext: ./mcp-x402
|
|
7
|
-
plan: starter
|
|
8
|
-
region: oregon
|
|
9
|
-
healthCheckPath: /health
|
|
10
|
-
envVars:
|
|
11
|
-
- key: NODE_ENV
|
|
12
|
-
value: production
|
|
13
|
-
- key: MCP_TRANSPORT
|
|
14
|
-
value: sse
|
|
15
|
-
- key: MCP_SSE_PORT
|
|
16
|
-
value: 3402
|
|
17
|
-
- key: SML_API_BASE
|
|
18
|
-
sync: false
|
|
19
|
-
- key: WALLET_SEED
|
|
20
|
-
sync: false
|
|
21
|
-
- key: BASE_RPC_URL
|
|
22
|
-
sync: false
|
|
23
|
-
- key: XRPL_RPC_URL
|
|
24
|
-
sync: false
|
|
25
|
-
- key: SOLANA_RPC_URL
|
|
26
|
-
sync: false
|
|
27
|
-
- key: DAILY_SPEND_CAP_USD
|
|
28
|
-
value: "50"
|
|
29
|
-
- key: AUTO_APPROVE_THRESHOLD_USD
|
|
30
|
-
value: "1"
|
|
31
|
-
- key: PRICE_CACHE_TTL_MS
|
|
32
|
-
value: "60000"
|
|
33
|
-
- key: PROOF402_URL
|
|
34
|
-
value: https://four02proof.onrender.com
|
|
35
|
-
- key: TESTNET
|
|
36
|
-
value: "false"
|
|
37
|
-
autoDeploy: true
|
|
38
|
-
# Render restarts the service automatically on crash
|
|
39
|
-
# healthCheckPath triggers restart if /health returns non-2xx
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@scriptmasterlabs/mcp-x402-sdk",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "5-line drop-in x402 payment wrapper for any MCP server author.",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"prepublishOnly": "npm run build"
|
|
10
|
-
},
|
|
11
|
-
"keywords": ["mcp", "x402", "autonomous-payments", "ai-agents"],
|
|
12
|
-
"author": "ScriptMasterLabs",
|
|
13
|
-
"license": "MIT",
|
|
14
|
-
"peerDependencies": {
|
|
15
|
-
"@modelcontextprotocol/sdk": ">=1.0.0",
|
|
16
|
-
"zod": ">=3.0.0"
|
|
17
|
-
}
|
|
18
|
-
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
export type SupportedCurrency = 'USDC' | 'RLUSD';
|
|
4
|
-
export type SupportedChain = 'base' | 'xrpl' | 'solana';
|
|
5
|
-
|
|
6
|
-
export interface X402PaymentConfig<TInput extends z.ZodTypeAny> {
|
|
7
|
-
price: string;
|
|
8
|
-
currency?: SupportedCurrency;
|
|
9
|
-
chain?: SupportedChain;
|
|
10
|
-
inputSchema: TInput;
|
|
11
|
-
handler: (input: z.infer<TInput>, receipt: PaymentReceipt) => Promise<ToolResult>;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface PaymentReceipt {
|
|
15
|
-
receipt_id: string;
|
|
16
|
-
tx_hash: string;
|
|
17
|
-
chain: string;
|
|
18
|
-
amount_paid: string;
|
|
19
|
-
currency: string;
|
|
20
|
-
timestamp: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface ToolResult {
|
|
24
|
-
content: Array<{ type: 'text'; text: string }>;
|
|
25
|
-
isError?: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const PROOF402_URL = process.env['PROOF402_URL'] ?? 'https://four02proof.onrender.com';
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* x402Payment — 5-line drop-in for any MCP server author.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* server.tool('my_tool', schema, x402Payment({
|
|
35
|
-
* price: '0.01',
|
|
36
|
-
* currency: 'USDC',
|
|
37
|
-
* inputSchema: MyInputSchema,
|
|
38
|
-
* handler: async (input, receipt) => {
|
|
39
|
-
* return { content: [{ type: 'text', text: JSON.stringify({ result: 'data', receipt }) }] };
|
|
40
|
-
* },
|
|
41
|
-
* }));
|
|
42
|
-
*/
|
|
43
|
-
export function x402Payment<TInput extends z.ZodTypeAny>(
|
|
44
|
-
config: X402PaymentConfig<TInput>,
|
|
45
|
-
): (rawArgs: unknown) => Promise<ToolResult> {
|
|
46
|
-
return async (rawArgs: unknown): Promise<ToolResult> => {
|
|
47
|
-
const parsed = config.inputSchema.safeParse(rawArgs);
|
|
48
|
-
if (!parsed.success) {
|
|
49
|
-
return {
|
|
50
|
-
content: [{ type: 'text', text: JSON.stringify({ error: 'validation_error', issues: parsed.error.issues }) }],
|
|
51
|
-
isError: true,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const args = parsed.data as z.infer<TInput>;
|
|
56
|
-
const walletAddress = (args as Record<string, unknown>)['wallet_address'] as string | undefined;
|
|
57
|
-
|
|
58
|
-
let receipt: PaymentReceipt;
|
|
59
|
-
try {
|
|
60
|
-
receipt = await processPayment({
|
|
61
|
-
price: config.price,
|
|
62
|
-
currency: config.currency ?? 'USDC',
|
|
63
|
-
chain: config.chain,
|
|
64
|
-
walletAddress,
|
|
65
|
-
});
|
|
66
|
-
} catch (err) {
|
|
67
|
-
return {
|
|
68
|
-
content: [{ type: 'text', text: JSON.stringify({ error: 'payment_failed', message: String(err) }) }],
|
|
69
|
-
isError: true,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return config.handler(args, receipt);
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async function processPayment(params: {
|
|
78
|
-
price: string;
|
|
79
|
-
currency: SupportedCurrency;
|
|
80
|
-
chain?: SupportedChain;
|
|
81
|
-
walletAddress?: string;
|
|
82
|
-
}): Promise<PaymentReceipt> {
|
|
83
|
-
// Delegates to the 402Proof payment endpoint
|
|
84
|
-
const res = await fetch(`${PROOF402_URL}/v1/pay`, {
|
|
85
|
-
method: 'POST',
|
|
86
|
-
headers: { 'Content-Type': 'application/json' },
|
|
87
|
-
body: JSON.stringify({
|
|
88
|
-
amount: params.price,
|
|
89
|
-
currency: params.currency,
|
|
90
|
-
chain: params.chain ?? 'base',
|
|
91
|
-
wallet: params.walletAddress,
|
|
92
|
-
}),
|
|
93
|
-
signal: AbortSignal.timeout(15_000),
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
if (!res.ok) {
|
|
97
|
-
throw new Error(`Payment failed: HTTP ${res.status}`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const body = (await res.json()) as {
|
|
101
|
-
receipt_id: string;
|
|
102
|
-
tx_hash: string;
|
|
103
|
-
chain: string;
|
|
104
|
-
amount: string;
|
|
105
|
-
currency: string;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
receipt_id: body.receipt_id,
|
|
110
|
-
tx_hash: body.tx_hash,
|
|
111
|
-
chain: body.chain,
|
|
112
|
-
amount_paid: body.amount,
|
|
113
|
-
currency: body.currency,
|
|
114
|
-
timestamp: Date.now(),
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export { x402Payment as default };
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"declaration": true
|
|
12
|
-
},
|
|
13
|
-
"include": ["src/**/*"]
|
|
14
|
-
}
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Backtest microservice — wraps backtester-mcp + yfinance
|
|
3
|
-
POST /backtest { ticker, strategy_signals, lookback_days, fees, slippage }
|
|
4
|
-
POST /validate { ticker, lookback_days, train_ratio } — walk-forward split
|
|
5
|
-
GET /health
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
import os
|
|
10
|
-
import json
|
|
11
|
-
import numpy as np
|
|
12
|
-
from datetime import datetime, timedelta
|
|
13
|
-
from typing import Any
|
|
14
|
-
|
|
15
|
-
from flask import Flask, request, jsonify
|
|
16
|
-
import backtester_mcp as bt
|
|
17
|
-
|
|
18
|
-
# yfinance is the free price source — no API key required
|
|
19
|
-
try:
|
|
20
|
-
import yfinance as yf
|
|
21
|
-
YF_AVAILABLE = True
|
|
22
|
-
except ImportError:
|
|
23
|
-
YF_AVAILABLE = False
|
|
24
|
-
|
|
25
|
-
app = Flask(__name__)
|
|
26
|
-
|
|
27
|
-
# ── helpers ──────────────────────────────────────────────────────────────────
|
|
28
|
-
|
|
29
|
-
def _fetch_prices(ticker: str, days: int) -> np.ndarray:
|
|
30
|
-
if not YF_AVAILABLE:
|
|
31
|
-
raise RuntimeError("yfinance not installed")
|
|
32
|
-
end = datetime.utcnow()
|
|
33
|
-
start = end - timedelta(days=days + 30) # buffer for weekends/holidays
|
|
34
|
-
df = yf.download(ticker, start=start.strftime("%Y-%m-%d"),
|
|
35
|
-
end=end.strftime("%Y-%m-%d"), progress=False, auto_adjust=True)
|
|
36
|
-
if df.empty:
|
|
37
|
-
raise ValueError(f"No price data for {ticker}")
|
|
38
|
-
return df["Close"].dropna().values[-days:]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _momentum_signals(prices: np.ndarray, window: int = 10, threshold: float = 0.001) -> np.ndarray:
|
|
42
|
-
"""Default long-only momentum signal for validation."""
|
|
43
|
-
returns = np.diff(np.log(prices))
|
|
44
|
-
mom = np.convolve(returns, np.ones(window) / window, mode="same")
|
|
45
|
-
signals = np.zeros(len(prices))
|
|
46
|
-
signals[1:] = np.where(mom[:-1] > threshold, 1, 0)
|
|
47
|
-
return signals
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _run_backtest(prices: np.ndarray, signals: np.ndarray,
|
|
51
|
-
fees: float, slippage: float) -> dict[str, Any]:
|
|
52
|
-
result = bt.backtest(prices, signals, fees=fees, slippage=slippage)
|
|
53
|
-
m = result.metrics
|
|
54
|
-
return {
|
|
55
|
-
"sharpe": round(m["sharpe"], 3),
|
|
56
|
-
"sortino": round(m["sortino"], 3),
|
|
57
|
-
"cagr": round(m["cagr"], 4),
|
|
58
|
-
"total_return": round(m["total_return"], 4),
|
|
59
|
-
"max_drawdown": round(m["max_drawdown"], 4),
|
|
60
|
-
"max_drawdown_duration_days": int(m["max_drawdown_duration"]),
|
|
61
|
-
"win_rate": round(m["win_rate"], 4),
|
|
62
|
-
"profit_factor": round(m["profit_factor"], 3),
|
|
63
|
-
"calmar": round(m["calmar"], 3),
|
|
64
|
-
"volatility": round(m["volatility"], 4),
|
|
65
|
-
"num_trades": int(m["num_trades"]),
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# ── routes ───────────────────────────────────────────────────────────────────
|
|
70
|
-
|
|
71
|
-
@app.get("/health")
|
|
72
|
-
def health():
|
|
73
|
-
return jsonify({
|
|
74
|
-
"status": "ok",
|
|
75
|
-
"engine": f"backtester-mcp v{bt.__version__}",
|
|
76
|
-
"yfinance": YF_AVAILABLE,
|
|
77
|
-
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
@app.post("/backtest")
|
|
82
|
-
def backtest():
|
|
83
|
-
body = request.get_json(force=True)
|
|
84
|
-
ticker: str = body.get("ticker", "").upper()
|
|
85
|
-
custom_signals: list | None = body.get("signals") # optional float array
|
|
86
|
-
lookback: int = int(body.get("lookback_days", 252))
|
|
87
|
-
fees: float = float(body.get("fees", 0.001))
|
|
88
|
-
slippage: float = float(body.get("slippage", 0.0005))
|
|
89
|
-
window: int = int(body.get("momentum_window", 10))
|
|
90
|
-
threshold: float = float(body.get("momentum_threshold", 0.001))
|
|
91
|
-
|
|
92
|
-
if not ticker:
|
|
93
|
-
return jsonify({"error": "ticker required"}), 400
|
|
94
|
-
|
|
95
|
-
try:
|
|
96
|
-
prices = _fetch_prices(ticker, lookback)
|
|
97
|
-
except Exception as e:
|
|
98
|
-
return jsonify({"error": str(e)}), 422
|
|
99
|
-
|
|
100
|
-
if custom_signals:
|
|
101
|
-
signals = np.array(custom_signals, dtype=float)
|
|
102
|
-
if len(signals) != len(prices):
|
|
103
|
-
return jsonify({"error": f"signals length {len(signals)} != prices length {len(prices)}"}), 400
|
|
104
|
-
else:
|
|
105
|
-
signals = _momentum_signals(prices, window=window, threshold=threshold)
|
|
106
|
-
|
|
107
|
-
try:
|
|
108
|
-
metrics = _run_backtest(prices, signals, fees, slippage)
|
|
109
|
-
except Exception as e:
|
|
110
|
-
return jsonify({"error": str(e)}), 500
|
|
111
|
-
|
|
112
|
-
verdict = "ROBUST" if metrics["sharpe"] > 1.5 and metrics["max_drawdown"] > -0.25 else \
|
|
113
|
-
"MODERATE" if metrics["sharpe"] > 0.8 else \
|
|
114
|
-
"WEAK" if metrics["sharpe"] > 0 else "OVERFITTED"
|
|
115
|
-
|
|
116
|
-
return jsonify({
|
|
117
|
-
"ticker": ticker,
|
|
118
|
-
"lookback_days": len(prices),
|
|
119
|
-
"fees": fees,
|
|
120
|
-
"slippage": slippage,
|
|
121
|
-
"metrics": metrics,
|
|
122
|
-
"verdict": verdict,
|
|
123
|
-
"engine": f"backtester-mcp v{bt.__version__}",
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
@app.post("/validate")
|
|
128
|
-
def walk_forward():
|
|
129
|
-
"""Walk-forward OOS validation — splits data into train/test."""
|
|
130
|
-
body = request.get_json(force=True)
|
|
131
|
-
ticker: str = body.get("ticker", "").upper()
|
|
132
|
-
lookback: int = int(body.get("lookback_days", 504))
|
|
133
|
-
train_ratio: float = float(body.get("train_ratio", 0.7))
|
|
134
|
-
fees: float = float(body.get("fees", 0.001))
|
|
135
|
-
slippage: float = float(body.get("slippage", 0.0005))
|
|
136
|
-
|
|
137
|
-
if not ticker:
|
|
138
|
-
return jsonify({"error": "ticker required"}), 400
|
|
139
|
-
|
|
140
|
-
try:
|
|
141
|
-
prices = _fetch_prices(ticker, lookback)
|
|
142
|
-
except Exception as e:
|
|
143
|
-
return jsonify({"error": str(e)}), 422
|
|
144
|
-
|
|
145
|
-
split = int(len(prices) * train_ratio)
|
|
146
|
-
train_prices = prices[:split]
|
|
147
|
-
oos_prices = prices[split:]
|
|
148
|
-
|
|
149
|
-
train_signals = _momentum_signals(train_prices)
|
|
150
|
-
oos_signals = _momentum_signals(oos_prices)
|
|
151
|
-
|
|
152
|
-
try:
|
|
153
|
-
train_metrics = _run_backtest(train_prices, train_signals, fees, slippage)
|
|
154
|
-
oos_metrics = _run_backtest(oos_prices, oos_signals, fees, slippage)
|
|
155
|
-
except Exception as e:
|
|
156
|
-
return jsonify({"error": str(e)}), 500
|
|
157
|
-
|
|
158
|
-
# Deflated Sharpe — penalize if OOS degrades significantly
|
|
159
|
-
sharpe_degradation = train_metrics["sharpe"] - oos_metrics["sharpe"]
|
|
160
|
-
oos_verdict = "PASS" if oos_metrics["sharpe"] > 0.5 and sharpe_degradation < 1.5 else "FAIL"
|
|
161
|
-
|
|
162
|
-
return jsonify({
|
|
163
|
-
"ticker": ticker,
|
|
164
|
-
"train_days": len(train_prices),
|
|
165
|
-
"oos_days": len(oos_prices),
|
|
166
|
-
"train_metrics": train_metrics,
|
|
167
|
-
"oos_metrics": oos_metrics,
|
|
168
|
-
"sharpe_degradation": round(sharpe_degradation, 3),
|
|
169
|
-
"oos_verdict": oos_verdict,
|
|
170
|
-
"engine": f"backtester-mcp v{bt.__version__}",
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if __name__ == "__main__":
|
|
175
|
-
port = int(os.environ.get("BACKTEST_PORT", 8300))
|
|
176
|
-
app.run(host="0.0.0.0", port=port, debug=False)
|