#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import { zodToJsonSchema } from "./common/schema.js";
// Import operations
import * as contracts from "./operations/contracts.js";
import * as events from "./operations/events.js";
import * as chains from "./operations/chains.js";
import * as addresses from "./operations/addresses.js";
import * as hsm from "./operations/hsm.js";
import * as txm from "./operations/txm.js";
import * as webhooks from "./operations/webhooks.js";
// Import common utilities
import { VERSION } from "./common/version.js";
import { initializeClient } from "./common/client.js";
import {
CurvegridError,
CurvegridValidationError,
CurvegridResourceNotFoundError,
CurvegridAuthenticationError,
CurvegridPermissionError,
CurvegridRateLimitError,
CurvegridConflictError,
isCurvegridError,
} from "./common/errors.js";
// Schema for initializing the client
const InitializeClientSchema = z.object({
baseUrl: z.string().describe("Base URL for the MultiBaas API"),
apiKey: z.string().describe("API key for authentication"),
});
// Import logging utility
import { log } from "./common/logging.js";
// Create the MCP server
const server = new Server(
{
name: "curvegrid-mcp-server",
version: VERSION,
},
{
capabilities: {
tools: {},
},
},
);
// Format error messages
function formatCurvegridError(error: CurvegridError): string {
let message = `MultiBaas API Error: ${error.message}`;
if (error instanceof CurvegridValidationError) {
message = `Validation Error: ${error.message}`;
if (error.response) {
message += `\nDetails: ${JSON.stringify(error.response)}`;
}
} else if (error instanceof CurvegridResourceNotFoundError) {
message = `Not Found: ${error.message}`;
} else if (error instanceof CurvegridAuthenticationError) {
message = `Authentication Failed: ${error.message}`;
} else if (error instanceof CurvegridPermissionError) {
message = `Permission Denied: ${error.message}`;
} else if (error instanceof CurvegridRateLimitError) {
message = `Rate Limit Exceeded: ${error.message}\nResets at: ${error.resetAt.toISOString()}`;
} else if (error instanceof CurvegridConflictError) {
message = `Conflict: ${error.message}`;
}
return message;
}
// Define the available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "initialize_client",
description:
"Initialize the MultiBaas API client with base URL and API key",
inputSchema: zodToJsonSchema(InitializeClientSchema),
},
// Contract operations
{
name: "link_address_contract",
description: "Link an address to a contract",
inputSchema: zodToJsonSchema(contracts.LinkAddressContractSchema),
},
{
name: "unlink_address_contract",
description: "Unlink an address from a contract",
inputSchema: zodToJsonSchema(contracts.UnlinkAddressContractSchema),
},
{
name: "create_contract",
description: "Create a new contract",
inputSchema: zodToJsonSchema(contracts.CreateContractSchema),
},
{
name: "delete_contract",
description: "Delete a contract",
inputSchema: zodToJsonSchema(contracts.DeleteContractSchema),
},
{
name: "deploy_contract",
description: "Deploy a contract",
inputSchema: zodToJsonSchema(contracts.DeployContractSchema),
},
{
name: "list_contract_versions",
description: "List versions of a contract",
inputSchema: zodToJsonSchema(contracts.ListContractVersionsSchema),
},
{
name: "get_contract_version",
description: "Get a specific version of a contract",
inputSchema: zodToJsonSchema(contracts.GetContractVersionSchema),
},
{
name: "get_function_type_conversions",
description: "Get function type conversions for a contract",
inputSchema: zodToJsonSchema(
contracts.GetFunctionTypeConversionsSchema,
),
},
{
name: "set_function_type_conversions",
description: "Set function type conversions for a contract",
inputSchema: zodToJsonSchema(
contracts.SetFunctionTypeConversionsSchema,
),
},
{
name: "get_event_type_conversions",
description: "Get event type conversions for a contract",
inputSchema: zodToJsonSchema(contracts.GetEventTypeConversionsSchema),
},
{
name: "set_event_type_conversions",
description: "Set event type conversions for a contract",
inputSchema: zodToJsonSchema(contracts.SetEventTypeConversionsSchema),
},
{
name: "list_contracts",
description: "List all contracts in MultiBaas",
inputSchema: zodToJsonSchema(contracts.ListContractsSchema),
},
{
name: "get_contract",
description: "Get details of a specific contract",
inputSchema: zodToJsonSchema(contracts.GetContractSchema),
},
{
name: "call_contract_method",
description: "Call a read-only method on a contract",
inputSchema: zodToJsonSchema(contracts.CallContractMethodSchema),
},
{
name: "send_contract_transaction",
description: "Send a transaction to a contract method",
inputSchema: zodToJsonSchema(contracts.SendContractTransactionSchema),
},
// Event operations
{
name: "list_events",
description: "List events for a contract",
inputSchema: zodToJsonSchema(events.ListEventsSchema),
},
{
name: "create_event_query",
description: "Create a new event query",
inputSchema: zodToJsonSchema(events.CreateEventQuerySchema),
},
{
name: "get_event_query_results",
description: "Get results for an event query",
inputSchema: zodToJsonSchema(events.GetEventQueryResultsSchema),
},
{
name: "get_event_count",
description: "Get count of events for a contract",
inputSchema: zodToJsonSchema(events.GetEventCountSchema),
},
{
name: "execute_arbitrary_event_query",
description: "Execute an arbitrary event query",
inputSchema: zodToJsonSchema(events.ArbitraryEventQuerySchema),
},
{
name: "list_event_queries",
description: "List all event queries",
inputSchema: zodToJsonSchema(events.ListEventQueriesSchema),
},
{
name: "get_event_query",
description: "Get details of a specific event query",
inputSchema: zodToJsonSchema(events.GetEventQuerySchema),
},
{
name: "delete_event_query",
description: "Delete an event query",
inputSchema: zodToJsonSchema(events.DeleteEventQuerySchema),
},
{
name: "count_event_query_records",
description: "Count records for an event query",
inputSchema: zodToJsonSchema(events.CountEventQueryRecordsSchema),
},
// Chain operations
{
name: "get_chain",
description: "Get details of a specific chain",
inputSchema: zodToJsonSchema(chains.GetChainSchema),
},
{
name: "transfer_eth",
description: "Transfer ETH between addresses",
inputSchema: zodToJsonSchema(chains.TransferEthSchema),
},
{
name: "submit_signed_transaction",
description: "Submit a signed transaction to the blockchain",
inputSchema: zodToJsonSchema(chains.SubmitSignedTransactionSchema),
},
{
name: "get_transaction",
description: "Get details of a specific transaction",
inputSchema: zodToJsonSchema(chains.GetTransactionSchema),
},
{
name: "get_transaction_receipt",
description: "Get receipt of a specific transaction",
inputSchema: zodToJsonSchema(chains.GetTransactionReceiptSchema),
},
{
name: "get_block",
description: "Get details of a specific block",
inputSchema: zodToJsonSchema(chains.GetBlockSchema),
},
// Address operations
{
name: "list_addresses",
description: "List all addresses in MultiBaas",
inputSchema: zodToJsonSchema(addresses.ListAddressesSchema),
},
{
name: "get_address",
description: "Get details of a specific address",
inputSchema: zodToJsonSchema(addresses.GetAddressSchema),
},
{
name: "set_address",
description: "Create or update an address alias",
inputSchema: zodToJsonSchema(addresses.SetAddressSchema),
},
{
name: "delete_address",
description: "Delete an address alias",
inputSchema: zodToJsonSchema(addresses.DeleteAddressSchema),
},
// HSM operations
{
name: "list_hsm_wallets",
description: "List all HSM wallets",
inputSchema: zodToJsonSchema(hsm.ListHsmWalletsSchema),
},
{
name: "get_hsm_wallet",
description: "Get details of a specific HSM wallet",
inputSchema: zodToJsonSchema(hsm.GetHsmWalletSchema),
},
{
name: "sign_message",
description: "Sign a message with an HSM wallet",
inputSchema: zodToJsonSchema(hsm.SignMessageSchema),
},
{
name: "sign_and_submit_transaction",
description: "Sign and submit a transaction with an HSM wallet",
inputSchema: zodToJsonSchema(hsm.SignAndSubmitTransactionSchema),
},
{
name: "set_local_nonce",
description: "Set local nonce for an HSM wallet",
inputSchema: zodToJsonSchema(hsm.SetLocalNonceSchema),
},
{
name: "import_hsm_key",
description: "import a HSM key",
inputSchema: zodToJsonSchema(hsm.AddHsmKeySchema),
},
{
name: "remove_hsm_key",
description: "Remove an HSM key",
inputSchema: zodToJsonSchema(hsm.RemoveHsmKeySchema),
},
{
name: "list_hsm",
description: "List all HSM configurations",
inputSchema: zodToJsonSchema(z.object({})),
},
// TXM operations
{
name: "list_txm_transactions",
description: "List all TXM transactions for a wallet",
inputSchema: zodToJsonSchema(txm.ListTxmTransactionsSchema),
},
{
name: "count_txm_transactions",
description: "Count TXM transactions for a wallet",
inputSchema: zodToJsonSchema(txm.CountTxmTransactionsSchema),
},
// Webhook operations
{
name: "list_webhooks",
description: "List all webhooks",
inputSchema: zodToJsonSchema(webhooks.ListWebhooksSchema),
},
{
name: "get_webhook",
description: "Get details of a specific webhook",
inputSchema: zodToJsonSchema(webhooks.GetWebhookSchema),
},
{
name: "create_webhook",
description: "Create a new webhook",
inputSchema: zodToJsonSchema(webhooks.CreateWebhookSchema),
},
{
name: "delete_webhook",
description: "Delete a webhook",
inputSchema: zodToJsonSchema(webhooks.DeleteWebhookSchema),
},
{
name: "list_webhook_events",
description: "List events for a webhook",
inputSchema: zodToJsonSchema(webhooks.ListWebhookEventsSchema),
},
],
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
// Initialize logging
log("info", "Processing tool request", { tool: request.params.name });
if (!request.params.arguments) {
throw new Error("Arguments are required");
}
switch (request.params.name) {
case "initialize_client": {
const args = InitializeClientSchema.parse(request.params.arguments);
const client = initializeClient(args.baseUrl, args.apiKey);
log("info", "Client initialized successfully", {
baseUrl: args.baseUrl,
});
return {
content: [
{
type: "text",
text: JSON.stringify(
{ success: true, message: "Client initialized successfully" },
null,
2,
),
},
],
};
}
// Contract operations
case "link_address_contract": {
const args = contracts.LinkAddressContractSchema.parse(
request.params.arguments,
);
const result = await contracts.linkAddressContract(args);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "unlink_address_contract": {
const args = contracts.UnlinkAddressContractSchema.parse(
request.params.arguments,
);
const result = await contracts.unlinkAddressContract(args);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "create_contract": {
const args = contracts.CreateContractSchema.parse(
request.params.arguments,
);
const result = await contracts.createContract(args);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "delete_contract": {
const args = contracts.DeleteContractSchema.parse(
request.params.arguments,
);
const result = await contracts.deleteContract(args);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "deploy_contract": {
const args = contracts.DeployContractSchema.parse(
request.params.arguments,
);
const result = await contracts.deployContract(args);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "list_contract_versions": {
const args = contracts.ListContractVersionsSchema.parse(
request.params.arguments,
);
const result = await contracts.listContractVersions(args);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "get_contract_version": {
const args = contracts.GetContractVersionSchema.parse(
request.params.arguments,
);
const result = await contracts.getContractVersion(args);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "get_function_type_conversions": {
const args = contracts.GetFunctionTypeConversionsSchema.parse(
request.params.arguments,
);
const result = await contracts.getFunctionTypeConversions(args);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "set_function_type_conversions": {
const args = contracts.SetFunctionTypeConversionsSchema.parse(
request.params.arguments,
);
const result = await contracts.setFunctionTypeConversions(args);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "get_event_type_conversions": {
const args = contracts.GetEventTypeConversionsSchema.parse(
request.params.arguments,
);
const result = await contracts.getEventTypeConversions(args);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "set_event_type_conversions": {
const args = contracts.SetEventTypeConversionsSchema.parse(
request.params.arguments,
);
const result = await contracts.setEventTypeConversions(args);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "list_contracts": {
const args = contracts.ListContractsSchema.parse(
request.params.arguments,
);
const result = await contracts.listContracts(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "get_contract": {
const args = contracts.GetContractSchema.parse(
request.params.arguments,
);
const result = await contracts.getContract(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "call_contract_method": {
const args = contracts.CallContractMethodSchema.parse(
request.params.arguments,
);
const result = await contracts.callContractMethod(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "send_contract_transaction": {
const args = contracts.SendContractTransactionSchema.parse(
request.params.arguments,
);
const result = await contracts.sendContractTransaction(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// Event operations
case "list_events": {
const args = events.ListEventsSchema.parse(request.params.arguments);
const result = await events.listEvents(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "create_event_query": {
const args = events.CreateEventQuerySchema.parse(
request.params.arguments,
);
const result = await events.createEventQuery(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "get_event_query_results": {
const args = events.GetEventQueryResultsSchema.parse(
request.params.arguments,
);
const result = await events.getEventQueryResults(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "get_event_count": {
const args = events.GetEventCountSchema.parse(request.params.arguments);
const result = await events.getEventCount(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "execute_arbitrary_event_query": {
const args = events.ArbitraryEventQuerySchema.parse(
request.params.arguments,
);
const result = await events.executeArbitraryEventQuery(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "list_event_queries": {
const result = await events.listEventQueries();
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "get_event_query": {
const args = events.GetEventQuerySchema.parse(request.params.arguments);
const result = await events.getEventQuery(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "delete_event_query": {
const args = events.DeleteEventQuerySchema.parse(
request.params.arguments,
);
const result = await events.deleteEventQuery(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "count_event_query_records": {
const args = events.CountEventQueryRecordsSchema.parse(
request.params.arguments,
);
const result = await events.countEventQueryRecords(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// Chain operations
case "get_chain": {
const args = chains.GetChainSchema.parse(request.params.arguments);
const result = await chains.getChain(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "transfer_eth": {
const args = chains.TransferEthSchema.parse(request.params.arguments);
const result = await chains.transferEth(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "submit_signed_transaction": {
const args = chains.SubmitSignedTransactionSchema.parse(
request.params.arguments,
);
const result = await chains.submitSignedTransaction(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "get_transaction": {
const args = chains.GetTransactionSchema.parse(
request.params.arguments,
);
const result = await chains.getTransaction(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "get_transaction_receipt": {
const args = chains.GetTransactionReceiptSchema.parse(
request.params.arguments,
);
const result = await chains.getTransactionReceipt(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "get_block": {
const args = chains.GetBlockSchema.parse(request.params.arguments);
const result = await chains.getBlock(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// Address operations
case "list_addresses": {
const args = addresses.ListAddressesSchema.parse(
request.params.arguments,
);
const result = await addresses.listAddresses(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "get_address": {
const args = addresses.GetAddressSchema.parse(request.params.arguments);
const result = await addresses.getAddress(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "set_address": {
const args = addresses.SetAddressSchema.parse(request.params.arguments);
const result = await addresses.setAddress(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "delete_address": {
const args = addresses.DeleteAddressSchema.parse(
request.params.arguments,
);
const result = await addresses.deleteAddress(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// HSM operations
case "list_hsm_wallets": {
const args = hsm.ListHsmWalletsSchema.parse(request.params.arguments);
const result = await hsm.listHsmWallets(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "get_hsm_wallet": {
const args = hsm.GetHsmWalletSchema.parse(request.params.arguments);
const result = await hsm.getHsmWallet(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "sign_message": {
const args = hsm.SignMessageSchema.parse(request.params.arguments);
const result = await hsm.signMessage(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "sign_and_submit_transaction": {
const args = hsm.SignAndSubmitTransactionSchema.parse(
request.params.arguments,
);
const result = await hsm.signAndSubmitTransaction(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "set_local_nonce": {
const args = hsm.SetLocalNonceSchema.parse(request.params.arguments);
const result = await hsm.setLocalNonce(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "import_hsm_key": {
const args = hsm.AddHsmKeySchema.parse(request.params.arguments);
const result = await hsm.importHsmKey(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "remove_hsm_key": {
const args = hsm.RemoveHsmKeySchema.parse(request.params.arguments);
const result = await hsm.removeHsmKey(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "list_hsm": {
const result = await hsm.listHsm();
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// TXM operations
case "list_txm_transactions": {
const args = txm.ListTxmTransactionsSchema.parse(
request.params.arguments,
);
const result = await txm.listTxmTransactions(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "count_txm_transactions": {
const args = txm.CountTxmTransactionsSchema.parse(
request.params.arguments,
);
const result = await txm.countWalletTransactions(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
// Webhook operations
case "list_webhooks": {
const args = webhooks.ListWebhooksSchema.parse(
request.params.arguments,
);
const result = await webhooks.listWebhooks(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "get_webhook": {
const args = webhooks.GetWebhookSchema.parse(request.params.arguments);
const result = await webhooks.getWebhook(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "create_webhook": {
const args = webhooks.CreateWebhookSchema.parse(
request.params.arguments,
);
const result = await webhooks.createWebhook(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "delete_webhook": {
const args = webhooks.DeleteWebhookSchema.parse(
request.params.arguments,
);
const result = await webhooks.deleteWebhook(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "list_webhook_events": {
const args = webhooks.ListWebhookEventsSchema.parse(
request.params.arguments,
);
const result = await webhooks.listWebhookEvents(args);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(`Invalid input: ${JSON.stringify(error.errors)}`);
}
if (isCurvegridError(error)) {
throw new Error(formatCurvegridError(error));
}
throw error;
}
});
// Run the server
async function runServer() {
const transport = new StdioServerTransport();
// Try to initialize client from environment variables on startup
try {
initializeClient();
} catch (error) {
// Log but don't fail - user can initialize manually
console.error(
"Note: MultiBaas client not initialized on startup. Call initialize_client to set up manually.",
);
}
await server.connect(transport);
}
runServer().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});