index.ts•10.2 kB
#!/usr/bin/env node
/**
* MCP Server for MIST.cash SDK
* Exposes private payment functionality on Starknet via Model Context Protocol
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
// Import tools
import { generarSecretoTransaccion } from './tools/generar-secreto.js';
import { obtenerAssetsTransaccion } from './tools/obtener-assets.js';
import { verificarExistenciaTransaccion } from './tools/verificar-existencia.js';
import { calcularHashTransaccion } from './tools/calcular-hash.js';
import { obtenerConfiguracionChamber } from './tools/obtener-configuracion.js';
// Import resources
import { getChamberContractInfo, getTransactionDetails, listResources } from './resources/index.js';
// Create MCP server instance
const server = new Server(
{
name: 'mcp-mistcash',
version: '1.0.0',
},
{
capabilities: {
tools: {},
resources: {},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'generar_secreto_transaccion',
description: 'Generate a transaction secret for private payments. Creates a cryptographic secret using a claiming key and recipient address.',
inputSchema: {
type: 'object',
properties: {
claiming_key: {
type: 'string',
description: 'Claiming key for the transaction',
},
recipient_address: {
type: 'string',
description: 'Starknet address of the recipient (must start with 0x)',
},
},
required: ['claiming_key', 'recipient_address'],
},
},
{
name: 'obtener_assets_transaccion',
description: 'Fetch assets from a transaction. WARNING: This shows assets even if already spent. Use verificar_existencia_transaccion for accurate verification.',
inputSchema: {
type: 'object',
properties: {
transaction_key: {
type: 'string',
description: 'Transaction key to query',
},
recipient_address: {
type: 'string',
description: 'Starknet address of the recipient',
},
provider_rpc_url: {
type: 'string',
description: 'Optional custom Starknet RPC URL',
},
},
required: ['transaction_key', 'recipient_address'],
},
},
{
name: 'verificar_existencia_transaccion',
description: 'Verify if a transaction exists with specific assets. For fully private transactions where assets are not publicly visible.',
inputSchema: {
type: 'object',
properties: {
claiming_key: {
type: 'string',
description: 'Claiming key for the transaction',
},
recipient: {
type: 'string',
description: 'Starknet address of the recipient',
},
token_address: {
type: 'string',
description: 'Token contract address (e.g., ETH, USDC)',
},
amount: {
type: 'string',
description: 'Amount in base units (wei)',
},
provider_rpc_url: {
type: 'string',
description: 'Optional custom Starknet RPC URL',
},
},
required: ['claiming_key', 'recipient', 'token_address', 'amount'],
},
},
{
name: 'calcular_hash_transaccion',
description: 'Calculate the unique hash of a transaction using transaction parameters.',
inputSchema: {
type: 'object',
properties: {
transaction_key: {
type: 'string',
description: 'Transaction key',
},
recipient_address: {
type: 'string',
description: 'Starknet address of the recipient',
},
token_address: {
type: 'string',
description: 'Token contract address',
},
amount: {
type: 'string',
description: 'Amount in base units (wei)',
},
},
required: ['transaction_key', 'recipient_address', 'token_address', 'amount'],
},
},
{
name: 'obtener_configuracion_chamber',
description: 'Get Chamber contract configuration including contract address, network, and supported tokens.',
inputSchema: {
type: 'object',
properties: {
network: {
type: 'string',
enum: ['mainnet', 'sepolia'],
description: 'Starknet network',
default: 'mainnet',
},
},
},
},
],
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const { name, arguments: args } = request.params;
switch (name) {
case 'generar_secreto_transaccion':
return {
content: [
{
type: 'text',
text: JSON.stringify(await generarSecretoTransaccion(args), null, 2),
},
],
};
case 'obtener_assets_transaccion':
return {
content: [
{
type: 'text',
text: JSON.stringify(await obtenerAssetsTransaccion(args), null, 2),
},
],
};
case 'verificar_existencia_transaccion':
return {
content: [
{
type: 'text',
text: JSON.stringify(await verificarExistenciaTransaccion(args), null, 2),
},
],
};
case 'calcular_hash_transaccion':
return {
content: [
{
type: 'text',
text: JSON.stringify(await calcularHashTransaccion(args), null, 2),
},
],
};
case 'obtener_configuracion_chamber':
return {
content: [
{
type: 'text',
text: JSON.stringify(await obtenerConfiguracionChamber(args || {}), null, 2),
},
],
};
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: JSON.stringify({ success: false, error: errorMessage }, null, 2),
},
],
isError: true,
};
}
});
// List available resources
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: listResources(),
};
});
// Read resource content
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
if (uri === 'chamber://contract-info') {
const network = (process.env.STARKNET_NETWORK || 'mainnet') as 'mainnet' | 'sepolia';
return getChamberContractInfo(network);
}
if (uri.startsWith('chamber://tx/')) {
const txHash = uri.replace('chamber://tx/', '');
return getTransactionDetails(txHash);
}
throw new Error(`Unknown resource: ${uri}`);
});
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('MCP MIST.cash Server running on stdio');
console.error(`Network: ${process.env.STARKNET_NETWORK || 'mainnet'}`);
console.error(`RPC URL: ${process.env.STARKNET_RPC_URL || 'default'}`);
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});