@goplausible/algorand-mcp 4.2.3 → 4.2.5

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
@@ -369,8 +369,8 @@ See [Secure Wallet](#secure-wallet) for full architecture details.
369
369
  | `wallet_remove_account` | Remove an account from the wallet by nickname or index |
370
370
  | `wallet_list_accounts` | List all accounts with nicknames, addresses, and limits |
371
371
  | `wallet_switch_account` | Switch the active account by nickname or index |
372
- | `wallet_get_info` | Get active account info: address, public key, balance, and spending limits |
373
- | `wallet_get_assets` | Get all asset holdings for the active account |
372
+ | `wallet_get_info` | Get info for the **active account this MCP server owns** (keychain-backed): address, public key, balance, opted-in counts, plus wallet-only fields (allowance, daily allowance, daily spent). For arbitrary on-chain accounts use `api_algod_get_account_info`. |
373
+ | `wallet_get_assets` | Get all ASA holdings for the **active account this MCP server owns**. For arbitrary on-chain accounts use `api_algod_get_account_info` or `api_algod_get_account_asset_info`. |
374
374
  | `wallet_sign_transaction` | Sign a single transaction with the active account (enforces spending limits) |
375
375
  | `wallet_sign_transaction_group` | Sign a group of transactions with the active account (auto-assigns group ID) |
376
376
  | `wallet_sign_data` | Sign arbitrary hex data with raw Ed25519 (noble, no SDK prefix) |
@@ -437,11 +437,13 @@ See [Secure Wallet](#secure-wallet) for full architecture details.
437
437
  | `compile_teal` | Compile TEAL source code |
438
438
  | `disassemble_teal` | Disassemble TEAL bytecode to source |
439
439
  | `send_raw_transaction` | Submit signed transactions to the network |
440
- | `simulate_raw_transactions` | Simulate raw transactions |
441
- | `simulate_transactions` | Simulate transactions with detailed config |
440
+ | `simulate_raw_transactions` | Simulate already-encoded transactions (base64 bytes). Pass/fail + log/cost only — no trace, no extra budget. |
441
+ | `simulate_transactions` | Simulate decoded transaction groups with full `SimulateRequest` config (trace, extra opcode budget, unnamed-resource handling, unsigned txns). |
442
442
 
443
443
  ### Algod API Tools (13 tools)
444
444
 
445
+ Live, current-state reads against an Algod node. **Default choice for account/application/asset lookups** — the matching indexer endpoints were intentionally disabled to keep the tool surface lean (see [.notes/redundant-tools-report.md](.notes/redundant-tools-report.md)). Use the indexer family below only when you need historical or filtered queries that algod cannot serve.
446
+
445
447
  | Tool | Description |
446
448
  |---|---|
447
449
  | `api_algod_get_account_info` | Get account balance, assets, and auth address |
@@ -458,27 +460,24 @@ See [Secure Wallet](#secure-wallet) for full architecture details.
458
460
  | `api_algod_get_node_status` | Get current node status |
459
461
  | `api_algod_get_node_status_after_block` | Get node status after a specific round |
460
462
 
461
- ### Indexer API Tools (17 tools)
463
+ ### Indexer API Tools (10 tools)
464
+
465
+ Historical / filtered queries against an Algorand Indexer instance. Use these for time-range scans, paginated searches, log retrieval, and creator/holder discovery — anything algod's current-state endpoints cannot answer.
466
+
467
+ Seven indexer endpoints that duplicated algod equivalents (account-by-id, account assets, account app local states, application by id, application box, application boxes, asset by id) were intentionally disabled. They live commented-out in [src/tools/apiManager/indexer/](src/tools/apiManager/indexer/) and can be re-enabled in one place if needed.
462
468
 
463
469
  | Tool | Description |
464
470
  |---|---|
465
- | `api_indexer_lookup_account_by_id` | Get account information |
466
- | `api_indexer_lookup_account_assets` | Get account assets |
467
- | `api_indexer_lookup_account_app_local_states` | Get account app local states |
468
471
  | `api_indexer_lookup_account_created_applications` | Get apps created by account |
469
- | `api_indexer_search_for_accounts` | Search accounts with filters |
470
- | `api_indexer_lookup_applications` | Get application information |
471
- | `api_indexer_lookup_application_logs` | Get application log messages |
472
- | `api_indexer_search_for_applications` | Search applications |
473
- | `api_indexer_lookup_application_box` | Get application box by name |
474
- | `api_indexer_lookup_application_boxes` | Get all application boxes |
475
- | `api_indexer_lookup_asset_by_id` | Get asset info and configuration |
476
- | `api_indexer_lookup_asset_balances` | Get asset holders and balances |
477
- | `api_indexer_lookup_asset_transactions` | Get transactions for an asset |
478
- | `api_indexer_search_for_assets` | Search assets |
479
- | `api_indexer_lookup_transaction_by_id` | Get transaction by ID |
480
- | `api_indexer_lookup_account_transactions` | Get account transaction history |
481
- | `api_indexer_search_for_transactions` | Search transactions |
472
+ | `api_indexer_search_for_accounts` | Search accounts with filters (asset/app holdings, balance ranges) |
473
+ | `api_indexer_lookup_application_logs` | Get application log messages over a round range |
474
+ | `api_indexer_search_for_applications` | Search applications by creator |
475
+ | `api_indexer_lookup_asset_balances` | Get all accounts holding an asset with their balances |
476
+ | `api_indexer_lookup_asset_transactions` | Get transactions involving an asset (time/round/address-role filters) |
477
+ | `api_indexer_search_for_assets` | Search assets by creator, name, or unit |
478
+ | `api_indexer_lookup_transaction_by_id` | Get a confirmed transaction by ID |
479
+ | `api_indexer_lookup_account_transactions` | Get an account's transaction history (time/round/type/asset filters) |
480
+ | `api_indexer_search_for_transactions` | Search transactions across the chain with filters |
482
481
 
483
482
  ### NFDomains Tools (6 tools)
484
483
 
@@ -701,7 +700,7 @@ npm test
701
700
  | `transactionManager` | Payment, asset, app transaction building; sign_transaction; assign_group_id |
702
701
  | `algodManager` | TEAL compile/disassemble, send raw, simulate |
703
702
  | `apiAlgod` | All 13 algod API tools with correct mock routing |
704
- | `apiIndexer` | All 17 indexer API tools with fluent builder mocks |
703
+ | `apiIndexer` | All 10 active indexer API tools with fluent builder mocks |
705
704
  | `apiNfd` | NFD get/search/browse with mocked fetch |
706
705
  | `apiTinyman` | Tinyman pool/swap with error handling |
707
706
  | `arc26Manager` | ARC-26 URI generation and QR code SVG output |
@@ -28,7 +28,7 @@ function getEndpoints(network) {
28
28
  // Memoization caches
29
29
  const algodClients = new Map();
30
30
  const indexerClients = new Map();
31
- export function getAlgodClient(network = 'mainnet') {
31
+ export function getAlgodClient(network = 'testnet') {
32
32
  const key = `${network}:${ALGORAND_TOKEN}`;
33
33
  let client = algodClients.get(key);
34
34
  if (!client) {
@@ -38,7 +38,7 @@ export function getAlgodClient(network = 'mainnet') {
38
38
  }
39
39
  return client;
40
40
  }
41
- export function getIndexerClient(network = 'mainnet') {
41
+ export function getIndexerClient(network = 'testnet') {
42
42
  const key = `${network}:${ALGORAND_TOKEN}`;
43
43
  let client = indexerClients.get(key);
44
44
  if (!client) {
@@ -53,5 +53,5 @@ export function extractNetwork(args) {
53
53
  if (network && !['mainnet', 'testnet', 'localnet'].includes(network)) {
54
54
  throw new Error(`Invalid network: ${network}. Must be mainnet, testnet, or localnet.`);
55
55
  }
56
- return (network || 'mainnet');
56
+ return (network || 'testnet');
57
57
  }
@@ -246,12 +246,12 @@ AlgodManager.algodTools = [
246
246
  },
247
247
  {
248
248
  name: 'simulate_raw_transactions',
249
- description: 'Simulate raw transactions',
249
+ description: 'Simulate already-encoded, optionally-signed transactions (base64 bytes). Use this when you have raw txn bytes ready and only need a pass/fail + log/cost result — no execution tracing, no extra opcode budget, no unnamed-resource handling. For a richer simulation (trace, extra budget, unsigned txns, multiple groups), use simulate_transactions.',
250
250
  inputSchema: withCommonParams(algodToolSchemas.simulateRawTransactions),
251
251
  },
252
252
  {
253
253
  name: 'simulate_transactions',
254
- description: 'Simulate transactions with detailed configuration',
254
+ description: 'Simulate one or more transaction groups built from decoded transaction objects, with a full SimulateRequest config (allowEmptySignatures, allowMoreLogging, allowUnnamedResources, execTraceConfig, extraOpcodeBudget, round). Use this when you need execution traces, extra opcode budget, to simulate unsigned txns, or to opt into Algorand v9+ simulate features. For a quick pass/fail check on pre-encoded bytes, use simulate_raw_transactions instead.',
255
255
  inputSchema: withCommonParams(algodToolSchemas.simulateTransactions),
256
256
  }
257
257
  ];
@@ -38,7 +38,7 @@ export declare const apiManager: ({
38
38
  src: string;
39
39
  mimeType?: string | undefined;
40
40
  sizes?: string[] | undefined;
41
- theme?: "dark" | "light" | undefined;
41
+ theme?: "light" | "dark" | undefined;
42
42
  }[] | undefined;
43
43
  title?: string | undefined;
44
44
  })[];
@@ -3,60 +3,64 @@ import { getIndexerClient, extractNetwork } from '../../../algorand-client.js';
3
3
  import { ResponseProcessor } from '../../../utils/responseProcessor.js';
4
4
  import { withCommonParams } from '../../commonParams.js';
5
5
  export const accountTools = [
6
- {
7
- name: 'api_indexer_lookup_account_by_id',
8
- description: 'Get account information from indexer',
9
- inputSchema: withCommonParams({
10
- type: 'object',
11
- properties: {
12
- address: {
13
- type: 'string',
14
- description: 'Account address'
15
- }
16
- },
17
- required: ['address']
18
- })
19
- },
20
- {
21
- name: 'api_indexer_lookup_account_assets',
22
- description: 'Get account assets',
23
- inputSchema: withCommonParams({
24
- type: 'object',
25
- properties: {
26
- address: {
27
- type: 'string',
28
- description: 'Account address'
29
- },
30
- limit: {
31
- type: 'integer',
32
- description: 'Maximum number of assets to return'
33
- },
34
- assetId: {
35
- type: 'integer',
36
- description: 'Filter by asset ID'
37
- },
38
- nextToken: {
39
- type: 'string',
40
- description: 'Token for retrieving the next page of results'
41
- }
42
- },
43
- required: ['address']
44
- })
45
- },
46
- {
47
- name: 'api_indexer_lookup_account_app_local_states',
48
- description: 'Get account application local states',
49
- inputSchema: withCommonParams({
50
- type: 'object',
51
- properties: {
52
- address: {
53
- type: 'string',
54
- description: 'Account address'
55
- }
56
- },
57
- required: ['address']
58
- })
59
- },
6
+ // Disabled: redundant with api_algod_get_account_info (live algod variant covers the same query).
7
+ // See .notes/redundant-tools-report.md §1.
8
+ // {
9
+ // name: 'api_indexer_lookup_account_by_id',
10
+ // description: 'Get account information from indexer',
11
+ // inputSchema: withCommonParams({
12
+ // type: 'object',
13
+ // properties: {
14
+ // address: {
15
+ // type: 'string',
16
+ // description: 'Account address'
17
+ // }
18
+ // },
19
+ // required: ['address']
20
+ // })
21
+ // },
22
+ // Disabled: redundant with api_algod_get_account_asset_info.
23
+ // {
24
+ // name: 'api_indexer_lookup_account_assets',
25
+ // description: 'Get account assets',
26
+ // inputSchema: withCommonParams({
27
+ // type: 'object',
28
+ // properties: {
29
+ // address: {
30
+ // type: 'string',
31
+ // description: 'Account address'
32
+ // },
33
+ // limit: {
34
+ // type: 'integer',
35
+ // description: 'Maximum number of assets to return'
36
+ // },
37
+ // assetId: {
38
+ // type: 'integer',
39
+ // description: 'Filter by asset ID'
40
+ // },
41
+ // nextToken: {
42
+ // type: 'string',
43
+ // description: 'Token for retrieving the next page of results'
44
+ // }
45
+ // },
46
+ // required: ['address']
47
+ // })
48
+ // },
49
+ // Disabled: redundant with api_algod_get_account_application_info.
50
+ // {
51
+ // name: 'api_indexer_lookup_account_app_local_states',
52
+ // description: 'Get account application local states',
53
+ // inputSchema: withCommonParams({
54
+ // type: 'object',
55
+ // properties: {
56
+ // address: {
57
+ // type: 'string',
58
+ // description: 'Account address'
59
+ // }
60
+ // },
61
+ // required: ['address']
62
+ // })
63
+ // },
60
64
  {
61
65
  name: 'api_indexer_lookup_account_created_applications',
62
66
  description: 'Get applications created by this account',
@@ -105,7 +109,7 @@ export const accountTools = [
105
109
  })
106
110
  }
107
111
  ];
108
- export async function lookupAccountByID(address, network = 'mainnet') {
112
+ export async function lookupAccountByID(address, network = 'testnet') {
109
113
  try {
110
114
  const indexerClient = getIndexerClient(network);
111
115
  console.error(`Looking up account info for address ${address}`);
@@ -121,7 +125,7 @@ export async function lookupAccountByID(address, network = 'mainnet') {
121
125
  throw new McpError(ErrorCode.InternalError, `Failed to get account info: ${error instanceof Error ? error.message : String(error)}`);
122
126
  }
123
127
  }
124
- export async function lookupAccountAssets(address, params, network = 'mainnet') {
128
+ export async function lookupAccountAssets(address, params, network = 'testnet') {
125
129
  try {
126
130
  const indexerClient = getIndexerClient(network);
127
131
  console.error(`Looking up assets for address ${address}`);
@@ -147,7 +151,7 @@ export async function lookupAccountAssets(address, params, network = 'mainnet')
147
151
  throw new McpError(ErrorCode.InternalError, `Failed to get account assets: ${error instanceof Error ? error.message : String(error)}`);
148
152
  }
149
153
  }
150
- export async function lookupAccountAppLocalStates(address, network = 'mainnet') {
154
+ export async function lookupAccountAppLocalStates(address, network = 'testnet') {
151
155
  try {
152
156
  const indexerClient = getIndexerClient(network);
153
157
  console.error(`Looking up app local states for address ${address}`);
@@ -163,7 +167,7 @@ export async function lookupAccountAppLocalStates(address, network = 'mainnet')
163
167
  throw new McpError(ErrorCode.InternalError, `Failed to get account application local states: ${error instanceof Error ? error.message : String(error)}`);
164
168
  }
165
169
  }
166
- export async function lookupAccountCreatedApplications(address, network = 'mainnet') {
170
+ export async function lookupAccountCreatedApplications(address, network = 'testnet') {
167
171
  try {
168
172
  const indexerClient = getIndexerClient(network);
169
173
  console.error(`Looking up created applications for address ${address}`);
@@ -179,7 +183,7 @@ export async function lookupAccountCreatedApplications(address, network = 'mainn
179
183
  throw new McpError(ErrorCode.InternalError, `Failed to get account created applications: ${error instanceof Error ? error.message : String(error)}`);
180
184
  }
181
185
  }
182
- export async function searchAccounts(params, network = 'mainnet') {
186
+ export async function searchAccounts(params, network = 'testnet') {
183
187
  try {
184
188
  const indexerClient = getIndexerClient(network);
185
189
  console.error('Searching accounts with params:', params);
@@ -218,16 +222,18 @@ export const handleAccountTools = ResponseProcessor.wrapResourceHandler(async fu
218
222
  const name = args.name;
219
223
  const network = extractNetwork(args);
220
224
  switch (name) {
221
- case 'api_indexer_lookup_account_by_id': {
222
- const { address } = args;
223
- const info = await lookupAccountByID(address, network);
224
- return info;
225
- }
226
- case 'api_indexer_lookup_account_app_local_states': {
227
- const { address } = args;
228
- const info = await lookupAccountAppLocalStates(address, network);
229
- return info;
230
- }
225
+ // Disabled: redundant with api_algod_get_account_info. See .notes/redundant-tools-report.md §1.
226
+ // case 'api_indexer_lookup_account_by_id': {
227
+ // const { address } = args;
228
+ // const info = await lookupAccountByID(address, network);
229
+ // return info;
230
+ // }
231
+ // Disabled: redundant with api_algod_get_account_application_info.
232
+ // case 'api_indexer_lookup_account_app_local_states': {
233
+ // const { address } = args;
234
+ // const info = await lookupAccountAppLocalStates(address, network);
235
+ // return info;
236
+ // }
231
237
  case 'api_indexer_lookup_account_created_applications': {
232
238
  const { address } = args;
233
239
  const info = await lookupAccountCreatedApplications(address, network);
@@ -237,11 +243,12 @@ export const handleAccountTools = ResponseProcessor.wrapResourceHandler(async fu
237
243
  const info = await searchAccounts(args, network);
238
244
  return info.accounts;
239
245
  }
240
- case 'api_indexer_lookup_account_assets': {
241
- const { address, ...params } = args;
242
- const info = await lookupAccountAssets(address, params, network);
243
- return info.assets;
244
- }
246
+ // Disabled: redundant with api_algod_get_account_asset_info.
247
+ // case 'api_indexer_lookup_account_assets': {
248
+ // const { address, ...params } = args;
249
+ // const info = await lookupAccountAssets(address, params, network);
250
+ // return info.assets;
251
+ // }
245
252
  default:
246
253
  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
247
254
  }
@@ -4,20 +4,22 @@ import { ResponseProcessor } from '../../../utils/responseProcessor.js';
4
4
  import { withCommonParams } from '../../commonParams.js';
5
5
  import algosdk from 'algosdk';
6
6
  export const applicationTools = [
7
- {
8
- name: 'api_indexer_lookup_applications',
9
- description: 'Get application information from indexer',
10
- inputSchema: withCommonParams({
11
- type: 'object',
12
- properties: {
13
- appId: {
14
- type: 'integer',
15
- description: 'Application ID'
16
- }
17
- },
18
- required: ['appId']
19
- })
20
- },
7
+ // Disabled: redundant with api_algod_get_application_by_id (live algod variant covers the same query).
8
+ // See .notes/redundant-tools-report.md §1.
9
+ // {
10
+ // name: 'api_indexer_lookup_applications',
11
+ // description: 'Get application information from indexer',
12
+ // inputSchema: withCommonParams({
13
+ // type: 'object',
14
+ // properties: {
15
+ // appId: {
16
+ // type: 'integer',
17
+ // description: 'Application ID'
18
+ // }
19
+ // },
20
+ // required: ['appId']
21
+ // })
22
+ // },
21
23
  {
22
24
  name: 'api_indexer_lookup_application_logs',
23
25
  description: 'Get application log messages',
@@ -77,44 +79,46 @@ export const applicationTools = [
77
79
  }
78
80
  })
79
81
  },
80
- {
81
- name: 'api_indexer_lookup_application_box',
82
- description: 'Get application box by name',
83
- inputSchema: withCommonParams({
84
- type: 'object',
85
- properties: {
86
- appId: {
87
- type: 'integer',
88
- description: 'Application ID'
89
- },
90
- boxName: {
91
- type: 'string',
92
- description: 'Box name Buffer'
93
- }
94
- },
95
- required: ['appId', 'boxName']
96
- })
97
- },
98
- {
99
- name: 'api_indexer_lookup_application_boxes',
100
- description: 'Get all application boxes',
101
- inputSchema: withCommonParams({
102
- type: 'object',
103
- properties: {
104
- appId: {
105
- type: 'integer',
106
- description: 'Application ID'
107
- },
108
- maxBoxes: {
109
- type: 'integer',
110
- description: 'Maximum number of boxes to return'
111
- }
112
- },
113
- required: ['appId']
114
- })
115
- }
82
+ // Disabled: redundant with api_algod_get_application_box.
83
+ // {
84
+ // name: 'api_indexer_lookup_application_box',
85
+ // description: 'Get application box by name',
86
+ // inputSchema: withCommonParams({
87
+ // type: 'object',
88
+ // properties: {
89
+ // appId: {
90
+ // type: 'integer',
91
+ // description: 'Application ID'
92
+ // },
93
+ // boxName: {
94
+ // type: 'string',
95
+ // description: 'Box name Buffer'
96
+ // }
97
+ // },
98
+ // required: ['appId', 'boxName']
99
+ // })
100
+ // },
101
+ // Disabled: redundant with api_algod_get_application_boxes.
102
+ // {
103
+ // name: 'api_indexer_lookup_application_boxes',
104
+ // description: 'Get all application boxes',
105
+ // inputSchema: withCommonParams({
106
+ // type: 'object',
107
+ // properties: {
108
+ // appId: {
109
+ // type: 'integer',
110
+ // description: 'Application ID'
111
+ // },
112
+ // maxBoxes: {
113
+ // type: 'integer',
114
+ // description: 'Maximum number of boxes to return'
115
+ // }
116
+ // },
117
+ // required: ['appId']
118
+ // })
119
+ // }
116
120
  ];
117
- export async function lookupApplications(appId, network = 'mainnet') {
121
+ export async function lookupApplications(appId, network = 'testnet') {
118
122
  try {
119
123
  const indexerClient = getIndexerClient(network);
120
124
  console.error(`Looking up application info for ID ${appId}`);
@@ -130,7 +134,7 @@ export async function lookupApplications(appId, network = 'mainnet') {
130
134
  throw new McpError(ErrorCode.InternalError, `Failed to get application info: ${error instanceof Error ? error.message : String(error)}`);
131
135
  }
132
136
  }
133
- export async function lookupApplicationLogs(appId, params, network = 'mainnet') {
137
+ export async function lookupApplicationLogs(appId, params, network = 'testnet') {
134
138
  try {
135
139
  const indexerClient = getIndexerClient(network);
136
140
  console.error(`Looking up logs for application ${appId}`);
@@ -165,7 +169,7 @@ export async function lookupApplicationLogs(appId, params, network = 'mainnet')
165
169
  throw new McpError(ErrorCode.InternalError, `Failed to get application logs: ${error instanceof Error ? error.message : String(error)}`);
166
170
  }
167
171
  }
168
- export async function searchForApplications(params, network = 'mainnet') {
172
+ export async function searchForApplications(params, network = 'testnet') {
169
173
  try {
170
174
  const indexerClient = getIndexerClient(network);
171
175
  console.error('Searching applications with params:', params);
@@ -191,7 +195,7 @@ export async function searchForApplications(params, network = 'mainnet') {
191
195
  throw new McpError(ErrorCode.InternalError, `Failed to search applications: ${error instanceof Error ? error.message : String(error)}`);
192
196
  }
193
197
  }
194
- export async function lookupApplicationBoxByIDandName(appId, boxName, network = 'mainnet') {
198
+ export async function lookupApplicationBoxByIDandName(appId, boxName, network = 'testnet') {
195
199
  try {
196
200
  const indexerClient = getIndexerClient(network);
197
201
  const encoder = new TextEncoder();
@@ -236,7 +240,7 @@ export async function lookupApplicationBoxByIDandName(appId, boxName, network =
236
240
  throw new McpError(ErrorCode.InternalError, `Failed to get application box: ${error instanceof Error ? error.message : String(error)}`);
237
241
  }
238
242
  }
239
- export async function searchForApplicationBoxes(appId, maxBoxes, network = 'mainnet') {
243
+ export async function searchForApplicationBoxes(appId, maxBoxes, network = 'testnet') {
240
244
  try {
241
245
  const indexerClient = getIndexerClient(network);
242
246
  console.error(`Searching boxes for application ${appId}`);
@@ -260,11 +264,12 @@ export const handleApplicationTools = ResponseProcessor.wrapResourceHandler(asyn
260
264
  const name = args.name;
261
265
  const network = extractNetwork(args);
262
266
  switch (name) {
263
- case 'api_indexer_lookup_applications': {
264
- const { appId } = args;
265
- const info = await lookupApplications(appId, network);
266
- return info.application;
267
- }
267
+ // Disabled: redundant with api_algod_get_application_by_id. See .notes/redundant-tools-report.md §1.
268
+ // case 'api_indexer_lookup_applications': {
269
+ // const { appId } = args;
270
+ // const info = await lookupApplications(appId, network);
271
+ // return info.application;
272
+ // }
268
273
  case 'api_indexer_lookup_application_logs': {
269
274
  const { appId, ...params } = args;
270
275
  const logs = await lookupApplicationLogs(appId, params, network);
@@ -274,16 +279,18 @@ export const handleApplicationTools = ResponseProcessor.wrapResourceHandler(asyn
274
279
  const info = await searchForApplications(args, network);
275
280
  return info.applications;
276
281
  }
277
- case 'api_indexer_lookup_application_box': {
278
- const { appId, boxName } = args;
279
- const box = await lookupApplicationBoxByIDandName(appId, boxName, network);
280
- return box;
281
- }
282
- case 'api_indexer_lookup_application_boxes': {
283
- const { appId, maxBoxes } = args;
284
- const boxes = await searchForApplicationBoxes(appId, maxBoxes, network);
285
- return boxes.boxes;
286
- }
282
+ // Disabled: redundant with api_algod_get_application_box.
283
+ // case 'api_indexer_lookup_application_box': {
284
+ // const { appId, boxName } = args;
285
+ // const box = await lookupApplicationBoxByIDandName(appId, boxName, network);
286
+ // return box;
287
+ // }
288
+ // Disabled: redundant with api_algod_get_application_boxes.
289
+ // case 'api_indexer_lookup_application_boxes': {
290
+ // const { appId, maxBoxes } = args;
291
+ // const boxes = await searchForApplicationBoxes(appId, maxBoxes, network);
292
+ // return boxes.boxes;
293
+ // }
287
294
  default:
288
295
  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
289
296
  }
@@ -3,20 +3,22 @@ import { getIndexerClient, extractNetwork } from '../../../algorand-client.js';
3
3
  import { ResponseProcessor } from '../../../utils/responseProcessor.js';
4
4
  import { withCommonParams } from '../../commonParams.js';
5
5
  export const assetTools = [
6
- {
7
- name: 'api_indexer_lookup_asset_by_id',
8
- description: 'Get asset information and configuration',
9
- inputSchema: withCommonParams({
10
- type: 'object',
11
- properties: {
12
- assetId: {
13
- type: 'integer',
14
- description: 'Asset ID'
15
- }
16
- },
17
- required: ['assetId']
18
- })
19
- },
6
+ // Disabled: redundant with api_algod_get_asset_by_id (live algod variant covers the same query).
7
+ // See .notes/redundant-tools-report.md §1.
8
+ // {
9
+ // name: 'api_indexer_lookup_asset_by_id',
10
+ // description: 'Get asset information and configuration',
11
+ // inputSchema: withCommonParams({
12
+ // type: 'object',
13
+ // properties: {
14
+ // assetId: {
15
+ // type: 'integer',
16
+ // description: 'Asset ID'
17
+ // }
18
+ // },
19
+ // required: ['assetId']
20
+ // })
21
+ // },
20
22
  {
21
23
  name: 'api_indexer_lookup_asset_balances',
22
24
  description: 'Get accounts holding this asset and their balances',
@@ -139,7 +141,7 @@ export const assetTools = [
139
141
  })
140
142
  }
141
143
  ];
142
- export async function lookupAssetByID(assetId, network = 'mainnet') {
144
+ export async function lookupAssetByID(assetId, network = 'testnet') {
143
145
  try {
144
146
  const indexerClient = getIndexerClient(network);
145
147
  console.error(`Looking up asset info for ID ${assetId}`);
@@ -155,7 +157,7 @@ export async function lookupAssetByID(assetId, network = 'mainnet') {
155
157
  throw new McpError(ErrorCode.InternalError, `Failed to get asset info: ${error instanceof Error ? error.message : String(error)}`);
156
158
  }
157
159
  }
158
- export async function lookupAssetBalances(assetId, params, network = 'mainnet') {
160
+ export async function lookupAssetBalances(assetId, params, network = 'testnet') {
159
161
  try {
160
162
  const indexerClient = getIndexerClient(network);
161
163
  console.error(`Looking up balances for asset ${assetId}`);
@@ -184,7 +186,7 @@ export async function lookupAssetBalances(assetId, params, network = 'mainnet')
184
186
  throw new McpError(ErrorCode.InternalError, `Failed to get asset balances: ${error instanceof Error ? error.message : String(error)}`);
185
187
  }
186
188
  }
187
- export async function lookupAssetTransactions(assetId, params, network = 'mainnet') {
189
+ export async function lookupAssetTransactions(assetId, params, network = 'testnet') {
188
190
  try {
189
191
  const indexerClient = getIndexerClient(network);
190
192
  console.error(`Looking up transactions for asset ${assetId}`);
@@ -228,7 +230,7 @@ export async function lookupAssetTransactions(assetId, params, network = 'mainne
228
230
  throw new McpError(ErrorCode.InternalError, `Failed to get asset transactions: ${error instanceof Error ? error.message : String(error)}`);
229
231
  }
230
232
  }
231
- export async function searchForAssets(params, network = 'mainnet') {
233
+ export async function searchForAssets(params, network = 'testnet') {
232
234
  try {
233
235
  const indexerClient = getIndexerClient(network);
234
236
  console.error('Searching assets with params:', params);
@@ -267,11 +269,12 @@ export const handleAssetTools = ResponseProcessor.wrapResourceHandler(async func
267
269
  const name = args.name;
268
270
  const network = extractNetwork(args);
269
271
  switch (name) {
270
- case 'api_indexer_lookup_asset_by_id': {
271
- const { assetId } = args;
272
- const info = await lookupAssetByID(assetId, network);
273
- return info.asset;
274
- }
272
+ // Disabled: redundant with api_algod_get_asset_by_id. See .notes/redundant-tools-report.md §1.
273
+ // case 'api_indexer_lookup_asset_by_id': {
274
+ // const { assetId } = args;
275
+ // const info = await lookupAssetByID(assetId, network);
276
+ // return info.asset;
277
+ // }
275
278
  case 'api_indexer_lookup_asset_balances': {
276
279
  const { assetId, ...params } = args;
277
280
  const balances = await lookupAssetBalances(assetId, params, network);
@@ -125,7 +125,7 @@ export const transactionTools = [
125
125
  })
126
126
  }
127
127
  ];
128
- export async function lookupTransactionByID(txId, network = 'mainnet') {
128
+ export async function lookupTransactionByID(txId, network = 'testnet') {
129
129
  try {
130
130
  const indexerClient = getIndexerClient(network);
131
131
  console.error(`Looking up transaction with ID ${txId}`);
@@ -145,7 +145,7 @@ export async function lookupTransactionByID(txId, network = 'mainnet') {
145
145
  throw new McpError(ErrorCode.InternalError, `Failed to get transaction: ${error instanceof Error ? error.message : String(error)}`);
146
146
  }
147
147
  }
148
- export async function lookupAccountTransactions(address, params, network = 'mainnet') {
148
+ export async function lookupAccountTransactions(address, params, network = 'testnet') {
149
149
  try {
150
150
  const indexerClient = getIndexerClient(network);
151
151
  console.error(`Looking up transactions for account ${address}`);
@@ -188,7 +188,7 @@ export async function lookupAccountTransactions(address, params, network = 'main
188
188
  throw new McpError(ErrorCode.InternalError, `Failed to get account transactions: ${error instanceof Error ? error.message : String(error)}`);
189
189
  }
190
190
  }
191
- export async function searchForTransactions(params, network = 'mainnet') {
191
+ export async function searchForTransactions(params, network = 'testnet') {
192
192
  try {
193
193
  const indexerClient = getIndexerClient(network);
194
194
  console.error('Searching transactions with params:', params);
@@ -17,11 +17,6 @@ export declare class Arc26Manager {
17
17
  * @param params The parameters for constructing the URI
18
18
  * @returns Object containing the URI and QR code as base64 data URL
19
19
  */
20
- generateUriAndQr(params: Arc26ToolInput): Promise<{
21
- uri: string;
22
- qrCodePng: string;
23
- qrCodeUtf8: string;
24
- }>;
25
20
  /**
26
21
  * Constructs an Algorand URI according to ARC-26 specification and generates a QR code
27
22
  * @param params The parameters for constructing the URI
@@ -1,4 +1,3 @@
1
- import * as QRCode from 'qrcode';
2
1
  import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
3
2
  import { withCommonParams } from './commonParams.js';
4
3
  export class Arc26Manager {
@@ -50,70 +49,70 @@ export class Arc26Manager {
50
49
  * @param params The parameters for constructing the URI
51
50
  * @returns Object containing the URI and QR code as base64 data URL
52
51
  */
53
- async generateUriAndQr(params) {
54
- // Validate address format (base32 string)
55
- if (!params.address || !/^[A-Z2-7]{58}$/.test(params.address)) {
56
- throw new McpError(ErrorCode.InvalidParams, 'Invalid Algorand address format');
57
- }
58
- // Start building the URI with the scheme and address
59
- let uri = `algorand://${params.address}`;
60
- // Build query parameters
61
- const queryParams = [];
62
- // Add optional parameters if provided
63
- if (params.label) {
64
- queryParams.push(`label=${encodeURIComponent(params.label)}`);
65
- }
66
- if (typeof params.amount === 'number') {
67
- if (params.amount < 0) {
68
- throw new McpError(ErrorCode.InvalidParams, 'Amount must be non-negative');
69
- }
70
- // Convert to microAlgos and ensure no decimals
71
- const microAlgos = Math.floor(params.amount);
72
- queryParams.push(`amount=${microAlgos}`);
73
- }
74
- if (typeof params.asset === 'number') {
75
- if (params.asset < 0) {
76
- throw new McpError(ErrorCode.InvalidParams, 'Asset ID must be non-negative');
77
- }
78
- queryParams.push(`asset=${params.asset}`);
79
- }
80
- if (params.note) {
81
- queryParams.push(`note=${encodeURIComponent(params.note)}`);
82
- }
83
- if (params.xnote) {
84
- queryParams.push(`xnote=${encodeURIComponent(params.xnote)}`);
85
- }
86
- // Add query parameters to URI if any exist
87
- if (queryParams.length > 0) {
88
- uri += '?' + queryParams.join('&');
89
- }
90
- // Generate QR code as SVG
91
- const qrCodeUtf8Raw = await QRCode.toString(uri, {
92
- type: 'utf8',
93
- errorCorrectionLevel: 'H',
94
- // margin: 1,
95
- width: 128
96
- });
97
- // Invert for better terminal contrast
98
- const qrCodeUtf8 = qrCodeUtf8Raw
99
- .replace(/█/g, '⬜').replace(/ /g, '█').replace(/⬜/g, ' ')
100
- .replace(/▀/g, '⬛').replace(/▄/g, '▀').replace(/⬛/g, '▄');
101
- const qrCodePng = await QRCode.toDataURL(uri, {
102
- type: 'image/png',
103
- errorCorrectionLevel: 'H',
104
- color: {
105
- dark: '#000000',
106
- light: '#FFFFFF'
107
- },
108
- margin: 4,
109
- width: 128
110
- });
111
- return {
112
- uri,
113
- qrCodePng: qrCodePng,
114
- qrCodeUtf8: qrCodeUtf8
115
- };
116
- }
52
+ // async generateUriAndQr(params: Arc26ToolInput): Promise<{ uri: string; qrCodePng: string; qrCodeUtf8: string }> {
53
+ // // Validate address format (base32 string)
54
+ // if (!params.address || !/^[A-Z2-7]{58}$/.test(params.address)) {
55
+ // throw new McpError(ErrorCode.InvalidParams, 'Invalid Algorand address format');
56
+ // }
57
+ // // Start building the URI with the scheme and address
58
+ // let uri = `algorand://${params.address}`;
59
+ // // Build query parameters
60
+ // const queryParams: string[] = [];
61
+ // // Add optional parameters if provided
62
+ // if (params.label) {
63
+ // queryParams.push(`label=${encodeURIComponent(params.label)}`);
64
+ // }
65
+ // if (typeof params.amount === 'number') {
66
+ // if (params.amount < 0) {
67
+ // throw new McpError(ErrorCode.InvalidParams, 'Amount must be non-negative');
68
+ // }
69
+ // // Convert to microAlgos and ensure no decimals
70
+ // const microAlgos = Math.floor(params.amount);
71
+ // queryParams.push(`amount=${microAlgos}`);
72
+ // }
73
+ // if (typeof params.asset === 'number') {
74
+ // if (params.asset < 0) {
75
+ // throw new McpError(ErrorCode.InvalidParams, 'Asset ID must be non-negative');
76
+ // }
77
+ // queryParams.push(`asset=${params.asset}`);
78
+ // }
79
+ // if (params.note) {
80
+ // queryParams.push(`note=${encodeURIComponent(params.note)}`);
81
+ // }
82
+ // if (params.xnote) {
83
+ // queryParams.push(`xnote=${encodeURIComponent(params.xnote)}`);
84
+ // }
85
+ // // Add query parameters to URI if any exist
86
+ // if (queryParams.length > 0) {
87
+ // uri += '?' + queryParams.join('&');
88
+ // }
89
+ // // Generate QR code as SVG
90
+ // const qrCodeUtf8Raw = await QRCode.toString(uri, {
91
+ // type: 'utf8',
92
+ // errorCorrectionLevel: 'H',
93
+ // // margin: 1,
94
+ // width: 128
95
+ // });
96
+ // // Invert for better terminal contrast
97
+ // const qrCodeUtf8 = qrCodeUtf8Raw
98
+ // .replace(/█/g, '⬜').replace(/ /g, '█').replace(/⬜/g, ' ')
99
+ // .replace(/▀/g, '⬛').replace(/▄/g, '▀').replace(/⬛/g, '▄');
100
+ // const qrCodePng = await QRCode.toDataURL(uri, {
101
+ // type: 'image/png',
102
+ // errorCorrectionLevel: 'H',
103
+ // color: {
104
+ // dark: '#000000',
105
+ // light: '#FFFFFF'
106
+ // },
107
+ // margin: 4,
108
+ // width: 128
109
+ // });
110
+ // return {
111
+ // uri,
112
+ // qrCodePng: qrCodePng,
113
+ // qrCodeUtf8: qrCodeUtf8
114
+ // };
115
+ // }
117
116
  /**
118
117
  * Constructs an Algorand URI according to ARC-26 specification and generates a QR code
119
118
  * @param params The parameters for constructing the URI
@@ -3,7 +3,7 @@ export const networkParam = {
3
3
  network: {
4
4
  type: 'string',
5
5
  enum: ['mainnet', 'testnet', 'localnet'],
6
- description: 'Algorand network to use (default: mainnet)',
6
+ description: 'Algorand network to use (default: testnet)',
7
7
  },
8
8
  };
9
9
  export const itemsPerPageParam = {
@@ -86,7 +86,7 @@ export class UtilityManager {
86
86
  type: 'text',
87
87
  text: JSON.stringify({
88
88
  name: 'Algorand MCP Server',
89
- version: '4.2.2',
89
+ version: '4.2.5',
90
90
  builder: 'GoPlausible',
91
91
  description: 'A Model Context Protocol (MCP) server providing comprehensive access to the Algorand blockchain. Supports account management, transaction building and signing, smart contract interaction, asset operations, ARC-26 URI generation, and deep integration with Algorand ecosystem services including NFDomains, Tinyman, Vestige, and Ultrade.',
92
92
  blockchain: 'Algorand — a carbon-negative, pure proof-of-stake Layer 1 blockchain delivering instant finality, low fees, and advanced smart contract capabilities via AVM (Algorand Virtual Machine).',
@@ -67,7 +67,6 @@ const walletToolSchemas = {
67
67
  addAccount: {
68
68
  type: 'object',
69
69
  properties: {
70
- mnemonic: { type: 'string', description: '25-word mnemonic to import. If omitted, a new account is generated.' },
71
70
  nickname: { type: 'string', description: 'Human-readable nickname for this account' },
72
71
  allowance: { type: 'number', description: 'Max per-transaction amount in microAlgos (0 = unlimited)' },
73
72
  dailyAllowance: { type: 'number', description: 'Max daily spending total in microAlgos (0 = unlimited)' }
@@ -767,12 +766,12 @@ WalletManager.walletTools = [
767
766
  },
768
767
  {
769
768
  name: 'wallet_get_info',
770
- description: 'Get the active wallet account info including address, public key, nickname, on-chain balance, and spending limits.',
769
+ description: 'Get info for the ACTIVE wallet account (an account whose private key this MCP server owns in the OS keychain). Returns nickname, address, public key, network, on-chain balance, min-balance, opted-in apps/assets counts, plus wallet-only fields: per-tx allowance, daily allowance, and daily-spent counter. Use this when you want to know the state of "the wallet you control." For an arbitrary on-chain account that this wallet does NOT own, use api_algod_get_account_info instead.',
771
770
  inputSchema: withCommonParams(walletToolSchemas.getInfo),
772
771
  },
773
772
  {
774
773
  name: 'wallet_get_assets',
775
- description: 'Get all asset holdings for the active wallet account.',
774
+ description: 'Get all ASA holdings for the ACTIVE wallet account (an account whose private key this MCP server owns). Returns the wallet nickname, address, network, and the assets array. Use this when listing holdings of "the wallet you control." For asset holdings of an arbitrary on-chain account, use api_algod_get_account_info (returns full account including assets) or api_algod_get_account_asset_info (single asset).',
776
775
  inputSchema: withCommonParams(walletToolSchemas.getAssets),
777
776
  },
778
777
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goplausible/algorand-mcp",
3
- "version": "4.2.3",
3
+ "version": "4.2.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -13,6 +13,9 @@
13
13
  "test:e2e": "NODE_OPTIONS='--experimental-vm-modules' jest --config tests/jest.config.e2e.js",
14
14
  "test:all": "npm test && npm run test:e2e",
15
15
  "typecheck": "tsc --noEmit",
16
+ "tag": "git tag v$npm_package_version && git push origin v$npm_package_version",
17
+ "retag": "git tag -f v$npm_package_version && git push -f origin v$npm_package_version",
18
+ "publish:npm": "npm publish",
16
19
  "prepublishOnly": "npm run clean && npm run build"
17
20
  },
18
21
  "keywords": [