@potswap_io/mcp 0.6.0 → 0.7.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
@@ -73,7 +73,8 @@ Use a dedicated agent wallet with only the funds you want the agent to control.
73
73
  | `get_token {mint}` | Price, mcap, liquidity, holders, audit data, **risk badges** (`riskLevel` 0 safe → 3 danger) |
74
74
  | `get_trending {kind}` | `new`, `trending`, `top`, `graduating` lists |
75
75
  | `get_quote {inputMint, outputMint, amount}` | Best route, fee, impact, router. `amount` is in UI units (1 = 1 SOL) |
76
- | `swap {…, confirm: true}` | Executes at the best route and returns the signature, amounts, fee and POT points |
76
+ | `swap {…, confirm: true, route?, opId?}` | Executes at the best route and returns the signature, amounts, fee and POT points. `route: direct` swaps on a Raydium CPMM pool built and validated locally (same fee); `opId` makes retries safe |
77
+ | `get_operation {id?}` | State of an idempotent swap (pending / sent / ok / failed / unknown) |
77
78
  | `get_balances {wallet?}` | SOL + SPL balances with USD |
78
79
  | `get_positions {wallet?}` | Holdings with USD value, average cost, unrealised and realised PnL (from swap history) |
79
80
  | `get_points {wallet?}` | Season 1 points, POT allocated / claimable, next unlock |
package/dist/server.d.ts CHANGED
@@ -5,9 +5,10 @@ import { type BalancesFn } from '@potswap_io/sdk';
5
5
  import { type Signer } from '@potswap_io/sdk';
6
6
  import { type TxRpc } from './agentTools.js';
7
7
  import type { Rpc } from '@potswap_io/sdk';
8
+ import { type OpsStore } from '@potswap_io/sdk';
8
9
  import type { StrategyStore } from '@potswap_io/sdk';
9
10
  import type { Scheduler } from '@potswap_io/sdk';
10
- export declare const VERSION = "0.6.0";
11
+ export declare const VERSION = "0.7.0";
11
12
  export interface ServerDeps {
12
13
  config?: Config;
13
14
  signer?: Signer | null;
@@ -22,6 +23,8 @@ export interface ServerDeps {
22
23
  txRpc?: TxRpc;
23
24
  /** Current slot for observation anchoring; injectable for tests (P15). */
24
25
  getSlot?: () => Promise<number>;
26
+ /** Idempotent operation records (P14.3); defaults to a file store in the state dir. */
27
+ ops?: OpsStore;
25
28
  }
26
29
  export declare function createServer(deps?: ServerDeps): McpServer;
27
30
  export { SOL_MINT, USDC_MINT };
package/dist/server.js CHANGED
@@ -11,7 +11,8 @@ import { riskBadges, riskLevel } from '@potswap_io/sdk';
11
11
  import { registerStrategyTools } from './strategies/tools.js';
12
12
  import { registerAgentTools } from './agentTools.js';
13
13
  import { registerObservationTools } from './observationTools.js';
14
- export const VERSION = '0.6.0';
14
+ import { FileOpsStore, rpcFromConnection } from '@potswap_io/sdk';
15
+ export const VERSION = '0.7.0';
15
16
  const ok = (data, summary) => ({
16
17
  content: [{ type: 'text', text: redact(summary ?? JSON.stringify(data, null, 2)) }], structuredContent: redactValue(data),
17
18
  });
@@ -23,7 +24,7 @@ export function createServer(deps = {}) {
23
24
  const signer = deps.signer !== undefined ? deps.signer : loadSigner({ privateKey: config.privateKey, keypairPath: config.keypairPath, stateDir: config.stateDir });
24
25
  const api = new PotswapApi(config.apiBase, deps.fetchFn);
25
26
  const readBalances = deps.balances ?? rpcBalances(config.rpcUrl);
26
- const trade = { api, config, signer, balances: readBalances };
27
+ const trade = { api, config, signer, balances: readBalances, rpc: deps.rpc ?? rpcFromConnection(new Connection(config.rpcUrl, 'confirmed')), ops: deps.ops ?? new FileOpsStore(config.stateDir) };
27
28
  const server = new McpServer({ name: 'potswap', version: VERSION });
28
29
  const walletOr = (w) => w ?? signer?.publicKey ?? null;
29
30
  server.registerTool('search_tokens', {
@@ -70,12 +71,12 @@ export function createServer(deps = {}) {
70
71
  });
71
72
  server.registerTool('get_quote', {
72
73
  title: 'Get a swap quote',
73
- description: 'Best route across Metis, JupiterZ, DFlow and OKX. Amount is in UI units of the input token (1 = 1 SOL). Includes the Potswap fee (returned to the wallet as POT points).',
74
+ description: 'Best route across Metis, JupiterZ, DFlow and OKX, plus `direct` when a Raydium CPMM pool exists (same Potswap fee, compared net). Amount is in UI units of the input token (1 = 1 SOL). Includes the Potswap fee (returned to the wallet as POT points).',
74
75
  inputSchema: quoteShape,
75
76
  }, async (a) => {
76
77
  try {
77
- const q = await buildQuote(api, a);
78
- return ok({ ...q }, `${a.amount} ${q.inputSymbol} → ${q.outAmount} ${q.outputSymbol} via ${q.router} (fee ${q.feeLabel ?? `${q.feeBps} bps`}, impact ${q.priceImpactPct}%)`);
78
+ const q = await buildQuote(api, a, { direct: trade.rpc });
79
+ return ok({ ...q }, `${a.amount} ${q.inputSymbol} → ${q.outAmount} ${q.outputSymbol} via ${q.router} (fee ${q.feeLabel ?? `${q.feeBps} bps`}, impact ${q.priceImpactPct}%)${q.direct ? `\nDirect Raydium CPMM: ${q.direct.outAmount} ${q.outputSymbol} (${q.direct.savedVsAggregatorPct >= 0 ? '+' : ''}${q.direct.savedVsAggregatorPct.toFixed(2)}% vs aggregator, pool fee ${q.direct.poolTradeFeeBps} bps)` : ''}`);
79
80
  }
80
81
  catch (e) {
81
82
  return fail(msg(e));
@@ -83,19 +84,33 @@ export function createServer(deps = {}) {
83
84
  });
84
85
  server.registerTool('swap', {
85
86
  title: 'Swap tokens (signs with the agent wallet)',
86
- description: 'Executes a swap at the best route. Requires confirm=true; otherwise returns the quote only. Protected by POTSWAP_MAX_TRADE_SOL and POTSWAP_MAX_SLIPPAGE_BPS. Every error states whether funds were debited.',
87
- inputSchema: { ...quoteShape, confirm: z.boolean().default(false), maxSlippageBps: z.number().int().min(1).max(5000).optional() },
87
+ description: 'Executes a swap at the best route. Requires confirm=true; otherwise returns the quote only. Protected by POTSWAP_MAX_TRADE_SOL, POTSWAP_MAX_SLIPPAGE_BPS and the transaction validator. route=direct uses a Raydium CPMM pool built locally (same fee); route=auto picks the better net output. opId makes the call safe to retry (a repeat returns the stored result). Every error states whether funds were debited.',
88
+ inputSchema: { ...quoteShape, confirm: z.boolean().default(false), maxSlippageBps: z.number().int().min(1).max(5000).optional(), route: z.enum(['jupiter', 'direct', 'auto']).default('jupiter'), opId: z.string().min(4).max(64).optional() },
88
89
  }, async (a) => {
89
- const out = await executeSwap(trade, { inputMint: a.inputMint, outputMint: a.outputMint, amount: a.amount, slippageBps: a.slippageBps ?? a.maxSlippageBps, confirm: a.confirm });
90
+ const out = await executeSwap(trade, { inputMint: a.inputMint, outputMint: a.outputMint, amount: a.amount, slippageBps: a.slippageBps ?? a.maxSlippageBps, confirm: a.confirm, route: a.route, opId: a.opId });
90
91
  switch (out.kind) {
91
92
  case 'no-wallet': return fail(out.message);
92
93
  case 'refused': return fail(out.message, { quote: out.quote });
93
94
  case 'quote-only': return ok({ confirmRequired: true, quote: out.quote }, `Quote only (confirm=true to execute): ${a.amount} ${out.quote.inputSymbol} → ${out.quote.outAmount} ${out.quote.outputSymbol} via ${out.quote.router}`);
94
95
  case 'dry-run': return ok({ dryRun: true, quote: out.quote }, `DRY RUN, nothing signed: ${a.amount} ${out.quote.inputSymbol} → ${out.quote.outAmount} ${out.quote.outputSymbol}`);
95
96
  case 'failed': return fail(out.message, { signature: out.signature, explorerUrl: out.explorerUrl, quote: out.quote });
96
- case 'ok': return ok({ ...out.result }, `Swapped ${a.amount} ${out.result.inputSymbol} → ${out.result.outAmount} ${out.result.outputSymbol} via ${out.result.router}. ${out.result.explorerUrl}`);
97
+ case 'ok': return ok({ ...out.result }, `${out.result.replayed ? 'Already done (replayed): ' : 'Swapped '}${a.amount} ${out.result.inputSymbol} → ${out.result.outAmount} ${out.result.outputSymbol} via ${out.result.router}. ${out.result.explorerUrl}`);
97
98
  }
98
99
  });
100
+ server.registerTool('get_operation', {
101
+ title: 'Operation record',
102
+ description: 'State of an idempotent swap by opId: pending, sent, ok, failed or unknown, with the signature and result when known. Lists recent operations without an id.',
103
+ inputSchema: { id: z.string().min(4).max(64).optional(), limit: z.number().int().min(1).max(100).default(20) },
104
+ }, async ({ id, limit }) => {
105
+ if (!trade.ops)
106
+ return fail('No operation store configured.');
107
+ if (id) {
108
+ const r = trade.ops.get(id);
109
+ return r ? ok({ ...r }, `${r.id}: ${r.state}${r.signature ? ` · ${r.signature}` : ''}${r.error ? ` · ${r.error}` : ''}`) : fail(`No operation ${id}.`);
110
+ }
111
+ const rows = trade.ops.list(limit);
112
+ return ok({ operations: rows }, rows.map((r) => `${r.updatedAt} ${r.id} ${r.state}${r.signature ? ` ${r.signature.slice(0, 8)}…` : ''}`).join('\n') || 'No operations yet.');
113
+ });
99
114
  server.registerTool('get_balances', {
100
115
  title: 'Wallet balances',
101
116
  description: 'SOL and SPL token balances with USD values. Defaults to the agent wallet.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@potswap_io/mcp",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Potswap MCP server: best-route Solana swaps, wallet, transfers, staking, DCA / rebalance / take-profit / limit strategies and POT rewards for AI agents. Non-custodial; built on @potswap_io/sdk.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -40,7 +40,7 @@
40
40
  ],
41
41
  "dependencies": {
42
42
  "@modelcontextprotocol/sdk": "^1.32.2",
43
- "@potswap_io/sdk": "^0.2.0",
43
+ "@potswap_io/sdk": "^0.3.0",
44
44
  "@solana/web3.js": "^1.99.0",
45
45
  "zod": "^4.6.7"
46
46
  },