estatehelm 1.0.6 → 1.0.8
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 +50 -22
- package/dist/index.js +190 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,19 +24,27 @@ This will:
|
|
|
24
24
|
|
|
25
25
|
### 2. Configure Claude Code
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
**Windows:**
|
|
28
|
+
```bash
|
|
29
|
+
claude mcp add --transport stdio estatehelm -- cmd /c npx estatehelm mcp
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Mac/Linux:**
|
|
33
|
+
```bash
|
|
34
|
+
claude mcp add --transport stdio estatehelm -- npx estatehelm mcp
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**For staging environment:**
|
|
38
|
+
```bash
|
|
39
|
+
# Windows
|
|
40
|
+
claude mcp add --transport stdio estatehelm-staging -- cmd /c npx estatehelm mcp --staging
|
|
41
|
+
|
|
42
|
+
# Mac/Linux
|
|
43
|
+
claude mcp add --transport stdio estatehelm-staging -- npx estatehelm mcp --staging
|
|
38
44
|
```
|
|
39
45
|
|
|
46
|
+
Verify with `claude mcp list`.
|
|
47
|
+
|
|
40
48
|
### 3. Use with Claude Code
|
|
41
49
|
|
|
42
50
|
Start Claude Code and you can now ask questions like:
|
|
@@ -122,20 +130,40 @@ The server exposes your EstateHelm data as MCP resources:
|
|
|
122
130
|
|
|
123
131
|
- `estatehelm://households` - List all households
|
|
124
132
|
- `estatehelm://households/{id}` - Specific household
|
|
125
|
-
- `estatehelm://households/{id}/
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
- `estatehelm://households/{id}/insurance` - Insurance policies
|
|
130
|
-
- `estatehelm://households/{id}/bank_account` - Bank accounts
|
|
131
|
-
- And more...
|
|
133
|
+
- `estatehelm://households/{id}/{entityType}` - Entities by type
|
|
134
|
+
|
|
135
|
+
**Supported entity types:**
|
|
136
|
+
`pet`, `property`, `vehicle`, `contact`, `insurance`, `bank_account`, `investment`, `subscription`, `maintenance_task`, `password`, `access_code`, `document`, `medical`, `prescription`, `credential`, `utility`
|
|
132
137
|
|
|
133
138
|
## MCP Tools
|
|
134
139
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
| Tool | Description | Parameters |
|
|
141
|
+
|------|-------------|------------|
|
|
142
|
+
| `search_entities` | Full-text search across entities | `query` (required), `householdId`, `entityType` |
|
|
143
|
+
| `get_household_summary` | Overview with entity counts | `householdId` (required) |
|
|
144
|
+
| `get_expiring_items` | Items expiring within N days | `days` (default: 30), `householdId` |
|
|
145
|
+
| `get_file` | Download and decrypt a file attachment | `fileId`, `householdId`, `entityId`, `entityType` (all required) |
|
|
146
|
+
| `refresh` | Force re-sync from server | none |
|
|
147
|
+
|
|
148
|
+
**`get_expiring_items` checks:**
|
|
149
|
+
- Insurance policies (`expiration_date`)
|
|
150
|
+
- Vehicle registrations (`registration_expiration`)
|
|
151
|
+
- Vehicle tabs (`tabs_expiration`)
|
|
152
|
+
- Subscriptions (`expiration_date`)
|
|
153
|
+
|
|
154
|
+
**`get_file` returns:**
|
|
155
|
+
- `fileName`: Original filename
|
|
156
|
+
- `mimeType`: File MIME type
|
|
157
|
+
- `size`: File size in bytes
|
|
158
|
+
- `data`: Base64-encoded decrypted file content
|
|
159
|
+
|
|
160
|
+
## MCP Prompts
|
|
161
|
+
|
|
162
|
+
| Prompt | Description |
|
|
163
|
+
|--------|-------------|
|
|
164
|
+
| `household_summary` | "Give me an overview of my household" |
|
|
165
|
+
| `expiring_soon` | "What's expiring in the next 30 days?" |
|
|
166
|
+
| `emergency_contacts` | "Show me emergency contacts" |
|
|
139
167
|
|
|
140
168
|
## Security
|
|
141
169
|
|
package/dist/index.js
CHANGED
|
@@ -1297,10 +1297,40 @@ async function unwrapHouseholdKey(wrappedKey, privateKey, algorithm = DEFAULT_KE
|
|
|
1297
1297
|
// ../encryption/src/webauthnDeviceBound.ts
|
|
1298
1298
|
var RP_ID = typeof window !== "undefined" ? window.location.hostname : "estatehelm.com";
|
|
1299
1299
|
|
|
1300
|
+
// ../encryption/src/fileEncryption.ts
|
|
1301
|
+
var FILE_IV_SIZE = 12;
|
|
1302
|
+
async function decryptFileFromArrayBuffer(householdKey, entityId, entityType, encryptedArrayBuffer, mimeType) {
|
|
1303
|
+
const packed = new Uint8Array(encryptedArrayBuffer);
|
|
1304
|
+
if (packed.length < FILE_IV_SIZE + 16) {
|
|
1305
|
+
throw new Error("Encrypted data too short");
|
|
1306
|
+
}
|
|
1307
|
+
const entityKeyBytes = await deriveEntityKey(householdKey, entityId, entityType);
|
|
1308
|
+
const entityKey = await importEntityKey(entityKeyBytes);
|
|
1309
|
+
const iv = packed.slice(0, FILE_IV_SIZE);
|
|
1310
|
+
const ciphertext = packed.slice(FILE_IV_SIZE);
|
|
1311
|
+
try {
|
|
1312
|
+
const plaintext = await crypto.subtle.decrypt(
|
|
1313
|
+
{ name: "AES-GCM", iv },
|
|
1314
|
+
entityKey,
|
|
1315
|
+
ciphertext
|
|
1316
|
+
);
|
|
1317
|
+
return {
|
|
1318
|
+
bytes: new Uint8Array(plaintext),
|
|
1319
|
+
mimeType
|
|
1320
|
+
};
|
|
1321
|
+
} catch (error) {
|
|
1322
|
+
if (error instanceof Error && error.name === "OperationError") {
|
|
1323
|
+
throw new Error("Failed to decrypt file: Authentication failed");
|
|
1324
|
+
}
|
|
1325
|
+
throw error;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1300
1329
|
// src/config.ts
|
|
1301
1330
|
var import_env_paths = __toESM(require("env-paths"));
|
|
1302
1331
|
var path = __toESM(require("path"));
|
|
1303
1332
|
var fs = __toESM(require("fs"));
|
|
1333
|
+
var os = __toESM(require("os"));
|
|
1304
1334
|
var paths = (0, import_env_paths.default)("estatehelm", { suffix: "" });
|
|
1305
1335
|
var DATA_DIR = paths.data;
|
|
1306
1336
|
var CACHE_DB_PATH = path.join(DATA_DIR, "cache.db");
|
|
@@ -1367,19 +1397,25 @@ function getDeviceId() {
|
|
|
1367
1397
|
}
|
|
1368
1398
|
function getDevicePlatform() {
|
|
1369
1399
|
const platform = process.platform;
|
|
1400
|
+
const hostname2 = os.hostname();
|
|
1401
|
+
let osName;
|
|
1370
1402
|
switch (platform) {
|
|
1371
1403
|
case "darwin":
|
|
1372
|
-
|
|
1404
|
+
osName = "macOS";
|
|
1405
|
+
break;
|
|
1373
1406
|
case "win32":
|
|
1374
|
-
|
|
1407
|
+
osName = "Windows";
|
|
1408
|
+
break;
|
|
1375
1409
|
case "linux":
|
|
1376
|
-
|
|
1410
|
+
osName = "Linux";
|
|
1411
|
+
break;
|
|
1377
1412
|
default:
|
|
1378
|
-
|
|
1413
|
+
osName = platform;
|
|
1379
1414
|
}
|
|
1415
|
+
return `mcp-${osName}-${hostname2}`;
|
|
1380
1416
|
}
|
|
1381
1417
|
function getDeviceUserAgent() {
|
|
1382
|
-
return `estatehelm-mcp/1.0 (${
|
|
1418
|
+
return `estatehelm-mcp/1.0 (${os.hostname()}, ${process.platform})`;
|
|
1383
1419
|
}
|
|
1384
1420
|
function sanitizeToken(token) {
|
|
1385
1421
|
if (token.length <= 8) return "***";
|
|
@@ -1547,11 +1583,11 @@ function waitForCallback(port) {
|
|
|
1547
1583
|
const sessionToken = url.searchParams.get("session_token");
|
|
1548
1584
|
const error = url.searchParams.get("error");
|
|
1549
1585
|
if (error) {
|
|
1550
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
1586
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
1551
1587
|
res.end(`
|
|
1552
1588
|
<!DOCTYPE html>
|
|
1553
1589
|
<html>
|
|
1554
|
-
<head><title>Authentication Failed</title>
|
|
1590
|
+
<head><meta charset="utf-8"><title>Authentication Failed</title>
|
|
1555
1591
|
<style>body{font-family:system-ui;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#fef2f2}.card{background:white;padding:2rem;border-radius:1rem;box-shadow:0 10px 25px rgba(0,0,0,0.1);text-align:center}h1{color:#dc2626}</style></head>
|
|
1556
1592
|
<body><div class="card"><h1>Authentication Failed</h1><p>${url.searchParams.get("error_description") || error}</p></div></body>
|
|
1557
1593
|
</html>
|
|
@@ -1561,13 +1597,13 @@ function waitForCallback(port) {
|
|
|
1561
1597
|
return;
|
|
1562
1598
|
}
|
|
1563
1599
|
if (sessionToken) {
|
|
1564
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1600
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1565
1601
|
res.end(`
|
|
1566
1602
|
<!DOCTYPE html>
|
|
1567
1603
|
<html>
|
|
1568
|
-
<head><title>Authentication Successful</title>
|
|
1604
|
+
<head><meta charset="utf-8"><title>Authentication Successful</title>
|
|
1569
1605
|
<style>body{font-family:system-ui;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:linear-gradient(115deg,#fff1be 28%,#ee87cb 70%,#b060ff 100%)}.card{background:white;padding:2rem;border-radius:1rem;box-shadow:0 10px 25px rgba(0,0,0,0.1);text-align:center}h1{color:#059669}</style></head>
|
|
1570
|
-
<body><div class="card"><h1
|
|
1606
|
+
<body><div class="card"><h1>✓ Authentication Successful</h1><p>You can close this window and return to your terminal.</p></div></body>
|
|
1571
1607
|
</html>
|
|
1572
1608
|
`);
|
|
1573
1609
|
server.close();
|
|
@@ -1628,7 +1664,15 @@ ${loginUrl}
|
|
|
1628
1664
|
privateKeyBytes: base64Encode(privateKeyBytes)
|
|
1629
1665
|
});
|
|
1630
1666
|
console.log("\n\u2713 Login complete!");
|
|
1631
|
-
console.log("
|
|
1667
|
+
console.log("\nTo use with Claude Code, run:");
|
|
1668
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1669
|
+
if (process.platform === "win32") {
|
|
1670
|
+
console.log(" claude mcp add --transport stdio estatehelm -- cmd /c npx estatehelm mcp");
|
|
1671
|
+
} else {
|
|
1672
|
+
console.log(" claude mcp add --transport stdio estatehelm -- npx estatehelm mcp");
|
|
1673
|
+
}
|
|
1674
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1675
|
+
console.log("\nOr run manually: npx estatehelm mcp");
|
|
1632
1676
|
}
|
|
1633
1677
|
async function checkLogin() {
|
|
1634
1678
|
const credentials = await getCredentials();
|
|
@@ -1748,10 +1792,16 @@ var SqliteCacheStore = class {
|
|
|
1748
1792
|
this.initializeSchema();
|
|
1749
1793
|
}
|
|
1750
1794
|
initializeSchema() {
|
|
1751
|
-
const
|
|
1752
|
-
"SELECT
|
|
1795
|
+
const tableExists = this.db.prepare(
|
|
1796
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='metadata'"
|
|
1753
1797
|
).get();
|
|
1754
|
-
|
|
1798
|
+
let currentVersion = 0;
|
|
1799
|
+
if (tableExists) {
|
|
1800
|
+
const versionRow = this.db.prepare(
|
|
1801
|
+
"SELECT value FROM metadata WHERE key = 'schema_version'"
|
|
1802
|
+
).get();
|
|
1803
|
+
currentVersion = versionRow ? parseInt(versionRow.value, 10) : 0;
|
|
1804
|
+
}
|
|
1755
1805
|
if (currentVersion < DB_VERSION) {
|
|
1756
1806
|
this.migrate(currentVersion);
|
|
1757
1807
|
}
|
|
@@ -2400,6 +2450,40 @@ async function getCacheStats() {
|
|
|
2400
2450
|
const cache = getCache();
|
|
2401
2451
|
return cache.getCacheStats();
|
|
2402
2452
|
}
|
|
2453
|
+
async function downloadAndDecryptFile(client, householdId, fileId, entityId, entityType) {
|
|
2454
|
+
const keyType = getKeyTypeForEntity(entityType);
|
|
2455
|
+
const householdKeyBytes = householdKeysCache.get(`${householdId}:${keyType}`);
|
|
2456
|
+
if (!householdKeyBytes) {
|
|
2457
|
+
throw new Error(`No key available for ${householdId}:${keyType}`);
|
|
2458
|
+
}
|
|
2459
|
+
const fileInfo = await client.get(
|
|
2460
|
+
`/households/${householdId}/files/${fileId}`
|
|
2461
|
+
);
|
|
2462
|
+
if (!fileInfo.downloadUrl) {
|
|
2463
|
+
throw new Error("No download URL returned for file");
|
|
2464
|
+
}
|
|
2465
|
+
const response = await fetch(fileInfo.downloadUrl);
|
|
2466
|
+
if (!response.ok) {
|
|
2467
|
+
throw new Error(`Failed to download file: ${response.status}`);
|
|
2468
|
+
}
|
|
2469
|
+
const encryptedBytes = await response.arrayBuffer();
|
|
2470
|
+
const cryptoVersion = fileInfo.cryptoVersion ?? 1;
|
|
2471
|
+
const keyDerivationId = cryptoVersion === 2 ? fileId : fileInfo.entityId || entityId;
|
|
2472
|
+
const mimeType = fileInfo.fileType || "application/octet-stream";
|
|
2473
|
+
const decrypted = await decryptFileFromArrayBuffer(
|
|
2474
|
+
householdKeyBytes,
|
|
2475
|
+
keyDerivationId,
|
|
2476
|
+
entityType,
|
|
2477
|
+
encryptedBytes,
|
|
2478
|
+
mimeType
|
|
2479
|
+
);
|
|
2480
|
+
return {
|
|
2481
|
+
data: decrypted.data,
|
|
2482
|
+
dataBase64: base64Encode(decrypted.data),
|
|
2483
|
+
mimeType: decrypted.mimeType,
|
|
2484
|
+
fileName: fileInfo.fileName
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2403
2487
|
|
|
2404
2488
|
// src/server.ts
|
|
2405
2489
|
async function startServer(mode) {
|
|
@@ -2577,6 +2661,32 @@ async function startServer(mode) {
|
|
|
2577
2661
|
type: "object",
|
|
2578
2662
|
properties: {}
|
|
2579
2663
|
}
|
|
2664
|
+
},
|
|
2665
|
+
{
|
|
2666
|
+
name: "get_file",
|
|
2667
|
+
description: "Download and decrypt a file attachment. Returns base64-encoded file data that can be saved to disk.",
|
|
2668
|
+
inputSchema: {
|
|
2669
|
+
type: "object",
|
|
2670
|
+
properties: {
|
|
2671
|
+
fileId: {
|
|
2672
|
+
type: "string",
|
|
2673
|
+
description: "The file ID to download"
|
|
2674
|
+
},
|
|
2675
|
+
householdId: {
|
|
2676
|
+
type: "string",
|
|
2677
|
+
description: "The household ID the file belongs to"
|
|
2678
|
+
},
|
|
2679
|
+
entityId: {
|
|
2680
|
+
type: "string",
|
|
2681
|
+
description: "The entity ID the file is attached to"
|
|
2682
|
+
},
|
|
2683
|
+
entityType: {
|
|
2684
|
+
type: "string",
|
|
2685
|
+
description: "The entity type (e.g., insurance, document)"
|
|
2686
|
+
}
|
|
2687
|
+
},
|
|
2688
|
+
required: ["fileId", "householdId", "entityId", "entityType"]
|
|
2689
|
+
}
|
|
2580
2690
|
}
|
|
2581
2691
|
]
|
|
2582
2692
|
};
|
|
@@ -2677,15 +2787,15 @@ async function startServer(mode) {
|
|
|
2677
2787
|
for (const household of searchHouseholds) {
|
|
2678
2788
|
const insurance = await getDecryptedEntities(household.id, "insurance", privateKey);
|
|
2679
2789
|
for (const policy of insurance) {
|
|
2680
|
-
if (policy.
|
|
2681
|
-
const expires = new Date(policy.
|
|
2790
|
+
if (policy.expiration_date) {
|
|
2791
|
+
const expires = new Date(policy.expiration_date);
|
|
2682
2792
|
if (expires <= cutoff) {
|
|
2683
2793
|
expiring.push({
|
|
2684
2794
|
householdId: household.id,
|
|
2685
2795
|
householdName: household.name,
|
|
2686
2796
|
type: "insurance",
|
|
2687
|
-
name: policy.name || policy.
|
|
2688
|
-
expiresAt: policy.
|
|
2797
|
+
name: policy.name || policy.policy_number,
|
|
2798
|
+
expiresAt: policy.expiration_date,
|
|
2689
2799
|
daysUntil: Math.ceil((expires.getTime() - now.getTime()) / (24 * 60 * 60 * 1e3))
|
|
2690
2800
|
});
|
|
2691
2801
|
}
|
|
@@ -2693,15 +2803,29 @@ async function startServer(mode) {
|
|
|
2693
2803
|
}
|
|
2694
2804
|
const vehicles = await getDecryptedEntities(household.id, "vehicle", privateKey);
|
|
2695
2805
|
for (const vehicle of vehicles) {
|
|
2696
|
-
|
|
2697
|
-
|
|
2806
|
+
const vehicleName = `${vehicle.year || ""} ${vehicle.make || ""} ${vehicle.model || ""}`.trim();
|
|
2807
|
+
if (vehicle.registration_expiration) {
|
|
2808
|
+
const expires = new Date(vehicle.registration_expiration);
|
|
2698
2809
|
if (expires <= cutoff) {
|
|
2699
2810
|
expiring.push({
|
|
2700
2811
|
householdId: household.id,
|
|
2701
2812
|
householdName: household.name,
|
|
2702
2813
|
type: "vehicle_registration",
|
|
2703
|
-
name:
|
|
2704
|
-
expiresAt: vehicle.
|
|
2814
|
+
name: vehicleName,
|
|
2815
|
+
expiresAt: vehicle.registration_expiration,
|
|
2816
|
+
daysUntil: Math.ceil((expires.getTime() - now.getTime()) / (24 * 60 * 60 * 1e3))
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
if (vehicle.tabs_expiration) {
|
|
2821
|
+
const expires = new Date(vehicle.tabs_expiration);
|
|
2822
|
+
if (expires <= cutoff) {
|
|
2823
|
+
expiring.push({
|
|
2824
|
+
householdId: household.id,
|
|
2825
|
+
householdName: household.name,
|
|
2826
|
+
type: "vehicle_tabs",
|
|
2827
|
+
name: vehicleName,
|
|
2828
|
+
expiresAt: vehicle.tabs_expiration,
|
|
2705
2829
|
daysUntil: Math.ceil((expires.getTime() - now.getTime()) / (24 * 60 * 60 * 1e3))
|
|
2706
2830
|
});
|
|
2707
2831
|
}
|
|
@@ -2709,16 +2833,16 @@ async function startServer(mode) {
|
|
|
2709
2833
|
}
|
|
2710
2834
|
const subscriptions = await getDecryptedEntities(household.id, "subscription", privateKey);
|
|
2711
2835
|
for (const sub of subscriptions) {
|
|
2712
|
-
if (sub.
|
|
2713
|
-
const
|
|
2714
|
-
if (
|
|
2836
|
+
if (sub.expiration_date) {
|
|
2837
|
+
const expires = new Date(sub.expiration_date);
|
|
2838
|
+
if (expires <= cutoff) {
|
|
2715
2839
|
expiring.push({
|
|
2716
2840
|
householdId: household.id,
|
|
2717
2841
|
householdName: household.name,
|
|
2718
2842
|
type: "subscription",
|
|
2719
|
-
name: sub.name || sub.
|
|
2720
|
-
expiresAt: sub.
|
|
2721
|
-
daysUntil: Math.ceil((
|
|
2843
|
+
name: sub.name || sub.service_name,
|
|
2844
|
+
expiresAt: sub.expiration_date,
|
|
2845
|
+
daysUntil: Math.ceil((expires.getTime() - now.getTime()) / (24 * 60 * 60 * 1e3))
|
|
2722
2846
|
});
|
|
2723
2847
|
}
|
|
2724
2848
|
}
|
|
@@ -2745,6 +2869,44 @@ async function startServer(mode) {
|
|
|
2745
2869
|
]
|
|
2746
2870
|
};
|
|
2747
2871
|
}
|
|
2872
|
+
case "get_file": {
|
|
2873
|
+
const { fileId, householdId, entityId, entityType } = args;
|
|
2874
|
+
try {
|
|
2875
|
+
const result = await downloadAndDecryptFile(
|
|
2876
|
+
client,
|
|
2877
|
+
householdId,
|
|
2878
|
+
fileId,
|
|
2879
|
+
entityId,
|
|
2880
|
+
entityType
|
|
2881
|
+
);
|
|
2882
|
+
return {
|
|
2883
|
+
content: [
|
|
2884
|
+
{
|
|
2885
|
+
type: "text",
|
|
2886
|
+
text: JSON.stringify({
|
|
2887
|
+
success: true,
|
|
2888
|
+
fileName: result.fileName,
|
|
2889
|
+
mimeType: result.mimeType,
|
|
2890
|
+
size: result.data.length,
|
|
2891
|
+
data: result.dataBase64
|
|
2892
|
+
}, null, 2)
|
|
2893
|
+
}
|
|
2894
|
+
]
|
|
2895
|
+
};
|
|
2896
|
+
} catch (err) {
|
|
2897
|
+
return {
|
|
2898
|
+
content: [
|
|
2899
|
+
{
|
|
2900
|
+
type: "text",
|
|
2901
|
+
text: JSON.stringify({
|
|
2902
|
+
success: false,
|
|
2903
|
+
error: err.message
|
|
2904
|
+
}, null, 2)
|
|
2905
|
+
}
|
|
2906
|
+
]
|
|
2907
|
+
};
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2748
2910
|
default:
|
|
2749
2911
|
throw new Error(`Unknown tool: ${name}`);
|
|
2750
2912
|
}
|