@illli-studio/mory 0.1.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 +21 -0
- package/bundle/server.mjs +87 -0
- package/bundle/web/assets/index-BAhB-d6Z.css +1 -0
- package/bundle/web/assets/index-CJ5unMda.js +78 -0
- package/bundle/web/index.html +16 -0
- package/bundle/web/mory-logo-premium.svg +51 -0
- package/client/index.d.ts +49 -0
- package/client/index.js +16 -0
- package/package.json +23 -0
- package/src/cli.mjs +104 -0
- package/src/mcp.mjs +48 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="theme-color" content="#101820" />
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="/mory-logo-premium.svg" />
|
|
8
|
+
<link rel="apple-touch-icon" href="/mory-logo-premium.svg" />
|
|
9
|
+
<title>Mory</title>
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-CJ5unMda.js"></script>
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BAhB-d6Z.css">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="root"></div>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="_图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 427.87 427.87">
|
|
3
|
+
<defs>
|
|
4
|
+
<style>
|
|
5
|
+
.cls-1, .cls-2 {
|
|
6
|
+
fill: #0b3b32;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.cls-3 {
|
|
10
|
+
font-family: AdobeSongStd-Light-GBpc-EUC-H, 'Adobe Song Std';
|
|
11
|
+
font-weight: 300;
|
|
12
|
+
letter-spacing: -.1em;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.cls-4 {
|
|
16
|
+
fill: #f5f1e7;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.cls-5, .cls-6 {
|
|
20
|
+
letter-spacing: -.1em;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.cls-6, .cls-7 {
|
|
24
|
+
font-family: CooperBlack, 'Cooper Black';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.cls-2 {
|
|
28
|
+
font-size: 86px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.cls-8 {
|
|
32
|
+
fill: #9bb5a0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.cls-9 {
|
|
36
|
+
letter-spacing: -.15em;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.cls-10 {
|
|
40
|
+
fill: #2c6870;
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
43
|
+
</defs>
|
|
44
|
+
<rect class="cls-1" x="0" y="0" width="427.87" height="427.87" rx="102.32" ry="102.32"/>
|
|
45
|
+
<g>
|
|
46
|
+
<rect class="cls-10" x="61.29" y="68.46" width="351.95" height="290.95" rx="68.04" ry="68.04" transform="translate(-21.32 401.54) rotate(-78)"/>
|
|
47
|
+
<rect class="cls-8" x="25.31" y="68.46" width="358.99" height="290.95" rx="68.04" ry="68.04" transform="translate(-32.5 391.14) rotate(-83)"/>
|
|
48
|
+
<rect class="cls-4" x="45.86" y="45" width="286.26" height="337.88" rx="56.31" ry="56.31" transform="translate(-7.35 6.73) rotate(-2)"/>
|
|
49
|
+
</g>
|
|
50
|
+
<text class="cls-2" transform="translate(64.68 198.47) rotate(-1.97)"><tspan class="cls-6" x="0" y="0">ME</tspan><tspan class="cls-3" x="118.74" y="0">.</tspan><tspan class="cls-7"><tspan class="cls-5" x="0" y="83">MO</tspan><tspan class="cls-9" x="126.26" y="83">R</tspan><tspan class="cls-5" x="180.32" y="83">Y</tspan></tspan></text>
|
|
51
|
+
</svg>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface MoryClientOptions {
|
|
2
|
+
baseUrl?: string;
|
|
3
|
+
token?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface RememberInput {
|
|
6
|
+
text: string;
|
|
7
|
+
projectId?: string;
|
|
8
|
+
kind?: string;
|
|
9
|
+
metadata?: Record<string, unknown>;
|
|
10
|
+
source?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare class MoryClient {
|
|
13
|
+
readonly baseUrl: string;
|
|
14
|
+
readonly token: string;
|
|
15
|
+
constructor(options?: MoryClientOptions);
|
|
16
|
+
private request;
|
|
17
|
+
remember(input: RememberInput): Promise<Record<string, unknown>>;
|
|
18
|
+
search(query: string, options?: {
|
|
19
|
+
projectId?: string;
|
|
20
|
+
limit?: number;
|
|
21
|
+
}): Promise<Record<string, unknown>>;
|
|
22
|
+
context(query: string, options?: {
|
|
23
|
+
projectId?: string;
|
|
24
|
+
limit?: number;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
context: string;
|
|
27
|
+
memories: unknown[];
|
|
28
|
+
}>;
|
|
29
|
+
list(options?: {
|
|
30
|
+
projectId?: string;
|
|
31
|
+
limit?: number;
|
|
32
|
+
}): Promise<{
|
|
33
|
+
memories: unknown[];
|
|
34
|
+
total: number;
|
|
35
|
+
}>;
|
|
36
|
+
export(): Promise<{
|
|
37
|
+
schemaVersion: number;
|
|
38
|
+
exportedAt: string;
|
|
39
|
+
memories: unknown[];
|
|
40
|
+
}>;
|
|
41
|
+
import(memories: unknown[]): Promise<{
|
|
42
|
+
addedCount: number;
|
|
43
|
+
duplicateCount: number;
|
|
44
|
+
}>;
|
|
45
|
+
get(id: string): Promise<Record<string, unknown>>;
|
|
46
|
+
forget(id: string): Promise<{
|
|
47
|
+
ok: boolean;
|
|
48
|
+
}>;
|
|
49
|
+
}
|
package/client/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export class MoryClient {
|
|
2
|
+
baseUrl;
|
|
3
|
+
token;
|
|
4
|
+
constructor(options = {}) { this.baseUrl = (options.baseUrl ?? "http://127.0.0.1:8787").replace(/\/$/, ""); this.token = options.token ?? ""; }
|
|
5
|
+
async request(path, init = {}) { const response = await fetch(this.baseUrl + path, { ...init, headers: { authorization: `Bearer ${this.token}`, "content-type": "application/json", ...(init.headers || {}) } }); const result = await response.json(); if (!response.ok)
|
|
6
|
+
throw new Error(result.error || `Mory API error (${response.status})`); return result; }
|
|
7
|
+
remember(input) { return this.request("/v1/memories/remember", { method: "POST", body: JSON.stringify({ ...input, source: input.source ?? "sdk", actor: { type: "agent", id: "sdk" }, scope: input.projectId ? { projectId: input.projectId } : {} }) }); }
|
|
8
|
+
search(query, options = {}) { return this.request("/v1/memories/search", { method: "POST", body: JSON.stringify({ query, limit: options.limit ?? 10, scope: options.projectId ? { projectId: options.projectId } : {} }) }); }
|
|
9
|
+
context(query, options = {}) { return this.request("/v1/context", { method: "POST", body: JSON.stringify({ query, limit: options.limit ?? 12, scope: options.projectId ? { projectId: options.projectId } : {} }) }); }
|
|
10
|
+
list(options = {}) { const params = new URLSearchParams({ limit: String(options.limit ?? 50) }); if (options.projectId)
|
|
11
|
+
params.set("projectId", options.projectId); return this.request(`/v1/memories?${params}`, { method: "GET" }); }
|
|
12
|
+
export() { return this.request("/v1/export", { method: "GET" }); }
|
|
13
|
+
import(memories) { return this.request("/v1/import", { method: "POST", body: JSON.stringify({ memories }) }); }
|
|
14
|
+
get(id) { return this.request(`/v1/memories/${encodeURIComponent(id)}`, { method: "GET" }); }
|
|
15
|
+
forget(id) { return this.request(`/v1/memories/${encodeURIComponent(id)}`, { method: "DELETE" }); }
|
|
16
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@illli-studio/mory",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local-first memory runtime with a web console and MCP server.",
|
|
5
|
+
"license": "AGPL-3.0-or-later",
|
|
6
|
+
"publishConfig": { "access": "public" },
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"mory": "./src/cli.mjs"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": { "types": "./client/index.d.ts", "import": "./client/index.js" },
|
|
13
|
+
"./client": { "types": "./client/index.d.ts", "import": "./client/index.js" },
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": ["src", "bundle", "client", "README.md"],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"cli": "node src/cli.mjs",
|
|
19
|
+
"start": "node src/cli.mjs start",
|
|
20
|
+
"prepack": "node scripts/prepack.mjs"
|
|
21
|
+
},
|
|
22
|
+
"engines": { "node": ">=22" }
|
|
23
|
+
}
|
package/src/cli.mjs
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { randomBytes } from "node:crypto";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { runMcp } from "./mcp.mjs";
|
|
9
|
+
|
|
10
|
+
const packageRoot = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
11
|
+
const repoRoot = resolve(packageRoot, "../..");
|
|
12
|
+
const dataDir = process.env.MORY_HOME || join(homedir(), ".mory");
|
|
13
|
+
const configPath = join(dataDir, "config.json");
|
|
14
|
+
const pidPath = join(dataDir, "mory.pid");
|
|
15
|
+
const defaultConfig = () => ({ port: 8787, token: randomBytes(24).toString("hex"), database: join(dataDir, "mory.sqlite") });
|
|
16
|
+
|
|
17
|
+
function config() {
|
|
18
|
+
mkdirSync(dataDir, { recursive: true });
|
|
19
|
+
if (!existsSync(configPath)) writeFileSync(configPath, JSON.stringify(defaultConfig(), null, 2) + "\n");
|
|
20
|
+
return JSON.parse(readFileSync(configPath, "utf8"));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function apiEntry() { return process.env.MORY_API_ENTRY || (existsSync(join(repoRoot, "apps", "api", "server.mjs")) ? join(repoRoot, "apps", "api", "server.mjs") : join(packageRoot, "bundle", "server.mjs")); }
|
|
24
|
+
function webDist() { return process.env.MORY_WEB_DIST || (existsSync(join(repoRoot, "apps", "web", "dist", "index.html")) ? join(repoRoot, "apps", "web", "dist") : join(packageRoot, "bundle", "web")); }
|
|
25
|
+
|
|
26
|
+
async function waitForHealth(base, token, timeout = 8000) {
|
|
27
|
+
const started = Date.now();
|
|
28
|
+
while (Date.now() - started < timeout) {
|
|
29
|
+
try { const response = await fetch(base + "/health", { headers: { authorization: `Bearer ${token}` } }); if (response.ok) return true; } catch {}
|
|
30
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 150));
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function start() {
|
|
36
|
+
const current = config();
|
|
37
|
+
const base = `http://127.0.0.1:${current.port}`;
|
|
38
|
+
if (await waitForHealth(base, current.token, 500)) { console.log(`Mory is already running at ${base}`); return; }
|
|
39
|
+
const env = { ...process.env, MORY_PORT: String(current.port), MORY_API_TOKEN: current.token, MORY_DATABASE: current.database, MORY_WEB_DIST: webDist() };
|
|
40
|
+
const child = spawn(process.execPath, [apiEntry()], { env, stdio: "inherit", detached: false });
|
|
41
|
+
if (!await waitForHealth(base, current.token)) { child.kill(); throw new Error("Mory failed to start. Run `mory doctor` for diagnostics."); }
|
|
42
|
+
writeFileSync(pidPath, String(child.pid));
|
|
43
|
+
child.once("exit", () => { if (existsSync(pidPath) && readFileSync(pidPath, "utf8").trim() === String(child.pid)) unlinkSync(pidPath); });
|
|
44
|
+
console.log(`Mory is running at ${base}`);
|
|
45
|
+
console.log(`Data: ${current.database}`);
|
|
46
|
+
console.log("MCP: run `mory mcp` from your agent configuration");
|
|
47
|
+
process.on("SIGINT", () => child.kill("SIGINT"));
|
|
48
|
+
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function stop() {
|
|
52
|
+
const current = config();
|
|
53
|
+
if (!await waitForHealth(`http://127.0.0.1:${current.port}`, current.token, 500)) { if (existsSync(pidPath)) unlinkSync(pidPath); console.log("Mory is not running."); return; }
|
|
54
|
+
if (!existsSync(pidPath)) throw new Error("Mory is running, but its PID file is missing. Stop the process manually after checking the port.");
|
|
55
|
+
const pid = Number(readFileSync(pidPath, "utf8").trim());
|
|
56
|
+
if (!Number.isInteger(pid) || pid <= 0) throw new Error("Invalid Mory PID file.");
|
|
57
|
+
process.kill(pid);
|
|
58
|
+
unlinkSync(pidPath);
|
|
59
|
+
console.log("Mory stopped.");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function doctor() {
|
|
63
|
+
const current = config();
|
|
64
|
+
console.log(JSON.stringify({ node: process.version, dataDir, database: current.database, webDist: webDist(), webBuilt: existsSync(join(webDist(), "index.html")), apiEntry: apiEntry(), apiEntryExists: existsSync(apiEntry()) }, null, 2));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function apiRequest(path, current, init = {}) {
|
|
68
|
+
const response = await fetch(`http://127.0.0.1:${current.port}${path}`, { ...init, headers: { authorization: `Bearer ${current.token}`, "content-type": "application/json", ...(init.headers || {}) } });
|
|
69
|
+
const result = await response.json();
|
|
70
|
+
if (!response.ok) throw new Error(result.error || `Mory API error (${response.status})`);
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function exportMemories(outputPath) {
|
|
75
|
+
const current = config();
|
|
76
|
+
if (!await waitForHealth(`http://127.0.0.1:${current.port}`, current.token, 500)) throw new Error("Mory is not running. Start it before exporting.");
|
|
77
|
+
const result = await apiRequest("/v1/export", current);
|
|
78
|
+
const target = resolve(outputPath || join(dataDir, "mory-export.json"));
|
|
79
|
+
writeFileSync(target, JSON.stringify(result, null, 2) + "\n");
|
|
80
|
+
console.log(`Exported ${result.memories.length} memories to ${target}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function importMemories(inputPath) {
|
|
84
|
+
const current = config();
|
|
85
|
+
if (!await waitForHealth(`http://127.0.0.1:${current.port}`, current.token, 500)) throw new Error("Mory is not running. Start it before importing.");
|
|
86
|
+
const source = resolve(inputPath || join(dataDir, "mory-export.json"));
|
|
87
|
+
const parsed = JSON.parse(readFileSync(source, "utf8"));
|
|
88
|
+
const result = await apiRequest("/v1/import", current, { method: "POST", body: JSON.stringify({ memories: parsed.memories || parsed }) });
|
|
89
|
+
console.log(`Imported ${result.addedCount} memories; skipped ${result.duplicateCount} duplicates.`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const [command = "start", ...args] = process.argv.slice(2);
|
|
93
|
+
try {
|
|
94
|
+
if (command === "init") { config(); console.log(`Initialized Mory at ${dataDir}`); }
|
|
95
|
+
else if (command === "start") await start();
|
|
96
|
+
else if (command === "stop") await stop();
|
|
97
|
+
else if (command === "export") await exportMemories(args[0]);
|
|
98
|
+
else if (command === "import") await importMemories(args[0]);
|
|
99
|
+
else if (command === "mcp") await runMcp({ baseUrl: process.env.MORY_API_URL || `http://127.0.0.1:${config().port}`, token: process.env.MORY_API_TOKEN || config().token });
|
|
100
|
+
else if (command === "doctor") await doctor();
|
|
101
|
+
else if (command === "status") { const c = config(); console.log(JSON.stringify({ url: `http://127.0.0.1:${c.port}`, dataDir, running: await waitForHealth(`http://127.0.0.1:${c.port}`, c.token, 500) }, null, 2)); }
|
|
102
|
+
else if (command === "token") console.log(config().token);
|
|
103
|
+
else { console.error(`Unknown command: ${command}`); process.exitCode = 1; }
|
|
104
|
+
} catch (error) { console.error(error instanceof Error ? error.message : error); process.exitCode = 1; }
|
package/src/mcp.mjs
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const protocolVersion = "2025-06-18";
|
|
2
|
+
|
|
3
|
+
async function callApi(baseUrl, token, path, method = "POST", body) {
|
|
4
|
+
const response = await fetch(baseUrl.replace(/\/$/, "") + path, { method, headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, body: body === undefined ? undefined : JSON.stringify(body) });
|
|
5
|
+
const result = await response.json();
|
|
6
|
+
if (!response.ok) throw new Error(result.error || `Mory API error (${response.status})`);
|
|
7
|
+
return result;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const tools = [
|
|
11
|
+
{ name: "mory_remember", description: "Store durable information in the user's local Mory memory.", inputSchema: { type: "object", properties: { text: { type: "string" }, projectId: { type: "string" }, kind: { type: "string" }, metadata: { type: "object" } }, required: ["text"] } },
|
|
12
|
+
{ name: "mory_search", description: "Search the user's local Mory memories.", inputSchema: { type: "object", properties: { query: { type: "string" }, projectId: { type: "string" }, limit: { type: "number" } }, required: ["query"] } },
|
|
13
|
+
{ name: "mory_context", description: "Retrieve relevant memory as prompt-ready context.", inputSchema: { type: "object", properties: { query: { type: "string" }, projectId: { type: "string" }, limit: { type: "number" } }, required: ["query"] } },
|
|
14
|
+
{ name: "mory_get", description: "Get one local Mory memory by id.", inputSchema: { type: "object", properties: { memoryId: { type: "string" } }, required: ["memoryId"] } },
|
|
15
|
+
{ name: "mory_list", description: "List recent local Mory memories.", inputSchema: { type: "object", properties: { projectId: { type: "string" }, limit: { type: "number" } } } },
|
|
16
|
+
{ name: "mory_forget", description: "Delete a memory by id using Mory's tombstone deletion.", inputSchema: { type: "object", properties: { memoryId: { type: "string" } }, required: ["memoryId"] } }
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
async function handle(message, options) {
|
|
20
|
+
if (message.method === "initialize") return { protocolVersion, capabilities: { tools: {} }, serverInfo: { name: "mory", version: "0.1.0" } };
|
|
21
|
+
if (message.method === "notifications/initialized") return null;
|
|
22
|
+
if (message.method === "tools/list") return { tools };
|
|
23
|
+
if (message.method !== "tools/call") return {};
|
|
24
|
+
const { name, arguments: input = {} } = message.params || {};
|
|
25
|
+
let result;
|
|
26
|
+
if (name === "mory_remember") result = await callApi(options.baseUrl, options.token, "/v1/memories/remember", "POST", { text: input.text, source: "mcp", actor: { type: "agent", id: "mcp" }, kind: input.kind, metadata: input.metadata, scope: input.projectId ? { projectId: input.projectId } : {} });
|
|
27
|
+
else if (name === "mory_search") result = await callApi(options.baseUrl, options.token, "/v1/memories/search", "POST", { query: input.query, limit: input.limit || 10, scope: input.projectId ? { projectId: input.projectId } : {} });
|
|
28
|
+
else if (name === "mory_context") result = await callApi(options.baseUrl, options.token, "/v1/context", "POST", { query: input.query, limit: input.limit || 12, scope: input.projectId ? { projectId: input.projectId } : {} });
|
|
29
|
+
else if (name === "mory_get") result = await callApi(options.baseUrl, options.token, `/v1/memories/${encodeURIComponent(input.memoryId)}`, "GET");
|
|
30
|
+
else if (name === "mory_list") result = await callApi(options.baseUrl, options.token, `/v1/memories?limit=${encodeURIComponent(input.limit || 20)}${input.projectId ? `&projectId=${encodeURIComponent(input.projectId)}` : ""}`, "GET");
|
|
31
|
+
else if (name === "mory_forget") result = await callApi(options.baseUrl, options.token, `/v1/memories/${encodeURIComponent(input.memoryId)}`, "DELETE");
|
|
32
|
+
else throw new Error(`Unknown tool: ${name}`);
|
|
33
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function runMcp(options) {
|
|
37
|
+
let buffer = "";
|
|
38
|
+
for await (const chunk of process.stdin) {
|
|
39
|
+
buffer += chunk.toString();
|
|
40
|
+
let newline;
|
|
41
|
+
while ((newline = buffer.indexOf("\n")) >= 0) {
|
|
42
|
+
const line = buffer.slice(0, newline).trim(); buffer = buffer.slice(newline + 1);
|
|
43
|
+
if (!line) continue;
|
|
44
|
+
try { const message = JSON.parse(line); const result = await handle(message, options); if (message.id !== undefined && result !== null) process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: message.id, result }) + "\n"); }
|
|
45
|
+
catch (error) { const id = (() => { try { return JSON.parse(line).id; } catch { return null; } })(); process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, error: { code: -32000, message: error instanceof Error ? error.message : String(error) } }) + "\n"); }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|