pion-mcp 0.0.1 → 0.1.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/README.md CHANGED
@@ -1,21 +1,84 @@
1
1
  # Pion
2
2
 
3
3
  **Model Context Protocol server for Pi Network** — connect AI agents
4
- (Claude, Cursor, and any MCP-compatible client) to the Pi Network SDK.
4
+ (Claude, Cursor, and any MCP-compatible client) to Pi Network chain data.
5
5
 
6
- > ⚠️ Early development. Not yet functional. Testnet only.
6
+ > ⚠️ v0.1 testnet, read-only.
7
7
 
8
8
  ## Why "Pion"?
9
9
  The pion is the π meson — the particle physicists named after pi.
10
10
  Fittingly, particle physicists study pion interactions to search
11
11
  for MCPs (millicharged particles). We couldn't resist.
12
12
 
13
- ## Planned tools
14
- - `get_wallet_balance` — read-only wallet queries
15
- - `create_payment_request` — Pi payment flows for agents
16
- - `query_transaction` — chain data lookup
13
+ ## Tools
17
14
 
18
- ## Status
19
- Working. Watch/star to follow along.
15
+ All three are zero-permission reads against Pi's public Horizon API
16
+ (Tier A in [`docs/tool-mapping.md`](docs/tool-mapping.md)). **No API keys, no
17
+ wallet secrets, no user consent** — and nothing here can move value.
18
+
19
+ | Tool | What it does |
20
+ |---|---|
21
+ | `get_wallet_balance` | Pi and custom-token balances for a wallet address |
22
+ | `get_account_payments` | Paginated payment history for an address |
23
+ | `query_transaction` | Verify a single transaction by hash |
24
+
25
+ Amounts are decimal strings. Pi is reported as the asset `PI`, custom tokens as
26
+ `CODE:ISSUER`, and liquidity-pool shares as `pool:ID`.
27
+
28
+ ## Usage
29
+
30
+ MCP clients can run it straight from npm — no install step:
31
+
32
+ ```jsonc
33
+ // Claude Desktop: claude_desktop_config.json
34
+ {
35
+ "mcpServers": {
36
+ "pion": {
37
+ "command": "npx",
38
+ "args": ["-y", "pion-mcp"]
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ ```sh
45
+ # Claude Code
46
+ claude mcp add pion -- npx -y pion-mcp
47
+ ```
48
+
49
+ Or run it from a clone:
50
+
51
+ ```sh
52
+ npm install
53
+ npm run build
54
+ claude mcp add pion -- node /absolute/path/to/pion-mcp/dist/index.js
55
+ ```
56
+
57
+ ### Configuration
58
+
59
+ | Variable | Default | Purpose |
60
+ |---|---|---|
61
+ | `PION_HORIZON_URL` | `https://api.testnet.minepi.com` | Horizon base URL |
62
+
63
+ There are no secrets to configure. The mainnet Horizon URL is still an open
64
+ question — see the TODO in [`docs/pi-sdk-notes.md`](docs/pi-sdk-notes.md).
65
+
66
+ ## Development
67
+
68
+ ```sh
69
+ npm run build # compile src/ -> dist/
70
+ npm run typecheck # types only, no emit
71
+ npm run smoke # end-to-end: drives the built server against live testnet
72
+ ```
73
+
74
+ `npm run smoke` spawns the server over stdio as a real MCP client, discovers a
75
+ funded account from the current ledger, and exercises all three tools plus the
76
+ not-found and invalid-input paths. It needs network access.
77
+
78
+ ## Roadmap
79
+
80
+ v0.2 adds Tier B/C behind env config (`PI_SERVER_API_KEY`, `PI_WALLET_SECRET`):
81
+ user verification and App-to-User payments, testnet-default with explicit
82
+ opt-in for anything that moves value. See [`docs/tool-mapping.md`](docs/tool-mapping.md).
20
83
 
21
84
  *Unofficial community project — not affiliated with Pi Network.*
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Minimal read-only client for Pi's Horizon (Stellar) API.
3
+ *
4
+ * Every endpoint used here is public and unauthenticated — no API key, no
5
+ * bearer token, no wallet secret. See docs/pi-sdk-notes.md, "Layer 3".
6
+ */
7
+ /** Horizon base URL. Defaults to Pi testnet; override with PION_HORIZON_URL. */
8
+ export declare const HORIZON_URL: string;
9
+ /** A Horizon request that failed — network, timeout, or non-2xx response. */
10
+ export declare class HorizonError extends Error {
11
+ readonly status?: number | undefined;
12
+ constructor(message: string, status?: number | undefined);
13
+ }
14
+ type QueryParams = Record<string, string | number | undefined>;
15
+ export declare function horizonGet<T>(path: string, params?: QueryParams): Promise<T>;
16
+ /** Renders a Horizon asset triplet as "PI" or "CODE:ISSUER". */
17
+ export declare function formatAsset(assetType: string | undefined, code: string | undefined, issuer: string | undefined): string;
18
+ /** Extracts Horizon's paging cursor from a `_links.next.href` value. */
19
+ export declare function cursorFromLink(href: string | undefined): string | undefined;
20
+ export {};
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Minimal read-only client for Pi's Horizon (Stellar) API.
3
+ *
4
+ * Every endpoint used here is public and unauthenticated — no API key, no
5
+ * bearer token, no wallet secret. See docs/pi-sdk-notes.md, "Layer 3".
6
+ */
7
+ const DEFAULT_HORIZON_URL = "https://api.testnet.minepi.com";
8
+ const REQUEST_TIMEOUT_MS = 15_000;
9
+ /** Horizon base URL. Defaults to Pi testnet; override with PION_HORIZON_URL. */
10
+ export const HORIZON_URL = (process.env.PION_HORIZON_URL ?? DEFAULT_HORIZON_URL).replace(/\/+$/, "");
11
+ /** A Horizon request that failed — network, timeout, or non-2xx response. */
12
+ export class HorizonError extends Error {
13
+ status;
14
+ constructor(message, status) {
15
+ super(message);
16
+ this.status = status;
17
+ this.name = "HorizonError";
18
+ }
19
+ }
20
+ export async function horizonGet(path, params) {
21
+ const url = new URL(HORIZON_URL + path);
22
+ for (const [key, value] of Object.entries(params ?? {})) {
23
+ if (value !== undefined)
24
+ url.searchParams.set(key, String(value));
25
+ }
26
+ const controller = new AbortController();
27
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
28
+ let response;
29
+ try {
30
+ response = await fetch(url, {
31
+ headers: { accept: "application/json" },
32
+ signal: controller.signal,
33
+ });
34
+ }
35
+ catch (err) {
36
+ if (controller.signal.aborted) {
37
+ throw new HorizonError(`Horizon request timed out after ${REQUEST_TIMEOUT_MS}ms: ${path}`);
38
+ }
39
+ throw new HorizonError(`Could not reach Horizon at ${HORIZON_URL}: ${err.message}`);
40
+ }
41
+ finally {
42
+ clearTimeout(timer);
43
+ }
44
+ if (!response.ok) {
45
+ throw new HorizonError(await describeFailure(response, path), response.status);
46
+ }
47
+ return (await response.json());
48
+ }
49
+ async function describeFailure(response, path) {
50
+ let problem = {};
51
+ try {
52
+ problem = (await response.json());
53
+ }
54
+ catch {
55
+ // Non-JSON error body — fall through to the generic message.
56
+ }
57
+ if (response.status === 404) {
58
+ return `Not found on Horizon (${HORIZON_URL}${path}). The account or transaction does not exist on this network, or has never been funded.`;
59
+ }
60
+ const parts = [problem.title, problem.detail, problem.extras?.reason].filter(Boolean);
61
+ return parts.length > 0
62
+ ? `Horizon returned ${response.status}: ${parts.join(" — ")}`
63
+ : `Horizon returned ${response.status} ${response.statusText} for ${path}`;
64
+ }
65
+ /** Renders a Horizon asset triplet as "PI" or "CODE:ISSUER". */
66
+ export function formatAsset(assetType, code, issuer) {
67
+ if (!assetType || assetType === "native")
68
+ return "PI";
69
+ return issuer ? `${code}:${issuer}` : (code ?? assetType);
70
+ }
71
+ /** Extracts Horizon's paging cursor from a `_links.next.href` value. */
72
+ export function cursorFromLink(href) {
73
+ if (!href)
74
+ return undefined;
75
+ try {
76
+ return new URL(href, HORIZON_URL).searchParams.get("cursor") ?? undefined;
77
+ }
78
+ catch {
79
+ return undefined;
80
+ }
81
+ }
82
+ //# sourceMappingURL=horizon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"horizon.js","sourceRoot":"","sources":["../src/horizon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Pion — MCP server for Pi Network.
4
+ *
5
+ * v0.1 scope: Tier A only (see docs/tool-mapping.md) — zero-permission,
6
+ * read-only chain queries against Pi's public Horizon API. No API keys, no
7
+ * wallet secrets, no user consent required, and nothing here can move value.
8
+ */
9
+ import { createRequire } from "node:module";
10
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
+ import { HORIZON_URL } from "./horizon.js";
13
+ import { registerGetAccountPayments } from "./tools/get-account-payments.js";
14
+ import { registerGetWalletBalance } from "./tools/get-wallet-balance.js";
15
+ import { registerQueryTransaction } from "./tools/query-transaction.js";
16
+ // Single source of truth for the version. `../package.json` resolves to the
17
+ // package root from both dist/index.js and src/index.ts, so this is correct
18
+ // whether running the build or the sources directly. Resolved at runtime
19
+ // rather than imported so the JSON never has to be copied into dist/.
20
+ const { version: VERSION } = createRequire(import.meta.url)("../package.json");
21
+ const NETWORK = HORIZON_URL.includes("testnet") ? "Pi Testnet" : `custom (${HORIZON_URL})`;
22
+ if (process.argv.includes("--version") || process.argv.includes("-v")) {
23
+ process.stdout.write(`${VERSION}\n`);
24
+ process.exit(0);
25
+ }
26
+ if (process.argv.includes("--help") || process.argv.includes("-h")) {
27
+ process.stdout.write([
28
+ `pion-mcp ${VERSION} — MCP server for Pi Network (read-only)`,
29
+ "",
30
+ "Runs an MCP server over stdio. Point an MCP client at it rather than",
31
+ "invoking it directly.",
32
+ "",
33
+ "Tools: get_wallet_balance, get_account_payments, query_transaction",
34
+ "",
35
+ "Environment:",
36
+ " PION_HORIZON_URL Horizon base URL (default: https://api.testnet.minepi.com)",
37
+ "",
38
+ ].join("\n"));
39
+ process.exit(0);
40
+ }
41
+ const server = new McpServer({ name: "pion-mcp", version: VERSION }, {
42
+ instructions: `Pion exposes read-only Pi Network chain data from Horizon at ${HORIZON_URL} ` +
43
+ `(${NETWORK}). All three tools are public ledger reads: they cannot send payments, ` +
44
+ "sign anything, or access a user's wallet. Amounts are decimal strings; Pi itself " +
45
+ 'is reported as the asset "PI" and custom tokens as "CODE:ISSUER".',
46
+ });
47
+ registerGetWalletBalance(server, NETWORK);
48
+ registerGetAccountPayments(server, NETWORK);
49
+ registerQueryTransaction(server, NETWORK);
50
+ async function main() {
51
+ // stdout is the JSON-RPC channel — every log line must go to stderr.
52
+ await server.connect(new StdioServerTransport());
53
+ console.error(`pion-mcp ${VERSION} ready on stdio — Horizon: ${HORIZON_URL} (${NETWORK})`);
54
+ }
55
+ main().catch((error) => {
56
+ console.error("pion-mcp failed to start:", error);
57
+ process.exit(1);
58
+ });
59
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;GAMG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAExE,4EAA4E;AAC5E,4EAA4E;AAC5E,yEAAyE;AACzE,sEAAsE;AACtE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAE5E,CAAC;AAEF,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,WAAW,GAAG,CAAC;AAE3F,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;IACtE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;IACnE,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB;QACE,YAAY,OAAO,0CAA0C;QAC7D,EAAE;QACF,sEAAsE;QACtE,uBAAuB;QACvB,EAAE;QACF,oEAAoE;QACpE,EAAE;QACF,cAAc;QACd,gFAAgF;QAChF,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,EACtC;IACE,YAAY,EACV,gEAAgE,WAAW,GAAG;QAC9E,IAAI,OAAO,yEAAyE;QACpF,mFAAmF;QACnF,mEAAmE;CACtE,CACF,CAAC;AAEF,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC1C,0BAA0B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC5C,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1C,KAAK,UAAU,IAAI;IACjB,qEAAqE;IACrE,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,KAAK,CAAC,YAAY,OAAO,8BAA8B,WAAW,KAAK,OAAO,GAAG,CAAC,CAAC;AAC7F,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC;IAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ /** Stellar/Pi public key: 56 base32 characters beginning with G. */
4
+ export declare const walletAddress: z.ZodString;
5
+ /** Stellar transaction hash: 64 hex characters. */
6
+ export declare const transactionHash: z.ZodString;
7
+ export declare const pagingLimit: z.ZodDefault<z.ZodNumber>;
8
+ export declare const pagingCursor: z.ZodOptional<z.ZodString>;
9
+ export declare const pagingOrder: z.ZodDefault<z.ZodEnum<{
10
+ asc: "asc";
11
+ desc: "desc";
12
+ }>>;
13
+ /** A successful tool result: JSON text for humans, structured content for agents. */
14
+ export declare function ok<T extends Record<string, unknown>>(data: T): CallToolResult;
15
+ /** A failed tool result. `isError` keeps the failure inside the conversation. */
16
+ export declare function fail(error: unknown): CallToolResult;
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ import { HORIZON_URL, HorizonError } from "../horizon.js";
3
+ /** Stellar/Pi public key: 56 base32 characters beginning with G. */
4
+ export const walletAddress = z
5
+ .string()
6
+ .regex(/^G[A-Z2-7]{55}$/, "must be a 56-character Pi wallet address starting with G (e.g. GABC...XYZ)")
7
+ .describe("Pi wallet address (Stellar public key, 56 characters, starts with G)");
8
+ /** Stellar transaction hash: 64 hex characters. */
9
+ export const transactionHash = z
10
+ .string()
11
+ .regex(/^[0-9a-fA-F]{64}$/, "must be a 64-character hex transaction hash")
12
+ .describe("Transaction hash (64 hex characters)");
13
+ export const pagingLimit = z
14
+ .number()
15
+ .int()
16
+ .min(1)
17
+ .max(200)
18
+ .default(10)
19
+ .describe("How many records to return (1-200). Defaults to 10.");
20
+ export const pagingCursor = z
21
+ .string()
22
+ .optional()
23
+ .describe("Paging cursor from a previous call's `next_cursor`. Omit for the first page.");
24
+ export const pagingOrder = z
25
+ .enum(["asc", "desc"])
26
+ .default("desc")
27
+ .describe("`desc` returns newest records first (the default); `asc` returns oldest first.");
28
+ /** A successful tool result: JSON text for humans, structured content for agents. */
29
+ export function ok(data) {
30
+ return {
31
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
32
+ structuredContent: data,
33
+ };
34
+ }
35
+ /** A failed tool result. `isError` keeps the failure inside the conversation. */
36
+ export function fail(error) {
37
+ const message = error instanceof HorizonError
38
+ ? error.message
39
+ : `Unexpected error querying ${HORIZON_URL}: ${error instanceof Error ? error.message : String(error)}`;
40
+ return { content: [{ type: "text", text: message }], isError: true };
41
+ }
42
+ //# sourceMappingURL=common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/tools/common.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE1D,oEAAoE;AACpE,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC;KAC3B,MAAM,EAAE;KACR,KAAK,CACJ,iBAAiB,EACjB,4EAA4E,CAC7E;KACA,QAAQ,CAAC,sEAAsE,CAAC,CAAC;AAEpF,mDAAmD;AACnD,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC;KAC7B,MAAM,EAAE;KACR,KAAK,CAAC,mBAAmB,EAAE,6CAA6C,CAAC;KACzE,QAAQ,CAAC,sCAAsC,CAAC,CAAC;AAEpD,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC;KACzB,MAAM,EAAE;KACR,GAAG,EAAE;KACL,GAAG,CAAC,CAAC,CAAC;KACN,GAAG,CAAC,GAAG,CAAC;KACR,OAAO,CAAC,EAAE,CAAC;KACX,QAAQ,CAAC,qDAAqD,CAAC,CAAC;AAEnE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC;KAC1B,MAAM,EAAE;KACR,QAAQ,EAAE;KACV,QAAQ,CAAC,8EAA8E,CAAC,CAAC;AAE5F,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC;KACzB,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;KACrB,OAAO,CAAC,MAAM,CAAC;KACf,QAAQ,CAAC,gFAAgF,CAAC,CAAC;AAE9F,qFAAqF;AACrF,MAAM,UAAU,EAAE,CAAoC,IAAO;IAC3D,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;QAChE,iBAAiB,EAAE,IAAI;KACxB,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,IAAI,CAAC,KAAc;IACjC,MAAM,OAAO,GACX,KAAK,YAAY,YAAY;QAC3B,CAAC,CAAC,KAAK,CAAC,OAAO;QACf,CAAC,CAAC,6BAA6B,WAAW,KACtC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,CAAC;IACT,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACvE,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerGetAccountPayments(server: McpServer, network: string): void;
@@ -0,0 +1,108 @@
1
+ import { z } from "zod";
2
+ import { cursorFromLink, formatAsset, horizonGet } from "../horizon.js";
3
+ import { fail, ok, pagingCursor, pagingLimit, pagingOrder, walletAddress } from "./common.js";
4
+ const paymentShape = z.object({
5
+ id: z.string(),
6
+ type: z.string(),
7
+ created_at: z.string(),
8
+ transaction_hash: z.string(),
9
+ successful: z.boolean().optional(),
10
+ from: z.string().optional(),
11
+ to: z.string().optional(),
12
+ amount: z.string().optional(),
13
+ asset: z.string().optional(),
14
+ source_amount: z.string().optional(),
15
+ source_asset: z.string().optional(),
16
+ });
17
+ const outputSchema = {
18
+ network: z.string(),
19
+ account_id: z.string(),
20
+ count: z.number(),
21
+ next_cursor: z.string().optional(),
22
+ payments: z.array(paymentShape),
23
+ };
24
+ /** Flattens Horizon's per-type payment records into one consistent shape. */
25
+ function normalize(record) {
26
+ const base = {
27
+ id: record.id,
28
+ type: record.type,
29
+ created_at: record.created_at,
30
+ transaction_hash: record.transaction_hash,
31
+ ...(record.transaction_successful !== undefined
32
+ ? { successful: record.transaction_successful }
33
+ : {}),
34
+ };
35
+ switch (record.type) {
36
+ case "create_account":
37
+ return {
38
+ ...base,
39
+ from: record.funder,
40
+ to: record.account,
41
+ amount: record.starting_balance,
42
+ asset: "PI",
43
+ };
44
+ case "account_merge":
45
+ return { ...base, from: record.account, to: record.into };
46
+ default:
47
+ // Covers payment, path_payment_*, and any other value-moving operation
48
+ // Horizon surfaces here (e.g. invoke_host_function), which may carry no
49
+ // asset fields at all — never assume native.
50
+ return {
51
+ ...base,
52
+ from: record.from,
53
+ to: record.to,
54
+ amount: record.amount,
55
+ ...(record.asset_type !== undefined
56
+ ? { asset: formatAsset(record.asset_type, record.asset_code, record.asset_issuer) }
57
+ : {}),
58
+ ...(record.source_amount !== undefined
59
+ ? {
60
+ source_amount: record.source_amount,
61
+ source_asset: formatAsset(record.source_asset_type, record.source_asset_code, record.source_asset_issuer),
62
+ }
63
+ : {}),
64
+ };
65
+ }
66
+ }
67
+ export function registerGetAccountPayments(server, network) {
68
+ server.registerTool("get_account_payments", {
69
+ title: "List Pi wallet payment history",
70
+ description: "List payments sent to or from a Pi wallet address, newest first. " +
71
+ "Call this to answer questions about an address's transaction history — whether " +
72
+ "a payment arrived, who funded an account, or what it recently sent. Covers " +
73
+ "payments, account creations, path payments, and account merges. Results are " +
74
+ "paginated: pass the returned `next_cursor` back as `cursor` for the next page. " +
75
+ "Reads public ledger data only.",
76
+ inputSchema: {
77
+ address: walletAddress,
78
+ limit: pagingLimit,
79
+ cursor: pagingCursor,
80
+ order: pagingOrder,
81
+ },
82
+ outputSchema,
83
+ annotations: { readOnlyHint: true, openWorldHint: true },
84
+ }, async ({ address, limit, cursor, order }) => {
85
+ try {
86
+ const page = await horizonGet(`/accounts/${address}/payments`, {
87
+ limit,
88
+ order,
89
+ cursor,
90
+ });
91
+ const payments = page._embedded.records.map(normalize);
92
+ // Horizon always emits a `next` link, even past the end of the result
93
+ // set. Only surface a cursor when the page came back full.
94
+ const nextCursor = payments.length === limit ? cursorFromLink(page._links?.next?.href) : undefined;
95
+ return ok({
96
+ network,
97
+ account_id: address,
98
+ count: payments.length,
99
+ ...(nextCursor !== undefined ? { next_cursor: nextCursor } : {}),
100
+ payments,
101
+ });
102
+ }
103
+ catch (error) {
104
+ return fail(error);
105
+ }
106
+ });
107
+ }
108
+ //# sourceMappingURL=get-account-payments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-account-payments.js","sourceRoot":"","sources":["../../src/tools/get-account-payments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerGetWalletBalance(server: McpServer, network: string): void;
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+ import { formatAsset, horizonGet } from "../horizon.js";
3
+ import { fail, ok, walletAddress } from "./common.js";
4
+ const outputSchema = {
5
+ network: z.string(),
6
+ account_id: z.string(),
7
+ sequence: z.string(),
8
+ subentry_count: z.number(),
9
+ last_modified_ledger: z.number(),
10
+ balances: z.array(z.object({
11
+ asset: z.string(),
12
+ balance: z.string(),
13
+ asset_type: z.string(),
14
+ limit: z.string().optional(),
15
+ is_authorized: z.boolean().optional(),
16
+ })),
17
+ };
18
+ export function registerGetWalletBalance(server, network) {
19
+ server.registerTool("get_wallet_balance", {
20
+ title: "Get Pi wallet balance",
21
+ description: "Read the current Pi and custom-token balances of a Pi wallet address. " +
22
+ "Call this whenever you need to know how much Pi an address holds, whether it " +
23
+ "holds a particular token, or whether the account exists on-chain at all. " +
24
+ "Reads public ledger data only — it cannot move funds and needs no credentials.",
25
+ inputSchema: { address: walletAddress },
26
+ outputSchema,
27
+ annotations: { readOnlyHint: true, openWorldHint: true },
28
+ }, async ({ address }) => {
29
+ try {
30
+ const account = await horizonGet(`/accounts/${address}`);
31
+ return ok({
32
+ network,
33
+ account_id: account.account_id,
34
+ sequence: account.sequence,
35
+ subentry_count: account.subentry_count,
36
+ last_modified_ledger: account.last_modified_ledger,
37
+ balances: account.balances.map((entry) => ({
38
+ // Liquidity-pool shares carry a pool id instead of a code/issuer.
39
+ asset: entry.liquidity_pool_id !== undefined
40
+ ? `pool:${entry.liquidity_pool_id}`
41
+ : formatAsset(entry.asset_type, entry.asset_code, entry.asset_issuer),
42
+ balance: entry.balance,
43
+ asset_type: entry.asset_type,
44
+ ...(entry.limit !== undefined ? { limit: entry.limit } : {}),
45
+ ...(entry.is_authorized !== undefined ? { is_authorized: entry.is_authorized } : {}),
46
+ })),
47
+ });
48
+ }
49
+ catch (error) {
50
+ return fail(error);
51
+ }
52
+ });
53
+ }
54
+ //# sourceMappingURL=get-wallet-balance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-wallet-balance.js","sourceRoot":"","sources":["../../src/tools/get-wallet-balance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAoBtD,MAAM,YAAY,GAAG;IACnB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE;IAC1B,oBAAoB,EAAE,CAAC,CAAC,MAAM,EAAE;IAChC,QAAQ,EAAE,CAAC,CAAC,KAAK,CACf,CAAC,CAAC,MAAM,CAAC;QACP,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;QACjB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;QACnB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;QACtB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC5B,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;KACtC,CAAC,CACH;CACF,CAAC;AAEF,MAAM,UAAU,wBAAwB,CAAC,MAAiB,EAAE,OAAe;IACzE,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,uBAAuB;QAC9B,WAAW,EACT,wEAAwE;YACxE,+EAA+E;YAC/E,2EAA2E;YAC3E,gFAAgF;QAClF,WAAW,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE;QACvC,YAAY;QACZ,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;KACzD,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACpB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,UAAU,CAAiB,aAAa,OAAO,EAAE,CAAC,CAAC;YACzE,OAAO,EAAE,CAAC;gBACR,OAAO;gBACP,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,cAAc,EAAE,OAAO,CAAC,cAAc;gBACtC,oBAAoB,EAAE,OAAO,CAAC,oBAAoB;gBAClD,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;oBACzC,kEAAkE;oBAClE,KAAK,EACH,KAAK,CAAC,iBAAiB,KAAK,SAAS;wBACnC,CAAC,CAAC,QAAQ,KAAK,CAAC,iBAAiB,EAAE;wBACnC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,YAAY,CAAC;oBACzE,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,UAAU,EAAE,KAAK,CAAC,UAAU;oBAC5B,GAAG,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC5D,GAAG,CAAC,KAAK,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACrF,CAAC,CAAC;aACJ,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerQueryTransaction(server: McpServer, network: string): void;
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+ import { horizonGet } from "../horizon.js";
3
+ import { fail, ok, transactionHash } from "./common.js";
4
+ const outputSchema = {
5
+ network: z.string(),
6
+ hash: z.string(),
7
+ successful: z.boolean(),
8
+ ledger: z.number(),
9
+ created_at: z.string(),
10
+ source_account: z.string(),
11
+ source_account_sequence: z.string(),
12
+ fee_account: z.string().optional(),
13
+ fee_charged: z.string(),
14
+ operation_count: z.number(),
15
+ memo_type: z.string(),
16
+ memo: z.string().optional(),
17
+ result_code: z.string().optional(),
18
+ };
19
+ export function registerQueryTransaction(server, network) {
20
+ server.registerTool("query_transaction", {
21
+ title: "Look up a Pi transaction",
22
+ description: "Look up a single Pi transaction by its hash and report whether it succeeded, " +
23
+ "which ledger it landed in, who submitted it, the fee charged, and its memo. " +
24
+ "Call this to verify that a specific transaction actually went through — a user " +
25
+ "or another service claiming a payment was made is not proof; this is. " +
26
+ "Reads public ledger data only.",
27
+ inputSchema: { hash: transactionHash },
28
+ outputSchema,
29
+ annotations: { readOnlyHint: true, openWorldHint: true },
30
+ }, async ({ hash }) => {
31
+ try {
32
+ const tx = await horizonGet(`/transactions/${hash.toLowerCase()}`);
33
+ return ok({
34
+ network,
35
+ hash: tx.hash,
36
+ successful: tx.successful,
37
+ ledger: tx.ledger,
38
+ created_at: tx.created_at,
39
+ source_account: tx.source_account,
40
+ source_account_sequence: tx.source_account_sequence,
41
+ ...(tx.fee_account !== undefined ? { fee_account: tx.fee_account } : {}),
42
+ fee_charged: tx.fee_charged,
43
+ operation_count: tx.operation_count,
44
+ memo_type: tx.memo_type,
45
+ ...(tx.memo !== undefined ? { memo: tx.memo } : {}),
46
+ ...(tx.result_code !== undefined ? { result_code: tx.result_code } : {}),
47
+ });
48
+ }
49
+ catch (error) {
50
+ return fail(error);
51
+ }
52
+ });
53
+ }
54
+ //# sourceMappingURL=query-transaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-transaction.js","sourceRoot":"","sources":["../../src/tools/query-transaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAiBxD,MAAM,YAAY,GAAG;IACnB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE;IACvB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE;IAC1B,uBAAuB,EAAE,CAAC,CAAC,MAAM,EAAE;IACnC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE;IAC3B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACnC,CAAC;AAEF,MAAM,UAAU,wBAAwB,CAAC,MAAiB,EAAE,OAAe;IACzE,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,KAAK,EAAE,0BAA0B;QACjC,WAAW,EACT,+EAA+E;YAC/E,8EAA8E;YAC9E,iFAAiF;YACjF,wEAAwE;YACxE,gCAAgC;QAClC,WAAW,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE;QACtC,YAAY;QACZ,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;KACzD,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACjB,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,UAAU,CAAqB,iBAAiB,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YACvF,OAAO,EAAE,CAAC;gBACR,OAAO;gBACP,IAAI,EAAE,EAAE,CAAC,IAAI;gBACb,UAAU,EAAE,EAAE,CAAC,UAAU;gBACzB,MAAM,EAAE,EAAE,CAAC,MAAM;gBACjB,UAAU,EAAE,EAAE,CAAC,UAAU;gBACzB,cAAc,EAAE,EAAE,CAAC,cAAc;gBACjC,uBAAuB,EAAE,EAAE,CAAC,uBAAuB;gBACnD,GAAG,CAAC,EAAE,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACxE,WAAW,EAAE,EAAE,CAAC,WAAW;gBAC3B,eAAe,EAAE,EAAE,CAAC,eAAe;gBACnC,SAAS,EAAE,EAAE,CAAC,SAAS;gBACvB,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACnD,GAAG,CAAC,EAAE,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACzE,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,12 +1,47 @@
1
1
  {
2
2
  "name": "pion-mcp",
3
- "version": "0.0.1",
4
- "description": "Pion — Model Context Protocol (MCP) server for Pi Network. Early development stub.",
5
- "keywords": ["mcp", "model-context-protocol", "pi-network", "ai-agents", "blockchain"],
3
+ "version": "0.1.1",
4
+ "description": "Pion — Model Context Protocol (MCP) server for Pi Network. Read-only chain queries against Pi testnet Horizon.",
5
+ "keywords": [
6
+ "mcp",
7
+ "model-context-protocol",
8
+ "pi-network",
9
+ "ai-agents",
10
+ "blockchain",
11
+ "stellar",
12
+ "horizon"
13
+ ],
6
14
  "license": "Apache-2.0",
7
15
  "repository": {
8
16
  "type": "git",
9
- "url": "https://github.com/jleeblack/pion-mcp.git"
17
+ "url": "git+https://github.com/jleeblack/pion-mcp.git"
10
18
  },
11
- "main": "index.js"
12
- }
19
+ "type": "module",
20
+ "main": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "bin": {
23
+ "pion-mcp": "dist/index.js"
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "engines": {
29
+ "node": ">=18.17"
30
+ },
31
+ "scripts": {
32
+ "build": "tsc",
33
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
34
+ "typecheck": "tsc --noEmit",
35
+ "start": "node dist/index.js",
36
+ "smoke": "node scripts/smoke.mjs",
37
+ "prepack": "npm run clean && npm run build"
38
+ },
39
+ "dependencies": {
40
+ "@modelcontextprotocol/sdk": "^1.30.0",
41
+ "zod": "^4.4.3"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^26.1.2",
45
+ "typescript": "^7.0.2"
46
+ }
47
+ }
package/index.js DELETED
@@ -1 +0,0 @@
1
- console.log("Pion — MCP server for Pi Network. Coming soon: github.com/YOURUSERNAME/pion-mcp");