Algorand MCP

import { Transaction, makeApplicationNoOpTxnFromObject, OnApplicationComplete } from 'algosdk'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { AppCallTxnParams } from './types.js'; /** * Creates an application call (NoOp) transaction * @param params The parameters for calling the application * @returns The created transaction * @throws {McpError} If the transaction creation fails */ export function makeApplicationCallTxn(params: AppCallTxnParams): Transaction { try { // Create a new object with the required structure const txnParams = { from: params.from, appIndex: params.appIndex, suggestedParams: params.suggestedParams, note: params.note, lease: params.lease, rekeyTo: params.rekeyTo, appArgs: params.appArgs, accounts: params.accounts, foreignApps: params.foreignApps, foreignAssets: params.foreignAssets, boxes: params.boxes }; // Pass the onComplete parameter separately return makeApplicationNoOpTxnFromObject({ ...txnParams, onComplete: OnApplicationComplete.NoOpOC } as any); // Use type assertion since the types are not perfectly aligned } catch (error) { console.error('[MCP Error] Failed to create application call transaction:', error); throw new McpError( ErrorCode.InternalError, `Failed to create application call transaction: ${error instanceof Error ? error.message : 'Unknown error'}` ); } } /** * Handles the application call tool request * @param args The tool arguments * @param suggestedParams The suggested transaction parameters * @returns The transaction parameters * @throws {McpError} If the parameters are invalid */ export function handleCallTxn(args: Record<string, unknown>, suggestedParams: any): Record<string, any> { try { if (!args.from || !args.appIndex) { console.error('[MCP Error] Invalid application call parameters'); throw new McpError(ErrorCode.InvalidParams, 'Invalid application call parameters'); } // Create transaction with proper parameter handling const txnParams: Record<string, any> = { from: String(args.from), appIndex: Number(args.appIndex), fee: suggestedParams.fee, firstRound: suggestedParams.firstRound, lastRound: suggestedParams.lastRound, genesisID: suggestedParams.genesisID, genesisHash: suggestedParams.genesisHash, type: 'appl', onComplete: OnApplicationComplete.NoOpOC }; // Handle optional fields if (typeof args.note === 'string') { const noteBytes = new TextEncoder().encode(args.note); txnParams.note = Buffer.from(noteBytes).toString('base64'); } if (typeof args.lease === 'string') { const leaseBytes = new TextEncoder().encode(args.lease); txnParams.lease = Buffer.from(leaseBytes).toString('base64'); } if (typeof args.rekeyTo === 'string') { txnParams.rekeyTo = String(args.rekeyTo); } if (Array.isArray(args.appArgs)) { txnParams.appArgs = args.appArgs.map(arg => { const bytes = new TextEncoder().encode(String(arg)); return Buffer.from(bytes).toString('base64'); }); } if (Array.isArray(args.accounts)) { txnParams.accounts = args.accounts.filter((acc): acc is string => typeof acc === 'string'); } if (Array.isArray(args.foreignApps)) { txnParams.foreignApps = args.foreignApps.filter((app): app is number => typeof app === 'number'); } if (Array.isArray(args.foreignAssets)) { txnParams.foreignAssets = args.foreignAssets.filter((asset): asset is number => typeof asset === 'number'); } return txnParams; } catch (error) { if (error instanceof McpError) { throw error; } console.error('[MCP Error] Failed to handle application call:', error); throw new McpError( ErrorCode.InternalError, `Failed to handle application call: ${error instanceof Error ? error.message : 'Unknown error'}` ); } }