opindex-agent 0.2.0 → 0.2.1

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/mcp/server.mjs CHANGED
@@ -33,6 +33,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
33
33
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
34
34
  import { z } from 'zod';
35
35
  import { createHash } from 'node:crypto';
36
+ import { readFileSync } from 'node:fs';
36
37
  import { PublicKey, VersionedTransaction } from '@solana/web3.js';
37
38
  import {
38
39
  createAssociatedTokenAccountIdempotentInstruction,
@@ -47,6 +48,16 @@ import { wallet } from '../src/wallet.mjs';
47
48
  import { connection, returnAddress, rpcSource } from '../src/returns.mjs';
48
49
 
49
50
  const USDC = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
51
+
52
+ /**
53
+ * The version that is actually running, read from the package that shipped it.
54
+ *
55
+ * Hosts install this with `npx` or with `npm install -g`, and the second never
56
+ * updates itself. A stale client looks exactly like a current one from inside
57
+ * a conversation -- fewer tools, older descriptions -- so `agent_wallet` says
58
+ * which one this is, and the person can compare it with npm's latest.
59
+ */
60
+ const VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
50
61
  const PUBLIC_RPC = 'https://api.mainnet-beta.solana.com';
51
62
 
52
63
  const mint = z.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/, 'a base58 Solana mint address');
@@ -98,7 +109,7 @@ async function paid(tool, args, { method, body, query } = {}) {
98
109
  return { status: response.status, body: parsed, paid: response.headers.has('payment-response') };
99
110
  }
100
111
 
101
- const server = new McpServer({ name: 'opindex', version: '0.2.0' });
112
+ const server = new McpServer({ name: 'opindex', version: VERSION });
102
113
 
103
114
  // ---------------------------------------------------------------- free tools
104
115
 
@@ -149,6 +160,10 @@ server.registerTool(
149
160
  ? `derived from the chain: the first USDC transfer in, ${ret.signature}`
150
161
  : ret.why,
151
162
  rpc: rpcSource(),
163
+ client_version: VERSION,
164
+ client_update:
165
+ 'Compare client_version with `npm view opindex-agent version`. Installed globally (command `opindex-mcp`), ' +
166
+ 'it never updates itself: `npm install -g opindex-agent@latest`, then restart the host.',
152
167
  note:
153
168
  'The agent needs no SOL to pay for answers — the service\'s facilitator pays that network fee. ' +
154
169
  'SOL is for the swaps the agent signs itself.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opindex-agent",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "An agent's Solana wallet, token risk answers paid for over x402, and swaps it signs itself. Library plus an MCP server.",
5
5
  "license": "MIT",
6
6
  "author": "MONOLITH LABS LTD <info@monolithlabs.uk>",
@@ -41,7 +41,7 @@ const call = (method, params) =>
41
41
  const checks = [];
42
42
  const check = (name, ok, saw) => checks.push({ name, ok, saw });
43
43
 
44
- await call('initialize', {
44
+ const init = await call('initialize', {
45
45
  protocolVersion: '2024-11-05',
46
46
  capabilities: {},
47
47
  clientInfo: { name: 'invariants', version: '0' },
@@ -49,6 +49,16 @@ await call('initialize', {
49
49
  child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' })}\n`);
50
50
 
51
51
  const { result } = await call('tools/list', {});
52
+
53
+ // The version a person reads to tell a stale client from a current one has to
54
+ // be the one npm shipped. It is read from package.json at start, so this fails
55
+ // only if someone writes a version into the server by hand again.
56
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
57
+ check(
58
+ 'the server reports the package version',
59
+ init.result?.serverInfo?.version === pkg.version,
60
+ `${init.result?.serverInfo?.version} vs ${pkg.version}`,
61
+ );
52
62
  const tools = result?.tools ?? [];
53
63
  const byName = new Map(tools.map((t) => [t.name, t]));
54
64
  const params = (name) => Object.keys(byName.get(name)?.inputSchema?.properties ?? {});
@@ -165,7 +175,6 @@ check(
165
175
  // starts with `./` — and says so in a warning that prints the normalised path,
166
176
  // which is the valid form, so the message points at a string that is fine.
167
177
  // That cost one failed publish; it costs nothing to pin.
168
- const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
169
178
  const bins = Object.entries(pkg.bin ?? {});
170
179
  check('the package declares an executable', bins.length > 0, JSON.stringify(pkg.bin));
171
180
  for (const [name, target] of bins) {