Algorand MCP
by GoPlausible
import {
Transaction,
makePaymentTxnWithSuggestedParamsFromObject,
makeKeyRegistrationTxnWithSuggestedParamsFromObject,
SuggestedParams
} from 'algosdk';
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import { algodClient } from '../../algorand-client.js';
// Tool schemas
export const accountTransactionSchemas = {
makePaymentTxn: {
type: 'object',
description: 'Create a payment transaction with proper Algorand address strings',
properties: {
from: {
type: 'string',
description: 'Sender address in standard Algorand format (58 characters)'
},
to: {
type: 'string',
description: 'Receiver address in standard Algorand format (58 characters)'
},
amount: {
type: 'integer',
description: 'Amount in microAlgos'
},
note: {
type: 'string',
optional: true,
description: 'Optional transaction note'
},
closeRemainderTo: {
type: 'string',
optional: true,
description: 'Optional close remainder to address in standard Algorand format'
},
rekeyTo: {
type: 'string',
optional: true,
description: 'Optional rekey to address in standard Algorand format'
}
},
required: ['from', 'to', 'amount']
},
makeKeyRegTxn: {
type: 'object',
properties: {
from: { type: 'string' },
voteKey: { type: 'string' },
selectionKey: { type: 'string' },
stateProofKey: { type: 'string' },
voteFirst: { type: 'integer' },
voteLast: { type: 'integer' },
voteKeyDilution: { type: 'integer' },
nonParticipation: { type: 'boolean', optional: true },
note: { type: 'string', optional: true },
rekeyTo: { type: 'string', optional: true }
},
required: ['from', 'voteKey', 'selectionKey', 'stateProofKey', 'voteFirst', 'voteLast', 'voteKeyDilution']
}
};
// Tool definitions
export const accountTransactionTools = [
{
name: 'make_payment_txn',
description: 'Create a payment transaction',
inputSchema: accountTransactionSchemas.makePaymentTxn,
},
{
name: 'make_keyreg_txn',
description: 'Create a key registration transaction',
inputSchema: accountTransactionSchemas.makeKeyRegTxn,
}
];
export class AccountTransactionManager {
/**
* Creates a payment transaction
*/
static makePaymentTxn(txn: {
from: string;
to: string;
amount: number;
closeRemainderTo?: string;
note?: Uint8Array;
rekeyTo?: string;
suggestedParams: SuggestedParams;
}): Transaction {
return makePaymentTxnWithSuggestedParamsFromObject(txn);
}
/**
* Creates a key registration transaction
*/
static makeKeyRegTxn(txn: {
from: string;
note?: Uint8Array;
rekeyTo?: string;
suggestedParams: SuggestedParams;
voteKey: string;
selectionKey: string;
stateProofKey: string;
voteFirst: number;
voteLast: number;
voteKeyDilution: number;
nonParticipation?: boolean;
}): Transaction {
return makeKeyRegistrationTxnWithSuggestedParamsFromObject(txn);
}
// Tool handlers
static async handleTool(name: string, args: Record<string, unknown>) {
const params = await algodClient.getTransactionParams().do();
const suggestedParams = { ...params, flatFee: true, fee: params.minFee };
switch (name) {
case 'make_payment_txn':
if (!args.from || !args.to || typeof args.amount !== 'number') {
throw new McpError(ErrorCode.InvalidParams, 'Invalid payment transaction parameters');
}
try {
// Create payment transaction using SDK's maker function
const txn = AccountTransactionManager.makePaymentTxn({
from: String(args.from),
to: String(args.to),
amount: Number(args.amount),
note: typeof args.note === 'string' ? new TextEncoder().encode(args.note) : undefined,
closeRemainderTo: typeof args.closeRemainderTo === 'string' ? args.closeRemainderTo : undefined,
rekeyTo: typeof args.rekeyTo === 'string' ? args.rekeyTo : undefined,
suggestedParams
});
// Create a clean transaction object for JSON serialization
const cleanTxn: Record<string, any> = {
from: String(args.from),
to: String(args.to),
amount: Number(args.amount),
fee: suggestedParams.fee,
firstRound: suggestedParams.firstRound,
lastRound: suggestedParams.lastRound,
genesisID: suggestedParams.genesisID,
genesisHash: suggestedParams.genesisHash,
type: 'pay'
};
// Add note if provided
if (typeof args.note === 'string') {
const noteBytes = new TextEncoder().encode(args.note);
cleanTxn['note'] = Buffer.from(noteBytes).toString('base64');
}
// Add optional parameters if they exist
if (args.closeRemainderTo) {
cleanTxn['closeRemainderTo'] = String(args.closeRemainderTo);
}
if (args.rekeyTo) {
cleanTxn['rekeyTo'] = String(args.rekeyTo);
}
return {
content: [{
type: 'text',
text: JSON.stringify(cleanTxn, null, 2),
}],
};
} catch (error) {
throw new McpError(
ErrorCode.InvalidParams,
`Failed to create payment transaction: ${error instanceof Error ? error.message : String(error)}`
);
}
case 'make_keyreg_txn':
if (!args.from || !args.voteKey || !args.selectionKey || !args.stateProofKey ||
typeof args.voteFirst !== 'number' || typeof args.voteLast !== 'number' ||
typeof args.voteKeyDilution !== 'number') {
throw new McpError(ErrorCode.InvalidParams, 'Invalid key registration parameters');
}
const keyRegTxn = AccountTransactionManager.makeKeyRegTxn({
from: String(args.from),
voteKey: String(args.voteKey),
selectionKey: String(args.selectionKey),
stateProofKey: String(args.stateProofKey),
voteFirst: Number(args.voteFirst),
voteLast: Number(args.voteLast),
voteKeyDilution: Number(args.voteKeyDilution),
nonParticipation: typeof args.nonParticipation === 'boolean' ? args.nonParticipation : undefined,
note: typeof args.note === 'string' ? new TextEncoder().encode(args.note) : undefined,
rekeyTo: typeof args.rekeyTo === 'string' ? args.rekeyTo : undefined,
suggestedParams,
});
return {
content: [{
type: 'text',
text: JSON.stringify(keyRegTxn, null, 2),
}],
};
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown account transaction tool: ${name}`
);
}
}
}