Skip to main content
Glama

Bankless Onchain MCP Server

Official
by Bankless
index.ts10.8 kB
#!/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 'zod-to-json-schema'; import * as contracts from './operations/contracts.js'; import * as events from './operations/events.js'; import * as transactions from './operations/transactions.js'; import * as tokens from './operations/tokens.js'; import * as blocks from './operations/blocks.js'; import {VERSION} from "./common/version.js"; import { BanklessError, BanklessValidationError, BanklessResourceNotFoundError, BanklessAuthenticationError, BanklessRateLimitError, isBanklessError } from './common/banklessErrors.js'; const server = new Server( { name: "bankless-onchain-mcp-server", version: VERSION, }, { capabilities: { resources: {}, tools: {}, }, } ); function formatBanklessError(error: BanklessError): string { let message = `Bankless API Error: ${error.message}`; if (error instanceof BanklessValidationError) { message = `Validation Error: ${error.message}`; if (error.response) { message += `\nDetails: ${JSON.stringify(error.response)}`; } } else if (error instanceof BanklessResourceNotFoundError) { message = `Not Found: ${error.message}`; } else if (error instanceof BanklessAuthenticationError) { message = `Authentication Failed: ${error.message}`; } else if (error instanceof BanklessRateLimitError) { message = `Rate Limit Exceeded: ${error.message}\nResets at: ${error.resetAt.toISOString()}`; } return message; } server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // Contract Tools { name: "read_contract", description: `Read contract state from a blockchain. important: In case of a tuple, don't use type tuple, but specify the inner types (found in the source) in order. For nested structs, include the substructs types. Example: struct DataTypeA { DataTypeB b; //the liquidity index. Expressed in ray uint128 liquidityIndex; } struct DataTypeB { address token; } results in outputs for function with return type DataTypeA (tuple in abi): outputs: [{"type": "address"}, {"type": "uint128"}]`, inputSchema: zodToJsonSchema(contracts.ReadContractSchema), }, { name: "get_proxy", description: "Gets the proxy address for a given network and contract", inputSchema: zodToJsonSchema(contracts.GetProxySchema), }, { name: "get_abi", description: "Gets the ABI for a given contract on a specific network", inputSchema: zodToJsonSchema(contracts.GetAbiSchema), }, { name: "get_source", description: "Gets the source code for a given contract on a specific network", inputSchema: zodToJsonSchema(contracts.GetSourceSchema), }, // Event Tools { name: "get_events", description: "Fetches event logs for a given network and filter criteria", inputSchema: zodToJsonSchema(events.GetEventLogsSchema), }, { name: "build_event_topic", description: "Builds an event topic signature based on event name and arguments", inputSchema: zodToJsonSchema(events.BuildEventTopicSchema), }, // Transaction Tools { name: "get_transaction_history_for_user", description: "Gets transaction history for a user and optional contract", inputSchema: zodToJsonSchema(transactions.TransactionHistorySchema), }, { name: "get_transaction_info", description: "Gets detailed information about a specific transaction", inputSchema: zodToJsonSchema(transactions.TransactionInfoSchema), }, // Token Tools { name: "get_token_balances_on_network", description: "Gets all token balances for a given address on a specific network", inputSchema: zodToJsonSchema(tokens.TokenBalancesOnNetworkSchema), }, // Block Tools { name: "get_block_info", description: "Gets detailed information about a specific block by number or hash", inputSchema: zodToJsonSchema(blocks.BlockInfoSchema), } ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { try { if (!request.params.arguments) { throw new Error("Arguments are required"); } switch (request.params.name) { // Contract Tools case "read_contract": { const args = contracts.ReadContractSchema.parse(request.params.arguments); const result = await contracts.readContractState( args.network, args.contract, args.method, args.inputs, args.outputs ); return { content: [{type: "text", text: JSON.stringify(result, null, 2)}], }; } case "get_proxy": { const args = contracts.GetProxySchema.parse(request.params.arguments); const result = await contracts.getProxy( args.network, args.contract ); return { content: [{type: "text", text: JSON.stringify(result, null, 2)}], }; } case "get_abi": { const args = contracts.GetAbiSchema.parse(request.params.arguments); const result = await contracts.getAbi( args.network, args.contract ); return { content: [{type: "text", text: JSON.stringify(result, null, 2)}], }; } case "get_source": { const args = contracts.GetSourceSchema.parse(request.params.arguments); const result = await contracts.getSource( args.network, args.contract ); return { content: [{type: "text", text: JSON.stringify(result, null, 2)}], }; } // Event Tools case "get_events": { const args = events.GetEventLogsSchema.parse(request.params.arguments); const result = await events.getEvents( args.network, args.addresses, args.topic, args.optionalTopics, args.fromBlock, args.toBlock ); return { content: [{type: "text", text: JSON.stringify(result, null, 2)}], }; } case "build_event_topic": { const args = events.BuildEventTopicSchema.parse(request.params.arguments); const result = await events.buildEventTopic( args.network, args.name, args.arguments ); return { content: [{type: "text", text: JSON.stringify(result, null, 2)}], }; } // Transaction Tools case "get_transaction_history_for_user": { const args = transactions.TransactionHistorySchema.parse(request.params.arguments); const result = await transactions.getTransactionHistory( args.network, args.user, args.contract, args.methodId, args.startBlock, args.includeData ); return { content: [{type: "text", text: JSON.stringify(result, null, 2)}], }; } case "get_transaction_info": { const args = transactions.TransactionInfoSchema.parse(request.params.arguments); const result = await transactions.getTransactionInfo( args.network, args.txHash ); return { content: [{type: "text", text: JSON.stringify(result, null, 2)}], }; } // Token Tools case "get_token_balances_on_network": { const args = tokens.TokenBalancesOnNetworkSchema.parse(request.params.arguments); const result = await tokens.getTokenBalancesOnNetwork( args.network, args.address ); return { content: [{type: "text", text: JSON.stringify(result, null, 2)}], }; } // Block Tools case "get_block_info": { const args = blocks.BlockInfoSchema.parse(request.params.arguments); const result = await blocks.getBlockInfo( args.network, args.blockId ); 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 (isBanklessError(error)) { throw new Error(formatBanklessError(error)); } throw error; } }); async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Bankless Onchain MCP Server running on stdio"); } runServer().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Bankless/onchain-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server