@potswap_io/mcp 0.5.0 → 0.6.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
@@ -78,6 +78,7 @@ Use a dedicated agent wallet with only the funds you want the agent to control.
78
78
  | `get_positions {wallet?}` | Holdings with USD value, average cost, unrealised and realised PnL (from swap history) |
79
79
  | `get_points {wallet?}` | Season 1 points, POT allocated / claimable, next unlock |
80
80
  | `get_leaderboard` | Top wallets by fee paid |
81
+ | `observe_publish`, `observe_query`, `observe_verify`, `observe_consume` | Signed, slot-anchored market observations on the open index: publish with your wallet, read everyone's with a per-publisher median, verify locally, record that you used one (earns the publisher Season 2 rewards). `limit_order_create` can trigger on the observation median (`priceSource: observations`) |
81
82
  | `get_launch_status` | Launch-pool meter (SOL / 500 target, from chain), season and day, fee split, unclaimed reward balances and the public claim ladder |
82
83
  | `get_price_history {mint, timeframe, limit}` | OHLCV candles from the token's top pool (`1m`…`1d`) |
83
84
 
@@ -0,0 +1,8 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { type Config, type TradeContext } from '@potswap_io/sdk';
3
+ export interface ObservationToolDeps {
4
+ trade: TradeContext;
5
+ config: Config;
6
+ getSlot?: () => Promise<number>;
7
+ }
8
+ export declare function registerObservationTools(server: McpServer, deps: ObservationToolDeps): void;
@@ -0,0 +1,72 @@
1
+ import { Connection } from '@solana/web3.js';
2
+ import { z } from 'zod';
3
+ import { NO_WALLET, OBSERVATION_KINDS, aggregate, redact, redactValue, signConsumption, signObservation, verifyObservation } from '@potswap_io/sdk';
4
+ const ok = (data, summary) => ({ content: [{ type: 'text', text: redact(summary ?? JSON.stringify(data, null, 2)) }], structuredContent: redactValue(data) });
5
+ const fail = (message, extra = {}) => ({ content: [{ type: 'text', text: redact(message) }], structuredContent: redactValue({ error: message, ...extra }), isError: true });
6
+ const MINT = z.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/);
7
+ const KIND = z.enum(OBSERVATION_KINDS);
8
+ export function registerObservationTools(server, deps) {
9
+ const { trade, config } = deps;
10
+ const api = trade.api;
11
+ const getSlot = deps.getSlot ?? (() => new Connection(config.rpcUrl, 'confirmed').getSlot());
12
+ server.registerTool('observe_publish', {
13
+ title: 'Publish a signed observation',
14
+ description: 'Signs a market observation (price, liquidity, volume24h, holders, risk, route) with the agent wallet, anchored to the current slot, and posts it to the open Potswap index. Free; no funds move. Other agents can verify and consume it; verified consumption earns the publisher Season 2 rewards.',
15
+ inputSchema: { kind: KIND, mint: MINT, value: z.number().finite(), unit: z.string().min(1).max(16).optional(), source: z.string().min(1).max(64), ttlSec: z.number().int().min(10).max(86400).default(300), note: z.string().max(280).optional() },
16
+ }, async (a) => {
17
+ if (!trade.signer)
18
+ return fail(NO_WALLET);
19
+ try {
20
+ const slot = await getSlot();
21
+ const obs = signObservation(trade.signer, { ...a, slot });
22
+ const r = await api.publishObservation(obs);
23
+ return ok({ ...obs, ...r }, `Published ${a.kind} for ${a.mint.slice(0, 4)}… = ${a.value} ${obs.unit} (id ${obs.id}, slot ${slot}, ttl ${obs.ttlSec}s)${r.duplicate ? ' — already indexed' : ''}.`);
24
+ }
25
+ catch (e) {
26
+ return fail(e instanceof Error ? e.message : String(e));
27
+ }
28
+ });
29
+ server.registerTool('observe_query', {
30
+ title: 'Query observations',
31
+ description: 'Fresh observations from the open index, newest first, with a per-publisher median aggregate when mint and kind are given. Free, no wallet needed.',
32
+ inputSchema: { mint: MINT.optional(), kind: KIND.optional(), publisher: MINT.optional(), limit: z.number().int().min(1).max(200).default(50), includeStale: z.boolean().default(false) },
33
+ }, async (a) => {
34
+ try {
35
+ const r = await api.observations({ mint: a.mint, kind: a.kind, publisher: a.publisher, limit: a.limit, fresh: !a.includeStale });
36
+ const agg = a.mint && a.kind ? aggregate(r.items, a.kind, a.mint, r.now ?? Date.now()) : undefined;
37
+ return ok({ ...r, ...(agg ? { aggregate: agg } : {}) }, agg
38
+ ? `${agg.count} fresh ${a.kind} observation(s) from ${agg.publishers} publisher(s)${agg.median !== null ? ` · median ${agg.median} · spread ${agg.spreadPct?.toFixed(2)}%` : ''}`
39
+ : `${r.items.length} observation(s)` + (r.items.length ? `\n` + r.items.slice(0, 10).map((o) => `${new Date(o.observedAt).toISOString()} ${o.kind} ${o.mint.slice(0, 4)}… = ${o.value} ${o.unit} by ${o.publisher.slice(0, 4)}… (${o.source})`).join('\n') : ''));
40
+ }
41
+ catch (e) {
42
+ return fail(e instanceof Error ? e.message : String(e));
43
+ }
44
+ });
45
+ server.registerTool('observe_verify', {
46
+ title: 'Verify an observation',
47
+ description: 'Checks an observation locally: schema, id, Ed25519 signature by its publisher, freshness; for price observations also the deviation from the current Potswap price.',
48
+ inputSchema: { observation: z.record(z.string(), z.unknown()) },
49
+ }, async ({ observation }) => {
50
+ const o = observation;
51
+ let referencePrice = null;
52
+ if (o.kind === 'price' && o.mint)
53
+ referencePrice = (await api.prices([o.mint]).catch(() => ({})))[o.mint] ?? null;
54
+ const v = verifyObservation(observation, { referencePrice });
55
+ return ok({ ...v, referencePrice }, v.ok ? `Valid${v.fresh ? ', fresh' : ', expired'}${v.deviationPct !== undefined && v.deviationPct !== null ? ` · ${v.deviationPct >= 0 ? '+' : ''}${v.deviationPct.toFixed(2)}% vs current price` : ''}.` : `Invalid: ${v.reason}`);
56
+ });
57
+ server.registerTool('observe_consume', {
58
+ title: 'Record that you used an observation',
59
+ description: 'Signs a receipt with the agent wallet saying this observation informed a decision. Receipts weight the publisher\'s Season 2 rewards. Free.',
60
+ inputSchema: { id: z.string().regex(/^[0-9a-f]{32}$/) },
61
+ }, async ({ id }) => {
62
+ if (!trade.signer)
63
+ return fail(NO_WALLET);
64
+ try {
65
+ const r = await api.consumeObservation(signConsumption(trade.signer, id));
66
+ return ok(r, `Receipt recorded for ${id} (publisher ${String(r.publisher).slice(0, 4)}…)${r.duplicate ? ' — already recorded' : ''}.`);
67
+ }
68
+ catch (e) {
69
+ return fail(e instanceof Error ? e.message : String(e));
70
+ }
71
+ });
72
+ }
package/dist/server.d.ts CHANGED
@@ -7,7 +7,7 @@ import { type TxRpc } from './agentTools.js';
7
7
  import type { Rpc } from '@potswap_io/sdk';
8
8
  import type { StrategyStore } from '@potswap_io/sdk';
9
9
  import type { Scheduler } from '@potswap_io/sdk';
10
- export declare const VERSION = "0.5.0";
10
+ export declare const VERSION = "0.6.0";
11
11
  export interface ServerDeps {
12
12
  config?: Config;
13
13
  signer?: Signer | null;
@@ -20,6 +20,8 @@ export interface ServerDeps {
20
20
  /** Transfer / transaction RPC surfaces; injectable for tests (P13). */
21
21
  rpc?: Rpc;
22
22
  txRpc?: TxRpc;
23
+ /** Current slot for observation anchoring; injectable for tests (P15). */
24
+ getSlot?: () => Promise<number>;
23
25
  }
24
26
  export declare function createServer(deps?: ServerDeps): McpServer;
25
27
  export { SOL_MINT, USDC_MINT };
package/dist/server.js CHANGED
@@ -10,7 +10,8 @@ import { loadSigner } from '@potswap_io/sdk';
10
10
  import { riskBadges, riskLevel } from '@potswap_io/sdk';
11
11
  import { registerStrategyTools } from './strategies/tools.js';
12
12
  import { registerAgentTools } from './agentTools.js';
13
- export const VERSION = '0.5.0';
13
+ import { registerObservationTools } from './observationTools.js';
14
+ export const VERSION = '0.6.0';
14
15
  const ok = (data, summary) => ({
15
16
  content: [{ type: 'text', text: redact(summary ?? JSON.stringify(data, null, 2)) }], structuredContent: redactValue(data),
16
17
  });
@@ -148,6 +149,7 @@ export function createServer(deps = {}) {
148
149
  });
149
150
  registerAgentTools(server, { trade, config, rpc: deps.rpc, txRpc: deps.txRpc, schedulerActive: () => !!deps.scheduler?.active });
150
151
  registerStrategyTools(server, { trade, store: deps.strategyStore, scheduler: deps.scheduler });
152
+ registerObservationTools(server, { trade, config, getSlot: deps.getSlot });
151
153
  return server;
152
154
  }
153
155
  const quoteShape = {
@@ -112,12 +112,14 @@ export function registerStrategyTools(server, deps) {
112
112
  });
113
113
  server.registerTool('limit_order_create', {
114
114
  title: 'Create a limit order (price-triggered swap)',
115
- description: 'Buy or sell a token when its USD price crosses a level. Runs through the strategy scheduler (needs this host open or the daemon). Fills once at the best route with the normal fee and points, then completes.',
115
+ description: 'Buy or sell a token when its USD price crosses a level. Runs through the strategy scheduler (needs this host open or the daemon). Fills once at the best route with the normal fee and points, then completes. priceSource=observations uses the median of fresh signed observations instead of the price API.',
116
116
  inputSchema: {
117
117
  side: z.enum(['buy', 'sell']), mint: MINT, quoteMint: MINT.default(SOL_MINT),
118
118
  amount: z.number().positive().describe('Input amount in UI units: quote (SOL) amount for buys, token amount for sells'),
119
119
  triggerPriceUsd: z.number().positive(), condition: z.enum(['above', 'below']),
120
120
  expiresAt: z.string().datetime().optional(), maxSlippageBps: z.number().int().min(1).max(5000).optional(),
121
+ priceSource: z.enum(['potswap', 'observations']).default('potswap').describe('observations = median of fresh signed observations from at least minPublishers publishers (PRD P15)'),
122
+ minPublishers: z.number().int().min(1).max(50).default(2),
121
123
  confirm: z.boolean().default(false),
122
124
  },
123
125
  }, async (a) => {
@@ -126,7 +128,7 @@ export function registerStrategyTools(server, deps) {
126
128
  return g;
127
129
  if (a.mint === a.quoteMint)
128
130
  return fail('mint and quoteMint must differ. Nothing was created.');
129
- const s = { ...base('limit', a.maxSlippageBps), kind: 'limit', side: a.side, mint: a.mint, quoteMint: a.quoteMint, amount: a.amount, triggerPriceUsd: a.triggerPriceUsd, condition: a.condition, expiresAt: a.expiresAt, nextRunAt: null };
131
+ const s = { ...base('limit', a.maxSlippageBps), kind: 'limit', side: a.side, mint: a.mint, quoteMint: a.quoteMint, amount: a.amount, triggerPriceUsd: a.triggerPriceUsd, condition: a.condition, expiresAt: a.expiresAt, nextRunAt: null, ...(a.priceSource === 'observations' ? { priceSource: 'observations', minPublishers: a.minPublishers } : {}) };
130
132
  return created(s, `Limit ${a.side} created: ${a.amount} ${a.side === 'buy' ? 'quote' : 'tokens'} when price ${a.condition} $${a.triggerPriceUsd}${a.expiresAt ? ` until ${a.expiresAt}` : ''}.`);
131
133
  });
132
134
  server.registerTool('strategy_list', { title: 'List strategies', description: 'All strategies with status, next run, totals and last result.', inputSchema: {} }, async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@potswap_io/mcp",
3
- "version": "0.5.0",
3
+ "version": "0.6.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.1.0",
43
+ "@potswap_io/sdk": "^0.2.0",
44
44
  "@solana/web3.js": "^1.99.0",
45
45
  "zod": "^4.6.7"
46
46
  },