myceliumail 1.0.1 → 1.0.3
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/dist/bin/myceliumail.js +2 -0
- package/dist/bin/myceliumail.js.map +1 -1
- package/dist/commands/key-announce.d.ts +6 -0
- package/dist/commands/key-announce.d.ts.map +1 -0
- package/dist/commands/key-announce.js +63 -0
- package/dist/commands/key-announce.js.map +1 -0
- package/docs/LESSONS_LEARNED.md +127 -0
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +2 -2
- package/mcp-server/src/lib/config.ts +36 -2
- package/mcp-server/src/lib/storage.ts +32 -22
- package/package.json +1 -1
- package/src/bin/myceliumail.ts +2 -0
- package/src/commands/key-announce.ts +70 -0
package/dist/bin/myceliumail.js
CHANGED
|
@@ -12,6 +12,7 @@ import { loadConfig } from '../lib/config.js';
|
|
|
12
12
|
import { createKeygenCommand } from '../commands/keygen.js';
|
|
13
13
|
import { createKeysCommand } from '../commands/keys.js';
|
|
14
14
|
import { createKeyImportCommand } from '../commands/key-import.js';
|
|
15
|
+
import { createKeyAnnounceCommand } from '../commands/key-announce.js';
|
|
15
16
|
import { createSendCommand } from '../commands/send.js';
|
|
16
17
|
import { createInboxCommand } from '../commands/inbox.js';
|
|
17
18
|
import { createReadCommand } from '../commands/read.js';
|
|
@@ -33,6 +34,7 @@ Config: ~/.myceliumail/config.json
|
|
|
33
34
|
program.addCommand(createKeygenCommand());
|
|
34
35
|
program.addCommand(createKeysCommand());
|
|
35
36
|
program.addCommand(createKeyImportCommand());
|
|
37
|
+
program.addCommand(createKeyAnnounceCommand());
|
|
36
38
|
program.addCommand(createSendCommand());
|
|
37
39
|
program.addCommand(createInboxCommand());
|
|
38
40
|
program.addCommand(createReadCommand());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"myceliumail.js","sourceRoot":"","sources":["../../src/bin/myceliumail.ts"],"names":[],"mappings":";AAEA;;;;GAIG;AAEH,4CAA4C;AAC5C,OAAO,eAAe,CAAC;AAEvB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,kBAAkB;AAClB,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACF,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,+DAA+D,CAAC;KAC5E,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,6BAA6B;AAC7B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;AAC5B,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE;iBACZ,MAAM,CAAC,OAAO;;CAE9B,CAAC,CAAC;AAEH,oBAAoB;AACpB,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAC1C,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAC7C,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;AACzC,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAC7C,OAAO,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAC7C,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;AAEzC,gBAAgB;AAChB,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"myceliumail.js","sourceRoot":"","sources":["../../src/bin/myceliumail.ts"],"names":[],"mappings":";AAEA;;;;GAIG;AAEH,4CAA4C;AAC5C,OAAO,eAAe,CAAC;AAEvB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,kBAAkB;AAClB,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACF,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,+DAA+D,CAAC;KAC5E,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,6BAA6B;AAC7B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;AAC5B,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE;iBACZ,MAAM,CAAC,OAAO;;CAE9B,CAAC,CAAC;AAEH,oBAAoB;AACpB,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAC1C,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAC7C,OAAO,CAAC,UAAU,CAAC,wBAAwB,EAAE,CAAC,CAAC;AAC/C,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;AACzC,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAC7C,OAAO,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAC7C,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;AAEzC,gBAAgB;AAChB,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-announce.d.ts","sourceRoot":"","sources":["../../src/commands/key-announce.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,wBAAwB,IAAI,OAAO,CA6DlD"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* key-announce command - Publish your public key to Supabase
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { loadConfig, hasSupabaseConfig } from '../lib/config.js';
|
|
6
|
+
import { hasKeyPair, loadKeyPair, getPublicKeyBase64 } from '../lib/crypto.js';
|
|
7
|
+
export function createKeyAnnounceCommand() {
|
|
8
|
+
return new Command('key-announce')
|
|
9
|
+
.description('Publish your public key to the network')
|
|
10
|
+
.action(async () => {
|
|
11
|
+
const config = loadConfig();
|
|
12
|
+
const agentId = config.agentId;
|
|
13
|
+
// Check for keypair
|
|
14
|
+
if (!hasKeyPair(agentId)) {
|
|
15
|
+
console.error(`❌ No keypair found for ${agentId}`);
|
|
16
|
+
console.log(' Run: mycmail keygen');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
// Check for Supabase
|
|
20
|
+
if (!hasSupabaseConfig(config)) {
|
|
21
|
+
console.error('❌ Supabase not configured');
|
|
22
|
+
console.log(' Set supabase_url and supabase_key in ~/.myceliumail/config.json');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const keyPair = loadKeyPair(agentId);
|
|
26
|
+
if (!keyPair) {
|
|
27
|
+
console.error('❌ Failed to load keypair');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const publicKey = getPublicKeyBase64(keyPair);
|
|
31
|
+
try {
|
|
32
|
+
// Make direct Supabase request
|
|
33
|
+
const url = `${config.supabaseUrl}/rest/v1/agent_keys`;
|
|
34
|
+
const response = await fetch(url, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
'apikey': config.supabaseKey,
|
|
39
|
+
'Authorization': `Bearer ${config.supabaseKey}`,
|
|
40
|
+
'Prefer': 'resolution=merge-duplicates',
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
agent_id: agentId,
|
|
44
|
+
public_key: publicKey,
|
|
45
|
+
updated_at: new Date().toISOString(),
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const error = await response.text();
|
|
50
|
+
throw new Error(error);
|
|
51
|
+
}
|
|
52
|
+
console.log('📢 Public key announced successfully!\n');
|
|
53
|
+
console.log(`Agent ID: ${agentId}`);
|
|
54
|
+
console.log(`Public Key: ${publicKey}`);
|
|
55
|
+
console.log('\n✅ Other agents can now send you encrypted messages');
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error('❌ Failed to announce key:', error);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=key-announce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-announce.js","sourceRoot":"","sources":["../../src/commands/key-announce.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE/E,MAAM,UAAU,wBAAwB;IACpC,OAAO,IAAI,OAAO,CAAC,cAAc,CAAC;SAC7B,WAAW,CAAC,wCAAwC,CAAC;SACrD,MAAM,CAAC,KAAK,IAAI,EAAE;QACf,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAE/B,oBAAoB;QACpB,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,KAAK,CAAC,0BAA0B,OAAO,EAAE,CAAC,CAAC;YACnD,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;QAED,qBAAqB;QACrB,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;YAC3C,OAAO,CAAC,GAAG,CAAC,oEAAoE,CAAC,CAAC;YAClF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;QAED,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;YAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;QAED,MAAM,SAAS,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAE9C,IAAI,CAAC;YACD,+BAA+B;YAC/B,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,WAAW,qBAAqB,CAAC;YACvD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC9B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACL,cAAc,EAAE,kBAAkB;oBAClC,QAAQ,EAAE,MAAM,CAAC,WAAY;oBAC7B,eAAe,EAAE,UAAU,MAAM,CAAC,WAAW,EAAE;oBAC/C,QAAQ,EAAE,6BAA6B;iBAC1C;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACjB,QAAQ,EAAE,OAAO;oBACjB,UAAU,EAAE,SAAS;oBACrB,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;iBACvC,CAAC;aACL,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACf,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACpC,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC;YAC3B,CAAC;YAED,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,aAAa,OAAO,EAAE,CAAC,CAAC;YACpC,OAAO,CAAC,GAAG,CAAC,eAAe,SAAS,EAAE,CAAC,CAAC;YACxC,OAAO,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC;QACxE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC;YAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;IACL,CAAC,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Lessons Learned: MCP Supabase Connection Debugging
|
|
2
|
+
|
|
3
|
+
## Date
|
|
4
|
+
December 17-18, 2025
|
|
5
|
+
|
|
6
|
+
## Issue Summary
|
|
7
|
+
Watson (Claude Desktop MCP user) could not see Supabase messages. The MCP server only showed locally cached messages instead of fetching from the cloud.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Root Causes Identified
|
|
12
|
+
|
|
13
|
+
### 1. Schema Mismatch (Code Bug)
|
|
14
|
+
**Problem:** The MCP server's `storage.ts` was using incorrect column names when querying Supabase:
|
|
15
|
+
- Used `recipient` instead of `to_agent`
|
|
16
|
+
- Used `sender` instead of `from_agent`
|
|
17
|
+
- Used `body` instead of `message`
|
|
18
|
+
|
|
19
|
+
**Symptom:** Supabase queries returned empty results because the columns didn't exist.
|
|
20
|
+
|
|
21
|
+
**Fix:** Updated all Supabase queries in `storage.ts` to use the correct column names matching the actual `agent_messages` table schema.
|
|
22
|
+
|
|
23
|
+
**Lesson:** Always verify that code column names match the actual database schema. Consider using generated types from Supabase to catch these mismatches at compile time.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
### 2. Silent Error Swallowing (Bad Pattern)
|
|
28
|
+
**Problem:** All Supabase operations had empty `catch` blocks:
|
|
29
|
+
```typescript
|
|
30
|
+
} catch {
|
|
31
|
+
// Fall through to local
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Symptom:** Errors were completely hidden. When Supabase failed, the code silently fell back to local storage without any indication of why.
|
|
36
|
+
|
|
37
|
+
**Fix:** Added proper error logging to all catch blocks:
|
|
38
|
+
```typescript
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error('getInbox failed, falling back to local:', err);
|
|
41
|
+
// Fall through to local
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Lesson:** Never use empty catch blocks. Always log errors, even when recovering gracefully. Silent failures are debugging nightmares.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
### 3. NPX Caching (Deployment Issue)
|
|
50
|
+
**Problem:** `npx myceliumail-mcp` was aggressively caching old versions of the package, even after publishing fixes to npm.
|
|
51
|
+
|
|
52
|
+
**Symptom:** Even after publishing v1.0.6 with fixes, Claude Desktop kept running the broken old version.
|
|
53
|
+
|
|
54
|
+
**Attempted Solutions:**
|
|
55
|
+
- `npx -y myceliumail-mcp@1.0.6` - Did not reliably bust cache
|
|
56
|
+
- `npm cache clean --force` - Not effective for npx cache
|
|
57
|
+
|
|
58
|
+
**Final Fix:** Configured Claude Desktop to run the local build directly:
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"command": "node",
|
|
62
|
+
"args": ["/path/to/dist/server.js"]
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Lesson:** For development and debugging, always use local builds with direct paths. Don't trust `npx` caching behavior during active development. For production users, consider adding version-specific instructions: `npx myceliumail-mcp@latest`.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
### 4. Missing Global Config Support (Feature Gap)
|
|
71
|
+
**Problem:** The MCP server only read Supabase credentials from environment variables, but Claude Desktop's MCP config doesn't support complex env var setup easily.
|
|
72
|
+
|
|
73
|
+
**Fix:** Added fallback to read from `~/.myceliumail/config.json`, same as the CLI tool.
|
|
74
|
+
|
|
75
|
+
**Lesson:** MCP servers should support multiple config sources (env vars, config files) for flexibility across different deployment contexts.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Debugging Techniques Used
|
|
80
|
+
|
|
81
|
+
### 1. File-Based Debug Logging
|
|
82
|
+
When console output was being swallowed by the MCP runtime, we added file-based logging:
|
|
83
|
+
```typescript
|
|
84
|
+
const LOG_FILE = join(homedir(), '.myceliumail', 'debug.log');
|
|
85
|
+
appendFileSync(LOG_FILE, `[${timestamp}] ${msg}\n`);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This proved essential for diagnosing issues in environments where stderr isn't visible.
|
|
89
|
+
|
|
90
|
+
### 2. Reproduction Script
|
|
91
|
+
Created `debug-mcp-connection.js` to test the Supabase connection outside of the MCP context:
|
|
92
|
+
```javascript
|
|
93
|
+
const { hasSupabase } = require('./dist/lib/config.js');
|
|
94
|
+
const { getInbox } = require('./dist/lib/storage.js');
|
|
95
|
+
// Test directly
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
This isolated whether the issue was in the code or the MCP runtime environment.
|
|
99
|
+
|
|
100
|
+
### 3. Direct Supabase Verification
|
|
101
|
+
Used the Supabase MCP tool to verify data existed:
|
|
102
|
+
```sql
|
|
103
|
+
SELECT * FROM agent_messages WHERE to_agent = 'watson';
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This confirmed the data was in the cloud and the issue was in fetching, not storage.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Prevention Guidelines
|
|
111
|
+
|
|
112
|
+
1. **Schema Validation:** Use TypeScript generated types from Supabase CLI to catch column name mismatches at compile time.
|
|
113
|
+
|
|
114
|
+
2. **Error Visibility:** Always log errors, never swallow them silently. Use a consistent error logging pattern.
|
|
115
|
+
|
|
116
|
+
3. **Multiple Config Sources:** Support env vars AND config files for flexibility.
|
|
117
|
+
|
|
118
|
+
4. **Local Development:** Always test with local builds during debugging. Don't rely on npm/npx for rapid iteration.
|
|
119
|
+
|
|
120
|
+
5. **Integration Tests:** Add tests that actually hit Supabase (in a test project) to catch schema drift.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Related Files Modified
|
|
125
|
+
- `mcp-server/src/lib/config.ts` - Added config file support
|
|
126
|
+
- `mcp-server/src/lib/storage.ts` - Fixed column names, added error logging
|
|
127
|
+
- `mcp-server/package.json` - Version bumps
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myceliumail-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "myceliumail-mcp",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.7",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
12
12
|
"tweetnacl": "^1.0.3",
|
package/mcp-server/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myceliumail-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "MCP server for Myceliumail - End-to-End Encrypted Messaging for AI Agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server.js",
|
|
@@ -46,4 +46,4 @@
|
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": ">=18.0.0"
|
|
48
48
|
}
|
|
49
|
-
}
|
|
49
|
+
}
|
|
@@ -1,19 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Myceliumail MCP - Config Module
|
|
3
|
+
*
|
|
4
|
+
* Reads configuration from:
|
|
5
|
+
* 1. Environment variables (highest priority)
|
|
6
|
+
* 2. ~/.myceliumail/config.json (fallback)
|
|
3
7
|
*/
|
|
4
8
|
|
|
9
|
+
import { existsSync, readFileSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
|
|
13
|
+
const CONFIG_FILE = join(homedir(), '.myceliumail', 'config.json');
|
|
14
|
+
|
|
15
|
+
interface FileConfig {
|
|
16
|
+
agent_id?: string;
|
|
17
|
+
supabase_url?: string;
|
|
18
|
+
supabase_key?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let cachedFileConfig: FileConfig | null = null;
|
|
22
|
+
|
|
23
|
+
function loadFileConfig(): FileConfig {
|
|
24
|
+
if (cachedFileConfig) return cachedFileConfig;
|
|
25
|
+
|
|
26
|
+
if (existsSync(CONFIG_FILE)) {
|
|
27
|
+
try {
|
|
28
|
+
const raw = readFileSync(CONFIG_FILE, 'utf-8');
|
|
29
|
+
cachedFileConfig = JSON.parse(raw);
|
|
30
|
+
return cachedFileConfig!;
|
|
31
|
+
} catch {
|
|
32
|
+
// Invalid config file - ignore
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
|
|
5
38
|
export function getAgentId(): string {
|
|
6
39
|
return process.env.MYCELIUMAIL_AGENT_ID ||
|
|
7
40
|
process.env.MYCELIUMAIL_AGENT ||
|
|
41
|
+
loadFileConfig().agent_id ||
|
|
8
42
|
'anonymous';
|
|
9
43
|
}
|
|
10
44
|
|
|
11
45
|
export function getSupabaseUrl(): string | undefined {
|
|
12
|
-
return process.env.SUPABASE_URL;
|
|
46
|
+
return process.env.SUPABASE_URL || loadFileConfig().supabase_url;
|
|
13
47
|
}
|
|
14
48
|
|
|
15
49
|
export function getSupabaseKey(): string | undefined {
|
|
16
|
-
return process.env.SUPABASE_ANON_KEY;
|
|
50
|
+
return process.env.SUPABASE_ANON_KEY || loadFileConfig().supabase_key;
|
|
17
51
|
}
|
|
18
52
|
|
|
19
53
|
export function hasSupabase(): boolean {
|
|
@@ -59,6 +59,7 @@ function toMessage(stored: StoredMessage): Message {
|
|
|
59
59
|
// Supabase helpers
|
|
60
60
|
async function supabaseRequest<T>(path: string, options: RequestInit = {}): Promise<T> {
|
|
61
61
|
const url = `${getSupabaseUrl()}/rest/v1${path}`;
|
|
62
|
+
|
|
62
63
|
const response = await fetch(url, {
|
|
63
64
|
...options,
|
|
64
65
|
headers: {
|
|
@@ -69,7 +70,13 @@ async function supabaseRequest<T>(path: string, options: RequestInit = {}): Prom
|
|
|
69
70
|
...options.headers,
|
|
70
71
|
},
|
|
71
72
|
});
|
|
72
|
-
|
|
73
|
+
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
const text = await response.text();
|
|
76
|
+
console.error(`Supabase request failed (${response.status}): ${text}`);
|
|
77
|
+
throw new Error(text);
|
|
78
|
+
}
|
|
79
|
+
|
|
73
80
|
if (response.status === 204) return {} as T;
|
|
74
81
|
return response.json() as Promise<T>;
|
|
75
82
|
}
|
|
@@ -106,10 +113,10 @@ export async function sendMessage(
|
|
|
106
113
|
const [result] = await supabaseRequest<StoredMessage[]>('/agent_messages', {
|
|
107
114
|
method: 'POST',
|
|
108
115
|
body: JSON.stringify({
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
from_agent: newMessage.sender,
|
|
117
|
+
to_agent: newMessage.recipient,
|
|
111
118
|
subject: newMessage.subject || null,
|
|
112
|
-
|
|
119
|
+
message: newMessage.body || null,
|
|
113
120
|
encrypted: newMessage.encrypted,
|
|
114
121
|
ciphertext: newMessage.ciphertext,
|
|
115
122
|
nonce: newMessage.nonce,
|
|
@@ -120,7 +127,8 @@ export async function sendMessage(
|
|
|
120
127
|
...newMessage,
|
|
121
128
|
id: (result as unknown as { id: string }).id
|
|
122
129
|
});
|
|
123
|
-
} catch {
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.error('sendMessage failed, falling back to local:', err);
|
|
124
132
|
// Fall through to local
|
|
125
133
|
}
|
|
126
134
|
}
|
|
@@ -138,32 +146,33 @@ export async function getInbox(
|
|
|
138
146
|
): Promise<Message[]> {
|
|
139
147
|
if (hasSupabase()) {
|
|
140
148
|
try {
|
|
141
|
-
let query = `/agent_messages?
|
|
149
|
+
let query = `/agent_messages?to_agent=eq.${agentId}&order=created_at.desc`;
|
|
142
150
|
if (options?.unreadOnly) query += '&read=eq.false';
|
|
143
151
|
if (options?.limit) query += `&limit=${options.limit}`;
|
|
144
152
|
|
|
145
153
|
const results = await supabaseRequest<Array<{
|
|
146
|
-
id: string;
|
|
147
|
-
subject: string;
|
|
154
|
+
id: string; from_agent: string; to_agent: string;
|
|
155
|
+
subject: string; message: string; encrypted: boolean;
|
|
148
156
|
ciphertext: string; nonce: string; sender_public_key: string;
|
|
149
|
-
read: boolean;
|
|
157
|
+
read: boolean; created_at: string;
|
|
150
158
|
}>>(query);
|
|
151
159
|
|
|
152
160
|
return results.map(r => ({
|
|
153
161
|
id: r.id,
|
|
154
|
-
sender: r.
|
|
155
|
-
recipient: r.
|
|
162
|
+
sender: r.from_agent,
|
|
163
|
+
recipient: r.to_agent,
|
|
156
164
|
subject: r.subject || '',
|
|
157
|
-
body: r.
|
|
165
|
+
body: r.message || '',
|
|
158
166
|
encrypted: r.encrypted,
|
|
159
167
|
ciphertext: r.ciphertext,
|
|
160
168
|
nonce: r.nonce,
|
|
161
169
|
senderPublicKey: r.sender_public_key,
|
|
162
170
|
read: r.read,
|
|
163
|
-
archived:
|
|
171
|
+
archived: false,
|
|
164
172
|
createdAt: new Date(r.created_at),
|
|
165
173
|
}));
|
|
166
|
-
} catch {
|
|
174
|
+
} catch (err) {
|
|
175
|
+
console.error('getInbox failed, falling back to local:', err);
|
|
167
176
|
// Fall through to local
|
|
168
177
|
}
|
|
169
178
|
}
|
|
@@ -181,30 +190,31 @@ export async function getMessage(id: string): Promise<Message | null> {
|
|
|
181
190
|
if (hasSupabase()) {
|
|
182
191
|
try {
|
|
183
192
|
const results = await supabaseRequest<Array<{
|
|
184
|
-
id: string;
|
|
185
|
-
subject: string;
|
|
193
|
+
id: string; from_agent: string; to_agent: string;
|
|
194
|
+
subject: string; message: string; encrypted: boolean;
|
|
186
195
|
ciphertext: string; nonce: string; sender_public_key: string;
|
|
187
|
-
read: boolean;
|
|
196
|
+
read: boolean; created_at: string;
|
|
188
197
|
}>>(`/agent_messages?id=eq.${id}`);
|
|
189
198
|
|
|
190
199
|
if (results.length > 0) {
|
|
191
200
|
const r = results[0];
|
|
192
201
|
return {
|
|
193
202
|
id: r.id,
|
|
194
|
-
sender: r.
|
|
195
|
-
recipient: r.
|
|
203
|
+
sender: r.from_agent,
|
|
204
|
+
recipient: r.to_agent,
|
|
196
205
|
subject: r.subject || '',
|
|
197
|
-
body: r.
|
|
206
|
+
body: r.message || '',
|
|
198
207
|
encrypted: r.encrypted,
|
|
199
208
|
ciphertext: r.ciphertext,
|
|
200
209
|
nonce: r.nonce,
|
|
201
210
|
senderPublicKey: r.sender_public_key,
|
|
202
211
|
read: r.read,
|
|
203
|
-
archived:
|
|
212
|
+
archived: false,
|
|
204
213
|
createdAt: new Date(r.created_at),
|
|
205
214
|
};
|
|
206
215
|
}
|
|
207
|
-
} catch {
|
|
216
|
+
} catch (err) {
|
|
217
|
+
console.error('getMessage failed, falling back to local:', err);
|
|
208
218
|
// Fall through
|
|
209
219
|
}
|
|
210
220
|
}
|
package/package.json
CHANGED
package/src/bin/myceliumail.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { loadConfig } from '../lib/config.js';
|
|
|
16
16
|
import { createKeygenCommand } from '../commands/keygen.js';
|
|
17
17
|
import { createKeysCommand } from '../commands/keys.js';
|
|
18
18
|
import { createKeyImportCommand } from '../commands/key-import.js';
|
|
19
|
+
import { createKeyAnnounceCommand } from '../commands/key-announce.js';
|
|
19
20
|
import { createSendCommand } from '../commands/send.js';
|
|
20
21
|
import { createInboxCommand } from '../commands/inbox.js';
|
|
21
22
|
import { createReadCommand } from '../commands/read.js';
|
|
@@ -41,6 +42,7 @@ Config: ~/.myceliumail/config.json
|
|
|
41
42
|
program.addCommand(createKeygenCommand());
|
|
42
43
|
program.addCommand(createKeysCommand());
|
|
43
44
|
program.addCommand(createKeyImportCommand());
|
|
45
|
+
program.addCommand(createKeyAnnounceCommand());
|
|
44
46
|
program.addCommand(createSendCommand());
|
|
45
47
|
program.addCommand(createInboxCommand());
|
|
46
48
|
program.addCommand(createReadCommand());
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* key-announce command - Publish your public key to Supabase
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { loadConfig, hasSupabaseConfig } from '../lib/config.js';
|
|
7
|
+
import { hasKeyPair, loadKeyPair, getPublicKeyBase64 } from '../lib/crypto.js';
|
|
8
|
+
|
|
9
|
+
export function createKeyAnnounceCommand(): Command {
|
|
10
|
+
return new Command('key-announce')
|
|
11
|
+
.description('Publish your public key to the network')
|
|
12
|
+
.action(async () => {
|
|
13
|
+
const config = loadConfig();
|
|
14
|
+
const agentId = config.agentId;
|
|
15
|
+
|
|
16
|
+
// Check for keypair
|
|
17
|
+
if (!hasKeyPair(agentId)) {
|
|
18
|
+
console.error(`❌ No keypair found for ${agentId}`);
|
|
19
|
+
console.log(' Run: mycmail keygen');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check for Supabase
|
|
24
|
+
if (!hasSupabaseConfig(config)) {
|
|
25
|
+
console.error('❌ Supabase not configured');
|
|
26
|
+
console.log(' Set supabase_url and supabase_key in ~/.myceliumail/config.json');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const keyPair = loadKeyPair(agentId);
|
|
31
|
+
if (!keyPair) {
|
|
32
|
+
console.error('❌ Failed to load keypair');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const publicKey = getPublicKeyBase64(keyPair);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Make direct Supabase request
|
|
40
|
+
const url = `${config.supabaseUrl}/rest/v1/agent_keys`;
|
|
41
|
+
const response = await fetch(url, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: {
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
'apikey': config.supabaseKey!,
|
|
46
|
+
'Authorization': `Bearer ${config.supabaseKey}`,
|
|
47
|
+
'Prefer': 'resolution=merge-duplicates',
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify({
|
|
50
|
+
agent_id: agentId,
|
|
51
|
+
public_key: publicKey,
|
|
52
|
+
updated_at: new Date().toISOString(),
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const error = await response.text();
|
|
58
|
+
throw new Error(error);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log('📢 Public key announced successfully!\n');
|
|
62
|
+
console.log(`Agent ID: ${agentId}`);
|
|
63
|
+
console.log(`Public Key: ${publicKey}`);
|
|
64
|
+
console.log('\n✅ Other agents can now send you encrypted messages');
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('❌ Failed to announce key:', error);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|