aipou-mcp-server 0.2.1 → 0.3.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 CHANGED
@@ -6,6 +6,25 @@ It does not detect hidden AI use, prove task quality, replace payment rails, or
6
6
 
7
7
  ## Fast Local Adoption Test
8
8
 
9
+ From a source checkout, create and verify one disposable local receipt:
10
+
11
+ ```bash
12
+ npm install
13
+ npm run demo -w mcp-server
14
+ ```
15
+
16
+ The command needs no wallet setup, funds, network, or claims. It prints a
17
+ framework-ready `workReceiptId` object and deletes its ephemeral wallet,
18
+ collector key, and receipt state before exiting.
19
+
20
+ After the next npm release, the same check will run without cloning:
21
+
22
+ ```bash
23
+ npx -y aipou-mcp-server --demo
24
+ ```
25
+
26
+ For a complete framework lifecycle example, continue with the adapter below.
27
+
9
28
  If you are evaluating AIPOU for an agent framework, start with the lifecycle adapter example:
10
29
 
11
30
  ```bash
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { canonicalJson, sha256Hex } from "./canonical.js";
4
+ test("canonicalJson serializes primitives correctly", () => {
5
+ assert.equal(canonicalJson(42), "42");
6
+ assert.equal(canonicalJson("hello"), "\"hello\"");
7
+ assert.equal(canonicalJson(true), "true");
8
+ assert.equal(canonicalJson(null), "null");
9
+ });
10
+ test("canonicalJson sorts object keys lexicographically", () => {
11
+ const unsorted = { b: 1, a: 2, c: 3 };
12
+ const sorted = { a: 2, b: 1, c: 3 };
13
+ assert.equal(canonicalJson(unsorted), canonicalJson(sorted));
14
+ assert.equal(canonicalJson(unsorted), "{\"a\":2,\"b\":1,\"c\":3}");
15
+ });
16
+ test("canonicalJson omits undefined values from objects", () => {
17
+ const obj = { a: 1, b: undefined, c: 3 };
18
+ assert.equal(canonicalJson(obj), "{\"a\":1,\"c\":3}");
19
+ });
20
+ test("canonicalJson handles nested objects with sorted keys", () => {
21
+ const obj = { outer: { z: 1, a: 2 } };
22
+ assert.equal(canonicalJson(obj), "{\"outer\":{\"a\":2,\"z\":1}}");
23
+ });
24
+ test("canonicalJson handles arrays preserving order", () => {
25
+ assert.equal(canonicalJson([3, 1, 2]), "[3,1,2]");
26
+ assert.equal(canonicalJson([{ b: 1, a: 2 }]), "[{\"a\":2,\"b\":1}]");
27
+ });
28
+ test("canonicalJson is deterministic for deeply nested mixed structures", () => {
29
+ const a = { z: [1, 2], y: { b: 2, a: 1 } };
30
+ const b = { y: { a: 1, b: 2 }, z: [1, 2] };
31
+ assert.equal(canonicalJson(a), canonicalJson(b));
32
+ });
33
+ test("sha256Hex produces a 0x-prefixed 64-char hex string", () => {
34
+ const hash = sha256Hex("test");
35
+ assert.match(hash, /^0x[0-9a-f]{64}$/);
36
+ assert.equal(hash, "0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08");
37
+ });
38
+ test("sha256Hex is deterministic for the same input", () => {
39
+ assert.equal(sha256Hex("hello"), sha256Hex("hello"));
40
+ assert.notEqual(sha256Hex("hello"), sha256Hex("world"));
41
+ });
42
+ test("canonicalJson handles empty objects and arrays", () => {
43
+ assert.equal(canonicalJson({}), "{}");
44
+ assert.equal(canonicalJson([]), "[]");
45
+ });
46
+ test("canonicalJson handles mixed undefined and null values", () => {
47
+ const obj = { a: null, b: undefined, c: 0, d: "" };
48
+ assert.equal(canonicalJson(obj), "{\"a\":null,\"c\":0,\"d\":\"\"}");
49
+ });
package/dist/demo.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ export interface LocalDemoResult {
2
+ mode: "local_receipt_demo";
3
+ wallet: string;
4
+ storage: "removed_after_demo";
5
+ frameworkMetadata: {
6
+ aipou: {
7
+ type: "aipou.receipt";
8
+ workReceiptId: string;
9
+ receiptId: string;
10
+ factId: string;
11
+ evidenceClass: "issuer_asserted";
12
+ scheme: "aipou-receipt-v1";
13
+ status: "local_demo";
14
+ trustTier: "client_signed" | "provider_signed";
15
+ };
16
+ };
17
+ verification: {
18
+ walletAuthorization: boolean;
19
+ collectorSignature: boolean;
20
+ };
21
+ safety: {
22
+ networkRequired: false;
23
+ fundsMoved: false;
24
+ claimSubmitted: false;
25
+ rawPromptStored: false;
26
+ rawOutputStored: false;
27
+ privateKeyPrinted: false;
28
+ };
29
+ }
30
+ export declare function runLocalReceiptDemo(): Promise<LocalDemoResult>;
package/dist/demo.js ADDED
@@ -0,0 +1,97 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { Wallet } from "ethers";
6
+ import { canonicalJson, sha256Hex } from "./canonical.js";
7
+ import { collectorFingerprint, verifyCollectorPayload } from "./collector.js";
8
+ import { verifyTaskAuthorization } from "./identity.js";
9
+ import { beginTask, completeTask } from "./receipts.js";
10
+ const TOKEN_ADDRESS = "0x55f0Cc5e51A1284D20337d6cbb18938C8A1ABCbB";
11
+ const CLAIMS_ADDRESS = "0x4ca4C98fB784D20EdC8E2A7F531dAab4c6e53058";
12
+ const RECEIPT_SCHEME = "aipou-receipt-v1";
13
+ const DEMO_ENVIRONMENT_KEYS = [
14
+ "AIPOU_AGENT_PRIVATE_KEY",
15
+ "AIPOU_CONTRACT_ADDRESS",
16
+ "AIPOU_CLAIMS_ADDRESS",
17
+ "AIPOU_DATA_DIR"
18
+ ];
19
+ function deriveFactId(publicKey, nonce) {
20
+ const fingerprint = collectorFingerprint(publicKey);
21
+ const material = `${RECEIPT_SCHEME}\n${fingerprint}\n${nonce.toLowerCase()}`;
22
+ return `0x${createHash("sha256").update(material).digest("hex")}`;
23
+ }
24
+ export async function runLocalReceiptDemo() {
25
+ const previousEnvironment = new Map(DEMO_ENVIRONMENT_KEYS.map((key) => [key, process.env[key]]));
26
+ const dataDir = await mkdtemp(path.join(os.tmpdir(), "aipou-one-command-demo-"));
27
+ const wallet = Wallet.createRandom();
28
+ process.env.AIPOU_AGENT_PRIVATE_KEY = wallet.privateKey;
29
+ process.env.AIPOU_CONTRACT_ADDRESS = TOKEN_ADDRESS;
30
+ process.env.AIPOU_CLAIMS_ADDRESS = CLAIMS_ADDRESS;
31
+ process.env.AIPOU_DATA_DIR = dataDir;
32
+ try {
33
+ const taskHash = sha256Hex(canonicalJson({
34
+ kind: "demo.one-command-adoption",
35
+ intent: "create a private local AIPOU receipt without setup"
36
+ }));
37
+ const outputHash = sha256Hex(canonicalJson({
38
+ status: "ok",
39
+ artifact: "local receipt reference"
40
+ }));
41
+ const session = await beginTask({
42
+ provider: "local-demo",
43
+ model: "aipou-one-command-demo",
44
+ client: "aipou-cli",
45
+ taskHash,
46
+ chainId: 8453,
47
+ verifyingContract: CLAIMS_ADDRESS
48
+ });
49
+ const receipt = await completeTask({
50
+ nonce: session.nonce,
51
+ inputTokens: 18,
52
+ outputTokens: 12,
53
+ durationSeconds: 1,
54
+ outputHash
55
+ });
56
+ const { collectorSignature, ...signedPayload } = receipt;
57
+ return {
58
+ mode: "local_receipt_demo",
59
+ wallet: receipt.wallet,
60
+ storage: "removed_after_demo",
61
+ frameworkMetadata: {
62
+ aipou: {
63
+ type: "aipou.receipt",
64
+ workReceiptId: receipt.receiptId,
65
+ receiptId: receipt.receiptId,
66
+ factId: deriveFactId(receipt.collectorPublicKey, receipt.nonce),
67
+ evidenceClass: "issuer_asserted",
68
+ scheme: RECEIPT_SCHEME,
69
+ status: "local_demo",
70
+ trustTier: receipt.trustTier
71
+ }
72
+ },
73
+ verification: {
74
+ walletAuthorization: verifyTaskAuthorization(session),
75
+ collectorSignature: verifyCollectorPayload(signedPayload, collectorSignature, receipt.collectorPublicKey)
76
+ },
77
+ safety: {
78
+ networkRequired: false,
79
+ fundsMoved: false,
80
+ claimSubmitted: false,
81
+ rawPromptStored: false,
82
+ rawOutputStored: false,
83
+ privateKeyPrinted: false
84
+ }
85
+ };
86
+ }
87
+ finally {
88
+ await rm(dataDir, { recursive: true, force: true });
89
+ for (const key of DEMO_ENVIRONMENT_KEYS) {
90
+ const previous = previousEnvironment.get(key);
91
+ if (previous === undefined)
92
+ delete process.env[key];
93
+ else
94
+ process.env[key] = previous;
95
+ }
96
+ }
97
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { runLocalReceiptDemo } from "./demo.js";
4
+ test("one-command demo creates a verified receipt without persistent secrets or claims", async () => {
5
+ const previousPrivateKey = process.env.AIPOU_AGENT_PRIVATE_KEY;
6
+ const previousDataDir = process.env.AIPOU_DATA_DIR;
7
+ const result = await runLocalReceiptDemo();
8
+ assert.equal(result.mode, "local_receipt_demo");
9
+ assert.match(result.wallet, /^0x[0-9a-fA-F]{40}$/);
10
+ assert.match(result.frameworkMetadata.aipou.workReceiptId, /^0x[0-9a-f]{64}$/);
11
+ assert.equal(result.frameworkMetadata.aipou.workReceiptId, result.frameworkMetadata.aipou.receiptId);
12
+ assert.match(result.frameworkMetadata.aipou.factId, /^0x[0-9a-f]{64}$/);
13
+ assert.deepEqual(result.verification, {
14
+ walletAuthorization: true,
15
+ collectorSignature: true
16
+ });
17
+ assert.deepEqual(result.safety, {
18
+ networkRequired: false,
19
+ fundsMoved: false,
20
+ claimSubmitted: false,
21
+ rawPromptStored: false,
22
+ rawOutputStored: false,
23
+ privateKeyPrinted: false
24
+ });
25
+ assert.equal(process.env.AIPOU_AGENT_PRIVATE_KEY, previousPrivateKey);
26
+ assert.equal(process.env.AIPOU_DATA_DIR, previousDataDir);
27
+ });
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { z } from "zod";
6
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
+ import { runLocalReceiptDemo } from "./demo.js";
9
10
  import { agentWallet } from "./identity.js";
10
11
  import { beginTask, completeTask, exportReceipts } from "./receipts.js";
11
12
  import { estimateReward } from "./rewards.js";
@@ -16,7 +17,7 @@ const usageCounts = {
16
17
  outputTokens: z.number().int().min(0).max(10_000_000),
17
18
  durationSeconds: z.number().int().min(0).max(86_400)
18
19
  };
19
- const server = new McpServer({ name: "aipou-mcp", version: "0.2.1" }, {
20
+ const server = new McpServer({ name: "aipou-mcp", version: "0.2.2" }, {
20
21
  instructions: "For meaningful AI tasks, call begin_ai_task before work and complete_ai_task after work using hashes, never raw prompts or outputs. " +
21
22
  "Use the dedicated farming identity only. Never reveal private keys or local collector state. " +
22
23
  "Call settle_all_ai_rewards after a broad user request such as 'claim my AIPOU' or 'settle all pending AIPOU'. " +
@@ -93,5 +94,22 @@ server.tool("settle_all_ai_rewards", "After an explicit broad claim request, set
93
94
  const result = await settleAllRewards(batchSize, maxBatches);
94
95
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
95
96
  });
96
- const transport = new StdioServerTransport();
97
- await server.connect(transport);
97
+ const cliArguments = new Set(process.argv.slice(2));
98
+ if (cliArguments.has("--demo")) {
99
+ console.log(JSON.stringify(await runLocalReceiptDemo(), null, 2));
100
+ }
101
+ else if (cliArguments.has("--help") || cliArguments.has("-h")) {
102
+ console.log(`AIPOU MCP Server
103
+
104
+ Usage:
105
+ aipou-mcp Start the MCP stdio server
106
+ aipou-mcp --demo Create and verify one disposable local receipt
107
+ aipou-mcp --help Show this help
108
+
109
+ The demo needs no wallet, funds, network, or configuration. Its temporary
110
+ wallet, collector key, and receipt state are removed before the command exits.`);
111
+ }
112
+ else {
113
+ const transport = new StdioServerTransport();
114
+ await server.connect(transport);
115
+ }
@@ -53,6 +53,8 @@ test("creates verifiable receipts and rejects nonce/evidence replay", async () =
53
53
  chainId: 8453,
54
54
  verifyingContract
55
55
  });
56
+ assert.match(repeatedSession.nonce, /^0x[0-9a-f]{64}$/);
57
+ assert.notEqual(repeatedSession.nonce, session.nonce);
56
58
  await assert.rejects(completeTask({
57
59
  nonce: repeatedSession.nonce,
58
60
  inputTokens: 1200,
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,132 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { estimateReward } from "./rewards.js";
4
+ test("client_signed tier applies 0.75 multiplier", () => {
5
+ const reward = estimateReward({
6
+ inputTokens: 1000,
7
+ outputTokens: 500,
8
+ durationSeconds: 300,
9
+ trustTier: "client_signed"
10
+ });
11
+ const tokenScore = Math.min(1500 / 1000, 30);
12
+ const durationScore = Math.min(300 / 300, 12);
13
+ const expected = (tokenScore + durationScore) * 0.75;
14
+ assert.equal(reward, expected.toFixed(4));
15
+ });
16
+ test("provider_signed tier applies 1.5 multiplier", () => {
17
+ const reward = estimateReward({
18
+ inputTokens: 1000,
19
+ outputTokens: 500,
20
+ durationSeconds: 300,
21
+ trustTier: "provider_signed"
22
+ });
23
+ const tokenScore = Math.min(1500 / 1000, 30);
24
+ const durationScore = Math.min(300 / 300, 12);
25
+ const expected = (tokenScore + durationScore) * 1.5;
26
+ assert.equal(reward, expected.toFixed(4));
27
+ });
28
+ test("provider_signed yields higher reward than client_signed for same input", () => {
29
+ const clientReward = Number(estimateReward({
30
+ inputTokens: 2000,
31
+ outputTokens: 800,
32
+ durationSeconds: 600,
33
+ trustTier: "client_signed"
34
+ }));
35
+ const providerReward = Number(estimateReward({
36
+ inputTokens: 2000,
37
+ outputTokens: 800,
38
+ durationSeconds: 600,
39
+ trustTier: "provider_signed"
40
+ }));
41
+ assert.ok(providerReward > clientReward);
42
+ });
43
+ test("token score is capped at 30", () => {
44
+ const lowTokens = estimateReward({
45
+ inputTokens: 500,
46
+ outputTokens: 500,
47
+ durationSeconds: 0,
48
+ trustTier: "client_signed"
49
+ });
50
+ const cappedTokens = estimateReward({
51
+ inputTokens: 100000,
52
+ outputTokens: 100000,
53
+ durationSeconds: 0,
54
+ trustTier: "client_signed"
55
+ });
56
+ assert.equal(Number(cappedTokens), 30 * 0.75);
57
+ assert.ok(Number(cappedTokens) > Number(lowTokens));
58
+ });
59
+ test("duration score is capped at 12", () => {
60
+ const lowDuration = estimateReward({
61
+ inputTokens: 0,
62
+ outputTokens: 0,
63
+ durationSeconds: 60,
64
+ trustTier: "client_signed"
65
+ });
66
+ const cappedDuration = estimateReward({
67
+ inputTokens: 0,
68
+ outputTokens: 0,
69
+ durationSeconds: 99999,
70
+ trustTier: "client_signed"
71
+ });
72
+ assert.equal(Number(cappedDuration), 12 * 0.75);
73
+ assert.ok(Number(cappedDuration) > Number(lowDuration));
74
+ });
75
+ test("total reward is capped at 50", () => {
76
+ const maxReward = estimateReward({
77
+ inputTokens: 1000000,
78
+ outputTokens: 1000000,
79
+ durationSeconds: 100000,
80
+ trustTier: "provider_signed"
81
+ });
82
+ assert.equal(Number(maxReward), 50);
83
+ });
84
+ test("zero input yields zero reward", () => {
85
+ const reward = estimateReward({
86
+ inputTokens: 0,
87
+ outputTokens: 0,
88
+ durationSeconds: 0,
89
+ trustTier: "client_signed"
90
+ });
91
+ assert.equal(reward, "0.0000");
92
+ });
93
+ test("negative token counts are treated as zero", () => {
94
+ const reward = estimateReward({
95
+ inputTokens: -500,
96
+ outputTokens: -200,
97
+ durationSeconds: 300,
98
+ trustTier: "client_signed"
99
+ });
100
+ const durationScore = Math.min(300 / 300, 12);
101
+ const expected = durationScore * 0.75;
102
+ assert.equal(reward, expected.toFixed(4));
103
+ });
104
+ test("negative duration is treated as zero", () => {
105
+ const reward = estimateReward({
106
+ inputTokens: 1000,
107
+ outputTokens: 0,
108
+ durationSeconds: -100,
109
+ trustTier: "client_signed"
110
+ });
111
+ const tokenScore = Math.min(1000 / 1000, 30);
112
+ const expected = tokenScore * 0.75;
113
+ assert.equal(reward, expected.toFixed(4));
114
+ });
115
+ test("reward is always a fixed 4-decimal string", () => {
116
+ const reward = estimateReward({
117
+ inputTokens: 100,
118
+ outputTokens: 100,
119
+ durationSeconds: 30,
120
+ trustTier: "client_signed"
121
+ });
122
+ assert.match(reward, /^\d+\.\d{4}$/);
123
+ });
124
+ test("high token count with low duration hits token cap but not total cap", () => {
125
+ const reward = estimateReward({
126
+ inputTokens: 50000,
127
+ outputTokens: 50000,
128
+ durationSeconds: 0,
129
+ trustTier: "client_signed"
130
+ });
131
+ assert.equal(Number(reward), 30 * 0.75);
132
+ });
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
- {
1
+ {
2
2
  "name": "aipou-mcp-server",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
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
+ "author": "0xddneto",
6
7
  "keywords": [
7
8
  "mcp",
8
9
  "model-context-protocol",
@@ -18,7 +19,7 @@
18
19
  "url": "git+https://github.com/0xddneto/AI-Proof-of-Us.git",
19
20
  "directory": "mcp-server"
20
21
  },
21
- "homepage": "https://github.com/0xddneto/AI-Proof-of-Us#readme",
22
+ "homepage": "https://0xddneto.github.io/AI-Proof-of-Us/",
22
23
  "bugs": {
23
24
  "url": "https://github.com/0xddneto/AI-Proof-of-Us/issues"
24
25
  },
@@ -33,10 +34,11 @@
33
34
  "bin": {
34
35
  "aipou-mcp": "dist/index.js"
35
36
  },
36
- "scripts": {
37
- "build": "tsc -p tsconfig.json",
38
- "test": "npm run build && node --test dist/protocol.test.js",
37
+ "scripts": {
38
+ "build": "tsc -p tsconfig.json",
39
+ "test": "npm run build && node --test dist/protocol.test.js dist/rewards.test.js dist/canonical.test.js dist/demo.test.js",
39
40
  "pack:check": "npm test && npm pack --dry-run",
41
+ "demo": "npm run build && node dist/index.js --demo",
40
42
  "dev": "tsx src/index.ts",
41
43
  "start": "node dist/index.js",
42
44
  "prepublishOnly": "npm test"
@@ -47,16 +49,16 @@
47
49
  "engines": {
48
50
  "node": ">=20"
49
51
  },
50
- "dependencies": {
51
- "@modelcontextprotocol/sdk": "^1.13.0",
52
- "dotenv": "^16.6.1",
53
- "ethers": "^6.17.0",
54
- "merkletreejs": "^0.6.0",
55
- "zod": "^3.23.8"
56
- },
57
- "devDependencies": {
58
- "@types/node": "^22.5.4",
59
- "tsx": "^4.19.0",
60
- "typescript": "^5.5.4"
61
- }
62
- }
52
+ "dependencies": {
53
+ "@modelcontextprotocol/sdk": "^1.13.0",
54
+ "dotenv": "^16.6.1",
55
+ "ethers": "^6.17.0",
56
+ "merkletreejs": "^0.6.0",
57
+ "zod": "^3.23.8"
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^22.5.4",
61
+ "tsx": "^4.19.0",
62
+ "typescript": "^5.5.4"
63
+ }
64
+ }
package/server.json CHANGED
@@ -3,18 +3,18 @@
3
3
  "name": "io.github.0xddneto/ai-proof-of-us",
4
4
  "title": "AI Proof of Us",
5
5
  "description": "Private signed AI task receipts with replay checks and optional validated AIPOU claims.",
6
- "websiteUrl": "https://github.com/0xddneto/AI-Proof-of-Us",
6
+ "websiteUrl": "https://0xddneto.github.io/AI-Proof-of-Us/",
7
7
  "repository": {
8
8
  "url": "https://github.com/0xddneto/AI-Proof-of-Us",
9
9
  "source": "github",
10
10
  "subfolder": "mcp-server"
11
11
  },
12
- "version": "0.2.1",
12
+ "version": "0.3.0",
13
13
  "packages": [
14
14
  {
15
15
  "registryType": "npm",
16
16
  "identifier": "aipou-mcp-server",
17
- "version": "0.2.1",
17
+ "version": "0.3.0",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  },