@ecadlabs/tezosx-mcp 1.0.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 +201 -0
- package/README.md +335 -0
- package/dist/adapters/index.d.ts +37 -0
- package/dist/adapters/index.js +57 -0
- package/dist/adapters/node.d.ts +18 -0
- package/dist/adapters/node.js +35 -0
- package/dist/adapters/types.d.ts +52 -0
- package/dist/adapters/types.js +25 -0
- package/dist/adapters/worker.d.ts +35 -0
- package/dist/adapters/worker.js +50 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +144 -0
- package/dist/server.d.ts +36 -0
- package/dist/server.js +80 -0
- package/dist/tools/create_x402_payment.d.ts +27 -0
- package/dist/tools/create_x402_payment.js +55 -0
- package/dist/tools/fetch_with_x402.d.ts +28 -0
- package/dist/tools/fetch_with_x402.js +143 -0
- package/dist/tools/get_address.d.ts +20 -0
- package/dist/tools/get_address.js +24 -0
- package/dist/tools/get_addresses.d.ts +22 -0
- package/dist/tools/get_addresses.js +32 -0
- package/dist/tools/get_balance.d.ts +22 -0
- package/dist/tools/get_balance.js +27 -0
- package/dist/tools/get_dashboard.d.ts +21 -0
- package/dist/tools/get_dashboard.js +29 -0
- package/dist/tools/get_limits.d.ts +22 -0
- package/dist/tools/get_limits.js +61 -0
- package/dist/tools/get_operation_history.d.ts +21 -0
- package/dist/tools/get_operation_history.js +58 -0
- package/dist/tools/index.d.ts +113 -0
- package/dist/tools/index.js +59 -0
- package/dist/tools/parse_x402_requirements.d.ts +23 -0
- package/dist/tools/parse_x402_requirements.js +66 -0
- package/dist/tools/reveal_account.d.ts +34 -0
- package/dist/tools/reveal_account.js +51 -0
- package/dist/tools/send_xtz.d.ts +32 -0
- package/dist/tools/send_xtz.js +86 -0
- package/dist/tools/x402/sign.d.ts +12 -0
- package/dist/tools/x402/sign.js +76 -0
- package/dist/tools/x402/types.d.ts +40 -0
- package/dist/tools/x402/types.js +16 -0
- package/dist/webserver.d.ts +1 -0
- package/dist/webserver.js +10 -0
- package/dist/worker.bundle.js +134265 -0
- package/dist/worker.d.ts +13 -0
- package/dist/worker.js +132 -0
- package/frontend/dist/assets/index-RtTL1nIl.js +257 -0
- package/frontend/dist/assets/index-mSsI3AqQ.css +1 -0
- package/frontend/dist/index.html +16 -0
- package/package.json +70 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment adapter interface for abstracting platform-specific operations.
|
|
3
|
+
* This allows the MCP server to run in both Node.js and Cloudflare Workers environments.
|
|
4
|
+
*/
|
|
5
|
+
export interface EnvConfig {
|
|
6
|
+
MCP_TRANSPORT?: string;
|
|
7
|
+
SKIP_FRONTEND?: string;
|
|
8
|
+
WEB_PORT?: string;
|
|
9
|
+
PORT?: string;
|
|
10
|
+
TEZOS_NETWORK?: string;
|
|
11
|
+
SPENDING_PRIVATE_KEY?: string;
|
|
12
|
+
SPENDING_CONTRACT?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface EnvironmentAdapter {
|
|
15
|
+
/**
|
|
16
|
+
* Get environment variable value
|
|
17
|
+
*/
|
|
18
|
+
getEnv(key: keyof EnvConfig): string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Check if we're running in a Workers environment
|
|
21
|
+
*/
|
|
22
|
+
isWorker(): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Base64 encode a string (Buffer.from in Node, btoa in Workers)
|
|
25
|
+
*/
|
|
26
|
+
base64Encode(data: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Base64 decode a string
|
|
29
|
+
*/
|
|
30
|
+
base64Decode(data: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Log a message (stderr in Node, console.log in Workers)
|
|
33
|
+
*/
|
|
34
|
+
log(message: string): void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Static file serving configuration for HTTP mode
|
|
38
|
+
*/
|
|
39
|
+
export interface StaticFileConfig {
|
|
40
|
+
/** Path to serve static files from (Node.js only) */
|
|
41
|
+
staticPath?: string;
|
|
42
|
+
/** Whether to use SPA fallback (serve index.html for unknown routes) */
|
|
43
|
+
spaFallback?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Runtime environment type
|
|
47
|
+
*/
|
|
48
|
+
export type RuntimeEnvironment = 'node' | 'worker';
|
|
49
|
+
/**
|
|
50
|
+
* Detect the current runtime environment
|
|
51
|
+
*/
|
|
52
|
+
export declare function detectEnvironment(): RuntimeEnvironment;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment adapter interface for abstracting platform-specific operations.
|
|
3
|
+
* This allows the MCP server to run in both Node.js and Cloudflare Workers environments.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Detect the current runtime environment
|
|
7
|
+
*/
|
|
8
|
+
export function detectEnvironment() {
|
|
9
|
+
// Check for Cloudflare Workers-specific globals
|
|
10
|
+
// @ts-ignore - caches is a Workers global
|
|
11
|
+
if (typeof globalThis.caches !== 'undefined' && typeof globalThis.caches.default !== 'undefined') {
|
|
12
|
+
return 'worker';
|
|
13
|
+
}
|
|
14
|
+
// Check for other Workers indicators
|
|
15
|
+
// @ts-ignore - WorkerGlobalScope is a Workers type
|
|
16
|
+
if (typeof WorkerGlobalScope !== 'undefined') {
|
|
17
|
+
return 'worker';
|
|
18
|
+
}
|
|
19
|
+
// Check for Node.js process
|
|
20
|
+
if (typeof process !== 'undefined' && process.versions?.node) {
|
|
21
|
+
return 'node';
|
|
22
|
+
}
|
|
23
|
+
// Default to worker if we can't determine (safer for bundled code)
|
|
24
|
+
return 'worker';
|
|
25
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Workers environment adapter
|
|
3
|
+
* Uses Web APIs and Worker bindings instead of Node.js filesystem/process
|
|
4
|
+
*/
|
|
5
|
+
import type { EnvironmentAdapter, EnvConfig } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Cloudflare Worker environment bindings type
|
|
8
|
+
* These are passed to the fetch handler by Cloudflare
|
|
9
|
+
*/
|
|
10
|
+
export interface WorkerEnv {
|
|
11
|
+
MCP_TRANSPORT?: string;
|
|
12
|
+
SKIP_FRONTEND?: string;
|
|
13
|
+
WEB_PORT?: string;
|
|
14
|
+
PORT?: string;
|
|
15
|
+
TEZOS_NETWORK?: string;
|
|
16
|
+
SPENDING_PRIVATE_KEY?: string;
|
|
17
|
+
SPENDING_CONTRACT?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class WorkerAdapter implements EnvironmentAdapter {
|
|
20
|
+
private env;
|
|
21
|
+
constructor(env?: WorkerEnv);
|
|
22
|
+
/**
|
|
23
|
+
* Update the environment bindings (called per-request with the env from fetch handler)
|
|
24
|
+
*/
|
|
25
|
+
setEnv(env: WorkerEnv): void;
|
|
26
|
+
getEnv(key: keyof EnvConfig): string | undefined;
|
|
27
|
+
isWorker(): boolean;
|
|
28
|
+
base64Encode(data: string): string;
|
|
29
|
+
base64Decode(data: string): string;
|
|
30
|
+
log(message: string): void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a Worker adapter instance
|
|
34
|
+
*/
|
|
35
|
+
export declare function createWorkerAdapter(env?: WorkerEnv): WorkerAdapter;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Workers environment adapter
|
|
3
|
+
* Uses Web APIs and Worker bindings instead of Node.js filesystem/process
|
|
4
|
+
*/
|
|
5
|
+
export class WorkerAdapter {
|
|
6
|
+
env;
|
|
7
|
+
constructor(env = {}) {
|
|
8
|
+
this.env = env;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Update the environment bindings (called per-request with the env from fetch handler)
|
|
12
|
+
*/
|
|
13
|
+
setEnv(env) {
|
|
14
|
+
this.env = env;
|
|
15
|
+
}
|
|
16
|
+
getEnv(key) {
|
|
17
|
+
return this.env[key];
|
|
18
|
+
}
|
|
19
|
+
isWorker() {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
base64Encode(data) {
|
|
23
|
+
// Use TextEncoder for proper UTF-8 handling
|
|
24
|
+
const encoder = new TextEncoder();
|
|
25
|
+
const bytes = encoder.encode(data);
|
|
26
|
+
let binary = '';
|
|
27
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
28
|
+
binary += String.fromCharCode(bytes[i]);
|
|
29
|
+
}
|
|
30
|
+
return btoa(binary);
|
|
31
|
+
}
|
|
32
|
+
base64Decode(data) {
|
|
33
|
+
const binary = atob(data);
|
|
34
|
+
const bytes = new Uint8Array(binary.length);
|
|
35
|
+
for (let i = 0; i < binary.length; i++) {
|
|
36
|
+
bytes[i] = binary.charCodeAt(i);
|
|
37
|
+
}
|
|
38
|
+
const decoder = new TextDecoder();
|
|
39
|
+
return decoder.decode(bytes);
|
|
40
|
+
}
|
|
41
|
+
log(message) {
|
|
42
|
+
console.log(`[tezosx-mcp] ${message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create a Worker adapter instance
|
|
47
|
+
*/
|
|
48
|
+
export function createWorkerAdapter(env) {
|
|
49
|
+
return new WorkerAdapter(env);
|
|
50
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export const DEFAULT_WEB_PORT = '13205';
|
|
3
|
+
import { config } from 'dotenv';
|
|
4
|
+
// Taquito
|
|
5
|
+
import { InMemorySigner } from "@taquito/signer";
|
|
6
|
+
import { TezosToolkit } from "@taquito/taquito";
|
|
7
|
+
// MCP
|
|
8
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
10
|
+
import { createTools } from "./tools/index.js";
|
|
11
|
+
// MCP hosted
|
|
12
|
+
import express from 'express';
|
|
13
|
+
// Webserver
|
|
14
|
+
import { startWebServer } from "./webserver.js";
|
|
15
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
config({ quiet: true });
|
|
19
|
+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
20
|
+
// Global error handlers
|
|
21
|
+
process.on('uncaughtException', (err) => {
|
|
22
|
+
console.error('[tezosx-mcp] Uncaught exception:', err);
|
|
23
|
+
});
|
|
24
|
+
process.on('unhandledRejection', (reason) => {
|
|
25
|
+
console.error('[tezosx-mcp] Unhandled rejection:', reason);
|
|
26
|
+
});
|
|
27
|
+
// Network configurations
|
|
28
|
+
const NETWORKS = {
|
|
29
|
+
mainnet: {
|
|
30
|
+
rpcUrl: 'https://mainnet.tezos.ecadinfra.com',
|
|
31
|
+
tzktApi: 'https://api.tzkt.io',
|
|
32
|
+
},
|
|
33
|
+
shadownet: {
|
|
34
|
+
rpcUrl: 'https://shadownet.tezos.ecadinfra.com',
|
|
35
|
+
tzktApi: 'https://api.shadownet.tzkt.io',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const log = (msg) => console.error(`[tezosx-mcp] ${msg}`);
|
|
39
|
+
const init = async () => {
|
|
40
|
+
log('Starting server...');
|
|
41
|
+
// Start web server for frontend (skip if SKIP_FRONTEND is set or if using HTTP transport)
|
|
42
|
+
const skipFrontend = process.env.SKIP_FRONTEND === 'true' || process.env.MCP_TRANSPORT === 'http';
|
|
43
|
+
if (!skipFrontend) {
|
|
44
|
+
const webPort = parseInt(process.env.WEB_PORT || DEFAULT_WEB_PORT, 10);
|
|
45
|
+
startWebServer(webPort);
|
|
46
|
+
log(`Frontend server started on port ${webPort}`);
|
|
47
|
+
}
|
|
48
|
+
const server = new McpServer({
|
|
49
|
+
name: "tezosx-mcp",
|
|
50
|
+
version: "1.0.0"
|
|
51
|
+
});
|
|
52
|
+
// Network configuration
|
|
53
|
+
const networkName = (process.env.TEZOS_NETWORK || 'mainnet');
|
|
54
|
+
const network = NETWORKS[networkName];
|
|
55
|
+
if (!network) {
|
|
56
|
+
throw new ReferenceError(`Invalid network: ${networkName}. Valid options: ${Object.keys(NETWORKS).join(', ')}`);
|
|
57
|
+
}
|
|
58
|
+
log(`Network: ${networkName}`);
|
|
59
|
+
// Taquito setup
|
|
60
|
+
const Tezos = new TezosToolkit(network.rpcUrl);
|
|
61
|
+
// Wallet configuration (optional - tools will guide user to configure if not set)
|
|
62
|
+
let walletConfig = null;
|
|
63
|
+
const privateKey = process.env.SPENDING_PRIVATE_KEY?.trim();
|
|
64
|
+
const spendingContract = process.env.SPENDING_CONTRACT?.trim();
|
|
65
|
+
if (privateKey && spendingContract) {
|
|
66
|
+
log('Configuring wallet...');
|
|
67
|
+
// Validate private key format
|
|
68
|
+
if (!privateKey.startsWith('edsk') && !privateKey.startsWith('spsk') && !privateKey.startsWith('p2sk')) {
|
|
69
|
+
log(`Warning: Invalid SPENDING_PRIVATE_KEY format. Must start with edsk, spsk, or p2sk. Wallet not configured.`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
try {
|
|
73
|
+
const signer = await InMemorySigner.fromSecretKey(privateKey);
|
|
74
|
+
Tezos.setSignerProvider(signer);
|
|
75
|
+
const spendingAddress = await Tezos.signer.publicKeyHash();
|
|
76
|
+
walletConfig = { Tezos, spendingContract, spendingAddress };
|
|
77
|
+
log(`Wallet configured: ${spendingAddress}`);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
log(`Warning: Failed to initialize signer: ${error instanceof Error ? error.message : 'Unknown error'}. Wallet not configured.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
log('Wallet not configured (missing SPENDING_PRIVATE_KEY or SPENDING_CONTRACT)');
|
|
86
|
+
}
|
|
87
|
+
// Tools
|
|
88
|
+
log('Registering tools...');
|
|
89
|
+
const http = process.env.MCP_TRANSPORT === 'http';
|
|
90
|
+
const tools = createTools(walletConfig, network.tzktApi, http);
|
|
91
|
+
tools.forEach(tool => {
|
|
92
|
+
server.registerTool(tool.name, tool.config, tool.handler);
|
|
93
|
+
});
|
|
94
|
+
log(`Registered ${tools.length} tools`);
|
|
95
|
+
const transport = process.env.MCP_TRANSPORT || 'stdio';
|
|
96
|
+
log(`Transport: ${transport}`);
|
|
97
|
+
if (transport === 'http') {
|
|
98
|
+
const app = express();
|
|
99
|
+
app.use(express.json());
|
|
100
|
+
// Dashboard frontend (serve from frontend/dist)
|
|
101
|
+
const frontendPath = join(__dirname, "../frontend/dist");
|
|
102
|
+
app.use(express.static(frontendPath));
|
|
103
|
+
// MCP endpoint
|
|
104
|
+
app.post('/mcp', async (req, res) => {
|
|
105
|
+
log('Received MCP request');
|
|
106
|
+
const httpTransport = new StreamableHTTPServerTransport({
|
|
107
|
+
sessionIdGenerator: undefined,
|
|
108
|
+
enableJsonResponse: true
|
|
109
|
+
});
|
|
110
|
+
res.on('close', () => httpTransport.close());
|
|
111
|
+
await server.connect(httpTransport);
|
|
112
|
+
await httpTransport.handleRequest(req, res, req.body);
|
|
113
|
+
});
|
|
114
|
+
// SPA fallback - serve index.html for all non-API routes
|
|
115
|
+
app.get('/{*path}', (req, res) => {
|
|
116
|
+
res.sendFile(join(frontendPath, 'index.html'));
|
|
117
|
+
});
|
|
118
|
+
const port = process.env.PORT || 3004;
|
|
119
|
+
// Keep reference to http server and wait for it to start
|
|
120
|
+
await new Promise((resolve) => {
|
|
121
|
+
const httpServer = app.listen(port, () => {
|
|
122
|
+
log(`HTTP server listening on port ${port}`);
|
|
123
|
+
log(`MCP endpoint: http://localhost:${port}/mcp`);
|
|
124
|
+
resolve();
|
|
125
|
+
});
|
|
126
|
+
// Keep process alive
|
|
127
|
+
httpServer.on('error', (err) => {
|
|
128
|
+
log(`HTTP server error: ${err.message}`);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
// Keep the process running
|
|
132
|
+
log('Server ready, waiting for requests...');
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
log('Connecting stdio transport...');
|
|
136
|
+
const stdioTransport = new StdioServerTransport();
|
|
137
|
+
await server.connect(stdioTransport);
|
|
138
|
+
log('Stdio transport connected');
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
init().catch(err => {
|
|
142
|
+
console.error('[tezosx-mcp] Fatal error:', err);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
});
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core MCP server setup - shared between Node.js and Workers environments
|
|
3
|
+
*/
|
|
4
|
+
import { TezosToolkit } from "@taquito/taquito";
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import type { EnvironmentAdapter } from "./adapters/index.js";
|
|
7
|
+
export type WalletConfig = {
|
|
8
|
+
Tezos: TezosToolkit;
|
|
9
|
+
spendingContract: string;
|
|
10
|
+
spendingAddress: string;
|
|
11
|
+
} | null;
|
|
12
|
+
declare const NETWORKS: {
|
|
13
|
+
readonly mainnet: {
|
|
14
|
+
readonly rpcUrl: "https://mainnet.tezos.ecadinfra.com";
|
|
15
|
+
readonly tzktApi: "https://api.tzkt.io";
|
|
16
|
+
};
|
|
17
|
+
readonly shadownet: {
|
|
18
|
+
readonly rpcUrl: "https://shadownet.tezos.ecadinfra.com";
|
|
19
|
+
readonly tzktApi: "https://api.shadownet.tzkt.io";
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export type NetworkName = keyof typeof NETWORKS;
|
|
23
|
+
export type NetworkConfig = typeof NETWORKS[NetworkName];
|
|
24
|
+
export interface ServerConfig {
|
|
25
|
+
server: McpServer;
|
|
26
|
+
walletConfig: WalletConfig;
|
|
27
|
+
network: NetworkConfig;
|
|
28
|
+
networkName: NetworkName;
|
|
29
|
+
isHttp: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Initialize the MCP server with tools and wallet configuration
|
|
33
|
+
* This is the shared core that works in both Node.js and Workers
|
|
34
|
+
*/
|
|
35
|
+
export declare function createMcpServer(adapter: EnvironmentAdapter): Promise<ServerConfig>;
|
|
36
|
+
export {};
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core MCP server setup - shared between Node.js and Workers environments
|
|
3
|
+
*/
|
|
4
|
+
import { InMemorySigner } from "@taquito/signer";
|
|
5
|
+
import { TezosToolkit } from "@taquito/taquito";
|
|
6
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
import { createTools } from "./tools/index.js";
|
|
8
|
+
// Network configurations
|
|
9
|
+
const NETWORKS = {
|
|
10
|
+
mainnet: {
|
|
11
|
+
rpcUrl: 'https://mainnet.tezos.ecadinfra.com',
|
|
12
|
+
tzktApi: 'https://api.tzkt.io',
|
|
13
|
+
},
|
|
14
|
+
shadownet: {
|
|
15
|
+
rpcUrl: 'https://shadownet.tezos.ecadinfra.com',
|
|
16
|
+
tzktApi: 'https://api.shadownet.tzkt.io',
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Initialize the MCP server with tools and wallet configuration
|
|
21
|
+
* This is the shared core that works in both Node.js and Workers
|
|
22
|
+
*/
|
|
23
|
+
export async function createMcpServer(adapter) {
|
|
24
|
+
const log = (msg) => adapter.log(msg);
|
|
25
|
+
log('Initializing MCP server...');
|
|
26
|
+
const server = new McpServer({
|
|
27
|
+
name: "tezosx-mcp",
|
|
28
|
+
version: "1.0.0"
|
|
29
|
+
});
|
|
30
|
+
// Network configuration
|
|
31
|
+
const networkName = (adapter.getEnv('TEZOS_NETWORK') || 'mainnet');
|
|
32
|
+
const network = NETWORKS[networkName];
|
|
33
|
+
if (!network) {
|
|
34
|
+
throw new ReferenceError(`Invalid network: ${networkName}. Valid options: ${Object.keys(NETWORKS).join(', ')}`);
|
|
35
|
+
}
|
|
36
|
+
log(`Network: ${networkName}`);
|
|
37
|
+
// Taquito setup
|
|
38
|
+
const Tezos = new TezosToolkit(network.rpcUrl);
|
|
39
|
+
// Wallet configuration (optional - tools will guide user to configure if not set)
|
|
40
|
+
let walletConfig = null;
|
|
41
|
+
const privateKey = adapter.getEnv('SPENDING_PRIVATE_KEY')?.trim();
|
|
42
|
+
const spendingContract = adapter.getEnv('SPENDING_CONTRACT')?.trim();
|
|
43
|
+
if (privateKey && spendingContract) {
|
|
44
|
+
log('Configuring wallet...');
|
|
45
|
+
// Validate private key format
|
|
46
|
+
if (!privateKey.startsWith('edsk') && !privateKey.startsWith('spsk') && !privateKey.startsWith('p2sk')) {
|
|
47
|
+
log(`Warning: Invalid SPENDING_PRIVATE_KEY format. Must start with edsk, spsk, or p2sk. Wallet not configured.`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
try {
|
|
51
|
+
const signer = await InMemorySigner.fromSecretKey(privateKey);
|
|
52
|
+
Tezos.setSignerProvider(signer);
|
|
53
|
+
const spendingAddress = await Tezos.signer.publicKeyHash();
|
|
54
|
+
walletConfig = { Tezos, spendingContract, spendingAddress };
|
|
55
|
+
log(`Wallet configured: ${spendingAddress}`);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
log(`Warning: Failed to initialize signer: ${error instanceof Error ? error.message : 'Unknown error'}. Wallet not configured.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
log('Wallet not configured (missing SPENDING_PRIVATE_KEY or SPENDING_CONTRACT)');
|
|
64
|
+
}
|
|
65
|
+
// Tools
|
|
66
|
+
log('Registering tools...');
|
|
67
|
+
const isHttp = adapter.getEnv('MCP_TRANSPORT') === 'http';
|
|
68
|
+
const tools = createTools(walletConfig, network.tzktApi, isHttp);
|
|
69
|
+
tools.forEach(tool => {
|
|
70
|
+
server.registerTool(tool.name, tool.config, tool.handler);
|
|
71
|
+
});
|
|
72
|
+
log(`Registered ${tools.length} tools`);
|
|
73
|
+
return {
|
|
74
|
+
server,
|
|
75
|
+
walletConfig,
|
|
76
|
+
network,
|
|
77
|
+
networkName,
|
|
78
|
+
isHttp
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { TezosToolkit } from "@taquito/taquito";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
export declare const createCreateX402PaymentTool: (Tezos: TezosToolkit) => {
|
|
4
|
+
name: string;
|
|
5
|
+
config: {
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
inputSchema: z.ZodObject<{
|
|
9
|
+
network: z.ZodString;
|
|
10
|
+
asset: z.ZodString;
|
|
11
|
+
amount: z.ZodString;
|
|
12
|
+
recipient: z.ZodString;
|
|
13
|
+
}, z.z.core.$strip>;
|
|
14
|
+
annotations: {
|
|
15
|
+
readOnlyHint: boolean;
|
|
16
|
+
destructiveHint: boolean;
|
|
17
|
+
idempotentHint: boolean;
|
|
18
|
+
openWorldHint: boolean;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
handler: (params: any) => Promise<{
|
|
22
|
+
content: {
|
|
23
|
+
type: "text";
|
|
24
|
+
text: string;
|
|
25
|
+
}[];
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import { signX402Payment } from "./x402/sign.js";
|
|
3
|
+
export const createCreateX402PaymentTool = (Tezos) => ({
|
|
4
|
+
name: "tezos_create_x402_payment",
|
|
5
|
+
config: {
|
|
6
|
+
title: "Create x402 Payment",
|
|
7
|
+
description: "Creates a signed Tezos payment for x402 protocol. Builds and signs a transfer operation without broadcasting it, then packages it as a base64-encoded X-PAYMENT header value.",
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
network: z.string().describe("The Tezos network (e.g., 'shadownet', 'mainnet')"),
|
|
10
|
+
asset: z.string().describe("The asset to pay with (e.g., 'XTZ')"),
|
|
11
|
+
amount: z.string().describe("The amount in mutez to send"),
|
|
12
|
+
recipient: z.string().describe("The recipient Tezos address (tz1...)")
|
|
13
|
+
}),
|
|
14
|
+
annotations: {
|
|
15
|
+
readOnlyHint: false,
|
|
16
|
+
destructiveHint: false,
|
|
17
|
+
idempotentHint: false,
|
|
18
|
+
openWorldHint: true,
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
handler: async (params) => {
|
|
22
|
+
const { network, asset, amount, recipient } = params;
|
|
23
|
+
// Validate asset type
|
|
24
|
+
if (asset !== "XTZ") {
|
|
25
|
+
throw new Error(`Unsupported asset: ${asset}. Only XTZ is supported.`);
|
|
26
|
+
}
|
|
27
|
+
const amountMutez = parseInt(amount, 10);
|
|
28
|
+
if (isNaN(amountMutez) || amountMutez <= 0) {
|
|
29
|
+
throw new Error(`Invalid amount: ${amount}. Must be a positive integer in mutez.`);
|
|
30
|
+
}
|
|
31
|
+
// Get source address from signer
|
|
32
|
+
const source = await Tezos.signer.publicKeyHash();
|
|
33
|
+
// Validate source has sufficient funds
|
|
34
|
+
const sourceBalance = await Tezos.tz.getBalance(source);
|
|
35
|
+
if (sourceBalance.toNumber() < amountMutez + 10000) { // Add buffer for fees
|
|
36
|
+
throw new Error(`Insufficient balance. ` +
|
|
37
|
+
`Required: ${amountMutez + 10000} mutez (including fees), ` +
|
|
38
|
+
`Available: ${sourceBalance.toNumber()} mutez`);
|
|
39
|
+
}
|
|
40
|
+
const signed = await signX402Payment(Tezos, {
|
|
41
|
+
network,
|
|
42
|
+
amount: amountMutez,
|
|
43
|
+
recipient,
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
content: [{
|
|
47
|
+
type: "text",
|
|
48
|
+
text: JSON.stringify({
|
|
49
|
+
headerValue: signed.base64,
|
|
50
|
+
payload: signed.payload,
|
|
51
|
+
}, null, 2)
|
|
52
|
+
}]
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { TezosToolkit } from "@taquito/taquito";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
export declare const createFetchWithX402Tool: (Tezos: TezosToolkit) => {
|
|
4
|
+
name: string;
|
|
5
|
+
config: {
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
inputSchema: z.ZodObject<{
|
|
9
|
+
url: z.ZodString;
|
|
10
|
+
maxAmountMutez: z.ZodString;
|
|
11
|
+
method: z.ZodOptional<z.ZodString>;
|
|
12
|
+
body: z.ZodOptional<z.ZodString>;
|
|
13
|
+
recipient: z.ZodOptional<z.ZodString>;
|
|
14
|
+
}, z.z.core.$strip>;
|
|
15
|
+
annotations: {
|
|
16
|
+
readOnlyHint: boolean;
|
|
17
|
+
destructiveHint: boolean;
|
|
18
|
+
idempotentHint: boolean;
|
|
19
|
+
openWorldHint: boolean;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
handler: (params: any) => Promise<{
|
|
23
|
+
content: {
|
|
24
|
+
type: "text";
|
|
25
|
+
text: string;
|
|
26
|
+
}[];
|
|
27
|
+
}>;
|
|
28
|
+
};
|