aipou-mcp-server 0.2.2 → 0.3.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 +19 -0
- package/dist/canonical.test.d.ts +1 -0
- package/dist/canonical.test.js +49 -0
- package/dist/demo.d.ts +30 -0
- package/dist/demo.js +97 -0
- package/dist/demo.test.d.ts +1 -0
- package/dist/demo.test.js +27 -0
- package/dist/index.js +22 -3
- package/dist/rewards.test.d.ts +1 -0
- package/dist/rewards.test.js +132 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +10 -0
- package/dist/version.test.d.ts +1 -0
- package/dist/version.test.js +26 -0
- package/package.json +4 -2
- package/server.json +2 -2
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
|
+
Create and verify one disposable local receipt without cloning anything:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx -y aipou-mcp-server --demo
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The command needs no wallet setup, funds, network, or claims. It prints a
|
|
16
|
+
framework-ready `workReceiptId` object and deletes its ephemeral wallet,
|
|
17
|
+
collector key, and receipt state before exiting.
|
|
18
|
+
|
|
19
|
+
From a source checkout, the same check runs with:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install
|
|
23
|
+
npm run demo -w mcp-server
|
|
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,17 +6,19 @@ 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";
|
|
12
13
|
import { getAipouStatus } from "./status.js";
|
|
14
|
+
import { getPackageVersion } from "./version.js";
|
|
13
15
|
const bytes32 = z.string().regex(/^0x[a-fA-F0-9]{64}$/);
|
|
14
16
|
const usageCounts = {
|
|
15
17
|
inputTokens: z.number().int().min(0).max(10_000_000),
|
|
16
18
|
outputTokens: z.number().int().min(0).max(10_000_000),
|
|
17
19
|
durationSeconds: z.number().int().min(0).max(86_400)
|
|
18
20
|
};
|
|
19
|
-
const server = new McpServer({ name: "aipou-mcp", version:
|
|
21
|
+
const server = new McpServer({ name: "aipou-mcp", version: await getPackageVersion() }, {
|
|
20
22
|
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
23
|
"Use the dedicated farming identity only. Never reveal private keys or local collector state. " +
|
|
22
24
|
"Call settle_all_ai_rewards after a broad user request such as 'claim my AIPOU' or 'settle all pending AIPOU'. " +
|
|
@@ -93,5 +95,22 @@ server.tool("settle_all_ai_rewards", "After an explicit broad claim request, set
|
|
|
93
95
|
const result = await settleAllRewards(batchSize, maxBatches);
|
|
94
96
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
95
97
|
});
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
+
const cliArguments = new Set(process.argv.slice(2));
|
|
99
|
+
if (cliArguments.has("--demo")) {
|
|
100
|
+
console.log(JSON.stringify(await runLocalReceiptDemo(), null, 2));
|
|
101
|
+
}
|
|
102
|
+
else if (cliArguments.has("--help") || cliArguments.has("-h")) {
|
|
103
|
+
console.log(`AIPOU MCP Server
|
|
104
|
+
|
|
105
|
+
Usage:
|
|
106
|
+
aipou-mcp Start the MCP stdio server
|
|
107
|
+
aipou-mcp --demo Create and verify one disposable local receipt
|
|
108
|
+
aipou-mcp --help Show this help
|
|
109
|
+
|
|
110
|
+
The demo needs no wallet, funds, network, or configuration. Its temporary
|
|
111
|
+
wallet, collector key, and receipt state are removed before the command exits.`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const transport = new StdioServerTransport();
|
|
115
|
+
await server.connect(transport);
|
|
116
|
+
}
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getPackageVersion(): Promise<string>;
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
const SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
3
|
+
export async function getPackageVersion() {
|
|
4
|
+
const packageUrl = new URL("../package.json", import.meta.url);
|
|
5
|
+
const metadata = JSON.parse(await readFile(packageUrl, "utf8"));
|
|
6
|
+
if (typeof metadata.version !== "string" || !SEMVER.test(metadata.version)) {
|
|
7
|
+
throw new Error("mcp-server/package.json must contain a valid semantic version");
|
|
8
|
+
}
|
|
9
|
+
return metadata.version;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
7
|
+
import { getPackageVersion } from "./version.js";
|
|
8
|
+
test("MCP handshake version comes from the published package metadata", async () => {
|
|
9
|
+
const metadata = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
10
|
+
assert.equal(await getPackageVersion(), metadata.version);
|
|
11
|
+
});
|
|
12
|
+
test("MCP initialization advertises the published package version", async () => {
|
|
13
|
+
const transport = new StdioClientTransport({
|
|
14
|
+
command: process.execPath,
|
|
15
|
+
args: [fileURLToPath(new URL("./index.js", import.meta.url))],
|
|
16
|
+
stderr: "pipe"
|
|
17
|
+
});
|
|
18
|
+
const client = new Client({ name: "aipou-version-test", version: "1.0.0" });
|
|
19
|
+
await client.connect(transport);
|
|
20
|
+
try {
|
|
21
|
+
assert.equal(client.getServerVersion()?.version, await getPackageVersion());
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
await transport.close();
|
|
25
|
+
}
|
|
26
|
+
});
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aipou-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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
|
+
"author": "0xddneto",
|
|
6
7
|
"keywords": [
|
|
7
8
|
"mcp",
|
|
8
9
|
"model-context-protocol",
|
|
@@ -35,8 +36,9 @@
|
|
|
35
36
|
},
|
|
36
37
|
"scripts": {
|
|
37
38
|
"build": "tsc -p tsconfig.json",
|
|
38
|
-
"test": "npm run build && node --test dist/protocol.test.js",
|
|
39
|
+
"test": "npm run build && node --test dist/protocol.test.js dist/rewards.test.js dist/canonical.test.js dist/demo.test.js dist/version.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"
|
package/server.json
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
"source": "github",
|
|
10
10
|
"subfolder": "mcp-server"
|
|
11
11
|
},
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.3.1",
|
|
13
13
|
"packages": [
|
|
14
14
|
{
|
|
15
15
|
"registryType": "npm",
|
|
16
16
|
"identifier": "aipou-mcp-server",
|
|
17
|
-
"version": "0.
|
|
17
|
+
"version": "0.3.1",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
|
20
20
|
},
|