aipou-mcp-server 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AIPOU MCP Server
2
2
 
3
- `aipou-mcp-server` records private, signed receipts for authorized AI tasks. It supports local receipt collection and optional claims for validated work on Base.
3
+ `aipou-mcp-server` records private, signed receipts for authorized AI tasks. It supports local receipt collection, reward status checks, and optional claims for validated work on Base.
4
4
 
5
5
  It does not detect hidden AI use, prove task quality, replace payment rails, or require raw prompts and outputs to leave the user's machine.
6
6
 
@@ -18,9 +18,17 @@ npm run demo
18
18
 
19
19
  The demo launches this MCP server over stdio, creates a local signed receipt with an ephemeral wallet, and prints the `receiptId` reference a framework can attach to metadata. It does not claim rewards or move funds.
20
20
 
21
+ ## Run From npm
22
+
23
+ MCP clients can launch the published package with:
24
+
25
+ ```bash
26
+ npx -y aipou-mcp-server
27
+ ```
28
+
21
29
  ## Run locally from this repository
22
30
 
23
- The npm package metadata is prepared, but the first public npm publish must be performed by the repository owner. Until `aipou-mcp-server` is published, run the MCP server from a local checkout:
31
+ For local development or source review, run the MCP server from a checkout:
24
32
 
25
33
  ```bash
26
34
  npm install
@@ -34,14 +42,6 @@ For development:
34
42
  npm run dev -w mcp-server
35
43
  ```
36
44
 
37
- ## Run after npm publication
38
-
39
- After the package is published to npm, MCP clients can launch it with:
40
-
41
- ```bash
42
- npx -y aipou-mcp-server
43
- ```
44
-
45
45
  Required configuration:
46
46
 
47
47
  ```text
@@ -50,13 +50,13 @@ AIPOU_CONTRACT_ADDRESS=0x55f0Cc5e51A1284D20337d6cbb18938C8A1ABCbB
50
50
  AIPOU_CLAIMS_ADDRESS=0x4ca4C98fB784D20EdC8E2A7F531dAab4c6e53058
51
51
  ```
52
52
 
53
- Use a new dedicated farming wallet, never a primary wallet. Do not commit the private key. Optional claims and settlement occur only after an explicit user request.
53
+ Use a new dedicated farming wallet, never a primary wallet. Do not commit the private key. Status checks can show already claimed and pending AIPOU without moving funds. Optional claims and settlement occur only after an explicit user request.
54
54
 
55
55
  Do not configure `AIPOU_VALIDATOR_PRIVATE_KEY` on a user installation. That key is only for the separate protocol validator service. The local Ed25519 collector key and receipt metadata are stored unencrypted under `AIPOU_DATA_DIR`; restrict that directory with operating-system permissions and use encrypted backups.
56
56
 
57
57
  The default network is Base mainnet. `AIPOU_RPC_URL` and `AIPOU_DATA_DIR` can be set when a custom RPC endpoint or receipt directory is needed.
58
58
 
59
- Publication checklist: [docs/npm-publication.md](https://github.com/0xddneto/AI-Proof-of-Us/blob/main/docs/npm-publication.md).
59
+ Publication details: [docs/npm-publication.md](https://github.com/0xddneto/AI-Proof-of-Us/blob/main/docs/npm-publication.md).
60
60
 
61
61
  ## Documentation
62
62
 
package/dist/claims.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- import type { SettlementResult } from "./types.js";
1
+ import type { SettlementAllResult, SettlementResult } from "./types.js";
2
2
  export declare function settleRewards(maxReceipts?: number): Promise<SettlementResult>;
3
+ export declare function settleAllRewards(batchSize?: number, maxBatches?: number): Promise<SettlementAllResult>;
package/dist/claims.js CHANGED
@@ -86,16 +86,17 @@ async function verifyReceipt(receipt, trustedCollectors) {
86
86
  throw new Error(`Invalid trust tier for ${receipt.receiptId}`);
87
87
  }
88
88
  export async function settleRewards(maxReceipts = 25) {
89
- const candidates = await unsettledReceipts(maxReceipts);
89
+ const candidates = await unsettledReceipts(Number.MAX_SAFE_INTEGER);
90
90
  if (candidates.length === 0)
91
91
  throw new Error("There are no unsettled receipts");
92
92
  const policy = settlementPolicyFromEnv();
93
93
  const recentlySettled = await settledReceiptsSince(Date.now() - DAY_MS);
94
- const { eligible: receipts, skipped } = filterEligibleReceipts(candidates, recentlySettled, policy);
95
- if (receipts.length === 0) {
94
+ const { eligible, skipped } = filterEligibleReceipts(candidates, recentlySettled, policy);
95
+ if (eligible.length === 0) {
96
96
  const reasons = skipped.map((item) => `${item.receiptId.slice(0, 10)}…: ${item.reason}`).join("; ");
97
97
  throw new Error(`No receipts pass the settlement policy. Skipped: ${reasons}`);
98
98
  }
99
+ const receipts = eligible.slice(0, maxReceipts);
99
100
  const trustedCollectors = await trustedCollectorFingerprints();
100
101
  for (const receipt of receipts)
101
102
  await verifyReceipt(receipt, trustedCollectors);
@@ -148,3 +149,40 @@ export async function settleRewards(maxReceipts = 25) {
148
149
  await markBatchSettled(batch);
149
150
  return { ...batch, skippedReceipts: skipped };
150
151
  }
152
+ export async function settleAllRewards(batchSize = 100, maxBatches = 20) {
153
+ const batches = [];
154
+ const skippedByReceiptId = new Map();
155
+ let stoppedReason;
156
+ for (let index = 0; index < maxBatches; index += 1) {
157
+ try {
158
+ const batch = await settleRewards(batchSize);
159
+ batches.push(batch);
160
+ for (const skipped of batch.skippedReceipts) {
161
+ skippedByReceiptId.set(skipped.receiptId, skipped);
162
+ }
163
+ }
164
+ catch (error) {
165
+ const message = error instanceof Error ? error.message : String(error);
166
+ if (message === "There are no unsettled receipts")
167
+ break;
168
+ stoppedReason = message;
169
+ break;
170
+ }
171
+ }
172
+ if (!stoppedReason && batches.length === maxBatches) {
173
+ stoppedReason = `Stopped after maxBatches=${maxBatches}; run settle_all_ai_rewards again for any remaining receipts`;
174
+ }
175
+ const pendingReceiptCount = (await unsettledReceipts(Number.MAX_SAFE_INTEGER)).length;
176
+ const skippedReceipts = [...skippedByReceiptId.values()];
177
+ return {
178
+ batches,
179
+ batchCount: batches.length,
180
+ settledReceiptCount: batches.reduce((total, batch) => total + batch.receiptIds.length, 0),
181
+ skippedReceipts,
182
+ pendingReceiptCount,
183
+ publishTransactions: batches.map((batch) => batch.publishTransaction),
184
+ claimTransactions: batches.map((batch) => batch.claimTransaction),
185
+ stoppedReason,
186
+ settledAt: new Date().toISOString()
187
+ };
188
+ }
@@ -2,6 +2,7 @@ export interface TokenContractConfig {
2
2
  name: string;
3
3
  symbol: string;
4
4
  address: string | null;
5
+ logoURI: string | null;
5
6
  decimals: number;
6
7
  chainId: number;
7
8
  chainName: string;
package/dist/contract.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ const DEFAULT_LOGO_URI = "https://raw.githubusercontent.com/0xddneto/AI-Proof-of-Us/main/assets/token/aipou.png";
3
4
  export const aipouTokenAbi = [
4
5
  "function name() view returns (string)",
5
6
  "function symbol() view returns (string)",
@@ -39,6 +40,7 @@ function envConfig() {
39
40
  name: "AI Proof of Use",
40
41
  symbol: "AIPOU",
41
42
  address,
43
+ logoURI: process.env.AIPOU_LOGO_URI || DEFAULT_LOGO_URI,
42
44
  decimals: 18,
43
45
  chainId: Number(process.env.AIPOU_CHAIN_ID || 8453),
44
46
  chainName: process.env.AIPOU_CHAIN_NAME || "Base Mainnet",
@@ -61,6 +63,7 @@ async function deploymentFileConfig() {
61
63
  name: deployment.token?.name || "AI Proof of Use",
62
64
  symbol: deployment.token?.symbol || "AIPOU",
63
65
  address,
66
+ logoURI: deployment.token?.logoURI || process.env.AIPOU_LOGO_URI || DEFAULT_LOGO_URI,
64
67
  decimals: deployment.token?.decimals || 18,
65
68
  chainId: deployment.chain?.id || 8453,
66
69
  chainName: deployment.chain?.name || "Base Mainnet",
@@ -89,6 +92,7 @@ export async function getTokenContractConfig() {
89
92
  name: "AI Proof of Use",
90
93
  symbol: "AIPOU",
91
94
  address: null,
95
+ logoURI: process.env.AIPOU_LOGO_URI || DEFAULT_LOGO_URI,
92
96
  decimals: 18,
93
97
  chainId: 8453,
94
98
  chainName: "Base Mainnet",
package/dist/index.js CHANGED
@@ -3,22 +3,24 @@ import "dotenv/config";
3
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
5
  import { z } from "zod";
6
- import { settleRewards } from "./claims.js";
6
+ import { settleAllRewards, settleRewards } from "./claims.js";
7
7
  import { collectorFingerprint, getCollectorPublicKey } from "./collector.js";
8
8
  import { aipouClaimsAbi, aipouTokenAbi, getTokenContractConfig } from "./contract.js";
9
9
  import { agentWallet } from "./identity.js";
10
10
  import { beginTask, completeTask, exportReceipts } from "./receipts.js";
11
11
  import { estimateReward } from "./rewards.js";
12
+ import { getAipouStatus } from "./status.js";
12
13
  const bytes32 = z.string().regex(/^0x[a-fA-F0-9]{64}$/);
13
14
  const usageCounts = {
14
15
  inputTokens: z.number().int().min(0).max(10_000_000),
15
16
  outputTokens: z.number().int().min(0).max(10_000_000),
16
17
  durationSeconds: z.number().int().min(0).max(86_400)
17
18
  };
18
- const server = new McpServer({ name: "aipou-mcp", version: "0.2.0" }, {
19
+ const server = new McpServer({ name: "aipou-mcp", version: "0.2.1" }, {
19
20
  instructions: "For meaningful AI tasks, call begin_ai_task before work and complete_ai_task after work using hashes, never raw prompts or outputs. " +
20
21
  "Use the dedicated farming identity only. Never reveal private keys or local collector state. " +
21
- "Call settle_ai_rewards only after the user explicitly asks to claim or settle their AIPOU; it submits two on-chain transactions. " +
22
+ "Call settle_all_ai_rewards after a broad user request such as 'claim my AIPOU' or 'settle all pending AIPOU'. " +
23
+ "Call settle_ai_rewards only when the user asks for a single limited batch. Settlement submits on-chain transactions. " +
22
24
  "The host client and its user always keep the final say on transaction confirmation policy."
23
25
  });
24
26
  server.tool("get_aipou_contract", "Return the configured AIPOU token contract address, Base network details, explorer URL, and minimal ABI.", {}, async () => {
@@ -37,6 +39,10 @@ server.tool("get_aipou_identity", "Return the dedicated farming wallet address a
37
39
  }]
38
40
  };
39
41
  });
42
+ server.tool("get_aipou_status", "Show recorded, pending, and already claimed AIPOU receipts plus the farming wallet's on-chain AIPOU balance. Does not reveal private keys or full receipt payloads.", {}, async () => {
43
+ const status = await getAipouStatus();
44
+ return { content: [{ type: "text", text: JSON.stringify(status, null, 2) }] };
45
+ });
40
46
  server.tool("estimate_ai_reward", "Estimate the client-signed AIPOU reward before a task is completed. The final tier is derived by the validator.", usageCounts, async (input) => ({
41
47
  content: [{
42
48
  type: "text",
@@ -76,9 +82,16 @@ server.tool("export_ai_receipts", "Export stored signed AI task receipts, option
76
82
  const receipts = await exportReceipts(wallet);
77
83
  return { content: [{ type: "text", text: JSON.stringify({ count: receipts.length, receipts }, null, 2) }] };
78
84
  });
79
- server.tool("settle_ai_rewards", "After an explicit user claim request, validate eligible receipts against the settlement policy, publish a Merkle root, and mint the included rewards. Submits two on-chain transactions; the host's own confirmation policy applies.", { maxReceipts: z.number().int().min(1).max(100).default(25) }, async ({ maxReceipts }) => {
85
+ server.tool("settle_ai_rewards", "After an explicit user claim request for one limited batch, validate eligible receipts against the settlement policy, publish a Merkle root, and mint the included rewards. Submits two on-chain transactions; the host's own confirmation policy applies.", { maxReceipts: z.number().int().min(1).max(100).default(25) }, async ({ maxReceipts }) => {
80
86
  const batch = await settleRewards(maxReceipts);
81
87
  return { content: [{ type: "text", text: JSON.stringify(batch, null, 2) }] };
82
88
  });
89
+ server.tool("settle_all_ai_rewards", "After an explicit broad claim request, settle all currently pending eligible receipts from the shared AIPOU data directory by submitting as many bounded batches as needed. Each batch publishes one Merkle root and submits one claim transaction.", {
90
+ batchSize: z.number().int().min(1).max(100).default(100),
91
+ maxBatches: z.number().int().min(1).max(50).default(20)
92
+ }, async ({ batchSize, maxBatches }) => {
93
+ const result = await settleAllRewards(batchSize, maxBatches);
94
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
95
+ });
83
96
  const transport = new StdioServerTransport();
84
97
  await server.connect(transport);
@@ -0,0 +1 @@
1
+ export declare function getAipouStatus(): Promise<Record<string, unknown>>;
package/dist/status.js ADDED
@@ -0,0 +1,73 @@
1
+ import { Contract, JsonRpcProvider, formatUnits } from "ethers";
2
+ import { aipouTokenAbi, getTokenContractConfig } from "./contract.js";
3
+ import { agentWallet } from "./identity.js";
4
+ import { exportStoredReceipts, protocolStateCounts, unsettledReceipts } from "./store.js";
5
+ function sumRewards(receipts) {
6
+ const total = receipts.reduce((sum, receipt) => sum + Number(receipt.estimatedReward), 0);
7
+ return Number.isInteger(total) ? total.toFixed(0) : total.toFixed(6).replace(/0+$/, "").replace(/\.$/, "");
8
+ }
9
+ export async function getAipouStatus() {
10
+ const wallet = agentWallet().address;
11
+ const contract = await getTokenContractConfig();
12
+ const receipts = await exportStoredReceipts(wallet);
13
+ const pendingReceipts = await unsettledReceipts(Number.MAX_SAFE_INTEGER);
14
+ const walletPendingReceipts = pendingReceipts.filter((receipt) => receipt.wallet.toLowerCase() === wallet.toLowerCase());
15
+ const claimedReceipts = receipts.filter((receipt) => receipt.claimTransaction);
16
+ const counts = await protocolStateCounts();
17
+ let onchain = { available: false };
18
+ if (contract.address) {
19
+ try {
20
+ const provider = new JsonRpcProvider(contract.rpcUrl);
21
+ const token = new Contract(contract.address, aipouTokenAbi, provider);
22
+ const [decimals, balance, totalSupply] = await Promise.all([
23
+ token.decimals(),
24
+ token.balanceOf(wallet),
25
+ token.totalSupply()
26
+ ]);
27
+ onchain = {
28
+ available: true,
29
+ wallet,
30
+ balance: formatUnits(balance, decimals),
31
+ totalSupply: formatUnits(totalSupply, decimals),
32
+ explorerUrl: `${contract.blockExplorer}/token/${contract.address}?a=${wallet}`
33
+ };
34
+ }
35
+ catch (error) {
36
+ onchain = {
37
+ available: false,
38
+ error: error instanceof Error ? error.message : String(error)
39
+ };
40
+ }
41
+ }
42
+ const lastClaim = claimedReceipts
43
+ .filter((receipt) => receipt.claimTransaction)
44
+ .sort((a, b) => Date.parse(b.recordedAt) - Date.parse(a.recordedAt))[0];
45
+ return {
46
+ wallet,
47
+ chainId: contract.chainId,
48
+ tokenAddress: contract.address,
49
+ claimsAddress: contract.claimsAddress,
50
+ receipts: {
51
+ recorded: receipts.length,
52
+ claimed: claimedReceipts.length,
53
+ pending: walletPendingReceipts.length,
54
+ incompleteSessions: counts.incompleteSessions,
55
+ batches: counts.batches
56
+ },
57
+ rewards: {
58
+ claimedEstimated: sumRewards(claimedReceipts),
59
+ pendingEstimated: sumRewards(walletPendingReceipts),
60
+ totalEstimated: sumRewards(receipts),
61
+ unit: "AIPOU"
62
+ },
63
+ latestClaim: lastClaim
64
+ ? {
65
+ receiptId: lastClaim.receiptId,
66
+ transaction: lastClaim.claimTransaction,
67
+ explorerUrl: `${contract.blockExplorer}/tx/${lastClaim.claimTransaction}`,
68
+ recordedAt: lastClaim.recordedAt
69
+ }
70
+ : null,
71
+ onchain
72
+ };
73
+ }
package/dist/store.d.ts CHANGED
@@ -4,5 +4,11 @@ export declare function getSession(nonce: string): Promise<TaskSession | null>;
4
4
  export declare function saveCompletedReceipt(evidenceKey: string, receipt: UsageReceipt): Promise<void>;
5
5
  export declare function exportStoredReceipts(wallet?: string): Promise<UsageReceipt[]>;
6
6
  export declare function unsettledReceipts(limit: number): Promise<UsageReceipt[]>;
7
+ export declare function protocolStateCounts(): Promise<{
8
+ sessions: number;
9
+ completedSessions: number;
10
+ incompleteSessions: number;
11
+ batches: number;
12
+ }>;
7
13
  export declare function settledReceiptsSince(sinceMs: number): Promise<UsageReceipt[]>;
8
14
  export declare function markBatchSettled(batch: ClaimBatch): Promise<void>;
package/dist/store.js CHANGED
@@ -103,6 +103,16 @@ export async function exportStoredReceipts(wallet) {
103
103
  export async function unsettledReceipts(limit) {
104
104
  return (await readState()).receipts.filter((receipt) => !receipt.claimTransaction).slice(0, limit);
105
105
  }
106
+ export async function protocolStateCounts() {
107
+ const state = await readState();
108
+ const sessions = Object.values(state.sessions);
109
+ return {
110
+ sessions: sessions.length,
111
+ completedSessions: sessions.filter((session) => session.completedAt).length,
112
+ incompleteSessions: sessions.filter((session) => !session.completedAt).length,
113
+ batches: state.batches.length
114
+ };
115
+ }
106
116
  export async function settledReceiptsSince(sinceMs) {
107
117
  const receipts = [...(await readArchivedReceipts()), ...(await readState()).receipts];
108
118
  return receipts.filter((receipt) => receipt.claimTransaction && Date.parse(receipt.recordedAt) >= sinceMs);
package/dist/types.d.ts CHANGED
@@ -64,6 +64,17 @@ export interface SkippedReceipt {
64
64
  export interface SettlementResult extends ClaimBatch {
65
65
  skippedReceipts: SkippedReceipt[];
66
66
  }
67
+ export interface SettlementAllResult {
68
+ batches: SettlementResult[];
69
+ batchCount: number;
70
+ settledReceiptCount: number;
71
+ skippedReceipts: SkippedReceipt[];
72
+ pendingReceiptCount: number;
73
+ publishTransactions: string[];
74
+ claimTransactions: string[];
75
+ stoppedReason?: string;
76
+ settledAt: string;
77
+ }
67
78
  export interface ProtocolState {
68
79
  sessions: Record<string, TaskSession>;
69
80
  receipts: UsageReceipt[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aipou-mcp-server",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "mcpName": "io.github.0xddneto/ai-proof-of-us",
5
5
  "description": "MCP server for private, signed AI task receipts and optional validated AIPOU claims.",
6
6
  "keywords": [
package/server.json CHANGED
@@ -9,12 +9,12 @@
9
9
  "source": "github",
10
10
  "subfolder": "mcp-server"
11
11
  },
12
- "version": "0.2.0",
12
+ "version": "0.2.1",
13
13
  "packages": [
14
14
  {
15
15
  "registryType": "npm",
16
16
  "identifier": "aipou-mcp-server",
17
- "version": "0.2.0",
17
+ "version": "0.2.1",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  },