Skip to content

Commit 4cc2223

Browse files
author
azeth-sync[bot]
committed
v0.2.3: sync from monorepo 2026-03-07
1 parent 6dfeb61 commit 4cc2223

4 files changed

Lines changed: 133 additions & 3 deletions

File tree

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@azeth/mcp-server",
3-
"version": "0.2.2",
3+
"version": "0.2.3",
44
"mcpName": "io.github.azeth-protocol/mcp-server",
55
"type": "module",
66
"description": "MCP server for the Azeth trust infrastructure — smart accounts, payments, reputation, and discovery tools for AI agents",
@@ -41,8 +41,8 @@
4141
"clean": "rm -rf dist"
4242
},
4343
"dependencies": {
44-
"@azeth/common": "^0.2.2",
45-
"@azeth/sdk": "^0.2.2",
44+
"@azeth/common": "^0.2.3",
45+
"@azeth/sdk": "^0.2.3",
4646
"@modelcontextprotocol/sdk": "^1.0.0",
4747
"dotenv": "^16.4.0",
4848
"viem": "^2.21.0",

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { registerMessagingTools } from './tools/messaging.js';
1111
import { registerGuardianTools } from './tools/guardian.js';
1212
import { registerGuardianApprovalTools } from './tools/guardian-approval.js';
1313
import { createRateLimiter, wrapServerWithRateLimit } from './utils/rate-limit.js';
14+
import { ensurePrivateKey } from './utils/auto-key.js';
1415

1516
const SERVER_NAME = '@azeth/mcp-server';
1617
const SERVER_VERSION = '0.1.0';
@@ -51,6 +52,7 @@ export function createSandboxServer(): McpServer {
5152

5253
/** Start the MCP server on stdio transport */
5354
async function main(): Promise<void> {
55+
ensurePrivateKey();
5456
const server = createServer();
5557
const transport = new StdioServerTransport();
5658
await server.connect(transport);

src/tools/account.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { TrustRegistryModuleAbi } from '@azeth/common/abis';
66
import { createClient, resolveChain, validateAddress } from '../utils/client.js';
77
import { resolveSmartAccount } from '../utils/resolve.js';
88
import { success, error, handleError, guardianRequiredError } from '../utils/response.js';
9+
import { wasAutoGenerated } from '../utils/auto-key.js';
910

1011
/** Minimal ABI for ERC-8004 Identity Registry tokenURI (external contract, not in generated ABIs) */
1112
const ERC8004_TOKEN_URI_ABI = [
@@ -189,6 +190,11 @@ export function registerAccountTools(server: McpServer): void {
189190
...(guardianAddress === client.address ? {
190191
warning: 'Guardian is set to the owner address (self-guardian). This means guardian co-signatures use the same key as the owner, providing no additional security. For production, set AZETH_GUARDIAN_KEY in your environment or provide a separate guardian address.',
191192
} : {}),
193+
...(wasAutoGenerated() ? {
194+
keySource: 'auto-generated',
195+
keyPath: '~/.azeth/key',
196+
keyWarning: 'This account uses an auto-generated demo key stored at ~/.azeth/key. For production, set AZETH_PRIVATE_KEY in your environment with a key you control.',
197+
} : {}),
192198
},
193199
{ txHash: result.txHash },
194200
);

src/utils/auto-key.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/**
2+
* Automatic private key generation and persistence for zero-friction MCP server usage.
3+
*
4+
* When AZETH_PRIVATE_KEY is not set in the environment, this module:
5+
* 1. Checks ~/.azeth/key for a persisted key
6+
* 2. If found, loads and validates it
7+
* 3. If not found, generates a new key and persists it
8+
* 4. Sets process.env['AZETH_PRIVATE_KEY'] so all downstream code works unchanged
9+
*
10+
* File permissions: ~/.azeth/ = 0o700, ~/.azeth/key = 0o600
11+
* The private key is NEVER logged — only the derived EOA address.
12+
*/
13+
14+
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync, statSync } from 'node:fs';
15+
import { homedir } from 'node:os';
16+
import { join } from 'node:path';
17+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
18+
19+
const KEY_FILE_RE = /^0x[0-9a-fA-F]{64}$/;
20+
21+
let _autoGenerated = false;
22+
23+
function log(msg: string): void {
24+
process.stderr.write(`[azeth-mcp] ${msg}\n`);
25+
}
26+
27+
/** Returns true if the current session's key was loaded from ~/.azeth/key or freshly generated (not from env). */
28+
export function wasAutoGenerated(): boolean {
29+
return _autoGenerated;
30+
}
31+
32+
/**
33+
* Ensure AZETH_PRIVATE_KEY is set in the environment.
34+
* If not set, attempt to load from ~/.azeth/key or generate a new one.
35+
* Call this once at startup before creating the MCP server.
36+
*/
37+
export function ensurePrivateKey(): void {
38+
if (process.env['AZETH_PRIVATE_KEY']) return;
39+
40+
const home = homedir();
41+
if (!home) {
42+
generateInMemoryKey('Could not determine home directory — key will not be persisted.');
43+
return;
44+
}
45+
46+
const azethDir = join(home, '.azeth');
47+
const keyFile = join(azethDir, 'key');
48+
49+
// Try loading an existing key
50+
if (existsSync(keyFile)) {
51+
try {
52+
const key = readFileSync(keyFile, 'utf-8').trim();
53+
if (KEY_FILE_RE.test(key)) {
54+
process.env['AZETH_PRIVATE_KEY'] = key;
55+
_autoGenerated = true;
56+
const address = privateKeyToAccount(key as `0x${string}`).address;
57+
log(`Loaded private key from ~/.azeth/key`);
58+
log(`EOA address: ${address}`);
59+
return;
60+
}
61+
log('WARNING: ~/.azeth/key contains invalid key. Regenerating.');
62+
} catch (err) {
63+
log(`WARNING: Could not read ~/.azeth/key: ${err instanceof Error ? err.message : String(err)}`);
64+
}
65+
}
66+
67+
// Ensure ~/.azeth/ directory exists and is a directory
68+
if (existsSync(azethDir)) {
69+
try {
70+
if (!statSync(azethDir).isDirectory()) {
71+
generateInMemoryKey('~/.azeth exists but is not a directory — key will not be persisted.');
72+
return;
73+
}
74+
} catch {
75+
generateInMemoryKey('Could not stat ~/.azeth — key will not be persisted.');
76+
return;
77+
}
78+
} else {
79+
try {
80+
mkdirSync(azethDir, { recursive: true, mode: 0o700 });
81+
} catch (err) {
82+
generateInMemoryKey(`Could not create ~/.azeth directory: ${err instanceof Error ? err.message : String(err)}`);
83+
return;
84+
}
85+
}
86+
87+
// Generate and persist a new key
88+
const newKey = generatePrivateKey();
89+
try {
90+
writeFileSync(keyFile, newKey, { mode: 0o600 });
91+
// Ensure permissions are correct even if umask interfered
92+
chmodSync(keyFile, 0o600);
93+
} catch (err) {
94+
// Persistence failed — use the key in-memory only
95+
process.env['AZETH_PRIVATE_KEY'] = newKey;
96+
_autoGenerated = true;
97+
const address = privateKeyToAccount(newKey).address;
98+
log(`Generated new private key (in-memory only — could not write ~/.azeth/key: ${err instanceof Error ? err.message : String(err)})`);
99+
log(`EOA address: ${address}`);
100+
log('WARNING: This key will be lost when the MCP server restarts.');
101+
return;
102+
}
103+
104+
process.env['AZETH_PRIVATE_KEY'] = newKey;
105+
_autoGenerated = true;
106+
const address = privateKeyToAccount(newKey).address;
107+
log('Generated new private key and saved to ~/.azeth/key');
108+
log(`EOA address: ${address}`);
109+
log('WARNING: This is an auto-generated demo key. For production, set AZETH_PRIVATE_KEY in your environment.');
110+
}
111+
112+
/** Fallback: generate an in-memory-only key when filesystem operations fail */
113+
function generateInMemoryKey(reason: string): void {
114+
log(`WARNING: ${reason}`);
115+
const key = generatePrivateKey();
116+
process.env['AZETH_PRIVATE_KEY'] = key;
117+
_autoGenerated = true;
118+
const address = privateKeyToAccount(key).address;
119+
log(`Generated ephemeral private key (in-memory only)`);
120+
log(`EOA address: ${address}`);
121+
log('WARNING: This key will be lost when the MCP server restarts. Set AZETH_PRIVATE_KEY for persistence.');
122+
}

0 commit comments

Comments
 (0)