@molpha/mcp 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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +270 -0
  3. package/dist/cli/doctor.js +27 -0
  4. package/dist/cli/provision.js +84 -0
  5. package/dist/src/apiconfig.js +47 -0
  6. package/dist/src/artifacts.js +80 -0
  7. package/dist/src/clients.js +51 -0
  8. package/dist/src/config.js +102 -0
  9. package/dist/src/determinism.js +31 -0
  10. package/dist/src/env.js +14 -0
  11. package/dist/src/errors.js +104 -0
  12. package/dist/src/feed.js +53 -0
  13. package/dist/src/guardrails.js +62 -0
  14. package/dist/src/hex.js +45 -0
  15. package/dist/src/mcp.js +82 -0
  16. package/dist/src/sdk.js +12 -0
  17. package/dist/src/server.js +17 -0
  18. package/dist/src/setup-validation.js +340 -0
  19. package/dist/src/signer/backends/memory.js +41 -0
  20. package/dist/src/signer/backends/privy.js +48 -0
  21. package/dist/src/signer/backends/turnkey.js +53 -0
  22. package/dist/src/signer/factory.js +37 -0
  23. package/dist/src/signer/types.js +1 -0
  24. package/dist/src/solana-address.js +29 -0
  25. package/dist/src/solana-compat.js +22 -0
  26. package/dist/src/submit.js +46 -0
  27. package/dist/src/subscription.js +48 -0
  28. package/dist/src/tools/agent_status.js +26 -0
  29. package/dist/src/tools/derive_feed.js +39 -0
  30. package/dist/src/tools/describe_feed.js +44 -0
  31. package/dist/src/tools/execute.js +60 -0
  32. package/dist/src/tools/fetch_verified.js +116 -0
  33. package/dist/src/tools/get_capabilities.js +45 -0
  34. package/dist/src/tools/get_latest.js +22 -0
  35. package/dist/src/tools/index.js +19 -0
  36. package/dist/src/tools/schemas.js +9 -0
  37. package/dist/src/tools/types.js +1 -0
  38. package/dist/src/tools/verify.js +28 -0
  39. package/dist/src/verifiers.js +95 -0
  40. package/dist/src/x402.js +552 -0
  41. package/package.json +81 -0
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+ import { getMolphaContext } from "../clients.js";
3
+ import { toolHandler } from "../mcp.js";
4
+ import { fetchAgentStatus } from "../x402.js";
5
+ import {} from "./types.js";
6
+ export function registerAgentStatusTool(server) {
7
+ server.registerTool("molpha_agent_status", {
8
+ title: "Get x402 agent escrow status",
9
+ description: "Read the caller's x402 agent escrow (advisory: USDC ATA balance, amount committed to unsettled rounds, unsettled round count) and the next round's quoted price for a given quorum. Escrow is derived server-side per (payer, gateway). A non-existent escrow is normal pre-first-round — it's created lazily. Call before payment: \"x402\" fetches to see whether the escrow is already funded, or to pre-fund it out of band.",
10
+ inputSchema: {
11
+ signaturesRequired: z.number().int().positive().max(255).default(1)
12
+ }
13
+ }, toolHandler(async ({ signaturesRequired }) => {
14
+ const { config, signer } = await getMolphaContext();
15
+ const status = await fetchAgentStatus(config, signer.publicKey, signaturesRequired);
16
+ return {
17
+ signaturesRequired,
18
+ caps: {
19
+ maxPriceUsdcAtomic: config.x402.maxPriceUsdcAtomic.toString(),
20
+ maxSpendPerDayUsdcAtomic: config.x402.maxSpendPerDayUsdcAtomic.toString()
21
+ },
22
+ ...status,
23
+ payer: signer.publicKey
24
+ };
25
+ }));
26
+ }
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+ import { deriveFeedId } from "../apiconfig.js";
3
+ import { getMolphaContext } from "../clients.js";
4
+ import { checkApiConfigDeterminism } from "../determinism.js";
5
+ import { toolHandler } from "../mcp.js";
6
+ import { requireSdkExport } from "../sdk.js";
7
+ import { apiConfigSchema } from "./schemas.js";
8
+ import {} from "./types.js";
9
+ export function registerDeriveFeedTool(server) {
10
+ server.registerTool("molpha_derive_feed", {
11
+ title: "Derive Molpha feedId",
12
+ description: "Locally derive the feedId for a declarative spec (apiConfig + quorum) — no transaction, no subscription required. feedId = keccak256(owner || apiConfigHash || signaturesRequired); the feed itself is created lazily on-chain at first settle (subscription or x402 round). Call this to preview a feedId before molpha_fetch_verified, or to check what feedId a given spec resolves to for the current signer. Prefer settled/finalized data — independent nodes must converge on a byte-identical value to co-sign.",
13
+ inputSchema: {
14
+ apiConfig: apiConfigSchema,
15
+ signaturesRequired: z.number().int().positive().max(255),
16
+ rejectNonDeterministic: z.boolean().optional()
17
+ }
18
+ }, toolHandler(async ({ apiConfig, signaturesRequired, rejectNonDeterministic = false }) => {
19
+ const { signer } = await getMolphaContext();
20
+ const determinism = checkApiConfigDeterminism(apiConfig);
21
+ if (!determinism.ok && determinism.warnings.some((w) => w.includes("required"))) {
22
+ throw new Error(determinism.warnings.join("; "));
23
+ }
24
+ if (rejectNonDeterministic && determinism.warnings.length > 0) {
25
+ throw new Error(`Non-deterministic source rejected: ${determinism.warnings.join("; ")}`);
26
+ }
27
+ const bytesToHex = requireSdkExport("bytesToHex");
28
+ const { feedId, apiConfigHash, canonicalApiConfig } = deriveFeedId(apiConfig, signaturesRequired, signer.publicKey);
29
+ return {
30
+ feedId,
31
+ apiConfigHash: bytesToHex(apiConfigHash),
32
+ canonicalApiConfig,
33
+ signaturesRequired,
34
+ owner: signer.publicKey,
35
+ determinismWarnings: determinism.warnings.length > 0 ? determinism.warnings : undefined,
36
+ note: "No transaction was sent. The feed account is created lazily on first settle via molpha_fetch_verified (subscription or x402)."
37
+ };
38
+ }));
39
+ }
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+ import { deriveFeedId } from "../apiconfig.js";
3
+ import { getMolphaContext, requireMethod } from "../clients.js";
4
+ import { settle } from "../errors.js";
5
+ import { describeValueEncoding, presentFeed } from "../feed.js";
6
+ import { normalizeFeedId } from "../hex.js";
7
+ import { toolHandler } from "../mcp.js";
8
+ import { apiConfigSchema } from "./schemas.js";
9
+ import {} from "./types.js";
10
+ export function registerDescribeFeedTool(server) {
11
+ server.registerTool("molpha_describe_feed", {
12
+ title: "Describe Molpha feed",
13
+ description: "Read a feed's on-chain state (last committed value, registryVersion, signaturesRequired) and the caller's subscription status. Pass feedId directly, or apiConfig + signaturesRequired to derive it first (see molpha_derive_feed). A missing feed account is normal pre-first-settle — feeds are created lazily. `feed.valueKind` is the attested encoding of the stored bytes (\"value\" = raw payload, \"hash\" = keccak digest), NOT a scale hint: Molpha attests no decimals on-chain. When apiConfig is supplied, `valueEncoding` reports the off-chain valueTransform that produced the number, explicitly flagged as unattested.",
14
+ inputSchema: {
15
+ feedId: z.string().min(1).optional(),
16
+ apiConfig: apiConfigSchema.optional(),
17
+ signaturesRequired: z.number().int().positive().max(255).optional()
18
+ }
19
+ }, toolHandler(async ({ feedId, apiConfig, signaturesRequired }) => {
20
+ const { config, solana, signer } = await getMolphaContext();
21
+ let resolvedFeedId = feedId;
22
+ if (!resolvedFeedId) {
23
+ if (!apiConfig || signaturesRequired === undefined) {
24
+ throw new Error("either feedId, or apiConfig + signaturesRequired, is required");
25
+ }
26
+ resolvedFeedId = deriveFeedId(apiConfig, signaturesRequired, signer.publicKey).feedId;
27
+ }
28
+ const [onChainFeed, subscription] = await Promise.all([
29
+ settle("solana.readFeed", async () => requireMethod(solana, "readFeed")(normalizeFeedId(resolvedFeedId))),
30
+ settle("solana.readSubscription", async () => requireMethod(solana, "readSubscription")())
31
+ ]);
32
+ return {
33
+ feedId: resolvedFeedId,
34
+ feed: onChainFeed.ok ? presentFeed(onChainFeed.value) : onChainFeed,
35
+ ...(apiConfig ? { valueEncoding: describeValueEncoding(apiConfig.valueTransform) } : {}),
36
+ subscription,
37
+ chains: {
38
+ solana: "devnet (canonical state)",
39
+ evm: config.evmNetworks,
40
+ starknet: config.starknetNetworks
41
+ }
42
+ };
43
+ }));
44
+ }
@@ -0,0 +1,60 @@
1
+ import { z } from "zod";
2
+ import { getMolphaContext } from "../clients.js";
3
+ import { prepareSignedResult, previewSubmit, submitSignedResult } from "../submit.js";
4
+ import { toolHandler } from "../mcp.js";
5
+ import {} from "./types.js";
6
+ /** The flat gateway/SDK shape. */
7
+ const flatResultSchema = z.object({
8
+ feedId: z.string().min(1),
9
+ value: z.string().optional(),
10
+ valuePacked: z.string().optional(),
11
+ timestamp: z.number().int(),
12
+ registryVersion: z.number().int(),
13
+ signaturesRequired: z.number().int(),
14
+ signersBitmap: z.string().min(1),
15
+ s: z.string().min(1),
16
+ commitmentAddr: z.string().min(1),
17
+ fresh: z.boolean().optional()
18
+ });
19
+ /** The artifact shape `molpha_fetch_verified` returns, accepted verbatim. */
20
+ const artifactResultSchema = z.object({
21
+ value: z.string().optional(),
22
+ fresh: z.boolean().optional(),
23
+ dataUpdate: z.object({
24
+ feedId: z.string().min(1),
25
+ registryVersion: z.number().int(),
26
+ signaturesRequired: z.number().int(),
27
+ value: z.string().optional(),
28
+ valuePacked: z.string().optional(),
29
+ canonicalTimestamp: z.number().int()
30
+ }),
31
+ signature: z.object({
32
+ signature: z.string().min(1),
33
+ commitment: z.string().min(1),
34
+ signersBitmap: z.string().min(1)
35
+ })
36
+ });
37
+ // Extra keys (`payment`, `trustAnchor`, `verifierArgs`) ride along on a pasted
38
+ // fetch_verified response; passthrough keeps that from being a validation error.
39
+ const signedResultSchema = z.union([
40
+ artifactResultSchema.passthrough(),
41
+ flatResultSchema.passthrough()
42
+ ]);
43
+ export function registerExecuteTool(server) {
44
+ server.registerTool("molpha_execute", {
45
+ title: "Execute Molpha data update on Solana",
46
+ description: "Submit a signed DataUpdate to the Solana feed via submit_data_update. Pass the output of molpha_fetch_verified through unmodified — both the artifact shape ({ dataUpdate, signature }) and the flat shape ({ s, commitmentAddr, timestamp }) are accepted, and short hex fields are zero-padded server-side. Permissionless on-chain; the owner key pays SOL fees. EVM/Starknet execution is deliberately out of scope (see molpha_verify) — use the verifier args from molpha_fetch_verified and call verify() yourself.",
47
+ inputSchema: {
48
+ result: signedResultSchema,
49
+ dryRun: z.boolean().optional()
50
+ }
51
+ }, toolHandler(async ({ result, dryRun }) => {
52
+ const { config, signer } = await getMolphaContext();
53
+ const isDryRun = dryRun ?? config.guardrails.dryRunDefault;
54
+ const prepared = prepareSignedResult(result);
55
+ if (isDryRun) {
56
+ return previewSubmit("molpha_execute", prepared, String(signer.publicKey));
57
+ }
58
+ return submitSignedResult(prepared);
59
+ }));
60
+ }
@@ -0,0 +1,116 @@
1
+ import { z } from "zod";
2
+ import { deriveFeedId } from "../apiconfig.js";
3
+ import { normalizeSignedResult, toDataUpdateArtifact } from "../artifacts.js";
4
+ import { getMolphaContext, requireMethod } from "../clients.js";
5
+ import {} from "../config.js";
6
+ import { settle } from "../errors.js";
7
+ import { normalizeFeedId } from "../hex.js";
8
+ import { toolHandler } from "../mcp.js";
9
+ import { prepareSignedResult, submitSignedResult } from "../submit.js";
10
+ import { readSubscriptionStatus } from "../subscription.js";
11
+ import { buildVerifierArgsForChains } from "../verifiers.js";
12
+ import { agentFetch } from "../x402.js";
13
+ import { apiConfigSchema } from "./schemas.js";
14
+ import {} from "./types.js";
15
+ const chainSchema = z.enum(["evm", "starknet", "solana"]);
16
+ const paymentSchema = z.enum(["auto", "subscription", "x402"]);
17
+ export function registerFetchVerifiedTool(server) {
18
+ server.registerTool("molpha_fetch_verified", {
19
+ title: "Fetch verified Molpha data",
20
+ description: "Trigger a signing round for a feed and return the self-contained signed payload PLUS prebuilt verifier arguments for each requested chain. The signed payload is the trust anchor — verify it or forward it to a contract; do not consume `value` alone. Only the `solana` leg can be settled from this server (via `autoSubmit`, or by passing this tool's output to molpha_execute unmodified); `evm` and `starknet` return contract-ready calldata only — executing verify() there is the agent's job by design (see molpha_verify). `payment` selects how the round is paid for: \"subscription\" uses the caller's active USDC subscription (fails if inactive), \"x402\" self-funds a per-request escrow (auto-funds up to the MOLPHA_X402_MAX_PRICE_USDC / MOLPHA_X402_MAX_SPEND_PER_DAY_USDC caps), and \"auto\" (default) uses the subscription when active and falls back to x402 otherwise. feedId is derived from apiConfig + signaturesRequired + the signer's pubkey when omitted (see molpha_derive_feed).",
21
+ inputSchema: {
22
+ apiConfig: apiConfigSchema,
23
+ signaturesRequired: z.number().int().positive().max(255).default(1),
24
+ feedId: z.string().min(1).optional(),
25
+ maxAge: z.number().int().nonnegative().optional(),
26
+ chains: z.array(chainSchema).min(1),
27
+ encryptSecrets: z.record(z.string()).optional(),
28
+ payment: paymentSchema.optional(),
29
+ autoSubmit: z
30
+ .boolean()
31
+ .optional()
32
+ .describe("Submit the signed DataUpdate to Solana in the same call, so a round-trip settle is one call instead of two. Requires \"solana\" in chains. Honours dryRun and the daily execute cap; a failed submit still returns the signed artifact so it can be retried via molpha_execute."),
33
+ dryRun: z.boolean().optional()
34
+ }
35
+ }, toolHandler(async ({ apiConfig, signaturesRequired, feedId, maxAge, chains, encryptSecrets, payment = "auto", autoSubmit = false, dryRun }) => {
36
+ const { config, gateway, solana, signer, connection } = await getMolphaContext();
37
+ const isDryRun = dryRun ?? config.guardrails.dryRunDefault;
38
+ const resolvedFeedId = resolveFeedId(feedId, apiConfig, signaturesRequired, signer.publicKey);
39
+ if (autoSubmit && !chains.includes("solana")) {
40
+ throw new Error("autoSubmit settles on Solana; include \"solana\" in chains (EVM/Starknet have no in-MCP execution path).");
41
+ }
42
+ const resolvedPayment = payment === "auto" ? (await readSubscriptionStatus(solana)).active ? "subscription" : "x402" : payment;
43
+ if (resolvedPayment === "x402" && encryptSecrets) {
44
+ throw new Error("encryptSecrets is not yet supported on the x402 payment path; use payment: \"subscription\", or omit encryptSecrets.");
45
+ }
46
+ if (resolvedPayment === "subscription") {
47
+ if (isDryRun) {
48
+ return {
49
+ dryRun: true,
50
+ action: "molpha_fetch_verified",
51
+ payment: "subscription",
52
+ feedId: resolvedFeedId,
53
+ signaturesRequired,
54
+ ...(autoSubmit ? { autoSubmit: "would submit the signed DataUpdate to Solana" } : {})
55
+ };
56
+ }
57
+ const requestSignedData = requireMethod(gateway, "requestSignedData");
58
+ const result = await requestSignedData({
59
+ feedId: normalizeFeedId(resolvedFeedId),
60
+ signaturesRequired,
61
+ apiConfig,
62
+ ...(maxAge !== undefined ? { maxAge } : {}),
63
+ ...(encryptSecrets ? { encrypt: { secrets: encryptSecrets } } : {})
64
+ });
65
+ return buildResult(result, chains, config, "subscription", autoSubmit);
66
+ }
67
+ const result = await agentFetch({ config, connection, signer, solana }, {
68
+ apiConfig,
69
+ signaturesRequired,
70
+ feedId: resolvedFeedId,
71
+ ...(maxAge !== undefined ? { maxAge } : {}),
72
+ ...(isDryRun ? { dryRun: true } : {})
73
+ });
74
+ if (isDryRun) {
75
+ return {
76
+ payment: "x402",
77
+ ...result,
78
+ ...(autoSubmit ? { autoSubmit: "would submit the signed DataUpdate to Solana" } : {})
79
+ };
80
+ }
81
+ return buildResult(result, chains, config, "x402", autoSubmit);
82
+ }));
83
+ }
84
+ async function buildResult(result, chains, config, payment, autoSubmit) {
85
+ // Canonicalize once: the gateway emits minimal hex (a one-signer bitmap comes
86
+ // back as "4"), which both the verifier-arg builders and submit_data_update
87
+ // reject at their fixed widths.
88
+ const normalized = normalizeSignedResult(result);
89
+ const artifact = toDataUpdateArtifact(normalized);
90
+ const out = {
91
+ payment,
92
+ ...artifact,
93
+ trustAnchor: "Consume the signed dataUpdate + signature (and verify or forward). Do not trust `value` alone.",
94
+ verifierArgs: buildVerifierArgsForChains(normalized, chains, config)
95
+ };
96
+ if (autoSubmit) {
97
+ // A failed submit must not discard the signed artifact — the caller can
98
+ // retry molpha_execute with the payload it is already holding.
99
+ const submitted = await settle("solana.submitDataUpdate", async () => submitSignedResult(prepareSignedResult(normalized)));
100
+ out.submitted = submitted.ok
101
+ ? submitted.value
102
+ : {
103
+ ok: false,
104
+ ...submitted.error,
105
+ retry: "Pass this response to molpha_execute unmodified to retry the Solana submit."
106
+ };
107
+ }
108
+ return out;
109
+ }
110
+ function resolveFeedId(feedId, apiConfig, signaturesRequired, owner) {
111
+ const derived = deriveFeedId(apiConfig, signaturesRequired, owner).feedId;
112
+ if (feedId !== undefined && normalizeFeedId(feedId) !== normalizeFeedId(derived)) {
113
+ throw new Error(`feedId does not match apiConfig + signaturesRequired for this signer: expected ${derived}, got ${feedId}`);
114
+ }
115
+ return derived;
116
+ }
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ import { getMolphaContext, requireMethod } from "../clients.js";
3
+ import { settle } from "../errors.js";
4
+ import { toolHandler } from "../mcp.js";
5
+ import { getVerifierMetadata } from "../verifiers.js";
6
+ import {} from "./types.js";
7
+ export function registerGetCapabilitiesTool(server) {
8
+ server.registerTool("molpha_get_capabilities", {
9
+ title: "Get Molpha capabilities",
10
+ description: "Returns the current Molpha verification surface: active registryVersion, registered node set, gateway endpoints, supported chains, signing scheme, and x402 spend caps. Call first to learn where a signed result can be verified.",
11
+ inputSchema: {
12
+ includeAbi: z.boolean().optional().describe("Include the EVM verifier ABI in the response.")
13
+ }
14
+ }, toolHandler(async ({ includeAbi = false }) => {
15
+ const { config, gateway, solana } = await getMolphaContext();
16
+ const [nodesResult, registryVersionResult] = await Promise.all([
17
+ settle("gateway.getNodes", async () => requireMethod(gateway, "getNodes")()),
18
+ settle("solana.getRegistryVersion", async () => requireMethod(solana, "getRegistryVersion")())
19
+ ]);
20
+ const nodes = nodesResult.ok ? nodesResult.value : [];
21
+ const registryVersion = registryVersionResult.ok ? registryVersionResult.value : undefined;
22
+ const verifiers = getVerifierMetadata(config, includeAbi);
23
+ return {
24
+ registryVersion,
25
+ signingScheme: "PoP-Schnorr (secp256k1, two-nonce binding)",
26
+ chains: {
27
+ solana: "devnet (canonical state)",
28
+ evm: config.evmNetworks,
29
+ starknet: config.starknetNetworks
30
+ },
31
+ gateways: config.gatewayEndpoints,
32
+ nodeCount: Array.isArray(nodes) ? nodes.length : 0,
33
+ nodes: nodesResult.ok ? nodes : nodesResult,
34
+ solanaRpc: config.solanaRpc,
35
+ verifiers,
36
+ payment: {
37
+ modes: ["subscription", "x402", "auto"],
38
+ x402Caps: {
39
+ maxPriceUsdcAtomic: config.x402.maxPriceUsdcAtomic.toString(),
40
+ maxSpendPerDayUsdcAtomic: config.x402.maxSpendPerDayUsdcAtomic.toString()
41
+ }
42
+ }
43
+ };
44
+ }));
45
+ }
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+ import { getMolphaContext, requireMethod } from "../clients.js";
3
+ import { presentFeed } from "../feed.js";
4
+ import { normalizeFeedId } from "../hex.js";
5
+ import { toolHandler } from "../mcp.js";
6
+ import {} from "./types.js";
7
+ export function registerGetLatestTool(server) {
8
+ server.registerTool("molpha_get_latest", {
9
+ title: "Get latest Molpha feed",
10
+ description: "Read the latest on-chain feed account for a Molpha feedId. `valueKind` is the attested encoding of the stored bytes (\"value\" = raw payload, \"hash\" = keccak digest) — it is not a scale hint. Molpha does not attest decimals on-chain; see molpha_describe_feed's valueEncoding for the (unsigned) apiConfig provenance.",
11
+ inputSchema: {
12
+ feedId: z.string().min(1)
13
+ }
14
+ }, toolHandler(async ({ feedId }) => {
15
+ const { solana } = await getMolphaContext();
16
+ const readFeed = requireMethod(solana, "readFeed");
17
+ return {
18
+ feedId,
19
+ feed: presentFeed(await readFeed(normalizeFeedId(feedId)))
20
+ };
21
+ }));
22
+ }
@@ -0,0 +1,19 @@
1
+ import { registerAgentStatusTool } from "./agent_status.js";
2
+ import { registerDeriveFeedTool } from "./derive_feed.js";
3
+ import { registerDescribeFeedTool } from "./describe_feed.js";
4
+ import { registerExecuteTool } from "./execute.js";
5
+ import { registerFetchVerifiedTool } from "./fetch_verified.js";
6
+ import { registerGetCapabilitiesTool } from "./get_capabilities.js";
7
+ import { registerGetLatestTool } from "./get_latest.js";
8
+ import { registerVerifyTool } from "./verify.js";
9
+ import {} from "./types.js";
10
+ export function registerTools(server) {
11
+ registerGetCapabilitiesTool(server);
12
+ registerDescribeFeedTool(server);
13
+ registerDeriveFeedTool(server);
14
+ registerAgentStatusTool(server);
15
+ registerFetchVerifiedTool(server);
16
+ registerGetLatestTool(server);
17
+ registerVerifyTool(server);
18
+ registerExecuteTool(server);
19
+ }
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ /** Shared apiConfig input shape for tools that accept a declarative feed source. */
3
+ export const apiConfigSchema = z.object({
4
+ url: z.string().min(1),
5
+ method: z.enum(["GET", "POST"]).optional(),
6
+ headers: z.record(z.string()).optional(),
7
+ responseParser: z.string().min(1),
8
+ valueTransform: z.string().optional()
9
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+ import { toSignedResult } from "../artifacts.js";
3
+ import { getMolphaContext } from "../clients.js";
4
+ import { toolHandler } from "../mcp.js";
5
+ import { buildVerifierArgsForChains, getVerifierMetadata } from "../verifiers.js";
6
+ import {} from "./types.js";
7
+ const chainSchema = z.enum(["evm", "starknet"]);
8
+ export function registerVerifyTool(server) {
9
+ server.registerTool("molpha_verify", {
10
+ title: "Verify Molpha result",
11
+ description: "Build the verifier address and call args for a signed DataUpdate on EVM or Starknet. This tool stops at calldata by design, not by omission: the Molpha verifier is stateless, so the agent (or its contract) executes verify() itself and the server never submits an EVM/Starknet transaction or vouches for a result it did not verify on-chain. There is no EVM/Starknet execution path anywhere in this MCP server. For Solana, submit the DataUpdate via molpha_execute (or molpha_fetch_verified autoSubmit) and read it back with molpha_get_latest — there is no separate simulate-verify path. Accepts the dataUpdate/signature objects from molpha_fetch_verified verbatim; short hex fields are zero-padded to their canonical widths server-side.",
12
+ inputSchema: {
13
+ dataUpdate: z.record(z.unknown()),
14
+ signature: z.record(z.unknown()),
15
+ chain: chainSchema,
16
+ includeAbi: z.boolean().optional()
17
+ }
18
+ }, toolHandler(async ({ dataUpdate, signature, chain, includeAbi = false }) => {
19
+ const { config } = await getMolphaContext();
20
+ const result = toSignedResult({ dataUpdate, signature });
21
+ return {
22
+ chain,
23
+ verifierArgs: buildVerifierArgsForChains(result, [chain], config),
24
+ note: "Execute verify() on-chain with the returned args; the MCP server does not assert validity.",
25
+ verifiers: getVerifierMetadata(config, includeAbi)
26
+ };
27
+ }));
28
+ }
@@ -0,0 +1,95 @@
1
+ import {} from "./config.js";
2
+ import { getSdkExport } from "./sdk.js";
3
+ const EVM_NETWORK_CHAIN_IDS = {
4
+ "evm-sepolia": [11155111],
5
+ "arbitrum-sepolia": [421614],
6
+ "avalanche-fuji": [43113],
7
+ "bsc-testnet": [97]
8
+ };
9
+ export function getVerifierMetadata(config, includeAbi = false) {
10
+ return {
11
+ evm: config.evmNetworks.map((network) => resolveEvmVerifierAddress(network)),
12
+ starknet: config.starknetNetworks.map((network) => resolveVerifierAddress("getMolphaStarknetVerifierAddress", network)),
13
+ ...(includeAbi ? { evmAbi: getSdkExport("MOLPHA_VERIFIER_ABI") ?? null } : {})
14
+ };
15
+ }
16
+ export function buildVerifierArgs(result) {
17
+ const errors = [];
18
+ const evm = callBuilder("buildEvmVerifierArgs", result, errors);
19
+ const starknet = callBuilder("buildStarknetVerifierArgs", result, errors);
20
+ return {
21
+ ...(evm !== undefined ? { evm } : {}),
22
+ ...(starknet !== undefined ? { starknet } : {}),
23
+ errors
24
+ };
25
+ }
26
+ export function buildVerifierArgsForChains(result, chains, config) {
27
+ const built = buildVerifierArgs(result);
28
+ const out = {};
29
+ if (chains.includes("evm") && built.evm !== undefined) {
30
+ out.evm = {
31
+ verifier: config.evmNetworks.map((network) => ({
32
+ network,
33
+ address: resolveEvmVerifierAddress(network).address
34
+ })),
35
+ chainIds: config.evmNetworks.flatMap((network) => EVM_NETWORK_CHAIN_IDS[network] ?? []),
36
+ args: built.evm
37
+ };
38
+ }
39
+ if (chains.includes("starknet") && built.starknet !== undefined) {
40
+ const starknetMeta = config.starknetNetworks.map((network) => resolveVerifierAddress("getMolphaStarknetVerifierAddress", network));
41
+ out.starknet = {
42
+ verifier: starknetMeta[0]?.address,
43
+ args: built.starknet
44
+ };
45
+ }
46
+ if (built.errors.length > 0) {
47
+ out.errors = built.errors;
48
+ }
49
+ return out;
50
+ }
51
+ /**
52
+ * The EVM verifier is deployed via CREATE2 — the same address on every
53
+ * supported chain — so this is a single SDK constant rather than a
54
+ * per-network lookup.
55
+ */
56
+ function resolveEvmVerifierAddress(network) {
57
+ const address = getSdkExport("MOLPHA_VERIFIER_ADDRESS");
58
+ if (!address) {
59
+ return { network, error: "MOLPHA_VERIFIER_ADDRESS is not exported by @molpha-oracle/sdk" };
60
+ }
61
+ return { network, address };
62
+ }
63
+ function resolveVerifierAddress(exportName, network) {
64
+ const resolver = getSdkExport(exportName);
65
+ if (typeof resolver !== "function") {
66
+ return { network, error: `${exportName} is not exported by @molpha-oracle/sdk` };
67
+ }
68
+ try {
69
+ const address = resolver(network);
70
+ return address ? { network, address } : { network, error: "no verifier address returned" };
71
+ }
72
+ catch (error) {
73
+ return {
74
+ network,
75
+ error: error instanceof Error ? error.message : String(error)
76
+ };
77
+ }
78
+ }
79
+ function callBuilder(exportName, result, errors) {
80
+ const builder = getSdkExport(exportName);
81
+ if (typeof builder !== "function") {
82
+ errors.push({ target: exportName, message: `${exportName} is not exported by @molpha-oracle/sdk` });
83
+ return undefined;
84
+ }
85
+ try {
86
+ return builder(result);
87
+ }
88
+ catch (error) {
89
+ errors.push({
90
+ target: exportName,
91
+ message: error instanceof Error ? error.message : String(error)
92
+ });
93
+ return undefined;
94
+ }
95
+ }