@goplausible/algorand-mcp 3.7.0 → 3.9.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/dist/index.js CHANGED
@@ -137,6 +137,10 @@ class AlgorandMcpServer {
137
137
  if (name.startsWith('api_')) {
138
138
  return handleApiManager(name, args);
139
139
  }
140
+ // Handle Alpha Arcade tools
141
+ if (name.startsWith('alpha_')) {
142
+ return handleApiManager(name, args);
143
+ }
140
144
  // Handle ARC-26 tools
141
145
  if (name === 'generate_algorand_uri') {
142
146
  return arc26Manager.handleTool(name, args);
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Alpha Arcade client factory for the local MCP wallet system.
3
+ * Creates AlphaClient instances backed by the WalletManager keychain signer.
4
+ */
5
+ import { AlphaClient } from '@alpha-arcade/sdk';
6
+ import { type NetworkId } from '../../../algorand-client.js';
7
+ export declare const MATCHER_APP_ID = 3078581851;
8
+ export declare const USDC_ASSET_ID = 31566704;
9
+ export declare const API_BASE_URL = "https://platform.alphaarcade.com/api";
10
+ declare const ALPHA_API_KEY: string;
11
+ export declare const formatPrice: (microunits: number) => string;
12
+ export declare const formatQty: (microunits: number) => string;
13
+ export declare const formatPriceFromProb: (microunits: number) => string;
14
+ export declare function friendlyTradeError(msg: string): string;
15
+ /**
16
+ * Create a read-only AlphaClient (no signing capability).
17
+ * Used for market queries, orderbook, positions lookups.
18
+ */
19
+ export declare function createReadOnlyClient(network: NetworkId): AlphaClient;
20
+ /**
21
+ * Create a trading AlphaClient backed by the local wallet keychain.
22
+ * Uses the active wallet account's secret key for signing.
23
+ */
24
+ export declare function createTradingClient(network: NetworkId): Promise<AlphaClient>;
25
+ /**
26
+ * Resolve wallet address — uses provided address or falls back to active wallet.
27
+ */
28
+ export declare function resolveWalletAddress(walletAddress?: string): Promise<string>;
29
+ export { ALPHA_API_KEY };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Alpha Arcade client factory for the local MCP wallet system.
3
+ * Creates AlphaClient instances backed by the WalletManager keychain signer.
4
+ */
5
+ import { AlphaClient } from '@alpha-arcade/sdk';
6
+ import algosdk from 'algosdk';
7
+ import { getAlgodClient, getIndexerClient } from '../../../algorand-client.js';
8
+ import { WalletManager } from '../../walletManager.js';
9
+ // ── Constants ──────────────────────────────────────────────────────────────
10
+ export const MATCHER_APP_ID = 3078581851;
11
+ export const USDC_ASSET_ID = 31566704;
12
+ export const API_BASE_URL = 'https://platform.alphaarcade.com/api';
13
+ // Read optional env vars once
14
+ const ALPHA_API_KEY = process.env.ALPHA_API_KEY || '';
15
+ const ALPHA_API_BASE_URL = process.env.ALPHA_API_BASE_URL || API_BASE_URL;
16
+ // ── Formatting helpers ─────────────────────────────────────────────────────
17
+ export const formatPrice = (microunits) => `$${(microunits / 1e6).toFixed(2)}`;
18
+ export const formatQty = (microunits) => `${(microunits / 1e6).toFixed(2)} shares`;
19
+ export const formatPriceFromProb = (microunits) => `$${(microunits / 1e6).toFixed(2)}`;
20
+ // ── Friendly error messages ────────────────────────────────────────────────
21
+ export function friendlyTradeError(msg) {
22
+ const m = msg || 'Unknown error';
23
+ const isUsdcError = m.includes(String(USDC_ASSET_ID)) || /usdc/i.test(m);
24
+ const overspendAlgo = m.match(/overspend.*?MicroAlgos:\{Raw:(\d+)\}.*?tried to spend \{(\d+)\}/i);
25
+ if (overspendAlgo) {
26
+ const have = (Number.parseInt(overspendAlgo[1], 10) / 1e6).toFixed(2);
27
+ const need = (Number.parseInt(overspendAlgo[2], 10) / 1e6).toFixed(2);
28
+ return `Insufficient ALGO balance. You have ~${have} ALGO but need ~${need} ALGO (escrow collateral + fees). Please fund your wallet with more ALGO.`;
29
+ }
30
+ const overspendAsset = m.match(/overspend.*?asset.*?(\d+).*?holding:\{Amount:(\d+)\}.*?tried to spend \{(\d+)\}/i);
31
+ if (overspendAsset) {
32
+ const assetId = overspendAsset[1];
33
+ const have = (Number.parseInt(overspendAsset[2], 10) / 1e6).toFixed(2);
34
+ const need = (Number.parseInt(overspendAsset[3], 10) / 1e6).toFixed(2);
35
+ const label = assetId === String(USDC_ASSET_ID) ? 'USDC' : `asset ${assetId}`;
36
+ return `Insufficient ${label} balance. You have ~$${have} but need ~$${need}. Please add more ${label} to your wallet.`;
37
+ }
38
+ if (/overspend/i.test(m)) {
39
+ const label = isUsdcError ? 'USDC' : 'ALGO';
40
+ return `Insufficient ${label} balance to cover this transaction. Please add more ${label} to your wallet.`;
41
+ }
42
+ if (/underflow/i.test(m)) {
43
+ const label = isUsdcError ? 'USDC' : 'token';
44
+ return `Insufficient ${label} balance for this operation. Make sure you hold enough ${label} before trading.`;
45
+ }
46
+ if (/asset \d+ missing/i.test(m) || /not opted in/i.test(m)) {
47
+ if (isUsdcError)
48
+ return "Your account hasn't opted in to USDC. Please opt in to USDC (asset 31566704) first.";
49
+ return "Your account hasn't opted in to a required asset. Please opt in first.";
50
+ }
51
+ if (/below min/i.test(m) || /min balance/i.test(m)) {
52
+ return 'This transaction would drop your account below the minimum ALGO balance. Please add more ALGO.';
53
+ }
54
+ if (/account.*has been closed/i.test(m) || /dead account/i.test(m)) {
55
+ return 'The target account has been closed and cannot receive transactions.';
56
+ }
57
+ if (/group size/i.test(m)) {
58
+ return 'Transaction group error. Please try again.';
59
+ }
60
+ if (/logic eval error/i.test(m) || /rejected by logic/i.test(m)) {
61
+ return 'The smart contract rejected this transaction. The order parameters may be invalid or the market conditions have changed. Please try again.';
62
+ }
63
+ if (/timeout/i.test(m) || /network/i.test(m) || /ECONNREFUSED/i.test(m)) {
64
+ return 'Network error reaching the Algorand node. Please try again in a moment.';
65
+ }
66
+ return m;
67
+ }
68
+ // ── Client factories ───────────────────────────────────────────────────────
69
+ /**
70
+ * Create a read-only AlphaClient (no signing capability).
71
+ * Used for market queries, orderbook, positions lookups.
72
+ */
73
+ export function createReadOnlyClient(network) {
74
+ const algodClient = getAlgodClient(network);
75
+ const indexerClient = getIndexerClient(network);
76
+ const dummySigner = async () => [];
77
+ return new AlphaClient({
78
+ algodClient,
79
+ indexerClient,
80
+ signer: dummySigner,
81
+ activeAddress: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ',
82
+ matcherAppId: MATCHER_APP_ID,
83
+ usdcAssetId: USDC_ASSET_ID,
84
+ apiBaseUrl: ALPHA_API_BASE_URL,
85
+ apiKey: ALPHA_API_KEY || undefined,
86
+ });
87
+ }
88
+ /**
89
+ * Create a trading AlphaClient backed by the local wallet keychain.
90
+ * Uses the active wallet account's secret key for signing.
91
+ */
92
+ export async function createTradingClient(network) {
93
+ const account = await WalletManager.getActiveWalletAccount();
94
+ const sk = await WalletManager.getActiveWalletSecretKey();
95
+ const address = account.address;
96
+ const algodClient = getAlgodClient(network);
97
+ const indexerClient = getIndexerClient(network);
98
+ const signer = async (txnGroup, indexesToSign) => {
99
+ return indexesToSign.map((idx) => algosdk.signTransaction(txnGroup[idx], sk).blob);
100
+ };
101
+ return new AlphaClient({
102
+ algodClient,
103
+ indexerClient,
104
+ signer,
105
+ activeAddress: address,
106
+ matcherAppId: MATCHER_APP_ID,
107
+ usdcAssetId: USDC_ASSET_ID,
108
+ apiBaseUrl: ALPHA_API_BASE_URL,
109
+ apiKey: ALPHA_API_KEY || undefined,
110
+ });
111
+ }
112
+ /**
113
+ * Resolve wallet address — uses provided address or falls back to active wallet.
114
+ */
115
+ export async function resolveWalletAddress(walletAddress) {
116
+ if (walletAddress)
117
+ return walletAddress;
118
+ const account = await WalletManager.getActiveWalletAccount();
119
+ return account.address;
120
+ }
121
+ export { ALPHA_API_KEY };
@@ -0,0 +1,3 @@
1
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ export declare const alphaTools: Tool[];
3
+ export declare function handleAlphaTools(name: string, args: any): Promise<any>;
@@ -0,0 +1,50 @@
1
+ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
+ import { marketTools, handleMarketTools } from './markets.js';
3
+ import { positionTools, handlePositionTools } from './positions.js';
4
+ import { tradingTools, handleTradingTools } from './trading.js';
5
+ import { shareTools, handleShareTools } from './shares.js';
6
+ // Combine all Alpha Arcade tools
7
+ export const alphaTools = [
8
+ ...marketTools,
9
+ ...positionTools,
10
+ ...tradingTools,
11
+ ...shareTools,
12
+ ];
13
+ // Handle all Alpha Arcade tools
14
+ export async function handleAlphaTools(name, args) {
15
+ try {
16
+ const combinedArgs = { name, ...args };
17
+ // Market query tools
18
+ if (name === 'alpha_get_live_markets' ||
19
+ name === 'alpha_get_reward_markets' ||
20
+ name === 'alpha_get_market' ||
21
+ name === 'alpha_get_orderbook') {
22
+ return handleMarketTools(combinedArgs);
23
+ }
24
+ // Position/order query tools
25
+ if (name === 'alpha_get_open_orders' || name === 'alpha_get_positions') {
26
+ return handlePositionTools(combinedArgs);
27
+ }
28
+ // Trading tools
29
+ if (name === 'alpha_create_limit_order' ||
30
+ name === 'alpha_create_market_order' ||
31
+ name === 'alpha_cancel_order' ||
32
+ name === 'alpha_amend_order' ||
33
+ name === 'alpha_propose_match') {
34
+ return handleTradingTools(combinedArgs);
35
+ }
36
+ // Share management tools
37
+ if (name === 'alpha_split_shares' ||
38
+ name === 'alpha_merge_shares' ||
39
+ name === 'alpha_claim') {
40
+ return handleShareTools(combinedArgs);
41
+ }
42
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown Alpha Arcade tool: ${name}`);
43
+ }
44
+ catch (error) {
45
+ if (error instanceof McpError) {
46
+ throw error;
47
+ }
48
+ throw new McpError(ErrorCode.InternalError, `Failed to handle Alpha Arcade tool: ${error instanceof Error ? error.message : String(error)}`);
49
+ }
50
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Alpha Arcade read-only market tools: get_live_markets, get_reward_markets, get_market, get_orderbook
3
+ */
4
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
5
+ export declare const marketTools: Tool[];
6
+ export declare function handleMarketTools(args: any): Promise<any>;
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Alpha Arcade read-only market tools: get_live_markets, get_reward_markets, get_market, get_orderbook
3
+ */
4
+ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
5
+ import { extractNetwork } from '../../../algorand-client.js';
6
+ import { withCommonParams } from '../../commonParams.js';
7
+ import { createReadOnlyClient, formatPriceFromProb, ALPHA_API_KEY, } from './alphaClient.js';
8
+ export const marketTools = [
9
+ {
10
+ name: 'alpha_get_live_markets',
11
+ description: 'Fetch all live Alpha Arcade prediction markets. Returns summary: id, title, marketAppId, prices, volume. Multi-choice markets have an options[] array — use options[].marketAppId for trading.',
12
+ inputSchema: withCommonParams({
13
+ type: 'object',
14
+ properties: {},
15
+ required: [],
16
+ }),
17
+ },
18
+ {
19
+ name: 'alpha_get_reward_markets',
20
+ description: 'Fetch Alpha Arcade markets with liquidity rewards (totalRewards, rewardsPaidOut, etc.). Same shape as alpha_get_live_markets but includes reward info. Requires ALPHA_API_KEY.',
21
+ inputSchema: withCommonParams({
22
+ type: 'object',
23
+ properties: {},
24
+ required: [],
25
+ }),
26
+ },
27
+ {
28
+ name: 'alpha_get_market',
29
+ description: 'Fetch full details for a single Alpha Arcade prediction market. Pass marketAppId (numeric, always required) and optionally marketId (UUID) for richer API data.',
30
+ inputSchema: withCommonParams({
31
+ type: 'object',
32
+ properties: {
33
+ marketAppId: {
34
+ type: 'number',
35
+ description: 'The market application ID (numeric, always required)',
36
+ },
37
+ marketId: {
38
+ type: 'string',
39
+ description: 'The market UUID (optional, used for API mode when ALPHA_API_KEY is set)',
40
+ },
41
+ },
42
+ required: ['marketAppId'],
43
+ }),
44
+ },
45
+ {
46
+ name: 'alpha_get_orderbook',
47
+ description: 'Fetch the on-chain orderbook as a unified YES-perspective view. Merges all 4 sides (YES bids/asks + NO bids/asks). Includes spread calculation.',
48
+ inputSchema: withCommonParams({
49
+ type: 'object',
50
+ properties: {
51
+ marketAppId: {
52
+ type: 'number',
53
+ description: 'The market app ID',
54
+ },
55
+ },
56
+ required: ['marketAppId'],
57
+ }),
58
+ },
59
+ ];
60
+ function formatMarketSummary(m) {
61
+ const vol = typeof m.volume === 'number' ? m.volume : Number.parseFloat(String(m.volume ?? ''));
62
+ const entry = {
63
+ id: m.id,
64
+ title: m.title || `Market ${m.marketAppId}`,
65
+ marketAppId: m.marketAppId,
66
+ image: m.image,
67
+ yesAssetId: m.yesAssetId || undefined,
68
+ noAssetId: m.noAssetId || undefined,
69
+ yesPrice: m.yesProb != null ? formatPriceFromProb(m.yesProb) : undefined,
70
+ noPrice: m.noProb != null ? formatPriceFromProb(m.noProb) : undefined,
71
+ volume: !Number.isNaN(vol) ? `$${vol.toFixed(2)}` : undefined,
72
+ endsAt: m.endTs ? new Date(m.endTs * 1000).toISOString() : undefined,
73
+ isResolved: m.isResolved ?? false,
74
+ source: m.source ?? 'unknown',
75
+ };
76
+ if (m.categories?.length)
77
+ entry.categories = m.categories;
78
+ if (m.feeBase != null)
79
+ entry.feeBase = m.feeBase;
80
+ if (m.options?.length) {
81
+ entry.options = m.options.map((o) => ({
82
+ title: o.title || `Option ${o.marketAppId}`,
83
+ marketAppId: o.marketAppId,
84
+ yesAssetId: o.yesAssetId,
85
+ noAssetId: o.noAssetId,
86
+ }));
87
+ }
88
+ return entry;
89
+ }
90
+ export async function handleMarketTools(args) {
91
+ const { name, marketAppId, marketId } = args;
92
+ const network = extractNetwork(args);
93
+ const client = createReadOnlyClient(network);
94
+ switch (name) {
95
+ case 'alpha_get_live_markets': {
96
+ let markets;
97
+ try {
98
+ markets = await client.getLiveMarkets();
99
+ }
100
+ catch (apiErr) {
101
+ if (ALPHA_API_KEY) {
102
+ markets = await client.getMarketsOnChain();
103
+ }
104
+ else {
105
+ throw apiErr;
106
+ }
107
+ }
108
+ const summary = markets.map(formatMarketSummary);
109
+ return { markets: summary, type: 'live' };
110
+ }
111
+ case 'alpha_get_reward_markets': {
112
+ if (!ALPHA_API_KEY) {
113
+ return { error: 'Reward markets require an ALPHA_API_KEY. Set it in your environment to access reward market data.' };
114
+ }
115
+ const markets = await client.getRewardMarkets();
116
+ const summary = markets.map((m) => {
117
+ const entry = formatMarketSummary(m);
118
+ if (m.totalRewards != null)
119
+ entry.totalRewards = m.totalRewards;
120
+ if (m.rewardsPaidOut != null)
121
+ entry.rewardsPaidOut = m.rewardsPaidOut;
122
+ if (m.rewardsSpreadDistance != null)
123
+ entry.rewardsSpreadDistance = m.rewardsSpreadDistance;
124
+ if (m.rewardsMinContracts != null)
125
+ entry.rewardsMinContracts = m.rewardsMinContracts;
126
+ if (m.lastRewardAmount != null)
127
+ entry.lastRewardAmount = m.lastRewardAmount;
128
+ if (m.lastRewardTs != null)
129
+ entry.lastRewardTs = new Date(m.lastRewardTs).toISOString();
130
+ return entry;
131
+ });
132
+ return { markets: summary, type: 'reward' };
133
+ }
134
+ case 'alpha_get_market': {
135
+ if (!marketAppId) {
136
+ throw new McpError(ErrorCode.InvalidParams, 'marketAppId is required');
137
+ }
138
+ let market = null;
139
+ if (ALPHA_API_KEY && marketId) {
140
+ try {
141
+ market = await client.getMarketFromApi(marketId);
142
+ }
143
+ catch {
144
+ // fall through to on-chain
145
+ }
146
+ }
147
+ if (!market) {
148
+ market = await client.getMarketOnChain(marketAppId).catch(() => null);
149
+ }
150
+ if (!market) {
151
+ return { error: `Market ${marketAppId} not found.` };
152
+ }
153
+ return market;
154
+ }
155
+ case 'alpha_get_orderbook': {
156
+ if (!marketAppId) {
157
+ throw new McpError(ErrorCode.InvalidParams, 'marketAppId is required');
158
+ }
159
+ const book = await client.getOrderbook(marketAppId);
160
+ const toUnified = (e, source, priceOverride) => {
161
+ const p = priceOverride ?? e.price;
162
+ const priceCents = p / 1000000;
163
+ const shares = e.quantity / 1000000;
164
+ return {
165
+ price: `${(priceCents * 100).toFixed(2)}¢`,
166
+ priceRaw: p,
167
+ shares: `${shares.toFixed(2)}`,
168
+ total: `$${(priceCents * shares).toFixed(2)}`,
169
+ escrowAppId: e.escrowAppId,
170
+ owner: e.owner,
171
+ source,
172
+ };
173
+ };
174
+ const asks = [
175
+ ...book.yes.asks.map((e) => toUnified(e, 'YES ask')),
176
+ ...book.no.bids.map((e) => toUnified(e, 'NO bid (= YES ask)', 1000000 - e.price)),
177
+ ].sort((a, b) => a.priceRaw - b.priceRaw);
178
+ const bids = [
179
+ ...book.yes.bids.map((e) => toUnified(e, 'YES bid')),
180
+ ...book.no.asks.map((e) => toUnified(e, 'NO ask (= YES bid)', 1000000 - e.price)),
181
+ ].sort((a, b) => b.priceRaw - a.priceRaw);
182
+ const bestAsk = asks.length > 0 ? asks[0].priceRaw : null;
183
+ const bestBid = bids.length > 0 ? bids[0].priceRaw : null;
184
+ const spread = bestAsk != null && bestBid != null
185
+ ? `${((bestAsk - bestBid) / 10000).toFixed(2)}¢`
186
+ : 'N/A';
187
+ const totalOrders = book.yes.bids.length + book.yes.asks.length + book.no.bids.length + book.no.asks.length;
188
+ return { unified: { asks, bids, spread }, totalOrders, marketAppId };
189
+ }
190
+ default:
191
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown Alpha market tool: ${name}`);
192
+ }
193
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Alpha Arcade position and order query tools: get_open_orders, get_positions
3
+ */
4
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
5
+ export declare const positionTools: Tool[];
6
+ export declare function handlePositionTools(args: any): Promise<any>;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Alpha Arcade position and order query tools: get_open_orders, get_positions
3
+ */
4
+ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
5
+ import { extractNetwork } from '../../../algorand-client.js';
6
+ import { withCommonParams } from '../../commonParams.js';
7
+ import { createReadOnlyClient, resolveWalletAddress, formatPrice, formatQty, } from './alphaClient.js';
8
+ export const positionTools = [
9
+ {
10
+ name: 'alpha_get_open_orders',
11
+ description: 'Fetch all open orders for a wallet on a specific Alpha Arcade market. Uses your active MCP wallet if walletAddress is not provided.',
12
+ inputSchema: withCommonParams({
13
+ type: 'object',
14
+ properties: {
15
+ marketAppId: {
16
+ type: 'number',
17
+ description: 'The market app ID',
18
+ },
19
+ walletAddress: {
20
+ type: 'string',
21
+ description: 'Algorand wallet address (uses active MCP wallet if omitted)',
22
+ },
23
+ },
24
+ required: ['marketAppId'],
25
+ }),
26
+ },
27
+ {
28
+ name: 'alpha_get_positions',
29
+ description: 'Fetch all YES/NO token positions for a wallet across all Alpha Arcade markets. Uses your active MCP wallet if walletAddress is not provided.',
30
+ inputSchema: withCommonParams({
31
+ type: 'object',
32
+ properties: {
33
+ walletAddress: {
34
+ type: 'string',
35
+ description: 'Algorand wallet address (uses active MCP wallet if omitted)',
36
+ },
37
+ },
38
+ required: [],
39
+ }),
40
+ },
41
+ ];
42
+ export async function handlePositionTools(args) {
43
+ const { name, marketAppId, walletAddress } = args;
44
+ const network = extractNetwork(args);
45
+ const client = createReadOnlyClient(network);
46
+ switch (name) {
47
+ case 'alpha_get_open_orders': {
48
+ if (!marketAppId) {
49
+ throw new McpError(ErrorCode.InvalidParams, 'marketAppId is required');
50
+ }
51
+ const address = await resolveWalletAddress(walletAddress);
52
+ const orders = await client.getOpenOrders(marketAppId, address);
53
+ const formatted = orders.map((o) => ({
54
+ escrowAppId: o.escrowAppId,
55
+ position: o.position === 1 ? 'YES' : 'NO',
56
+ side: o.side === 1 ? 'BUY' : 'SELL',
57
+ price: formatPrice(o.price),
58
+ quantity: formatQty(o.quantity),
59
+ filled: formatQty(o.quantityFilled),
60
+ remaining: formatQty(o.quantity - o.quantityFilled),
61
+ }));
62
+ return { orders: formatted, marketAppId, walletAddress: address };
63
+ }
64
+ case 'alpha_get_positions': {
65
+ const address = await resolveWalletAddress(walletAddress);
66
+ const positions = await client.getPositions(address);
67
+ const formatted = positions.map((p) => ({
68
+ marketAppId: p.marketAppId,
69
+ title: p.title || `Market ${p.marketAppId}`,
70
+ yesBalance: formatQty(p.yesBalance),
71
+ noBalance: formatQty(p.noBalance),
72
+ yesAssetId: p.yesAssetId,
73
+ noAssetId: p.noAssetId,
74
+ }));
75
+ return { positions: formatted, walletAddress: address };
76
+ }
77
+ default:
78
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown Alpha position tool: ${name}`);
79
+ }
80
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Alpha Arcade share management tools: split_shares, merge_shares, claim
3
+ */
4
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
5
+ export declare const shareTools: Tool[];
6
+ export declare function handleShareTools(args: any): Promise<any>;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Alpha Arcade share management tools: split_shares, merge_shares, claim
3
+ */
4
+ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
5
+ import { extractNetwork } from '../../../algorand-client.js';
6
+ import { withCommonParams } from '../../commonParams.js';
7
+ import { createTradingClient, friendlyTradeError, formatPrice, formatQty, } from './alphaClient.js';
8
+ export const shareTools = [
9
+ {
10
+ name: 'alpha_split_shares',
11
+ description: 'Split USDC into equal YES and NO outcome tokens on Alpha Arcade. 1 USDC (1000000 microunits) = 1 YES + 1 NO.',
12
+ inputSchema: withCommonParams({
13
+ type: 'object',
14
+ properties: {
15
+ marketAppId: { type: 'number', description: 'The market app ID' },
16
+ amount: { type: 'number', description: 'Amount to split in microunits (e.g. 1000000 = $1.00 USDC)' },
17
+ },
18
+ required: ['marketAppId', 'amount'],
19
+ }),
20
+ },
21
+ {
22
+ name: 'alpha_merge_shares',
23
+ description: 'Merge equal YES and NO outcome tokens back into USDC on Alpha Arcade. 1 YES + 1 NO = 1 USDC.',
24
+ inputSchema: withCommonParams({
25
+ type: 'object',
26
+ properties: {
27
+ marketAppId: { type: 'number', description: 'The market app ID' },
28
+ amount: { type: 'number', description: 'Amount to merge in microunits' },
29
+ },
30
+ required: ['marketAppId', 'amount'],
31
+ }),
32
+ },
33
+ {
34
+ name: 'alpha_claim',
35
+ description: 'Claim USDC from a resolved Alpha Arcade market by redeeming outcome tokens. Winning = 1:1 USDC.',
36
+ inputSchema: withCommonParams({
37
+ type: 'object',
38
+ properties: {
39
+ marketAppId: { type: 'number', description: 'The market app ID' },
40
+ assetId: { type: 'number', description: 'The outcome token ASA ID to redeem' },
41
+ amount: { type: 'number', description: 'Amount to claim in microunits (omit to claim entire balance)' },
42
+ },
43
+ required: ['marketAppId', 'assetId'],
44
+ }),
45
+ },
46
+ ];
47
+ export async function handleShareTools(args) {
48
+ const { name } = args;
49
+ const network = extractNetwork(args);
50
+ switch (name) {
51
+ case 'alpha_split_shares': {
52
+ const { marketAppId, amount } = args;
53
+ try {
54
+ const client = await createTradingClient(network);
55
+ const result = await client.splitShares({ marketAppId, amount });
56
+ return {
57
+ type: 'split',
58
+ marketAppId,
59
+ amount: formatQty(amount),
60
+ amountUSDC: formatPrice(amount),
61
+ txIds: result.txIds,
62
+ confirmedRound: result.confirmedRound,
63
+ };
64
+ }
65
+ catch (error) {
66
+ return { error: `Error splitting shares: ${friendlyTradeError(error.message)}` };
67
+ }
68
+ }
69
+ case 'alpha_merge_shares': {
70
+ const { marketAppId, amount } = args;
71
+ try {
72
+ const client = await createTradingClient(network);
73
+ const result = await client.mergeShares({ marketAppId, amount });
74
+ return {
75
+ type: 'merge',
76
+ marketAppId,
77
+ amount: formatQty(amount),
78
+ amountUSDC: formatPrice(amount),
79
+ txIds: result.txIds,
80
+ confirmedRound: result.confirmedRound,
81
+ };
82
+ }
83
+ catch (error) {
84
+ return { error: `Error merging shares: ${friendlyTradeError(error.message)}` };
85
+ }
86
+ }
87
+ case 'alpha_claim': {
88
+ const { marketAppId, assetId, amount } = args;
89
+ try {
90
+ const client = await createTradingClient(network);
91
+ const result = await client.claim({ marketAppId, assetId, amount });
92
+ return {
93
+ type: 'claim',
94
+ marketAppId,
95
+ assetId,
96
+ amountRequested: amount != null ? formatQty(amount) : 'Full balance',
97
+ amountClaimed: formatQty(result.amountClaimed),
98
+ txIds: result.txIds,
99
+ confirmedRound: result.confirmedRound,
100
+ };
101
+ }
102
+ catch (error) {
103
+ return { error: `Error claiming: ${friendlyTradeError(error.message)}` };
104
+ }
105
+ }
106
+ default:
107
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown Alpha share tool: ${name}`);
108
+ }
109
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Alpha Arcade trading tools: create_limit_order, create_market_order, cancel_order, amend_order, propose_match
3
+ */
4
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
5
+ export declare const tradingTools: Tool[];
6
+ export declare function handleTradingTools(args: any): Promise<any>;
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Alpha Arcade trading tools: create_limit_order, create_market_order, cancel_order, amend_order, propose_match
3
+ */
4
+ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
5
+ import { extractNetwork } from '../../../algorand-client.js';
6
+ import { withCommonParams } from '../../commonParams.js';
7
+ import { createTradingClient, friendlyTradeError, formatPrice, formatQty, } from './alphaClient.js';
8
+ export const tradingTools = [
9
+ {
10
+ name: 'alpha_create_limit_order',
11
+ description: 'Place a limit order on an Alpha Arcade prediction market. Price and quantity in microunits (500000 = $0.50, 1000000 = 1 share). Locks ~0.957 ALGO collateral. Returns escrowAppId — save it for cancel_order.',
12
+ inputSchema: withCommonParams({
13
+ type: 'object',
14
+ properties: {
15
+ marketAppId: { type: 'number', description: 'The market app ID' },
16
+ position: { type: 'number', enum: [0, 1], description: '1 = Yes, 0 = No' },
17
+ price: { type: 'number', description: 'Price in microunits (e.g. 500000 = $0.50)' },
18
+ quantity: { type: 'number', description: 'Quantity in microunits (e.g. 1000000 = 1 share)' },
19
+ isBuying: { type: 'boolean', description: 'true = buy order, false = sell order' },
20
+ },
21
+ required: ['marketAppId', 'position', 'price', 'quantity', 'isBuying'],
22
+ }),
23
+ },
24
+ {
25
+ name: 'alpha_create_market_order',
26
+ description: 'Place a market order with auto-matching on Alpha Arcade. Price, quantity, and slippage in microunits. Returns escrowAppId, matched quantity, and actual fill price.',
27
+ inputSchema: withCommonParams({
28
+ type: 'object',
29
+ properties: {
30
+ marketAppId: { type: 'number', description: 'The market app ID' },
31
+ position: { type: 'number', enum: [0, 1], description: '1 = Yes, 0 = No' },
32
+ price: { type: 'number', description: 'Price in microunits (e.g. 500000 = $0.50)' },
33
+ quantity: { type: 'number', description: 'Quantity in microunits (e.g. 1000000 = 1 share)' },
34
+ isBuying: { type: 'boolean', description: 'true = buy order, false = sell order' },
35
+ slippage: { type: 'number', description: 'Slippage tolerance in microunits (e.g. 50000 = $0.05)' },
36
+ },
37
+ required: ['marketAppId', 'position', 'price', 'quantity', 'isBuying', 'slippage'],
38
+ }),
39
+ },
40
+ {
41
+ name: 'alpha_cancel_order',
42
+ description: 'Cancel an open Alpha Arcade order. Requires escrowAppId and orderOwner. Refunds USDC/tokens and ~0.957 ALGO collateral.',
43
+ inputSchema: withCommonParams({
44
+ type: 'object',
45
+ properties: {
46
+ marketAppId: { type: 'number', description: 'The market app ID' },
47
+ escrowAppId: { type: 'number', description: 'The escrow app ID of the order to cancel' },
48
+ orderOwner: { type: 'string', description: 'The Algorand address that owns the order' },
49
+ },
50
+ required: ['marketAppId', 'escrowAppId', 'orderOwner'],
51
+ }),
52
+ },
53
+ {
54
+ name: 'alpha_amend_order',
55
+ description: 'Edit an existing unfilled Alpha Arcade order in-place (change price, quantity, or slippage). Faster than cancel + recreate.',
56
+ inputSchema: withCommonParams({
57
+ type: 'object',
58
+ properties: {
59
+ marketAppId: { type: 'number', description: 'The market app ID' },
60
+ escrowAppId: { type: 'number', description: 'The escrow app ID of the order to amend' },
61
+ price: { type: 'number', description: 'New price in microunits' },
62
+ quantity: { type: 'number', description: 'New quantity in microunits' },
63
+ slippage: { type: 'number', description: 'New slippage in microunits (default 0)' },
64
+ },
65
+ required: ['marketAppId', 'escrowAppId', 'price', 'quantity'],
66
+ }),
67
+ },
68
+ {
69
+ name: 'alpha_propose_match',
70
+ description: 'Propose a match between an existing maker order and your wallet as taker on Alpha Arcade.',
71
+ inputSchema: withCommonParams({
72
+ type: 'object',
73
+ properties: {
74
+ marketAppId: { type: 'number', description: 'The market app ID' },
75
+ makerEscrowAppId: { type: 'number', description: 'The escrow app ID of the maker order' },
76
+ makerAddress: { type: 'string', description: 'The Algorand address of the maker' },
77
+ quantityMatched: { type: 'number', description: 'Quantity to match in microunits' },
78
+ },
79
+ required: ['marketAppId', 'makerEscrowAppId', 'makerAddress', 'quantityMatched'],
80
+ }),
81
+ },
82
+ ];
83
+ export async function handleTradingTools(args) {
84
+ const { name } = args;
85
+ const network = extractNetwork(args);
86
+ switch (name) {
87
+ case 'alpha_create_limit_order': {
88
+ const { marketAppId, position, price, quantity, isBuying } = args;
89
+ const safePrice = Math.max(0, Math.round(price));
90
+ const safeQty = Math.max(0, Math.round(quantity));
91
+ if (safePrice <= 0 || safePrice > 1000000) {
92
+ return { error: `Invalid price: ${price}. Price must be between 1 and 1000000 microunits ($0.000001–$1.00).` };
93
+ }
94
+ if (safeQty <= 0 || safeQty > 1000000000000) {
95
+ return { error: `Invalid quantity: ${quantity}. Quantity must be positive and reasonable.` };
96
+ }
97
+ try {
98
+ const client = await createTradingClient(network);
99
+ const result = await client.createLimitOrder({
100
+ marketAppId,
101
+ position: position,
102
+ price: safePrice,
103
+ quantity: safeQty,
104
+ isBuying,
105
+ });
106
+ const posLabel = position === 1 ? 'YES' : 'NO';
107
+ const sideLabel = isBuying ? 'BUY' : 'SELL';
108
+ return {
109
+ type: 'limit',
110
+ marketAppId,
111
+ escrowAppId: result.escrowAppId,
112
+ position: posLabel,
113
+ side: sideLabel,
114
+ price: formatPrice(price),
115
+ quantity: formatQty(quantity),
116
+ txIds: result.txIds,
117
+ confirmedRound: result.confirmedRound,
118
+ };
119
+ }
120
+ catch (error) {
121
+ return { error: `Error creating limit order: ${friendlyTradeError(error.message)}` };
122
+ }
123
+ }
124
+ case 'alpha_create_market_order': {
125
+ const { marketAppId, position, price, quantity, isBuying, slippage } = args;
126
+ const safePrice = Math.max(0, Math.round(price));
127
+ const safeQty = Math.max(0, Math.round(quantity));
128
+ const safeSlip = Math.max(0, Math.round(slippage));
129
+ if (safePrice <= 0 || safePrice > 1000000) {
130
+ return { error: `Invalid price: ${price}. Price must be between 1 and 1000000 microunits ($0.000001–$1.00).` };
131
+ }
132
+ if (safeQty <= 0 || safeQty > 1000000000000) {
133
+ return { error: `Invalid quantity: ${quantity}. Quantity must be positive and reasonable.` };
134
+ }
135
+ const estFund = Math.floor((safeQty * (safePrice + safeSlip)) / 1000000);
136
+ if (estFund < 0 || estFund > Number.MAX_SAFE_INTEGER) {
137
+ return { error: `Computed fund amount (${estFund}) would overflow. Reduce your trade amount.` };
138
+ }
139
+ try {
140
+ const client = await createTradingClient(network);
141
+ const result = await client.createMarketOrder({
142
+ marketAppId,
143
+ position: position,
144
+ price: safePrice,
145
+ quantity: safeQty,
146
+ isBuying,
147
+ slippage: safeSlip,
148
+ });
149
+ const posLabel = position === 1 ? 'YES' : 'NO';
150
+ const sideLabel = isBuying ? 'BUY' : 'SELL';
151
+ return {
152
+ type: 'market',
153
+ marketAppId,
154
+ escrowAppId: result.escrowAppId,
155
+ position: posLabel,
156
+ side: sideLabel,
157
+ price: formatPrice(price),
158
+ quantity: formatQty(quantity),
159
+ fillPrice: formatPrice(result.matchedPrice ?? 0),
160
+ matchedQuantity: formatQty(result.matchedQuantity ?? 0),
161
+ txIds: result.txIds,
162
+ confirmedRound: result.confirmedRound,
163
+ };
164
+ }
165
+ catch (error) {
166
+ return { error: `Error creating market order: ${friendlyTradeError(error.message)}` };
167
+ }
168
+ }
169
+ case 'alpha_cancel_order': {
170
+ const { marketAppId, escrowAppId, orderOwner } = args;
171
+ try {
172
+ const client = await createTradingClient(network);
173
+ const result = await client.cancelOrder({ marketAppId, escrowAppId, orderOwner });
174
+ const success = result.success !== false;
175
+ return {
176
+ type: 'cancel',
177
+ success,
178
+ marketAppId,
179
+ escrowAppId,
180
+ orderOwner,
181
+ txIds: result.txIds ?? [],
182
+ confirmedRound: result.confirmedRound ?? 0,
183
+ };
184
+ }
185
+ catch (error) {
186
+ return { error: `Error cancelling order: ${friendlyTradeError(error.message)}` };
187
+ }
188
+ }
189
+ case 'alpha_amend_order': {
190
+ const { marketAppId, escrowAppId, price, quantity, slippage } = args;
191
+ try {
192
+ const client = await createTradingClient(network);
193
+ const result = await client.amendOrder({
194
+ marketAppId,
195
+ escrowAppId,
196
+ price,
197
+ quantity,
198
+ slippage,
199
+ });
200
+ const success = result.success !== false;
201
+ return {
202
+ type: 'amend',
203
+ success,
204
+ marketAppId,
205
+ escrowAppId,
206
+ newPrice: formatPrice(price),
207
+ newQuantity: formatQty(quantity),
208
+ txIds: result.txIds ?? [],
209
+ confirmedRound: result.confirmedRound ?? 0,
210
+ };
211
+ }
212
+ catch (error) {
213
+ return { error: `Error amending order: ${friendlyTradeError(error.message)}` };
214
+ }
215
+ }
216
+ case 'alpha_propose_match': {
217
+ const { marketAppId, makerEscrowAppId, makerAddress, quantityMatched } = args;
218
+ try {
219
+ const client = await createTradingClient(network);
220
+ const result = await client.proposeMatch({
221
+ marketAppId,
222
+ makerEscrowAppId,
223
+ makerAddress,
224
+ quantityMatched,
225
+ });
226
+ const success = result.success !== false;
227
+ return {
228
+ type: 'match',
229
+ success,
230
+ marketAppId,
231
+ makerEscrowAppId,
232
+ makerAddress,
233
+ quantityMatched: formatQty(quantityMatched),
234
+ txIds: result.txIds ?? [],
235
+ confirmedRound: result.confirmedRound ?? 0,
236
+ };
237
+ }
238
+ catch (error) {
239
+ return { error: `Error proposing match: ${error.message || 'Unknown error'}` };
240
+ }
241
+ }
242
+ default:
243
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown Alpha trading tool: ${name}`);
244
+ }
245
+ }
@@ -6,6 +6,7 @@ import { tinymanTools, handleTinymanTools } from './tinyman/index.js';
6
6
  // import { ultradeTools, handleUltradeTools } from './ultrade/index.js';
7
7
  import { haystackTools, handleHaystackTools } from './hayrouter/index.js';
8
8
  import { peraTools, handlePeraTools } from './pera/index.js';
9
+ import { alphaTools, handleAlphaTools } from './alpha/index.js';
9
10
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
10
11
  import { ResponseProcessor } from '../../utils/responseProcessor.js';
11
12
  // Combine all API tools
@@ -15,7 +16,8 @@ export const apiManager = [
15
16
  ...nfdTools,
16
17
  ...tinymanTools,
17
18
  ...haystackTools,
18
- ...peraTools
19
+ ...peraTools,
20
+ ...alphaTools
19
21
  ];
20
22
  // Handle all API tools
21
23
  export async function handleApiManager(name, args) {
@@ -45,6 +47,10 @@ export async function handleApiManager(name, args) {
45
47
  else if (name.startsWith('api_pera_')) {
46
48
  response = await handlePeraTools(name, args);
47
49
  }
50
+ // Alpha Arcade tools
51
+ else if (name.startsWith('alpha_')) {
52
+ response = await handleAlphaTools(name, args);
53
+ }
48
54
  else {
49
55
  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
50
56
  }
@@ -86,7 +86,7 @@ export class UtilityManager {
86
86
  type: 'text',
87
87
  text: JSON.stringify({
88
88
  name: 'Algorand MCP Server',
89
- version: '3.7.0',
89
+ version: '3.9.0',
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).',
@@ -102,6 +102,7 @@ export class UtilityManager {
102
102
  'Tinyman DEX integration',
103
103
  'Vestige analytics integration',
104
104
  'Ultrade orderbook DEX integration',
105
+ 'Alpha Arcade prediction market integration',
105
106
  ],
106
107
  links: {
107
108
  repository: 'https://github.com/GoPlausible/algorand-mcp',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goplausible/algorand-mcp",
3
- "version": "3.7.0",
3
+ "version": "3.9.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -42,13 +42,14 @@
42
42
  "typescript": "^5.0.0"
43
43
  },
44
44
  "dependencies": {
45
+ "@alpha-arcade/sdk": "latest",
45
46
  "@cf-wasm/photon": "^0.1.31",
46
47
  "@juit/qrcode": "^1.0.59",
47
48
  "@modelcontextprotocol/sdk": "^1.27.1",
48
49
  "@napi-rs/keyring": "^1.2.0",
49
50
  "@noble/curves": "^2.0.1",
50
51
  "@tinymanorg/tinyman-js-sdk": "^5.1.2",
51
- "@txnlab/haystack-router":"^2.0.5",
52
+ "@txnlab/haystack-router": "^2.0.5",
52
53
  "algo-msgpack-with-bigint": "^2.1.1",
53
54
  "algosdk": "^3.5.2",
54
55
  "hi-base32": "^0.5.1",
@@ -57,4 +58,4 @@
57
58
  "sql.js": "^1.14.0",
58
59
  "zod": "^3.25.61"
59
60
  }
60
- }
61
+ }