@chaindaddy/mcp 0.5.0 → 0.6.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 +10 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +134 -104
- package/build/index.js.map +1 -1
- package/build/skills.d.ts +10 -0
- package/build/skills.d.ts.map +1 -0
- package/build/skills.js +46 -0
- package/build/skills.js.map +1 -0
- package/package.json +2 -1
- package/skills/add-crypto-to-your-game/SKILL.md +114 -0
- package/skills/build-a-crown-app/SKILL.md +123 -0
- package/skills/check-token-health/SKILL.md +67 -0
- package/skills/export-a-listing/SKILL.md +51 -0
- package/skills/find-and-check-a-token/SKILL.md +86 -0
- package/skills/launch-a-token/SKILL.md +99 -0
- package/skills/sell-items-in-your-app/SKILL.md +108 -0
- package/skills/sign-in-with-chain-daddy/SKILL.md +116 -0
- package/skills/verify-receipts-and-sessions/SKILL.md +106 -0
package/README.md
CHANGED
|
@@ -143,7 +143,7 @@ or Developer Beta membership on the wallet that opens the store (otherwise `403
|
|
|
143
143
|
| `iap_set_webhook`, `iap_get_webhook`, `iap_test_webhook` | The store's webhook: set it (secret shown once), read its recent deliveries, send a test event |
|
|
144
144
|
|
|
145
145
|
Every write sends an `Idempotency-Key` and returns it as `idempotency_key`; retry with the same key and the
|
|
146
|
-
write never applies twice. The orchestration skill is [`skills/
|
|
146
|
+
write never applies twice. The orchestration skill is [`skills/sell-items-in-your-app`](skills/sell-items-in-your-app/SKILL.md).
|
|
147
147
|
|
|
148
148
|
```
|
|
149
149
|
"Open a store for my token on Base and add a gold badge for $5"
|
|
@@ -151,6 +151,15 @@ write never applies twice. The orchestration skill is [`skills/creator-store.md`
|
|
|
151
151
|
"Give every wallet in this list 10 credits"
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
+
## Skills
|
|
155
|
+
|
|
156
|
+
The server serves Chain Daddy's skills, step-by-step workflows such as `launch-a-token` and
|
|
157
|
+
`sell-items-in-your-app`, as MCP prompts (one per skill) and through two read-only tools for clients that
|
|
158
|
+
only call tools: `list_skills` and `read_skill`. They are the files in [`skills/`](skills), the one copy:
|
|
159
|
+
the remote server (`https://api.chaindaddy.io/mcp`) serves the same text, and the
|
|
160
|
+
[chaindaddy plugin](https://gitlab.com/opencrown/chaindaddy-plugins) copies them with its
|
|
161
|
+
`scripts/sync-skills.sh`.
|
|
162
|
+
|
|
154
163
|
## Example Conversations
|
|
155
164
|
|
|
156
165
|
### Search and Discover
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG;AAkDH,UAAU,OAAO;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,QAAQ,sBAA6B,CAAC;AAInD,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAU7D;AAKD,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAQ1F"}
|
package/build/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { SiweMessage } from "siwe";
|
|
|
15
15
|
import nacl from "tweetnacl";
|
|
16
16
|
import bs58 from "bs58";
|
|
17
17
|
import { CLI_LOGIN, hasCliLogin, loginFetch } from "./login.js";
|
|
18
|
+
import { loadSkills } from "./skills.js";
|
|
18
19
|
// Configuration
|
|
19
20
|
const API_URL = process.env.CHAINDADDY_API_URL || "https://api.chaindaddy.io";
|
|
20
21
|
// A recommender service you run yourself. The hosted API has none, so with
|
|
@@ -277,6 +278,10 @@ const VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
|
277
278
|
const server = new McpServer({
|
|
278
279
|
name: "chaindaddy",
|
|
279
280
|
version: VERSION,
|
|
281
|
+
}, {
|
|
282
|
+
instructions: "Chain Daddy is a registry of verified token pages: each token's ticker on each chain can carry one registration, held by the wallet that proved it created or controls the token. " +
|
|
283
|
+
"These tools search tickers, launch or import a token, and run its creator store. " +
|
|
284
|
+
"Step-by-step workflows (launching or checking a token, exporting a listing, selling items, sign-in, verifying receipts, Crown Apps) are this server's prompts, and list_skills / read_skill read the same skills: read the matching one before such a task.",
|
|
280
285
|
});
|
|
281
286
|
// ============================================================================
|
|
282
287
|
// TOOLS
|
|
@@ -1341,25 +1346,22 @@ server.registerTool("deploy_token", {
|
|
|
1341
1346
|
});
|
|
1342
1347
|
// Tool: list_launch_venues
|
|
1343
1348
|
//
|
|
1344
|
-
//
|
|
1345
|
-
//
|
|
1346
|
-
//
|
|
1347
|
-
// numbers for every venue (trader fee and creator share) — ranking on the
|
|
1348
|
-
// creator's share alone favours the launchpad that charges traders most — and do
|
|
1349
|
-
// not call any venue "best" or "recommended".
|
|
1349
|
+
// Chain Daddy's own launchpad and what it pays, per chain (ownOnly=true): an
|
|
1350
|
+
// agent never points a person at another launch platform (owner rule,
|
|
1351
|
+
// 2026-10-03). Present BOTH numbers (trader fee and creator share).
|
|
1350
1352
|
server.registerTool("list_launch_venues", {
|
|
1351
|
-
title: "List launch
|
|
1352
|
-
description: "
|
|
1353
|
+
title: "List Chain Daddy's launch options",
|
|
1354
|
+
description: "Chain Daddy's launchpad and what it pays, per chain. The launchpad (chaindaddy-launchpad) creates the token with its own locked trading pool, on EVM chains and on Solana (Meteora DBC): nothing up front, and the token's registration is free. Every economics field is basis points of trade volume (100 = $1 per $100 traded); show both traderFeeBps (what traders pay) and creatorBps (what reaches the creator). For a plain token with no pool, Chain Daddy's token factory (chaindaddy-classic, a one-time fee, registration included) is the other option. Recommend the launchpad unless the person wants a plain token.\n\nParams: chain_id (number?) — narrow to one chain (EVM chain id; Solana mainnet 900900), session_token (string?) — a signed-in session sees its own plan's rates\nReturns: {venues: [...], sort, generatedAt}\nErrors: API_ERROR",
|
|
1353
1355
|
inputSchema: {
|
|
1354
|
-
chain_id: z.number().optional().describe("Only
|
|
1356
|
+
chain_id: z.number().optional().describe("Only this chain"),
|
|
1355
1357
|
session_token: z.string().optional().describe("Optional session token from authenticate"),
|
|
1356
1358
|
},
|
|
1357
1359
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
1358
1360
|
}, async ({ chain_id, session_token }) => {
|
|
1359
1361
|
try {
|
|
1360
1362
|
const jwt = session_token ? sessions.get(session_token)?.jwt : undefined;
|
|
1361
|
-
const q = chain_id ?
|
|
1362
|
-
const data = await apiCall(`/api/v2/launch/venues${q}`, { jwt });
|
|
1363
|
+
const q = chain_id ? `&chainId=${chain_id}` : "";
|
|
1364
|
+
const data = await apiCall(`/api/v2/launch/venues?ownOnly=true${q}`, { jwt });
|
|
1363
1365
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
1364
1366
|
}
|
|
1365
1367
|
catch (error) {
|
|
@@ -1369,92 +1371,6 @@ server.registerTool("list_launch_venues", {
|
|
|
1369
1371
|
};
|
|
1370
1372
|
}
|
|
1371
1373
|
});
|
|
1372
|
-
// Tool: build_venue_launch
|
|
1373
|
-
server.registerTool("build_venue_launch", {
|
|
1374
|
-
title: "Build a third-party launch",
|
|
1375
|
-
description: "Build a token launch on a third-party contract (e.g. 'clanker') through Chain Daddy. Returns an unsigned transaction: the caller's OWN wallet — the session wallet — must sign and send transactions[0] on its chainId, then call submit_venue_launch with the hash. Chain Daddy never sends it. Once proven, the token's crown is free.\n\nParams: venue (string), chain_id (number), name (string), symbol (string), image (string?) — ipfs:// or https:// URL, description (string?), session_token (string)\nReturns: {intent: {id, status, ...}, transactions: [{chainId, to, data, value}]}\nErrors: NO_SESSION, SESSION_EXPIRED, NO_JWT, VENUE_UNAVAILABLE, API_ERROR",
|
|
1376
|
-
inputSchema: {
|
|
1377
|
-
venue: z.string().describe("Venue id from list_launch_venues with freeCrown=true (not chaindaddy-launchpad or chaindaddy-classic)"),
|
|
1378
|
-
chain_id: z.number().describe("Chain to launch on — one the venue lists as launchable"),
|
|
1379
|
-
name: z.string().describe("Token name"),
|
|
1380
|
-
symbol: z.string().describe("Ticker"),
|
|
1381
|
-
image: z.string().optional().describe("Token image URL"),
|
|
1382
|
-
description: z.string().optional().describe("Token description"),
|
|
1383
|
-
session_token: z.string().describe("Session token from authenticate"),
|
|
1384
|
-
},
|
|
1385
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1386
|
-
}, async ({ venue, chain_id, name, symbol, image, description, session_token }) => {
|
|
1387
|
-
try {
|
|
1388
|
-
const { session, jwt } = requireSessionJWT(session_token);
|
|
1389
|
-
const data = await apiPost(`/api/v2/launch/venues/${encodeURIComponent(venue)}/build`, {
|
|
1390
|
-
chainId: chain_id,
|
|
1391
|
-
wallet: session.wallet,
|
|
1392
|
-
name,
|
|
1393
|
-
symbol,
|
|
1394
|
-
...(image ? { image } : {}),
|
|
1395
|
-
...(description ? { description } : {}),
|
|
1396
|
-
}, {}, { jwt });
|
|
1397
|
-
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
1398
|
-
}
|
|
1399
|
-
catch (error) {
|
|
1400
|
-
const message = error.message;
|
|
1401
|
-
const code = message.startsWith("NO_SESSION") ? "NO_SESSION"
|
|
1402
|
-
: message.startsWith("SESSION_EXPIRED") ? "SESSION_EXPIRED"
|
|
1403
|
-
: message.startsWith("NO_JWT") ? "NO_JWT"
|
|
1404
|
-
: /not available|does not launch/.test(message) ? "VENUE_UNAVAILABLE"
|
|
1405
|
-
: "API_ERROR";
|
|
1406
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: code, message }) }], isError: true };
|
|
1407
|
-
}
|
|
1408
|
-
});
|
|
1409
|
-
// Tool: submit_venue_launch
|
|
1410
|
-
server.registerTool("submit_venue_launch", {
|
|
1411
|
-
title: "Report a launch transaction",
|
|
1412
|
-
description: "Report the transaction hash of a launch built by build_venue_launch, after the session wallet sent it. The server proves the launch from the launchpad's own on-chain record; poll get_venue_launch until status is 'confirmed' (freeCrown=true, tokenAddress set) or 'failed' (failureReason says why).\n\nParams: intent_id (string), tx_hash (string), session_token (string)\nReturns: {id, status, tokenAddress?, freeCrown, failureReason?}\nErrors: NO_SESSION, SESSION_EXPIRED, NO_JWT, API_ERROR",
|
|
1413
|
-
inputSchema: {
|
|
1414
|
-
intent_id: z.string().describe("intent.id from build_venue_launch"),
|
|
1415
|
-
tx_hash: z.string().describe("Hash of the sent launch transaction"),
|
|
1416
|
-
session_token: z.string().describe("Session token from authenticate"),
|
|
1417
|
-
},
|
|
1418
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1419
|
-
}, async ({ intent_id, tx_hash, session_token }) => {
|
|
1420
|
-
try {
|
|
1421
|
-
const { jwt } = requireSessionJWT(session_token);
|
|
1422
|
-
const data = await apiPost(`/api/v2/launch/intents/${encodeURIComponent(intent_id)}/submit`, { txHash: tx_hash }, {}, { jwt });
|
|
1423
|
-
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
1424
|
-
}
|
|
1425
|
-
catch (error) {
|
|
1426
|
-
const message = error.message;
|
|
1427
|
-
const code = message.startsWith("NO_SESSION") ? "NO_SESSION"
|
|
1428
|
-
: message.startsWith("SESSION_EXPIRED") ? "SESSION_EXPIRED"
|
|
1429
|
-
: message.startsWith("NO_JWT") ? "NO_JWT"
|
|
1430
|
-
: "API_ERROR";
|
|
1431
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: code, message }) }], isError: true };
|
|
1432
|
-
}
|
|
1433
|
-
});
|
|
1434
|
-
// Tool: get_venue_launch
|
|
1435
|
-
server.registerTool("get_venue_launch", {
|
|
1436
|
-
title: "Get a third-party launch",
|
|
1437
|
-
description: "Read a third-party launch's state. A submitted launch is re-proven on each read.\n\nParams: intent_id (string), session_token (string)\nReturns: {id, status, tokenAddress?, freeCrown, failureReason?}\nErrors: NO_SESSION, SESSION_EXPIRED, NO_JWT, API_ERROR",
|
|
1438
|
-
inputSchema: {
|
|
1439
|
-
intent_id: z.string().describe("intent.id from build_venue_launch"),
|
|
1440
|
-
session_token: z.string().describe("Session token from authenticate"),
|
|
1441
|
-
},
|
|
1442
|
-
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
1443
|
-
}, async ({ intent_id, session_token }) => {
|
|
1444
|
-
try {
|
|
1445
|
-
const { jwt } = requireSessionJWT(session_token);
|
|
1446
|
-
const data = await apiCall(`/api/v2/launch/intents/${encodeURIComponent(intent_id)}`, { jwt });
|
|
1447
|
-
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
1448
|
-
}
|
|
1449
|
-
catch (error) {
|
|
1450
|
-
const message = error.message;
|
|
1451
|
-
const code = message.startsWith("NO_SESSION") ? "NO_SESSION"
|
|
1452
|
-
: message.startsWith("SESSION_EXPIRED") ? "SESSION_EXPIRED"
|
|
1453
|
-
: message.startsWith("NO_JWT") ? "NO_JWT"
|
|
1454
|
-
: "API_ERROR";
|
|
1455
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: code, message }) }], isError: true };
|
|
1456
|
-
}
|
|
1457
|
-
});
|
|
1458
1374
|
// Tool: claim_crown
|
|
1459
1375
|
server.registerTool("claim_crown", {
|
|
1460
1376
|
title: "Build a registration claim action",
|
|
@@ -1752,6 +1668,13 @@ const iapSkuFields = {
|
|
|
1752
1668
|
rule: iapRule.nullable(),
|
|
1753
1669
|
maxSupply: z.number().int().positive().nullable(),
|
|
1754
1670
|
maxPerWallet: z.number().int().positive().nullable(),
|
|
1671
|
+
maxAppSpend: z
|
|
1672
|
+
.number()
|
|
1673
|
+
.int()
|
|
1674
|
+
.positive()
|
|
1675
|
+
.max(1_000_000)
|
|
1676
|
+
.nullable()
|
|
1677
|
+
.describe("Consumables only: lets Crown Apps on the token page spend this item for the wallet that holds it (the `iap-consume` app action, no server), at most this many per request. null (the default): no app can, only a store key with iap_consume. Leave it null for anything redeemed for a prize or off-platform value"),
|
|
1755
1678
|
saleStartsAt: z.string().nullable(),
|
|
1756
1679
|
saleEndsAt: z.string().nullable(),
|
|
1757
1680
|
status: z.enum(["draft", "active", "archived"]),
|
|
@@ -1837,27 +1760,92 @@ async function iapWrite(noun, method, path, body, auth, session_token, idempoten
|
|
|
1837
1760
|
return iapFail(error, key);
|
|
1838
1761
|
}
|
|
1839
1762
|
}
|
|
1763
|
+
function parsePage(input) {
|
|
1764
|
+
const raw = input.trim();
|
|
1765
|
+
const isUrl = raw.includes("://") || /^([a-z0-9-]+\.)+[a-z]{2,}(:\d+)?(\/|$)/i.test(raw);
|
|
1766
|
+
let segs = (isUrl ? new URL(raw.includes("://") ? raw : `https://${raw}`).pathname : raw).split("/").filter(Boolean).map((p) => decodeURIComponent(p));
|
|
1767
|
+
if (segs[0] === "_")
|
|
1768
|
+
segs = segs.slice(1);
|
|
1769
|
+
const symbol = (segs[0] ?? "").replace(/^\$/, "").toUpperCase();
|
|
1770
|
+
if (!symbol || symbol.startsWith("_") || segs.length > 2 || /^\d+$/.test(segs[1] ?? "")) {
|
|
1771
|
+
throw new IapError("INVALID_PARAMS", `"${input}" is not a token page: pass its symbol (FLUXGP) or its address (chaindaddy.io/FLUXGP/base), or chain_key with crown_id`, 400);
|
|
1772
|
+
}
|
|
1773
|
+
return { symbol, chain: segs[1] };
|
|
1774
|
+
}
|
|
1775
|
+
async function resolvePage(page, chain_key) {
|
|
1776
|
+
const ref = parsePage(page);
|
|
1777
|
+
const wanted = (ref.chain ?? chain_key ?? "").toLowerCase();
|
|
1778
|
+
const [listed, table] = (await Promise.all([
|
|
1779
|
+
apiCall(`/api/v2/symbols/${enc(ref.symbol)}/registrations`),
|
|
1780
|
+
apiCall("/api/v2/config/chains"),
|
|
1781
|
+
]));
|
|
1782
|
+
const names = new Map((table.chains ?? []).map((c) => [c.caip2.toLowerCase(), c]));
|
|
1783
|
+
const keyOf = (r) => names.get(r.chainKey.toLowerCase())?.key ?? r.chainKey;
|
|
1784
|
+
const all = Object.values(listed.crownsByChain ?? {}).flatMap((rows) => rows ?? []);
|
|
1785
|
+
const rows = wanted
|
|
1786
|
+
? all.filter((r) => [keyOf(r), r.chainKey, r.chainName, names.get(r.chainKey.toLowerCase())?.legacyId ?? ""].some((v) => v.toLowerCase() === wanted))
|
|
1787
|
+
: all;
|
|
1788
|
+
const list = (rs) => rs.map((r) => `${keyOf(r)} #${r.tokenId}`).join(", ");
|
|
1789
|
+
if (rows.length === 1)
|
|
1790
|
+
return { chainKey: keyOf(rows[0]), crownId: rows[0].tokenId };
|
|
1791
|
+
if (all.length === 0)
|
|
1792
|
+
throw new IapError("PAGE_NOT_FOUND", `there is no token page for ${ref.symbol}`, 404);
|
|
1793
|
+
if (rows.length === 0)
|
|
1794
|
+
throw new IapError("PAGE_NOT_FOUND", `${ref.symbol} has no page on ${wanted}; its pages: ${list(all)}`, 404);
|
|
1795
|
+
throw new IapError("PAGE_AMBIGUOUS", `${ref.symbol} is ${rows.length} token pages (${list(rows)}); pass chain_key with crown_id, or the page's address with its chain`, 409);
|
|
1796
|
+
}
|
|
1797
|
+
/** chain_key + crown_id, or the page they are looked up from. */
|
|
1798
|
+
async function pageInput(a, required) {
|
|
1799
|
+
if (a.page) {
|
|
1800
|
+
if (a.crown_id !== undefined)
|
|
1801
|
+
throw new IapError("INVALID_PARAMS", "pass page, or chain_key with crown_id, not both", 400);
|
|
1802
|
+
return resolvePage(a.page, a.chain_key);
|
|
1803
|
+
}
|
|
1804
|
+
if (required && (!a.chain_key || a.crown_id === undefined)) {
|
|
1805
|
+
throw new IapError("INVALID_PARAMS", "pass page (the token page's symbol or address), or chain_key with crown_id", 400);
|
|
1806
|
+
}
|
|
1807
|
+
return { chainKey: a.chain_key, crownId: a.crown_id };
|
|
1808
|
+
}
|
|
1809
|
+
const iapPage = z.string().optional().describe("The token page, instead of chain_key and crown_id: its symbol ('FLUXGP') or its address ('chaindaddy.io/FLUXGP/base')");
|
|
1840
1810
|
server.registerTool("iap_open_store", {
|
|
1841
1811
|
title: "Open a creator store",
|
|
1842
|
-
description: "Open (or re-open) the creator store for a token page. Idempotent per token page. Needs a Developer plan or Developer Beta membership, and the manager role on the token page.\n\nParams: chain_key (string, e.g. 'base')
|
|
1812
|
+
description: "Open (or re-open) the creator store for a token page. Idempotent per token page. Needs a Developer plan or Developer Beta membership, and the manager role on the token page.\n\nParams: page (the token page's symbol or address, e.g. 'FLUXGP' or 'chaindaddy.io/FLUXGP/base') OR chain_key (string, e.g. 'base') with crown_id (number, the token page's registration id); session_token?, idempotency_key?\nReturns: {ok, idempotency_key, store: {id, chainId, chainKey, crownId, symbol, tokenAddress, tokenDecimals, tokenOrigin, feeBps, payoutAddress, status, live, …}}\nErrors: NO_CREDENTIALS, NO_SESSION, NO_JWT, IAP_PLAN_REQUIRED, INVALID_PARAMS, PAGE_NOT_FOUND, PAGE_AMBIGUOUS (the symbol has several pages; the message lists them), HTTP_403, HTTP_404",
|
|
1843
1813
|
inputSchema: {
|
|
1844
|
-
|
|
1845
|
-
|
|
1814
|
+
page: iapPage,
|
|
1815
|
+
chain_key: z.string().optional().describe("Chain key of the token page, e.g. 'base', 'solana' (with crown_id; or with page, to pick one of a symbol's pages)"),
|
|
1816
|
+
crown_id: z.number().int().nonnegative().optional().describe("Registration id of the token page (with chain_key)"),
|
|
1846
1817
|
session_token: iapSessionToken,
|
|
1847
1818
|
idempotency_key: iapIdempotencyKey,
|
|
1848
1819
|
},
|
|
1849
1820
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1850
|
-
}, async ({ chain_key, crown_id, session_token, idempotency_key }) =>
|
|
1821
|
+
}, async ({ page, chain_key, crown_id, session_token, idempotency_key }) => {
|
|
1822
|
+
try {
|
|
1823
|
+
const at = await pageInput({ page, chain_key, crown_id }, true);
|
|
1824
|
+
return iapWrite("store", "POST", "/stores", at, "account", session_token, idempotency_key);
|
|
1825
|
+
}
|
|
1826
|
+
catch (error) {
|
|
1827
|
+
return iapFail(error);
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1851
1830
|
server.registerTool("iap_list_stores", {
|
|
1852
1831
|
title: "List creator stores",
|
|
1853
|
-
description: "List the creator stores you manage, optionally filtered to one token page.\n\nParams: chain_key?, crown_id?, session_token?\nReturns: {entries: Store[]}\nErrors: NO_CREDENTIALS, IAP_PLAN_REQUIRED",
|
|
1832
|
+
description: "List the creator stores you manage, optionally filtered to one token page. Each entry's id is the store_id the other iap_ tools take.\n\nParams: page? (the token page's symbol or address), chain_key?, crown_id?, session_token?\nReturns: {entries: Store[]}\nErrors: NO_CREDENTIALS, IAP_PLAN_REQUIRED, PAGE_NOT_FOUND, PAGE_AMBIGUOUS",
|
|
1854
1833
|
inputSchema: {
|
|
1834
|
+
page: iapPage,
|
|
1855
1835
|
chain_key: z.string().optional(),
|
|
1856
1836
|
crown_id: z.number().int().nonnegative().optional(),
|
|
1857
1837
|
session_token: iapSessionToken,
|
|
1858
1838
|
},
|
|
1859
1839
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
1860
|
-
}, async ({ chain_key, crown_id, session_token }) =>
|
|
1840
|
+
}, async ({ page, chain_key, crown_id, session_token }) => {
|
|
1841
|
+
try {
|
|
1842
|
+
const at = await pageInput({ page, chain_key, crown_id }, false);
|
|
1843
|
+
return iapRead(`/stores${iapQuery({ chainKey: at.chainKey, crownId: at.crownId })}`, "account", session_token);
|
|
1844
|
+
}
|
|
1845
|
+
catch (error) {
|
|
1846
|
+
return iapFail(error);
|
|
1847
|
+
}
|
|
1848
|
+
});
|
|
1861
1849
|
server.registerTool("iap_list_skus", {
|
|
1862
1850
|
title: "List store items",
|
|
1863
1851
|
description: "List a store's SKUs, including drafts and archived ones.\n\nParams: store_id, session_token?\nReturns: {entries: Sku[]} (price is base units of the SKU's currency — currency, currencyAddress, currencySymbol, currencyDecimals; null = not for sale)\nErrors: NO_CREDENTIALS, IAP_PLAN_REQUIRED, HTTP_404",
|
|
@@ -1866,7 +1854,7 @@ server.registerTool("iap_list_skus", {
|
|
|
1866
1854
|
}, async ({ store_id, session_token }) => iapRead(`/stores/${enc(store_id)}/skus`, "store", session_token));
|
|
1867
1855
|
server.registerTool("iap_create_sku", {
|
|
1868
1856
|
title: "Create a store item",
|
|
1869
|
-
description: "Create a SKU. Types: consumable (a balance: coins, boosts), non_consumable (owned once: skins, cars), timed (expiry extended by durationSeconds × quantity: season pass), unlock (never sold; held while `rule` holds). No price = reward-only. Price in base units (`price`) or in dollars (`priceUsdMicros`, with optional per-unit `minPrice`/`maxPrice` bounds in base units), never both. `currency` is what the buyer pays in: 'token' (default; recommended with priceUsdMicros — a steady dollar price, paid in the creator's token), 'native' or 'usdc'. GET /api/v2/iap/config lists each chain's currencies. `grants` makes a bundle. `splits` pays up to 2 more wallets (3 on a launchpad token) a share of each sale. New SKUs are drafts unless status is 'active'. Store key scope: catalog:write.\n\nParams: store_id, sku ({sku, type, name, currency?, price?, priceUsdMicros?, minPrice?, maxPrice?, grants?, splits?, durationSeconds?, rule?, maxSupply?, maxPerWallet?, saleStartsAt?, saleEndsAt?, status?, metadata?, description?, imageUrl?, sortOrder?}), session_token?, idempotency_key?\nReturns: {ok, idempotency_key, sku: Sku}\nErrors: NO_CREDENTIALS, IAP_PLAN_REQUIRED, HTTP_400 (validation), HTTP_409 (sku key taken)",
|
|
1857
|
+
description: "Create a SKU. Types: consumable (a balance: coins, boosts), non_consumable (owned once: skins, cars), timed (expiry extended by durationSeconds × quantity: season pass), unlock (never sold; held while `rule` holds). No price = reward-only. Price in base units (`price`) or in dollars (`priceUsdMicros`, with optional per-unit `minPrice`/`maxPrice` bounds in base units), never both. `currency` is what the buyer pays in: 'token' (default; recommended with priceUsdMicros — a steady dollar price, paid in the creator's token), 'native' or 'usdc'. GET /api/v2/iap/config lists each chain's currencies. `grants` makes a bundle. `splits` pays up to 2 more wallets (3 on a launchpad token) a share of each sale. New SKUs are drafts unless status is 'active'. Store key scope: catalog:write.\n\nParams: store_id, sku ({sku, type, name, currency?, price?, priceUsdMicros?, minPrice?, maxPrice?, grants?, splits?, durationSeconds?, rule?, maxSupply?, maxPerWallet?, maxAppSpend?, saleStartsAt?, saleEndsAt?, status?, metadata?, description?, imageUrl?, sortOrder?}), session_token?, idempotency_key?\nReturns: {ok, idempotency_key, sku: Sku}\nErrors: NO_CREDENTIALS, IAP_PLAN_REQUIRED, HTTP_400 (validation), HTTP_409 (sku key taken)",
|
|
1870
1858
|
inputSchema: { store_id: iapStoreId, sku: iapSkuInput, session_token: iapSessionToken, idempotency_key: iapIdempotencyKey },
|
|
1871
1859
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1872
1860
|
}, async ({ store_id, sku, session_token, idempotency_key }) => iapWrite("sku", "POST", `/stores/${enc(store_id)}/skus`, sku, "store", session_token, idempotency_key));
|
|
@@ -2148,6 +2136,48 @@ server.resource("chaindaddy://tools", "How the tools fit together: sign-in, crow
|
|
|
2148
2136
|
}],
|
|
2149
2137
|
}));
|
|
2150
2138
|
// ============================================================================
|
|
2139
|
+
// SKILLS
|
|
2140
|
+
// ============================================================================
|
|
2141
|
+
// Chain Daddy's Agent Skills (the package's skills/ directory, the same files
|
|
2142
|
+
// the remote server embeds and the plugin copies) are served as prompts, one
|
|
2143
|
+
// per skill, and through two read-only tools for clients that only call tools.
|
|
2144
|
+
const SKILLS = loadSkills();
|
|
2145
|
+
for (const skill of SKILLS) {
|
|
2146
|
+
server.registerPrompt(skill.name, { title: skill.title, description: skill.description }, () => ({
|
|
2147
|
+
description: skill.description,
|
|
2148
|
+
messages: [{ role: "user", content: { type: "text", text: skill.body } }],
|
|
2149
|
+
}));
|
|
2150
|
+
}
|
|
2151
|
+
server.registerTool("list_skills", {
|
|
2152
|
+
title: "List the skills",
|
|
2153
|
+
description: "List Chain Daddy's skills: step-by-step workflows for launching or finding a token, checking its health, exporting a listing, selling items for it, signing users in, verifying receipts and building Crown Apps. Read one with read_skill before doing that task. The same skills are this server's prompts.",
|
|
2154
|
+
inputSchema: {},
|
|
2155
|
+
outputSchema: { skills: z.array(z.object({ name: z.string(), title: z.string(), description: z.string() })) },
|
|
2156
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
2157
|
+
}, async () => ({
|
|
2158
|
+
content: [{
|
|
2159
|
+
type: "text",
|
|
2160
|
+
text: "Chain Daddy skills (read one with read_skill):\n" +
|
|
2161
|
+
SKILLS.map((s) => `\n- ${s.name} (${s.title}): ${s.description}`).join(""),
|
|
2162
|
+
}],
|
|
2163
|
+
structuredContent: { skills: SKILLS.map(({ name, title, description }) => ({ name, title, description })) },
|
|
2164
|
+
}));
|
|
2165
|
+
server.registerTool("read_skill", {
|
|
2166
|
+
title: "Read a skill",
|
|
2167
|
+
description: "Read one Chain Daddy skill in full, as Markdown: the workflow, the tools it uses and the rules it keeps. Pass a name from list_skills, and follow it for that task.",
|
|
2168
|
+
inputSchema: { name: z.string().describe("A skill name from list_skills.") },
|
|
2169
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
2170
|
+
}, async ({ name }) => {
|
|
2171
|
+
const skill = SKILLS.find((s) => s.name === name.trim());
|
|
2172
|
+
if (!skill) {
|
|
2173
|
+
return {
|
|
2174
|
+
content: [{ type: "text", text: `no skill is named ${JSON.stringify(name)}; the skills are: ${SKILLS.map((s) => s.name).join(", ")}` }],
|
|
2175
|
+
isError: true,
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
return { content: [{ type: "text", text: skill.body }] };
|
|
2179
|
+
});
|
|
2180
|
+
// ============================================================================
|
|
2151
2181
|
// START SERVER
|
|
2152
2182
|
// ============================================================================
|
|
2153
2183
|
async function main() {
|