pion-mcp 0.0.1 → 0.1.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 +71 -8
- package/dist/horizon.d.ts +20 -0
- package/dist/horizon.js +82 -0
- package/dist/horizon.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/common.d.ts +16 -0
- package/dist/tools/common.js +42 -0
- package/dist/tools/common.js.map +1 -0
- package/dist/tools/get-account-payments.d.ts +2 -0
- package/dist/tools/get-account-payments.js +108 -0
- package/dist/tools/get-account-payments.js.map +1 -0
- package/dist/tools/get-wallet-balance.d.ts +2 -0
- package/dist/tools/get-wallet-balance.js +54 -0
- package/dist/tools/get-wallet-balance.js.map +1 -0
- package/dist/tools/query-transaction.d.ts +2 -0
- package/dist/tools/query-transaction.js +54 -0
- package/dist/tools/query-transaction.js.map +1 -0
- package/package.json +41 -6
- package/index.js +0 -1
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
|
|
4
|
+
(Claude, Cursor, and any MCP-compatible client) to Pi Network chain data.
|
|
5
5
|
|
|
6
|
-
> ⚠️
|
|
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
|
-
##
|
|
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
|
-
|
|
19
|
-
|
|
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 {};
|
package/dist/horizon.js
ADDED
|
@@ -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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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
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 { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { HORIZON_URL } from "./horizon.js";
|
|
12
|
+
import { registerGetAccountPayments } from "./tools/get-account-payments.js";
|
|
13
|
+
import { registerGetWalletBalance } from "./tools/get-wallet-balance.js";
|
|
14
|
+
import { registerQueryTransaction } from "./tools/query-transaction.js";
|
|
15
|
+
// Kept in sync with package.json by hand — it is the only duplicated string.
|
|
16
|
+
const VERSION = "0.1.0";
|
|
17
|
+
const NETWORK = HORIZON_URL.includes("testnet") ? "Pi Testnet" : `custom (${HORIZON_URL})`;
|
|
18
|
+
if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
|
19
|
+
process.stdout.write(`${VERSION}\n`);
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
23
|
+
process.stdout.write([
|
|
24
|
+
`pion-mcp ${VERSION} — MCP server for Pi Network (read-only)`,
|
|
25
|
+
"",
|
|
26
|
+
"Runs an MCP server over stdio. Point an MCP client at it rather than",
|
|
27
|
+
"invoking it directly.",
|
|
28
|
+
"",
|
|
29
|
+
"Tools: get_wallet_balance, get_account_payments, query_transaction",
|
|
30
|
+
"",
|
|
31
|
+
"Environment:",
|
|
32
|
+
" PION_HORIZON_URL Horizon base URL (default: https://api.testnet.minepi.com)",
|
|
33
|
+
"",
|
|
34
|
+
].join("\n"));
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
const server = new McpServer({ name: "pion-mcp", version: VERSION }, {
|
|
38
|
+
instructions: `Pion exposes read-only Pi Network chain data from Horizon at ${HORIZON_URL} ` +
|
|
39
|
+
`(${NETWORK}). All three tools are public ledger reads: they cannot send payments, ` +
|
|
40
|
+
"sign anything, or access a user's wallet. Amounts are decimal strings; Pi itself " +
|
|
41
|
+
'is reported as the asset "PI" and custom tokens as "CODE:ISSUER".',
|
|
42
|
+
});
|
|
43
|
+
registerGetWalletBalance(server, NETWORK);
|
|
44
|
+
registerGetAccountPayments(server, NETWORK);
|
|
45
|
+
registerQueryTransaction(server, NETWORK);
|
|
46
|
+
async function main() {
|
|
47
|
+
// stdout is the JSON-RPC channel — every log line must go to stderr.
|
|
48
|
+
await server.connect(new StdioServerTransport());
|
|
49
|
+
console.error(`pion-mcp ${VERSION} ready on stdio — Horizon: ${HORIZON_URL} (${NETWORK})`);
|
|
50
|
+
}
|
|
51
|
+
main().catch((error) => {
|
|
52
|
+
console.error("pion-mcp failed to start:", error);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
});
|
|
55
|
+
//# 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,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,6EAA6E;AAC7E,MAAM,OAAO,GAAG,OAAO,CAAC;AAExB,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,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,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,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
|
|
4
|
-
"description": "Pion — Model Context Protocol (MCP) server for Pi Network.
|
|
5
|
-
"keywords": [
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
-
"
|
|
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");
|