aipou-mcp-server 0.3.0 → 0.4.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,21 +6,31 @@ 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:
9
+ Create and verify one disposable local receipt without cloning anything:
10
10
 
11
11
  ```bash
12
- npm install
13
- npm run demo -w mcp-server
12
+ npx -y aipou-mcp-server --demo
14
13
  ```
15
14
 
16
15
  The command needs no wallet setup, funds, network, or claims. It prints a
17
16
  framework-ready `workReceiptId` object and deletes its ephemeral wallet,
18
17
  collector key, and receipt state before exiting.
19
18
 
20
- After the next npm release, the same check will run without cloning:
19
+ To create a persistent dedicated wallet without printing its private key:
21
20
 
22
21
  ```bash
23
- npx -y aipou-mcp-server --demo
22
+ npx -y aipou-mcp-server --init
23
+ ```
24
+
25
+ The command writes a protected `agent-wallet.key` and prints an MCP
26
+ configuration using `AIPOU_AGENT_KEY_FILE`. Re-running it reports the existing
27
+ identity instead of overwriting the key.
28
+
29
+ From a source checkout, the same check runs with:
30
+
31
+ ```bash
32
+ npm install
33
+ npm run demo -w mcp-server
24
34
  ```
25
35
 
26
36
  For a complete framework lifecycle example, continue with the adapter below.
@@ -69,6 +79,10 @@ AIPOU_CONTRACT_ADDRESS=0x55f0Cc5e51A1284D20337d6cbb18938C8A1ABCbB
69
79
  AIPOU_CLAIMS_ADDRESS=0x4ca4C98fB784D20EdC8E2A7F531dAab4c6e53058
70
80
  ```
71
81
 
82
+ `AIPOU_AGENT_KEY_FILE=/protected/path/agent-wallet.key` is the recommended
83
+ alternative to the inline private-key variable. One of the two identity options
84
+ is required for persistent receipt collection.
85
+
72
86
  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.
73
87
 
74
88
  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.
package/dist/collector.js CHANGED
@@ -1,27 +1,8 @@
1
- import { execFile } from "node:child_process";
2
1
  import { createHash, createPublicKey, generateKeyPairSync, sign, verify } from "node:crypto";
3
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
4
3
  import path from "node:path";
5
- import { promisify } from "node:util";
6
4
  import { canonicalJson } from "./canonical.js";
7
- const execFileAsync = promisify(execFile);
8
- // mode 0o600 is a no-op on NTFS, so on Windows the private key would keep the
9
- // permissive inherited ACL. Strip inheritance and grant only the current user.
10
- // Best-effort: an ACL failure must not block receipt collection.
11
- async function restrictToCurrentUser(filePath) {
12
- if (process.platform !== "win32")
13
- return;
14
- const user = process.env.USERNAME;
15
- if (!user)
16
- return;
17
- const principal = process.env.USERDOMAIN ? `${process.env.USERDOMAIN}\\${user}` : user;
18
- try {
19
- await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${principal}:F`]);
20
- }
21
- catch {
22
- // keep the inherited ACL rather than failing key creation
23
- }
24
- }
5
+ import { restrictToCurrentUser } from "./secure-file.js";
25
6
  function dataDir() {
26
7
  return path.resolve(process.env.AIPOU_DATA_DIR || ".aipou");
27
8
  }
package/dist/identity.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { randomBytes } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import path from "node:path";
2
4
  import { Wallet, getAddress, hexlify, isAddress, verifyTypedData } from "ethers";
3
5
  export const authorizationTypes = {
4
6
  TaskAuthorization: [
@@ -10,10 +12,22 @@ export const authorizationTypes = {
10
12
  { name: "issuedAt", type: "uint256" }
11
13
  ]
12
14
  };
15
+ function readKeyFile(keyFile) {
16
+ try {
17
+ return readFileSync(path.resolve(keyFile), "utf8").trim();
18
+ }
19
+ catch {
20
+ throw new Error(`AIPOU_AGENT_KEY_FILE could not be read (${keyFile}); ` +
21
+ "run aipou-mcp --init to create a dedicated wallet key");
22
+ }
23
+ }
13
24
  export function agentWallet() {
14
- const privateKey = process.env.AIPOU_AGENT_PRIVATE_KEY;
25
+ const inlinePrivateKey = process.env.AIPOU_AGENT_PRIVATE_KEY?.trim();
26
+ const keyFile = process.env.AIPOU_AGENT_KEY_FILE;
27
+ const privateKey = inlinePrivateKey || (keyFile ? readKeyFile(keyFile) : undefined);
15
28
  if (!privateKey) {
16
- throw new Error("AIPOU_AGENT_PRIVATE_KEY is required; use a dedicated farming wallet, never a primary wallet");
29
+ throw new Error("AIPOU_AGENT_PRIVATE_KEY or AIPOU_AGENT_KEY_FILE is required; " +
30
+ "run aipou-mcp --init to create a dedicated wallet, never use a primary wallet");
17
31
  }
18
32
  return new Wallet(privateKey);
19
33
  }
package/dist/index.js CHANGED
@@ -8,16 +8,18 @@ import { collectorFingerprint, getCollectorPublicKey } from "./collector.js";
8
8
  import { aipouClaimsAbi, aipouTokenAbi, getTokenContractConfig } from "./contract.js";
9
9
  import { runLocalReceiptDemo } from "./demo.js";
10
10
  import { agentWallet } from "./identity.js";
11
+ import { describeExistingIdentity, initializePersistentIdentity } from "./init.js";
11
12
  import { beginTask, completeTask, exportReceipts } from "./receipts.js";
12
13
  import { estimateReward } from "./rewards.js";
13
14
  import { getAipouStatus } from "./status.js";
15
+ import { getPackageVersion } from "./version.js";
14
16
  const bytes32 = z.string().regex(/^0x[a-fA-F0-9]{64}$/);
15
17
  const usageCounts = {
16
18
  inputTokens: z.number().int().min(0).max(10_000_000),
17
19
  outputTokens: z.number().int().min(0).max(10_000_000),
18
20
  durationSeconds: z.number().int().min(0).max(86_400)
19
21
  };
20
- const server = new McpServer({ name: "aipou-mcp", version: "0.2.2" }, {
22
+ const server = new McpServer({ name: "aipou-mcp", version: await getPackageVersion() }, {
21
23
  instructions: "For meaningful AI tasks, call begin_ai_task before work and complete_ai_task after work using hashes, never raw prompts or outputs. " +
22
24
  "Use the dedicated farming identity only. Never reveal private keys or local collector state. " +
23
25
  "Call settle_all_ai_rewards after a broad user request such as 'claim my AIPOU' or 'settle all pending AIPOU'. " +
@@ -95,19 +97,51 @@ server.tool("settle_all_ai_rewards", "After an explicit broad claim request, set
95
97
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
96
98
  });
97
99
  const cliArguments = new Set(process.argv.slice(2));
100
+ function cliValue(name) {
101
+ const args = process.argv.slice(2);
102
+ const inline = args.find((argument) => argument.startsWith(`${name}=`));
103
+ if (inline)
104
+ return inline.slice(name.length + 1);
105
+ const index = args.indexOf(name);
106
+ if (index === -1)
107
+ return undefined;
108
+ const value = args[index + 1];
109
+ return value && !value.startsWith("--") ? value : undefined;
110
+ }
98
111
  if (cliArguments.has("--demo")) {
99
112
  console.log(JSON.stringify(await runLocalReceiptDemo(), null, 2));
100
113
  }
114
+ else if (cliArguments.has("--init")) {
115
+ const dataDir = cliValue("--data-dir");
116
+ try {
117
+ console.log(JSON.stringify(await initializePersistentIdentity(dataDir), null, 2));
118
+ }
119
+ catch (error) {
120
+ if (error.code === "EEXIST") {
121
+ console.log(JSON.stringify(await describeExistingIdentity(dataDir), null, 2));
122
+ }
123
+ else {
124
+ console.error(`aipou-mcp --init failed: ${error instanceof Error ? error.message : String(error)}`);
125
+ process.exitCode = 1;
126
+ }
127
+ }
128
+ }
101
129
  else if (cliArguments.has("--help") || cliArguments.has("-h")) {
102
130
  console.log(`AIPOU MCP Server
103
131
 
104
132
  Usage:
105
133
  aipou-mcp Start the MCP stdio server
106
134
  aipou-mcp --demo Create and verify one disposable local receipt
135
+ aipou-mcp --init Create a protected persistent farming identity
136
+ aipou-mcp --init --data-dir <path>
137
+ Initialize in a specific directory
107
138
  aipou-mcp --help Show this help
108
139
 
109
140
  The demo needs no wallet, funds, network, or configuration. Its temporary
110
- wallet, collector key, and receipt state are removed before the command exits.`);
141
+ wallet, collector key, and receipt state are removed before the command exits.
142
+ Init writes a new dedicated-wallet key with restricted access and never prints
143
+ the secret. Re-running init reports the existing identity instead of
144
+ overwriting it.`);
111
145
  }
112
146
  else {
113
147
  const transport = new StdioServerTransport();
package/dist/init.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ export interface InitializationResult {
2
+ mode: "persistent_identity_initialized" | "persistent_identity_already_initialized";
3
+ wallet: string;
4
+ dataDir: string;
5
+ keyFile: string;
6
+ platformAccessRestricted: true;
7
+ mcpConfig: {
8
+ mcpServers: {
9
+ aipou: {
10
+ command: "npx";
11
+ args: ["-y", "aipou-mcp-server"];
12
+ env: {
13
+ AIPOU_AGENT_KEY_FILE: string;
14
+ AIPOU_DATA_DIR: string;
15
+ AIPOU_CONTRACT_ADDRESS: string;
16
+ AIPOU_CLAIMS_ADDRESS: string;
17
+ };
18
+ };
19
+ };
20
+ };
21
+ safety: {
22
+ dedicatedWalletCreated: boolean;
23
+ privateKeyPrinted: false;
24
+ existingKeyOverwritten: false;
25
+ fundsMoved: false;
26
+ claimSubmitted: false;
27
+ };
28
+ }
29
+ export declare function initializePersistentIdentity(dataDirInput?: string): Promise<InitializationResult>;
30
+ export declare function describeExistingIdentity(dataDirInput?: string): Promise<InitializationResult>;
package/dist/init.js ADDED
@@ -0,0 +1,69 @@
1
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { Wallet } from "ethers";
5
+ import { restrictToCurrentUser } from "./secure-file.js";
6
+ const TOKEN_ADDRESS = "0x55f0Cc5e51A1284D20337d6cbb18938C8A1ABCbB";
7
+ const CLAIMS_ADDRESS = "0x4ca4C98fB784D20EdC8E2A7F531dAab4c6e53058";
8
+ function identityPaths(dataDirInput) {
9
+ const dataDir = path.resolve(dataDirInput || path.join(os.homedir(), ".aipou"));
10
+ return { dataDir, keyFile: path.join(dataDir, "agent-wallet.key") };
11
+ }
12
+ function buildResult(mode, walletAddress, dataDir, keyFile) {
13
+ return {
14
+ mode,
15
+ wallet: walletAddress,
16
+ dataDir,
17
+ keyFile,
18
+ platformAccessRestricted: true,
19
+ mcpConfig: {
20
+ mcpServers: {
21
+ aipou: {
22
+ command: "npx",
23
+ args: ["-y", "aipou-mcp-server"],
24
+ env: {
25
+ AIPOU_AGENT_KEY_FILE: keyFile,
26
+ AIPOU_DATA_DIR: dataDir,
27
+ AIPOU_CONTRACT_ADDRESS: TOKEN_ADDRESS,
28
+ AIPOU_CLAIMS_ADDRESS: CLAIMS_ADDRESS
29
+ }
30
+ }
31
+ }
32
+ },
33
+ safety: {
34
+ dedicatedWalletCreated: mode === "persistent_identity_initialized",
35
+ privateKeyPrinted: false,
36
+ existingKeyOverwritten: false,
37
+ fundsMoved: false,
38
+ claimSubmitted: false
39
+ }
40
+ };
41
+ }
42
+ export async function initializePersistentIdentity(dataDirInput) {
43
+ const { dataDir, keyFile } = identityPaths(dataDirInput);
44
+ const wallet = Wallet.createRandom();
45
+ await mkdir(dataDir, { recursive: true, mode: 0o700 });
46
+ await writeFile(keyFile, `${wallet.privateKey}\n`, {
47
+ encoding: "utf8",
48
+ mode: 0o600,
49
+ flag: "wx"
50
+ });
51
+ const platformAccessRestricted = await restrictToCurrentUser(keyFile);
52
+ if (!platformAccessRestricted) {
53
+ await rm(keyFile, { force: true });
54
+ throw new Error("Could not restrict dedicated-wallet key access to the current user");
55
+ }
56
+ return buildResult("persistent_identity_initialized", wallet.address, dataDir, keyFile);
57
+ }
58
+ // Second-run UX: an existing key is reported, never regenerated or printed.
59
+ // Re-applying the ACL heals a broken restriction; the existing key is never removed.
60
+ export async function describeExistingIdentity(dataDirInput) {
61
+ const { dataDir, keyFile } = identityPaths(dataDirInput);
62
+ const privateKey = (await readFile(keyFile, "utf8")).trim();
63
+ const wallet = new Wallet(privateKey);
64
+ const platformAccessRestricted = await restrictToCurrentUser(keyFile);
65
+ if (!platformAccessRestricted) {
66
+ throw new Error("Existing dedicated-wallet key found, but its access could not be restricted to the current user");
67
+ }
68
+ return buildResult("persistent_identity_already_initialized", wallet.address, dataDir, keyFile);
69
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,113 @@
1
+ import assert from "node:assert/strict";
2
+ import { execFile } from "node:child_process";
3
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import test from "node:test";
7
+ import { fileURLToPath } from "node:url";
8
+ import { promisify } from "node:util";
9
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
10
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
11
+ import { agentWallet } from "./identity.js";
12
+ import { describeExistingIdentity, initializePersistentIdentity } from "./init.js";
13
+ const execFileAsync = promisify(execFile);
14
+ function parseTextResult(result) {
15
+ const content = result.content;
16
+ const text = content?.find((part) => part.type === "text")?.text;
17
+ if (!text)
18
+ throw new Error("Tool result did not include a text JSON payload");
19
+ return JSON.parse(text);
20
+ }
21
+ test("persistent init creates a dedicated key file without printing the secret", async () => {
22
+ const dataDir = await mkdtemp(path.join(os.tmpdir(), "aipou-init-test-"));
23
+ const previousInlineKey = process.env.AIPOU_AGENT_PRIVATE_KEY;
24
+ const previousKeyFile = process.env.AIPOU_AGENT_KEY_FILE;
25
+ try {
26
+ const result = await initializePersistentIdentity(dataDir);
27
+ const privateKey = (await readFile(result.keyFile, "utf8")).trim();
28
+ assert.equal(result.mode, "persistent_identity_initialized");
29
+ assert.equal(result.platformAccessRestricted, true);
30
+ assert.match(result.wallet, /^0x[0-9a-fA-F]{40}$/);
31
+ assert.match(privateKey, /^0x[0-9a-fA-F]{64}$/);
32
+ assert.equal(JSON.stringify(result).includes(privateKey), false);
33
+ assert.equal(result.mcpConfig.mcpServers.aipou.env.AIPOU_AGENT_KEY_FILE, result.keyFile);
34
+ assert.deepEqual(result.safety, {
35
+ dedicatedWalletCreated: true,
36
+ privateKeyPrinted: false,
37
+ existingKeyOverwritten: false,
38
+ fundsMoved: false,
39
+ claimSubmitted: false
40
+ });
41
+ delete process.env.AIPOU_AGENT_PRIVATE_KEY;
42
+ process.env.AIPOU_AGENT_KEY_FILE = result.keyFile;
43
+ assert.equal(agentWallet().address, result.wallet);
44
+ await assert.rejects(initializePersistentIdentity(dataDir), /EEXIST|file already exists/i);
45
+ assert.equal((await readFile(result.keyFile, "utf8")).trim(), privateKey);
46
+ const existing = await describeExistingIdentity(dataDir);
47
+ assert.equal(existing.mode, "persistent_identity_already_initialized");
48
+ assert.equal(existing.wallet, result.wallet);
49
+ assert.equal(existing.safety.dedicatedWalletCreated, false);
50
+ assert.equal(JSON.stringify(existing).includes(privateKey), false);
51
+ assert.equal((await readFile(result.keyFile, "utf8")).trim(), privateKey);
52
+ }
53
+ finally {
54
+ if (previousInlineKey === undefined)
55
+ delete process.env.AIPOU_AGENT_PRIVATE_KEY;
56
+ else
57
+ process.env.AIPOU_AGENT_PRIVATE_KEY = previousInlineKey;
58
+ if (previousKeyFile === undefined)
59
+ delete process.env.AIPOU_AGENT_KEY_FILE;
60
+ else
61
+ process.env.AIPOU_AGENT_KEY_FILE = previousKeyFile;
62
+ await rm(dataDir, { recursive: true, force: true });
63
+ }
64
+ });
65
+ test("re-running CLI init reports the existing identity cleanly", async () => {
66
+ const dataDir = await mkdtemp(path.join(os.tmpdir(), "aipou-init-rerun-test-"));
67
+ const indexPath = fileURLToPath(new URL("./index.js", import.meta.url));
68
+ try {
69
+ const first = JSON.parse((await execFileAsync(process.execPath, [indexPath, "--init", "--data-dir", dataDir])).stdout);
70
+ const { stdout, stderr } = await execFileAsync(process.execPath, [indexPath, "--init", "--data-dir", dataDir]);
71
+ const second = JSON.parse(stdout);
72
+ const privateKey = (await readFile(second.keyFile, "utf8")).trim();
73
+ assert.equal(second.mode, "persistent_identity_already_initialized");
74
+ assert.equal(second.wallet, first.wallet);
75
+ assert.equal(second.safety.dedicatedWalletCreated, false);
76
+ assert.equal(second.safety.existingKeyOverwritten, false);
77
+ assert.equal(stdout.includes(privateKey), false);
78
+ assert.equal(stderr.includes("EEXIST"), false);
79
+ }
80
+ finally {
81
+ await rm(dataDir, { recursive: true, force: true });
82
+ }
83
+ });
84
+ test("CLI init output starts an MCP server with the generated key file", async () => {
85
+ const dataDir = await mkdtemp(path.join(os.tmpdir(), "aipou-init-cli-test-"));
86
+ const indexPath = fileURLToPath(new URL("./index.js", import.meta.url));
87
+ let transport;
88
+ try {
89
+ const { stdout } = await execFileAsync(process.execPath, [indexPath, "--init", "--data-dir", dataDir]);
90
+ const result = JSON.parse(stdout);
91
+ const privateKey = (await readFile(result.keyFile, "utf8")).trim();
92
+ assert.equal(stdout.includes(privateKey), false);
93
+ const config = result.mcpConfig.mcpServers.aipou;
94
+ transport = new StdioClientTransport({
95
+ command: process.execPath,
96
+ args: [indexPath],
97
+ stderr: "pipe",
98
+ env: { ...process.env, ...config.env }
99
+ });
100
+ const client = new Client({ name: "aipou-init-cli-test", version: "1.0.0" });
101
+ await client.connect(transport);
102
+ const identity = parseTextResult(await client.callTool({
103
+ name: "get_aipou_identity",
104
+ arguments: {}
105
+ }));
106
+ assert.equal(identity.wallet, result.wallet);
107
+ }
108
+ finally {
109
+ if (transport)
110
+ await transport.close();
111
+ await rm(dataDir, { recursive: true, force: true });
112
+ }
113
+ });
@@ -0,0 +1 @@
1
+ export declare function restrictToCurrentUser(filePath: string): Promise<boolean>;
@@ -0,0 +1,21 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execFileAsync = promisify(execFile);
4
+ // mode 0o600 is a no-op on NTFS, so Windows also needs an explicit ACL.
5
+ // A false result is surfaced by onboarding; existing receipt collection keeps
6
+ // its historical best-effort behavior.
7
+ export async function restrictToCurrentUser(filePath) {
8
+ if (process.platform !== "win32")
9
+ return true;
10
+ const user = process.env.USERNAME;
11
+ if (!user)
12
+ return false;
13
+ const principal = process.env.USERDOMAIN ? `${process.env.USERDOMAIN}\\${user}` : user;
14
+ try {
15
+ await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${principal}:F`]);
16
+ return true;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
@@ -0,0 +1 @@
1
+ export declare function getPackageVersion(): Promise<string>;
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "aipou-mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.4.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
6
  "author": "0xddneto",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "scripts": {
38
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
+ "test": "npm run build && node --test dist/protocol.test.js dist/rewards.test.js dist/canonical.test.js dist/demo.test.js dist/init.test.js dist/version.test.js",
40
40
  "pack:check": "npm test && npm pack --dry-run",
41
41
  "demo": "npm run build && node dist/index.js --demo",
42
42
  "dev": "tsx src/index.ts",
package/server.json CHANGED
@@ -9,23 +9,30 @@
9
9
  "source": "github",
10
10
  "subfolder": "mcp-server"
11
11
  },
12
- "version": "0.3.0",
12
+ "version": "0.4.0",
13
13
  "packages": [
14
14
  {
15
15
  "registryType": "npm",
16
16
  "identifier": "aipou-mcp-server",
17
- "version": "0.3.0",
17
+ "version": "0.4.0",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  },
21
21
  "environmentVariables": [
22
22
  {
23
- "description": "Private key for a new dedicated farming wallet. Never use a primary wallet.",
24
- "isRequired": true,
23
+ "description": "Optional inline private key for a dedicated farming wallet. Prefer AIPOU_AGENT_KEY_FILE and never use a primary wallet.",
24
+ "isRequired": false,
25
25
  "format": "string",
26
26
  "isSecret": true,
27
27
  "name": "AIPOU_AGENT_PRIVATE_KEY"
28
28
  },
29
+ {
30
+ "description": "Path to the protected dedicated-wallet key created by aipou-mcp --init.",
31
+ "isRequired": false,
32
+ "format": "string",
33
+ "isSecret": false,
34
+ "name": "AIPOU_AGENT_KEY_FILE"
35
+ },
29
36
  {
30
37
  "description": "AIPOU token contract address on the configured network.",
31
38
  "isRequired": true,