@goplausible/algorand-mcp 3.0.7 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # Algorand MCP Server
2
-
3
- [![npm downloads](https://img.shields.io/npm/dm/algorand-mcp.svg)](https://www.npmjs.com/package/algorand-mcp)
4
- [![npm version](https://badge.fury.io/js/algorand-mcp.svg)](https://badge.fury.io/js/algorand-mcp)
2
+ [![npm version](https://img.shields.io/npm/v/@goplausible/algorand-mcp.svg)](https://www.npmjs.com/package/@goplausible/algorand-mcp)
3
+ [![npm downloads](https://img.shields.io/npm/dm/@goplausible/algorand-mcp.svg)](https://www.npmjs.com/package/@goplausible/algorand-mcp)
5
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
5
 
7
6
  A comprehensive [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that gives AI agents and LLMs full access to the Algorand blockchain. Built by [GoPlausible](https://goplausible.com).
@@ -15,12 +14,14 @@ Algorand is a carbon-negative, pure proof-of-stake Layer 1 blockchain with insta
15
14
  ## Features
16
15
 
17
16
  - Secure wallet management via OS keychain — private keys never exposed to agents or LLMs
17
+ - Wallet accounts nicknames, allowances, and daily limits for safe spending control
18
18
  - Account creation, key management, and rekeying
19
19
  - Transaction building, signing, and submission (payments, assets, applications, key registration)
20
20
  - Atomic transaction groups
21
21
  - TEAL compilation and disassembly
22
22
  - Full Algod and Indexer API access
23
23
  - NFDomains (NFD) name service integration
24
+ - x402 and AP2 toolins for Algorand
24
25
  - Tinyman AMM integration (pools, swaps, liquidity)
25
26
  - ARC-26 URI and QR code generation
26
27
  - Algorand knowledge base with full developer documentation taxonomy
@@ -36,7 +37,7 @@ Algorand is a carbon-negative, pure proof-of-stake Layer 1 blockchain with insta
36
37
  ### From npm
37
38
 
38
39
  ```bash
39
- npm install -g algorand-mcp
40
+ npm install -g @goplausible/algorand-mcp
40
41
  ```
41
42
 
42
43
  ### From source
@@ -50,12 +51,56 @@ npm run build
50
51
 
51
52
  ## MCP Configuration
52
53
 
53
- The server runs over stdio. Add it to your MCP client configuration to start using it.
54
+ The server runs over **stdio**. There are three ways to invoke it pick whichever suits your setup:
55
+
56
+ | Method | Command | When to use |
57
+ |---|---|---|
58
+ | **npx** (recommended) | `npx @goplausible/algorand-mcp` | No install needed, always latest version |
59
+ | **Global install** | `algorand-mcp` | After `npm install -g @goplausible/algorand-mcp` |
60
+ | **Absolute path** | `node /path/to/dist/index.js` | Built from source or local clone |
61
+
62
+ **No environment variables are required** for standard use. Network selection, pagination, and node URLs are all handled dynamically per tool call.
63
+
64
+ ---
65
+
66
+ ### OpenClaw
67
+
68
+ No manual configuration needed — install the [`@goplausible/openclaw-algorand-plugin`](https://www.npmjs.com/package/@goplausible/openclaw-algorand-plugin) npm package and the Algorand MCP server is configured automatically:
69
+
70
+ ```bash
71
+ npm install -g @goplausible/openclaw-algorand-plugin
72
+ ```
73
+
74
+ ---
54
75
 
55
76
  ### Claude Desktop
56
77
 
57
78
  Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
58
79
 
80
+ **Using npx:**
81
+ ```json
82
+ {
83
+ "mcpServers": {
84
+ "algorand-mcp": {
85
+ "command": "npx",
86
+ "args": ["@goplausible/algorand-mcp"]
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ **Using global install:**
93
+ ```json
94
+ {
95
+ "mcpServers": {
96
+ "algorand-mcp": {
97
+ "command": "algorand-mcp"
98
+ }
99
+ }
100
+ }
101
+ ```
102
+
103
+ **Using absolute path:**
59
104
  ```json
60
105
  {
61
106
  "mcpServers": {
@@ -67,41 +112,143 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) o
67
112
  }
68
113
  ```
69
114
 
115
+ ---
116
+
70
117
  ### Claude Code
71
118
 
72
- Create `.mcp.json` in your project root (project scope) or configure in `~/.claude.json` (user scope):
119
+ Create `.mcp.json` in your project root (project scope) or `~/.claude.json` (user scope):
73
120
 
74
121
  ```json
75
122
  {
76
123
  "mcpServers": {
77
124
  "algorand-mcp": {
78
125
  "type": "stdio",
79
- "command": "node",
80
- "args": ["/absolute/path/to/algorand-mcp/dist/index.js"]
126
+ "command": "npx",
127
+ "args": ["@goplausible/algorand-mcp"]
81
128
  }
82
129
  }
83
130
  }
84
131
  ```
85
132
 
86
- ### Cursor / Windsurf
133
+ Or add interactively:
134
+ ```bash
135
+ claude mcp add algorand-mcp -- npx @goplausible/algorand-mcp
136
+ ```
87
137
 
88
- These editors use the same JSON format as Claude Desktop. Add the server entry to the MCP settings panel or the corresponding config file for your editor.
138
+ ---
89
139
 
90
- ### Using global npm install
140
+ ### Cursor
91
141
 
92
- If you installed globally via `npm install -g algorand-mcp`, you can use the binary name directly:
142
+ Add via **Settings > MCP Servers**, or edit `.cursor/mcp.json` in your project root:
93
143
 
94
144
  ```json
95
145
  {
96
146
  "mcpServers": {
97
147
  "algorand-mcp": {
98
- "command": "algorand-mcp"
148
+ "command": "npx",
149
+ "args": ["@goplausible/algorand-mcp"]
99
150
  }
100
151
  }
101
152
  }
102
153
  ```
103
154
 
104
- That's it — **no environment variables are required** for standard use. Network selection, pagination, and node URLs are all handled dynamically.
155
+ ---
156
+
157
+ ### Windsurf
158
+
159
+ Add via **Settings > MCP**, or edit `~/.codeium/windsurf/mcp_config.json`:
160
+
161
+ ```json
162
+ {
163
+ "mcpServers": {
164
+ "algorand-mcp": {
165
+ "command": "npx",
166
+ "args": ["@goplausible/algorand-mcp"]
167
+ }
168
+ }
169
+ }
170
+ ```
171
+
172
+ ---
173
+
174
+ ### VS Code / GitHub Copilot
175
+
176
+ Edit `.vscode/mcp.json` in your workspace root, or open **Settings > MCP Servers**:
177
+
178
+ ```json
179
+ {
180
+ "servers": {
181
+ "algorand-mcp": {
182
+ "type": "stdio",
183
+ "command": "npx",
184
+ "args": ["@goplausible/algorand-mcp"]
185
+ }
186
+ }
187
+ }
188
+ ```
189
+
190
+ ---
191
+
192
+ ### Cline
193
+
194
+ Add via the **MCP Servers** panel in the Cline sidebar, or edit `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` (macOS):
195
+
196
+ ```json
197
+ {
198
+ "mcpServers": {
199
+ "algorand-mcp": {
200
+ "command": "npx",
201
+ "args": ["@goplausible/algorand-mcp"],
202
+ "disabled": false
203
+ }
204
+ }
205
+ }
206
+ ```
207
+
208
+ ---
209
+
210
+ ### OpenAI Codex CLI
211
+
212
+ Create `.codex/mcp.json` in your project root or `~/.codex/mcp.json` for global scope:
213
+
214
+ ```json
215
+ {
216
+ "mcpServers": {
217
+ "algorand-mcp": {
218
+ "command": "npx",
219
+ "args": ["@goplausible/algorand-mcp"]
220
+ }
221
+ }
222
+ }
223
+ ```
224
+
225
+ ---
226
+
227
+ ### Open Code
228
+
229
+ Edit `~/.config/opencode/config.json`:
230
+
231
+ ```json
232
+ {
233
+ "mcp": {
234
+ "algorand-mcp": {
235
+ "type": "stdio",
236
+ "command": "npx",
237
+ "args": ["@goplausible/algorand-mcp"]
238
+ }
239
+ }
240
+ }
241
+ ```
242
+
243
+ ---
244
+
245
+ ### Any MCP-compatible client
246
+
247
+ The server speaks the standard MCP stdio protocol. For any client not listed above, configure it with:
248
+
249
+ - **Command:** `npx` (or `algorand-mcp` if globally installed, or `node /path/to/dist/index.js`)
250
+ - **Args:** `["@goplausible/algorand-mcp"]` (for npx)
251
+ - **Transport:** `stdio`
105
252
 
106
253
  ## Network Selection
107
254
 
@@ -256,7 +403,7 @@ See [Secure Wallet](#secure-wallet) for full architecture details.
256
403
  | `encode_obj` | Encode object to msgpack |
257
404
  | `decode_obj` | Decode msgpack to object |
258
405
 
259
- ### Transaction Tools (16 tools)
406
+ ### Transaction Tools (18 tools)
260
407
 
261
408
  | Tool | Description |
262
409
  |---|---|
@@ -276,6 +423,8 @@ See [Secure Wallet](#secure-wallet) for full architecture details.
276
423
  | `make_app_call_txn` | Create an application call transaction |
277
424
  | `assign_group_id` | Assign group ID for atomic transactions |
278
425
  | `sign_transaction` | Sign a transaction with a secret key |
426
+ | `encode_unsigned_transaction` | Encode an unsigned transaction to base64 msgpack bytes |
427
+ | `decode_signed_transaction` | Decode a signed transaction blob back to JSON with signature details |
279
428
 
280
429
  ### Algod Tools (5 tools)
281
430
 
@@ -0,0 +1,3 @@
1
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ export declare const haystackTools: Tool[];
3
+ export declare function handleHaystackTools(name: string, args: any): Promise<any>;
@@ -0,0 +1,35 @@
1
+ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
+ import { quoteTools, handleQuoteTools } from './quote.js';
3
+ import { swapTools, handleSwapTools } from './swap.js';
4
+ import { optinTools, handleOptinTools } from './optin.js';
5
+ // Combine all Haystack Router tools
6
+ export const haystackTools = [
7
+ ...quoteTools,
8
+ ...swapTools,
9
+ ...optinTools,
10
+ ];
11
+ // Handle all Haystack Router tools
12
+ export async function handleHaystackTools(name, args) {
13
+ try {
14
+ const combinedArgs = { name, ...args };
15
+ // Swap execution (must come before quote due to prefix matching)
16
+ if (name === 'api_haystack_execute_swap') {
17
+ return handleSwapTools(combinedArgs);
18
+ }
19
+ // Quote tools
20
+ if (name === 'api_haystack_get_swap_quote') {
21
+ return handleQuoteTools(combinedArgs);
22
+ }
23
+ // Opt-in check
24
+ if (name === 'api_haystack_needs_optin') {
25
+ return handleOptinTools(combinedArgs);
26
+ }
27
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown Haystack Router tool: ${name}`);
28
+ }
29
+ catch (error) {
30
+ if (error instanceof McpError) {
31
+ throw error;
32
+ }
33
+ throw new McpError(ErrorCode.InternalError, `Failed to handle Haystack Router tool: ${error instanceof Error ? error.message : String(error)}`);
34
+ }
35
+ }
@@ -0,0 +1,3 @@
1
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ export declare const optinTools: Tool[];
3
+ export declare function handleOptinTools(args: any): Promise<any>;
@@ -0,0 +1,47 @@
1
+ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
+ import { extractNetwork } from '../../../algorand-client.js';
3
+ import { withCommonParams } from '../../commonParams.js';
4
+ import { getRouterClient } from './routerClient.js';
5
+ export const optinTools = [
6
+ {
7
+ name: 'api_haystack_needs_optin',
8
+ description: 'Check if an Algorand address needs to opt into an asset before swapping. Returns true if opt-in is needed, false otherwise. Always returns false for ALGO (ASA 0). Use before executing a swap to determine if wallet_optin_asset should be called first.',
9
+ inputSchema: withCommonParams({
10
+ type: 'object',
11
+ properties: {
12
+ address: {
13
+ type: 'string',
14
+ description: 'Algorand address to check'
15
+ },
16
+ assetId: {
17
+ type: 'integer',
18
+ description: 'Asset ID to check opt-in status for'
19
+ }
20
+ },
21
+ required: ['address', 'assetId']
22
+ })
23
+ }
24
+ ];
25
+ export async function handleOptinTools(args) {
26
+ const { name, address, assetId } = args;
27
+ const network = extractNetwork(args);
28
+ if (network === 'localnet') {
29
+ throw new McpError(ErrorCode.InvalidRequest, 'Haystack Router is not available on localnet');
30
+ }
31
+ if (name === 'api_haystack_needs_optin') {
32
+ try {
33
+ const router = getRouterClient(network);
34
+ const needsOptIn = await router.needsAssetOptIn(address, assetId);
35
+ return {
36
+ address,
37
+ assetId,
38
+ needsOptIn,
39
+ network,
40
+ };
41
+ }
42
+ catch (error) {
43
+ throw new McpError(ErrorCode.InternalError, `Failed to check asset opt-in: ${error instanceof Error ? error.message : String(error)}`);
44
+ }
45
+ }
46
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown Haystack opt-in tool: ${name}`);
47
+ }
@@ -0,0 +1,3 @@
1
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ export declare const quoteTools: Tool[];
3
+ export declare function handleQuoteTools(args: any): Promise<any>;
@@ -0,0 +1,101 @@
1
+ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
+ import { extractNetwork } from '../../../algorand-client.js';
3
+ import { withCommonParams } from '../../commonParams.js';
4
+ import { getRouterClient } from './routerClient.js';
5
+ export const quoteTools = [
6
+ {
7
+ name: 'api_haystack_get_swap_quote',
8
+ description: 'Get an optimized swap quote from Haystack Router — a DEX aggregator that finds the best swap route across multiple Algorand DEXes (Tinyman V2, Pact, Folks) and LST protocols (tALGO, xALGO). Returns the best-price quote with route details, USD values, and price impact. Use this to preview a swap before executing. All amounts are in base units (e.g., 1000000 = 1 ALGO).',
9
+ inputSchema: withCommonParams({
10
+ type: 'object',
11
+ properties: {
12
+ fromASAID: {
13
+ type: 'integer',
14
+ description: 'Input asset ID (0 = ALGO, 31566704 = USDC, 312769 = USDt, etc.)'
15
+ },
16
+ toASAID: {
17
+ type: 'integer',
18
+ description: 'Output asset ID (0 = ALGO, 31566704 = USDC, 312769 = USDt, etc.)'
19
+ },
20
+ amount: {
21
+ type: 'integer',
22
+ description: 'Amount in base units (e.g., 1000000 = 1 ALGO with 6 decimals)'
23
+ },
24
+ type: {
25
+ type: 'string',
26
+ enum: ['fixed-input', 'fixed-output'],
27
+ description: 'Quote type: fixed-input (specify input amount, default) or fixed-output (specify desired output amount)',
28
+ default: 'fixed-input'
29
+ },
30
+ address: {
31
+ type: 'string',
32
+ description: 'User Algorand address (optional, needed for auto opt-in detection)'
33
+ },
34
+ maxGroupSize: {
35
+ type: 'integer',
36
+ description: 'Maximum transactions in atomic group (default: 16)',
37
+ default: 16
38
+ },
39
+ maxDepth: {
40
+ type: 'integer',
41
+ description: 'Maximum routing hops (default: 4)',
42
+ default: 4
43
+ }
44
+ },
45
+ required: ['fromASAID', 'toASAID', 'amount']
46
+ })
47
+ }
48
+ ];
49
+ export async function handleQuoteTools(args) {
50
+ const { name, fromASAID, toASAID, amount, type = 'fixed-input', address, maxGroupSize, maxDepth, } = args;
51
+ const network = extractNetwork(args);
52
+ if (network === 'localnet') {
53
+ throw new McpError(ErrorCode.InvalidRequest, 'Haystack Router is not available on localnet');
54
+ }
55
+ const router = getRouterClient(network);
56
+ if (name === 'api_haystack_get_swap_quote') {
57
+ try {
58
+ const quoteParams = {
59
+ fromASAID,
60
+ toASAID,
61
+ amount: BigInt(amount),
62
+ type,
63
+ };
64
+ if (address) {
65
+ quoteParams.address = address;
66
+ }
67
+ if (maxGroupSize !== undefined) {
68
+ quoteParams.maxGroupSize = maxGroupSize;
69
+ }
70
+ if (maxDepth !== undefined) {
71
+ quoteParams.maxDepth = maxDepth;
72
+ }
73
+ const quote = await router.newQuote(quoteParams);
74
+ // Serialize the quote response for MCP transport (convert BigInt to string)
75
+ const result = {
76
+ expectedOutput: quote.quote.toString(),
77
+ inputAmount: quote.amount.toString(),
78
+ fromASAID: quote.fromASAID,
79
+ toASAID: quote.toASAID,
80
+ type: quote.type,
81
+ usdIn: quote.usdIn,
82
+ usdOut: quote.usdOut,
83
+ userPriceImpact: quote.userPriceImpact,
84
+ marketPriceImpact: quote.marketPriceImpact,
85
+ route: quote.route,
86
+ flattenedRoute: quote.flattenedRoute,
87
+ requiredAppOptIns: quote.requiredAppOptIns,
88
+ protocolFees: quote.protocolFees,
89
+ createdAt: quote.createdAt,
90
+ };
91
+ if (quote.address) {
92
+ result.address = quote.address;
93
+ }
94
+ return result;
95
+ }
96
+ catch (error) {
97
+ throw new McpError(ErrorCode.InternalError, `Failed to get Haystack swap quote: ${error instanceof Error ? error.message : String(error)}`);
98
+ }
99
+ }
100
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown Haystack Router tool: ${name}`);
101
+ }
@@ -0,0 +1,2 @@
1
+ import { RouterClient } from '@txnlab/haystack-router';
2
+ export declare function getRouterClient(network: string): RouterClient;
@@ -0,0 +1,28 @@
1
+ import { RouterClient } from '@txnlab/haystack-router';
2
+ // Free tier API key (60 requests/min)
3
+ const HAYSTACK_API_KEY = process.env.HAYSTACK_API_KEY || '1b72df7e-1131-4449-8ce1-29b79dd3f51e';
4
+ // Algod URIs per network for RouterClient configuration
5
+ const ALGOD_URIS = {
6
+ mainnet: 'https://mainnet-api.algonode.cloud',
7
+ testnet: 'https://testnet-api.algonode.cloud',
8
+ };
9
+ // Memoized RouterClient instances per network
10
+ const routerClients = new Map();
11
+ export function getRouterClient(network) {
12
+ let client = routerClients.get(network);
13
+ if (!client) {
14
+ const config = {
15
+ apiKey: HAYSTACK_API_KEY,
16
+ autoOptIn: true,
17
+ };
18
+ if (network === 'testnet') {
19
+ config.algodUri = ALGOD_URIS.testnet;
20
+ }
21
+ else if (network === 'mainnet') {
22
+ config.algodUri = ALGOD_URIS.mainnet;
23
+ }
24
+ client = new RouterClient(config);
25
+ routerClients.set(network, client);
26
+ }
27
+ return client;
28
+ }
@@ -0,0 +1,3 @@
1
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ export declare const swapTools: Tool[];
3
+ export declare function handleSwapTools(args: any): Promise<any>;
@@ -0,0 +1,151 @@
1
+ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
+ import algosdk from 'algosdk';
3
+ import { extractNetwork } from '../../../algorand-client.js';
4
+ import { WalletManager } from '../../walletManager.js';
5
+ import { withCommonParams } from '../../commonParams.js';
6
+ import { getRouterClient } from './routerClient.js';
7
+ export const swapTools = [
8
+ {
9
+ name: 'api_haystack_execute_swap',
10
+ description: 'Execute an optimized token swap via Haystack Router — gets the best route across multiple DEXes (Tinyman V2, Pact, Folks) and LST protocols, then signs and submits the atomic transaction group using the active wallet account. This is an all-in-one tool: quote → sign → submit → confirm. All amounts are in base units (e.g., 1000000 = 1 ALGO).',
11
+ inputSchema: withCommonParams({
12
+ type: 'object',
13
+ properties: {
14
+ fromASAID: {
15
+ type: 'integer',
16
+ description: 'Input asset ID (0 = ALGO, 31566704 = USDC, 312769 = USDt, etc.)'
17
+ },
18
+ toASAID: {
19
+ type: 'integer',
20
+ description: 'Output asset ID (0 = ALGO, 31566704 = USDC, 312769 = USDt, etc.)'
21
+ },
22
+ amount: {
23
+ type: 'integer',
24
+ description: 'Amount in base units (e.g., 1000000 = 1 ALGO with 6 decimals)'
25
+ },
26
+ slippage: {
27
+ type: 'number',
28
+ description: 'Slippage tolerance percentage (e.g., 1 = 1%). Recommended: 0.5-1% stable pairs, 1-3% volatile, 3-5% low liquidity',
29
+ default: 1
30
+ },
31
+ type: {
32
+ type: 'string',
33
+ enum: ['fixed-input', 'fixed-output'],
34
+ description: 'Quote type: fixed-input (specify input, default) or fixed-output (specify desired output)',
35
+ default: 'fixed-input'
36
+ },
37
+ note: {
38
+ type: 'string',
39
+ description: 'Optional note to attach to the input transaction (plain text)'
40
+ },
41
+ maxGroupSize: {
42
+ type: 'integer',
43
+ description: 'Maximum transactions in atomic group (default: 16)',
44
+ default: 16
45
+ },
46
+ maxDepth: {
47
+ type: 'integer',
48
+ description: 'Maximum routing hops (default: 4)',
49
+ default: 4
50
+ }
51
+ },
52
+ required: ['fromASAID', 'toASAID', 'amount']
53
+ })
54
+ }
55
+ ];
56
+ export async function handleSwapTools(args) {
57
+ const { name, fromASAID, toASAID, amount, slippage = 1, type = 'fixed-input', note, maxGroupSize, maxDepth, } = args;
58
+ const network = extractNetwork(args);
59
+ if (network === 'localnet') {
60
+ throw new McpError(ErrorCode.InvalidRequest, 'Haystack Router is not available on localnet');
61
+ }
62
+ if (name === 'api_haystack_execute_swap') {
63
+ try {
64
+ // 1. Get active wallet account and secret key
65
+ const account = await WalletManager.getActiveWalletAccount();
66
+ const sk = await WalletManager.getActiveWalletSecretKey();
67
+ const address = account.address;
68
+ // 2. Create a signer using the wallet's secret key
69
+ const signer = async (txnGroup, indexesToSign) => {
70
+ return indexesToSign.map((index) => algosdk.signTransaction(txnGroup[index], sk).blob);
71
+ };
72
+ // 3. Get router client and fetch quote
73
+ const router = getRouterClient(network);
74
+ const quoteParams = {
75
+ fromASAID,
76
+ toASAID,
77
+ amount: BigInt(amount),
78
+ type,
79
+ address,
80
+ };
81
+ if (maxGroupSize !== undefined)
82
+ quoteParams.maxGroupSize = maxGroupSize;
83
+ if (maxDepth !== undefined)
84
+ quoteParams.maxDepth = maxDepth;
85
+ const quote = await router.newQuote(quoteParams);
86
+ // 4. Check spending limits (estimate based on input amount for ALGO sends)
87
+ const estimatedSpend = fromASAID === 0 ? Number(amount) : 0;
88
+ WalletManager.checkWalletSpendingLimits(account, estimatedSpend);
89
+ // 5. Build and execute swap
90
+ const swapConfig = {
91
+ quote,
92
+ address,
93
+ signer,
94
+ slippage,
95
+ };
96
+ if (note) {
97
+ swapConfig.note = new TextEncoder().encode(note);
98
+ }
99
+ const swap = await router.newSwap(swapConfig);
100
+ const result = await swap.execute();
101
+ // 6. Record spend
102
+ await WalletManager.recordWalletSpend(address, estimatedSpend);
103
+ // 7. Get swap summary
104
+ const summary = swap.getSummary();
105
+ const inputTxnId = swap.getInputTransactionId();
106
+ // 8. Build response
107
+ const response = {
108
+ status: 'confirmed',
109
+ confirmedRound: result.confirmedRound.toString(),
110
+ txIds: result.txIds,
111
+ signer: address,
112
+ nickname: account.nickname,
113
+ network,
114
+ quote: {
115
+ fromASAID: quote.fromASAID,
116
+ toASAID: quote.toASAID,
117
+ expectedOutput: quote.quote.toString(),
118
+ inputAmount: quote.amount.toString(),
119
+ type: quote.type,
120
+ usdIn: quote.usdIn,
121
+ usdOut: quote.usdOut,
122
+ userPriceImpact: quote.userPriceImpact,
123
+ route: quote.flattenedRoute,
124
+ },
125
+ slippage,
126
+ };
127
+ if (summary) {
128
+ response.summary = {
129
+ inputAssetId: summary.inputAssetId.toString(),
130
+ outputAssetId: summary.outputAssetId.toString(),
131
+ inputAmount: summary.inputAmount.toString(),
132
+ outputAmount: summary.outputAmount.toString(),
133
+ totalFees: summary.totalFees.toString(),
134
+ transactionCount: summary.transactionCount,
135
+ inputTxnId: summary.inputTxnId,
136
+ outputTxnId: summary.outputTxnId,
137
+ };
138
+ }
139
+ if (inputTxnId) {
140
+ response.inputTransactionId = inputTxnId;
141
+ }
142
+ return response;
143
+ }
144
+ catch (error) {
145
+ if (error instanceof McpError)
146
+ throw error;
147
+ throw new McpError(ErrorCode.InternalError, `Haystack swap failed: ${error instanceof Error ? error.message : String(error)}`);
148
+ }
149
+ }
150
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown Haystack swap tool: ${name}`);
151
+ }
@@ -5,6 +5,7 @@ import { nfdTools, handleNFDTools } from './nfd/index.js';
5
5
  import { tinymanTools, handleTinymanTools } from './tinyman/index.js';
6
6
  // import { ultradeTools, handleUltradeTools } from './ultrade/index.js';
7
7
  import { exampleTools, handleExampleTools } from './example/index.js';
8
+ import { haystackTools, handleHaystackTools } from './hayrouter/index.js';
8
9
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
9
10
  import { ResponseProcessor } from '../../utils/responseProcessor.js';
10
11
  // Combine all API tools
@@ -13,6 +14,7 @@ export const apiManager = [
13
14
  ...indexerTools,
14
15
  ...nfdTools,
15
16
  ...tinymanTools,
17
+ ...haystackTools,
16
18
  ...exampleTools
17
19
  ];
18
20
  // Handle all API tools
@@ -35,6 +37,10 @@ export async function handleApiManager(name, args) {
35
37
  else if (name.startsWith('api_algod_')) {
36
38
  response = await handleAlgodTools(name, args);
37
39
  }
40
+ // Haystack Router tools
41
+ else if (name.startsWith('api_haystack_')) {
42
+ response = await handleHaystackTools(name, args);
43
+ }
38
44
  else if (name.startsWith('api_example_')) {
39
45
  response = await handleExampleTools(name, args);
40
46
  }
@@ -1,4 +1,4 @@
1
- interface AccountRow {
1
+ export interface AccountRow {
2
2
  id: number;
3
3
  address: string;
4
4
  public_key: string;
@@ -42,5 +42,12 @@ export declare class WalletManager {
42
42
  static getAccounts(): Promise<AccountRow[]>;
43
43
  static hasAccounts(): Promise<boolean>;
44
44
  static getActiveAccountInfo(): Promise<AccountRow | null>;
45
+ /** Get active account details (address, nickname, spending limits). */
46
+ static getActiveWalletAccount(): Promise<AccountRow>;
47
+ /** Get active account's secret key from OS keychain. */
48
+ static getActiveWalletSecretKey(): Promise<Uint8Array>;
49
+ /** Check if transaction amount is within spending limits. Throws on violation. */
50
+ static checkWalletSpendingLimits(account: AccountRow, amountMicroAlgos: number): void;
51
+ /** Record a spend against the account's daily allowance tracker. */
52
+ static recordWalletSpend(address: string, amountMicroAlgos: number): Promise<void>;
45
53
  }
46
- export {};
@@ -726,6 +726,23 @@ export class WalletManager {
726
726
  return null;
727
727
  }
728
728
  }
729
+ // ── Public accessors for external tool integration (e.g., haystack-router) ──
730
+ /** Get active account details (address, nickname, spending limits). */
731
+ static async getActiveWalletAccount() {
732
+ return WalletManager.getActiveAccount();
733
+ }
734
+ /** Get active account's secret key from OS keychain. */
735
+ static async getActiveWalletSecretKey() {
736
+ return WalletManager.getActiveSecretKey();
737
+ }
738
+ /** Check if transaction amount is within spending limits. Throws on violation. */
739
+ static checkWalletSpendingLimits(account, amountMicroAlgos) {
740
+ return WalletManager.checkSpendingLimits(account, amountMicroAlgos);
741
+ }
742
+ /** Record a spend against the account's daily allowance tracker. */
743
+ static async recordWalletSpend(address, amountMicroAlgos) {
744
+ return WalletManager.recordSpend(address, amountMicroAlgos);
745
+ }
729
746
  }
730
747
  WalletManager.walletTools = [
731
748
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goplausible/algorand-mcp",
3
- "version": "3.0.7",
3
+ "version": "3.1.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -39,6 +39,7 @@
39
39
  "@napi-rs/keyring": "^1.2.0",
40
40
  "@noble/curves": "^2.0.1",
41
41
  "@tinymanorg/tinyman-js-sdk": "^5.1.2",
42
+ "@txnlab/haystack-router": "^2.0.5",
42
43
  "algo-msgpack-with-bigint": "^2.1.1",
43
44
  "algosdk": "^3.5.2",
44
45
  "hi-base32": "^0.5.1",