@mebular/mcp 0.0.0-stage → 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.
@@ -0,0 +1,91 @@
1
+ // <home>/runtime.json:守护启动后的非敏感发现信息。
2
+ //
3
+ // 只包含版本、pid、实际 HTTP 监听地址、Console/MCP 路径与 embed 协议版本。
4
+ // 不读取也不写入任何 Mebular 配置、身份或密钥。写入在 HTTP listen 成功之后
5
+ // 原子完成;停止时只删除 pid 与当前进程一致的 runtime.json,避免误删新实例。
6
+
7
+ import { readFile, rename, unlink, writeFile } from 'node:fs/promises';
8
+ import { join } from 'node:path';
9
+
10
+ export const RUNTIME_FILE = 'runtime.json';
11
+
12
+ /** @param {string} home */
13
+ export function runtimePath(home) {
14
+ return join(home, RUNTIME_FILE);
15
+ }
16
+
17
+ /**
18
+ * Build the exact non-sensitive runtime record.
19
+ * @param {{
20
+ * version: string,
21
+ * pid?: number,
22
+ * host: string,
23
+ * port: number,
24
+ * consolePath?: string,
25
+ * mcpPath?: string,
26
+ * embedProtocol: number,
27
+ * }} input
28
+ */
29
+ export function buildRuntimeRecord(input) {
30
+ return {
31
+ version: input.version,
32
+ pid: input.pid ?? process.pid,
33
+ http: { host: input.host, port: input.port },
34
+ consolePath: input.consolePath ?? '/console',
35
+ mcpPath: input.mcpPath ?? '/mcp',
36
+ embedProtocol: input.embedProtocol,
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Atomically write <home>/runtime.json. Call only after the HTTP listener is bound.
42
+ * @param {string} home
43
+ * @param {Parameters<typeof buildRuntimeRecord>[0]} input
44
+ * @returns {Promise<ReturnType<typeof buildRuntimeRecord>>}
45
+ */
46
+ export async function writeRuntimeFile(home, input) {
47
+ const record = buildRuntimeRecord(input);
48
+ const path = runtimePath(home);
49
+ const tmp = `${path}.tmp-${process.pid}`;
50
+ try {
51
+ await writeFile(tmp, JSON.stringify(record, null, 2), { encoding: 'utf-8', mode: 0o600 });
52
+ await rename(tmp, path);
53
+ } catch (error) {
54
+ await unlink(tmp).catch(() => undefined);
55
+ throw error;
56
+ }
57
+ return record;
58
+ }
59
+
60
+ /**
61
+ * Read <home>/runtime.json without throwing. Returns null for a missing,
62
+ * unreadable, or malformed file.
63
+ * @param {string} home
64
+ * @returns {Promise<Record<string, unknown> | null>}
65
+ */
66
+ export async function readRuntimeFile(home) {
67
+ try {
68
+ const value = JSON.parse(await readFile(runtimePath(home), 'utf-8'));
69
+ return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null;
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Remove runtime.json only when it still belongs to `pid`. A newer instance that
77
+ * already replaced the file is left untouched.
78
+ * @param {string} home
79
+ * @param {{ pid?: number }} [options]
80
+ * @returns {Promise<boolean>}
81
+ */
82
+ export async function clearRuntimeFile(home, { pid = process.pid } = {}) {
83
+ const record = await readRuntimeFile(home);
84
+ if (record === null || record.pid !== pid) return false;
85
+ try {
86
+ await unlink(runtimePath(home));
87
+ return true;
88
+ } catch {
89
+ return false;
90
+ }
91
+ }