pion-mcp 0.1.0 → 0.2.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 +39 -9
- package/dist/index.js +27 -11
- package/dist/index.js.map +1 -1
- package/dist/platform.d.ts +25 -0
- package/dist/platform.js +73 -0
- package/dist/platform.js.map +1 -0
- package/dist/tools/common.d.ts +4 -1
- package/dist/tools/common.js +8 -4
- package/dist/tools/common.js.map +1 -1
- package/dist/tools/verify-user.d.ts +2 -0
- package/dist/tools/verify-user.js +53 -0
- package/dist/tools/verify-user.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**Model Context Protocol server for Pi Network** — connect AI agents
|
|
4
4
|
(Claude, Cursor, and any MCP-compatible client) to Pi Network chain data.
|
|
5
5
|
|
|
6
|
-
> ⚠️ v0.
|
|
6
|
+
> ⚠️ v0.2 — testnet, read-only. Nothing here can move value.
|
|
7
7
|
|
|
8
8
|
## Why "Pion"?
|
|
9
9
|
The pion is the π meson — the particle physicists named after pi.
|
|
@@ -12,9 +12,11 @@ for MCPs (millicharged particles). We couldn't resist.
|
|
|
12
12
|
|
|
13
13
|
## Tools
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
(
|
|
17
|
-
|
|
15
|
+
Nothing here can move value. No server API key or wallet secret is read
|
|
16
|
+
anywhere. (Tiers refer to [`docs/tool-mapping.md`](docs/tool-mapping.md).)
|
|
17
|
+
|
|
18
|
+
**Tier A — chain reads.** Zero-permission queries against Pi's public Horizon
|
|
19
|
+
API. No credentials at all.
|
|
18
20
|
|
|
19
21
|
| Tool | What it does |
|
|
20
22
|
|---|---|
|
|
@@ -25,6 +27,23 @@ wallet secrets, no user consent** — and nothing here can move value.
|
|
|
25
27
|
Amounts are decimal strings. Pi is reported as the asset `PI`, custom tokens as
|
|
26
28
|
`CODE:ISSUER`, and liquidity-pool shares as `pool:ID`.
|
|
27
29
|
|
|
30
|
+
**Tier B — identity.**
|
|
31
|
+
|
|
32
|
+
| Tool | What it does |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `verify_user` | Validate a Pi user access token, returning the uid and username |
|
|
35
|
+
|
|
36
|
+
`verify_user` is the only tool that touches a credential, and it never holds
|
|
37
|
+
one: the caller passes a token per call, it goes to `GET /v2/me` and nowhere
|
|
38
|
+
else, and it is not stored, logged, or echoed back. A rejected token returns
|
|
39
|
+
`valid: false` with a reason rather than erroring, so an agent can branch on
|
|
40
|
+
the outcome.
|
|
41
|
+
|
|
42
|
+
Two caveats worth knowing. The `uid` is **app-specific** — the same person has
|
|
43
|
+
a different uid under a different Pi app, which is deliberate anti-correlation
|
|
44
|
+
design, so don't use it as a global identifier. And a token is the *only* proof
|
|
45
|
+
of identity: a client-supplied uid or username means nothing on its own.
|
|
46
|
+
|
|
28
47
|
## Usage
|
|
29
48
|
|
|
30
49
|
MCP clients can run it straight from npm — no install step:
|
|
@@ -59,8 +78,10 @@ claude mcp add pion -- node /absolute/path/to/pion-mcp/dist/index.js
|
|
|
59
78
|
| Variable | Default | Purpose |
|
|
60
79
|
|---|---|---|
|
|
61
80
|
| `PION_HORIZON_URL` | `https://api.testnet.minepi.com` | Horizon base URL |
|
|
81
|
+
| `PION_PLATFORM_URL` | `https://api.minepi.com` | Platform API base URL |
|
|
62
82
|
|
|
63
|
-
There are no secrets to configure
|
|
83
|
+
There are no secrets to configure — `verify_user` takes its token as a call
|
|
84
|
+
argument, not from the environment. The mainnet Horizon URL is still an open
|
|
64
85
|
question — see the TODO in [`docs/pi-sdk-notes.md`](docs/pi-sdk-notes.md).
|
|
65
86
|
|
|
66
87
|
## Development
|
|
@@ -72,13 +93,22 @@ npm run smoke # end-to-end: drives the built server against live testnet
|
|
|
72
93
|
```
|
|
73
94
|
|
|
74
95
|
`npm run smoke` spawns the server over stdio as a real MCP client, discovers a
|
|
75
|
-
funded account from the current ledger, and exercises
|
|
96
|
+
funded account from the current ledger, and exercises the chain tools plus the
|
|
76
97
|
not-found and invalid-input paths. It needs network access.
|
|
77
98
|
|
|
99
|
+
It covers `verify_user` only on the **rejection** path — confirming a genuine
|
|
100
|
+
token would need a real user credential, which the test deliberately does not
|
|
101
|
+
handle. The success path is unverified; see below.
|
|
102
|
+
|
|
78
103
|
## Roadmap
|
|
79
104
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
105
|
+
Tier C is next: App-to-User payments behind env config (`PI_SERVER_API_KEY`,
|
|
106
|
+
`PI_WALLET_SECRET`), testnet-default with explicit opt-in for anything that
|
|
107
|
+
moves value. See [`docs/tool-mapping.md`](docs/tool-mapping.md).
|
|
108
|
+
|
|
109
|
+
Known gap: `verify_user`'s success-path response shape is built from the Pi
|
|
110
|
+
platform docs, not observed traffic. The `uid` field is reliable; `username`
|
|
111
|
+
and `credentials` depend on granted scopes and are treated as optional. Worth
|
|
112
|
+
confirming against a real token before depending on them.
|
|
83
113
|
|
|
84
114
|
*Unofficial community project — not affiliated with Pi Network.*
|
package/dist/index.js
CHANGED
|
@@ -2,18 +2,29 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Pion — MCP server for Pi Network.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* read-only chain queries against Pi's public
|
|
7
|
-
*
|
|
5
|
+
* Scope (see docs/tool-mapping.md):
|
|
6
|
+
* Tier A — zero-permission, read-only chain queries against Pi's public
|
|
7
|
+
* Horizon API. No credentials of any kind.
|
|
8
|
+
* Tier B — verify_user, which validates a *user* access token supplied by
|
|
9
|
+
* the caller against the Platform API.
|
|
10
|
+
*
|
|
11
|
+
* No server API key or wallet secret is read anywhere in this server, and no
|
|
12
|
+
* tool can move value.
|
|
8
13
|
*/
|
|
14
|
+
import { createRequire } from "node:module";
|
|
9
15
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
16
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
17
|
import { HORIZON_URL } from "./horizon.js";
|
|
18
|
+
import { PLATFORM_URL } from "./platform.js";
|
|
12
19
|
import { registerGetAccountPayments } from "./tools/get-account-payments.js";
|
|
13
20
|
import { registerGetWalletBalance } from "./tools/get-wallet-balance.js";
|
|
14
21
|
import { registerQueryTransaction } from "./tools/query-transaction.js";
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
import { registerVerifyUser } from "./tools/verify-user.js";
|
|
23
|
+
// Single source of truth for the version. `../package.json` resolves to the
|
|
24
|
+
// package root from both dist/index.js and src/index.ts, so this is correct
|
|
25
|
+
// whether running the build or the sources directly. Resolved at runtime
|
|
26
|
+
// rather than imported so the JSON never has to be copied into dist/.
|
|
27
|
+
const { version: VERSION } = createRequire(import.meta.url)("../package.json");
|
|
17
28
|
const NETWORK = HORIZON_URL.includes("testnet") ? "Pi Testnet" : `custom (${HORIZON_URL})`;
|
|
18
29
|
if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
|
19
30
|
process.stdout.write(`${VERSION}\n`);
|
|
@@ -26,23 +37,28 @@ if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
|
26
37
|
"Runs an MCP server over stdio. Point an MCP client at it rather than",
|
|
27
38
|
"invoking it directly.",
|
|
28
39
|
"",
|
|
29
|
-
"Tools: get_wallet_balance, get_account_payments, query_transaction",
|
|
40
|
+
"Tools: get_wallet_balance, get_account_payments, query_transaction, verify_user",
|
|
30
41
|
"",
|
|
31
42
|
"Environment:",
|
|
32
|
-
" PION_HORIZON_URL
|
|
43
|
+
" PION_HORIZON_URL Horizon base URL (default: https://api.testnet.minepi.com)",
|
|
44
|
+
" PION_PLATFORM_URL Platform API base URL (default: https://api.minepi.com)",
|
|
33
45
|
"",
|
|
34
46
|
].join("\n"));
|
|
35
47
|
process.exit(0);
|
|
36
48
|
}
|
|
37
49
|
const server = new McpServer({ name: "pion-mcp", version: VERSION }, {
|
|
38
|
-
instructions: `Pion exposes read-only Pi Network
|
|
39
|
-
`
|
|
40
|
-
|
|
41
|
-
'
|
|
50
|
+
instructions: `Pion exposes read-only Pi Network data. get_wallet_balance, get_account_payments, ` +
|
|
51
|
+
`and query_transaction are public ledger reads from Horizon at ${HORIZON_URL} ` +
|
|
52
|
+
`(${NETWORK}), needing no credentials. Amounts are decimal strings; Pi itself is ` +
|
|
53
|
+
'reported as the asset "PI", custom tokens as "CODE:ISSUER", and liquidity-pool ' +
|
|
54
|
+
'shares as "pool:ID". verify_user is different: it checks a user access token ' +
|
|
55
|
+
`against the Pi Platform API at ${PLATFORM_URL} and requires the caller to supply ` +
|
|
56
|
+
"that token. No tool here can send payments, sign anything, or spend from a wallet.",
|
|
42
57
|
});
|
|
43
58
|
registerGetWalletBalance(server, NETWORK);
|
|
44
59
|
registerGetAccountPayments(server, NETWORK);
|
|
45
60
|
registerQueryTransaction(server, NETWORK);
|
|
61
|
+
registerVerifyUser(server);
|
|
46
62
|
async function main() {
|
|
47
63
|
// stdout is the JSON-RPC channel — every log line must go to stderr.
|
|
48
64
|
await server.connect(new StdioServerTransport());
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG;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,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,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,iFAAiF;QACjF,EAAE;QACF,cAAc;QACd,iFAAiF;QACjF,8EAA8E;QAC9E,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,oFAAoF;QACpF,iEAAiE,WAAW,GAAG;QAC/E,IAAI,OAAO,uEAAuE;QAClF,iFAAiF;QACjF,+EAA+E;QAC/E,kCAAkC,YAAY,qCAAqC;QACnF,oFAAoF;CACvF,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;AAC1C,kBAAkB,CAAC,MAAM,CAAC,CAAC;AAE3B,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,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal client for the Pi Platform API (Layer 2 in docs/pi-sdk-notes.md).
|
|
3
|
+
*
|
|
4
|
+
* Unlike Horizon, these endpoints are authenticated. This module only ever
|
|
5
|
+
* handles a *user* access token, which the caller supplies per request — it
|
|
6
|
+
* never reads a server API key or wallet secret, and it never persists,
|
|
7
|
+
* logs, or echoes back a token.
|
|
8
|
+
*/
|
|
9
|
+
/** Platform API base URL. Override with PION_PLATFORM_URL. */
|
|
10
|
+
export declare const PLATFORM_URL: string;
|
|
11
|
+
/** A Platform API request that failed for reasons other than a bad token. */
|
|
12
|
+
export declare class PlatformError extends Error {
|
|
13
|
+
readonly status?: number | undefined;
|
|
14
|
+
constructor(message: string, status?: number | undefined);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The token was rejected (401/403). This is a normal, expected answer to
|
|
18
|
+
* "is this token valid?" — not a malfunction — so callers report it as a
|
|
19
|
+
* result rather than an error.
|
|
20
|
+
*/
|
|
21
|
+
export declare class PlatformAuthError extends Error {
|
|
22
|
+
readonly status: number;
|
|
23
|
+
constructor(message: string, status: number);
|
|
24
|
+
}
|
|
25
|
+
export declare function platformGet<T>(path: string, accessToken: string): Promise<T>;
|
package/dist/platform.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal client for the Pi Platform API (Layer 2 in docs/pi-sdk-notes.md).
|
|
3
|
+
*
|
|
4
|
+
* Unlike Horizon, these endpoints are authenticated. This module only ever
|
|
5
|
+
* handles a *user* access token, which the caller supplies per request — it
|
|
6
|
+
* never reads a server API key or wallet secret, and it never persists,
|
|
7
|
+
* logs, or echoes back a token.
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_PLATFORM_URL = "https://api.minepi.com";
|
|
10
|
+
const REQUEST_TIMEOUT_MS = 15_000;
|
|
11
|
+
/** Platform API base URL. Override with PION_PLATFORM_URL. */
|
|
12
|
+
export const PLATFORM_URL = (process.env.PION_PLATFORM_URL ?? DEFAULT_PLATFORM_URL).replace(/\/+$/, "");
|
|
13
|
+
/** A Platform API request that failed for reasons other than a bad token. */
|
|
14
|
+
export class PlatformError extends Error {
|
|
15
|
+
status;
|
|
16
|
+
constructor(message, status) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.status = status;
|
|
19
|
+
this.name = "PlatformError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The token was rejected (401/403). This is a normal, expected answer to
|
|
24
|
+
* "is this token valid?" — not a malfunction — so callers report it as a
|
|
25
|
+
* result rather than an error.
|
|
26
|
+
*/
|
|
27
|
+
export class PlatformAuthError extends Error {
|
|
28
|
+
status;
|
|
29
|
+
constructor(message, status) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.status = status;
|
|
32
|
+
this.name = "PlatformAuthError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export async function platformGet(path, accessToken) {
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
38
|
+
let response;
|
|
39
|
+
try {
|
|
40
|
+
response = await fetch(`${PLATFORM_URL}${path}`, {
|
|
41
|
+
headers: {
|
|
42
|
+
authorization: `Bearer ${accessToken}`,
|
|
43
|
+
accept: "application/json",
|
|
44
|
+
},
|
|
45
|
+
signal: controller.signal,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (controller.signal.aborted) {
|
|
50
|
+
throw new PlatformError(`Platform API request timed out after ${REQUEST_TIMEOUT_MS}ms`);
|
|
51
|
+
}
|
|
52
|
+
// `err` can echo the request; report only its message, never the headers.
|
|
53
|
+
throw new PlatformError(`Could not reach the Pi Platform API at ${PLATFORM_URL}: ${err.message}`);
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
}
|
|
58
|
+
if (response.status === 401 || response.status === 403) {
|
|
59
|
+
throw new PlatformAuthError(response.status === 401
|
|
60
|
+
? "The Pi Platform API rejected this access token. It is invalid, expired, or was issued for a different app."
|
|
61
|
+
: "This access token is valid but lacks the scope required for this call.", response.status);
|
|
62
|
+
}
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
// 401 responses come back with an empty body, so never assume JSON here.
|
|
65
|
+
const body = await response.text().catch(() => "");
|
|
66
|
+
const detail = body.trim().slice(0, 300);
|
|
67
|
+
throw new PlatformError(detail.length > 0
|
|
68
|
+
? `Pi Platform API returned ${response.status}: ${detail}`
|
|
69
|
+
: `Pi Platform API returned ${response.status} ${response.statusText} for ${path}`, response.status);
|
|
70
|
+
}
|
|
71
|
+
return (await response.json());
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=platform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,oBAAoB,GAAG,wBAAwB,CAAC;AACtD,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC,8DAA8D;AAC9D,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,oBAAoB,CAAC,CAAC,OAAO,CACzF,MAAM,EACN,EAAE,CACH,CAAC;AAEF,6EAA6E;AAC7E,MAAM,OAAO,aAAc,SAAQ,KAAK;IAG3B,MAAM;IAFjB,YACE,OAAe,EACN,MAAe;QAExB,KAAK,CAAC,OAAO,CAAC,CAAC;sBAFN,MAAM;QAGf,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAG/B,MAAM;IAFjB,YACE,OAAe,EACN,MAAc;QAEvB,KAAK,CAAC,OAAO,CAAC,CAAC;sBAFN,MAAM;QAGf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAI,IAAY,EAAE,WAAmB;IACpE,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,kBAAkB,CAAC,CAAC;IAEvE,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,YAAY,GAAG,IAAI,EAAE,EAAE;YAC/C,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,WAAW,EAAE;gBACtC,MAAM,EAAE,kBAAkB;aAC3B;YACD,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9B,MAAM,IAAI,aAAa,CAAC,wCAAwC,kBAAkB,IAAI,CAAC,CAAC;QAC1F,CAAC;QACD,0EAA0E;QAC1E,MAAM,IAAI,aAAa,CACrB,0CAA0C,YAAY,KAAM,GAAa,CAAC,OAAO,EAAE,CACpF,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QACvD,MAAM,IAAI,iBAAiB,CACzB,QAAQ,CAAC,MAAM,KAAK,GAAG;YACrB,CAAC,CAAC,4GAA4G;YAC9G,CAAC,CAAC,wEAAwE,EAC5E,QAAQ,CAAC,MAAM,CAChB,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,yEAAyE;QACzE,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACzC,MAAM,IAAI,aAAa,CACrB,MAAM,CAAC,MAAM,GAAG,CAAC;YACf,CAAC,CAAC,4BAA4B,QAAQ,CAAC,MAAM,KAAK,MAAM,EAAE;YAC1D,CAAC,CAAC,4BAA4B,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,QAAQ,IAAI,EAAE,EACpF,QAAQ,CAAC,MAAM,CAChB,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAM,CAAC;AACtC,CAAC"}
|
package/dist/tools/common.d.ts
CHANGED
|
@@ -12,5 +12,8 @@ export declare const pagingOrder: z.ZodDefault<z.ZodEnum<{
|
|
|
12
12
|
}>>;
|
|
13
13
|
/** A successful tool result: JSON text for humans, structured content for agents. */
|
|
14
14
|
export declare function ok<T extends Record<string, unknown>>(data: T): CallToolResult;
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* A failed tool result. `isError` keeps the failure inside the conversation
|
|
17
|
+
* so the agent can react, rather than faulting the transport.
|
|
18
|
+
*/
|
|
16
19
|
export declare function fail(error: unknown): CallToolResult;
|
package/dist/tools/common.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { HorizonError } from "../horizon.js";
|
|
3
|
+
import { PlatformError } from "../platform.js";
|
|
3
4
|
/** Stellar/Pi public key: 56 base32 characters beginning with G. */
|
|
4
5
|
export const walletAddress = z
|
|
5
6
|
.string()
|
|
@@ -32,11 +33,14 @@ export function ok(data) {
|
|
|
32
33
|
structuredContent: data,
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* A failed tool result. `isError` keeps the failure inside the conversation
|
|
38
|
+
* so the agent can react, rather than faulting the transport.
|
|
39
|
+
*/
|
|
36
40
|
export function fail(error) {
|
|
37
|
-
const message = error instanceof HorizonError
|
|
41
|
+
const message = error instanceof HorizonError || error instanceof PlatformError
|
|
38
42
|
? error.message
|
|
39
|
-
: `Unexpected error
|
|
43
|
+
: `Unexpected error: ${error instanceof Error ? error.message : String(error)}`;
|
|
40
44
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
41
45
|
}
|
|
42
46
|
//# sourceMappingURL=common.js.map
|
package/dist/tools/common.js.map
CHANGED
|
@@ -1 +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,
|
|
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,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,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;;;GAGG;AACH,MAAM,UAAU,IAAI,CAAC,KAAc;IACjC,MAAM,OAAO,GACX,KAAK,YAAY,YAAY,IAAI,KAAK,YAAY,aAAa;QAC7D,CAAC,CAAC,KAAK,CAAC,OAAO;QACf,CAAC,CAAC,qBAAqB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IACpF,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,53 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { PlatformAuthError, PLATFORM_URL, platformGet } from "../platform.js";
|
|
3
|
+
import { fail, ok } from "./common.js";
|
|
4
|
+
const outputSchema = {
|
|
5
|
+
valid: z.boolean(),
|
|
6
|
+
uid: z.string().optional(),
|
|
7
|
+
username: z.string().optional(),
|
|
8
|
+
scopes: z.array(z.string()).optional(),
|
|
9
|
+
valid_until: z.string().optional(),
|
|
10
|
+
reason: z.string().optional(),
|
|
11
|
+
};
|
|
12
|
+
export function registerVerifyUser(server) {
|
|
13
|
+
server.registerTool("verify_user", {
|
|
14
|
+
title: "Verify a Pi user access token",
|
|
15
|
+
description: "Check whether a Pi user access token is genuine and, if so, who it belongs to. " +
|
|
16
|
+
"Call this to authenticate someone who claims a Pi identity — never trust a " +
|
|
17
|
+
"client-supplied uid or username on its own; this is the only thing that proves it. " +
|
|
18
|
+
"Returns `valid: false` with a reason for a rejected token rather than failing. " +
|
|
19
|
+
`Sends the token to the Pi Platform API (${PLATFORM_URL}/v2/me) and nothing else; ` +
|
|
20
|
+
"it is not stored or logged. Note the uid is app-specific — the same person has a " +
|
|
21
|
+
"different uid under a different Pi app.",
|
|
22
|
+
inputSchema: {
|
|
23
|
+
access_token: z
|
|
24
|
+
.string()
|
|
25
|
+
.min(1)
|
|
26
|
+
.describe("The user's Pi access token, obtained from Pi Browser authentication or Pi " +
|
|
27
|
+
"Sign-in OAuth. This is a credential — pass the token itself, not a uid."),
|
|
28
|
+
},
|
|
29
|
+
outputSchema,
|
|
30
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
31
|
+
}, async ({ access_token }) => {
|
|
32
|
+
try {
|
|
33
|
+
const me = await platformGet("/v2/me", access_token);
|
|
34
|
+
return ok({
|
|
35
|
+
valid: true,
|
|
36
|
+
uid: me.uid,
|
|
37
|
+
...(me.username !== undefined ? { username: me.username } : {}),
|
|
38
|
+
...(me.credentials?.scopes !== undefined ? { scopes: me.credentials.scopes } : {}),
|
|
39
|
+
...(me.credentials?.valid_until?.iso8601 !== undefined
|
|
40
|
+
? { valid_until: me.credentials.valid_until.iso8601 }
|
|
41
|
+
: {}),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
// A rejected token is a real answer to "is this valid?", not a fault.
|
|
46
|
+
if (error instanceof PlatformAuthError) {
|
|
47
|
+
return ok({ valid: false, reason: error.message });
|
|
48
|
+
}
|
|
49
|
+
return fail(error);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=verify-user.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-user.js","sourceRoot":"","sources":["../../src/tools/verify-user.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAYvC,MAAM,YAAY,GAAG;IACnB,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE;IAClB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC1B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACtC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,MAAiB;IAClD,MAAM,CAAC,YAAY,CACjB,aAAa,EACb;QACE,KAAK,EAAE,+BAA+B;QACtC,WAAW,EACT,iFAAiF;YACjF,6EAA6E;YAC7E,qFAAqF;YACrF,iFAAiF;YACjF,2CAA2C,YAAY,4BAA4B;YACnF,mFAAmF;YACnF,yCAAyC;QAC3C,WAAW,EAAE;YACX,YAAY,EAAE,CAAC;iBACZ,MAAM,EAAE;iBACR,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,CACP,4EAA4E;gBAC1E,yEAAyE,CAC5E;SACJ;QACD,YAAY;QACZ,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;KACzD,EACD,KAAK,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE;QACzB,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,WAAW,CAAa,QAAQ,EAAE,YAAY,CAAC,CAAC;YACjE,OAAO,EAAE,CAAC;gBACR,KAAK,EAAE,IAAI;gBACX,GAAG,EAAE,EAAE,CAAC,GAAG;gBACX,GAAG,CAAC,EAAE,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC/D,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAClF,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,KAAK,SAAS;oBACpD,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,OAAO,EAAE;oBACrD,CAAC,CAAC,EAAE,CAAC;aACR,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,sEAAsE;YACtE,IAAI,KAAK,YAAY,iBAAiB,EAAE,CAAC;gBACvC,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACrD,CAAC;YACD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|