@alfe.ai/mcp-server 0.2.5 → 0.2.7
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 +30 -0
- package/dist/bin.cjs +47 -1
- package/dist/bin.js +47 -1
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +76 -26
- package/dist/index.d.cts +156 -68
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +156 -68
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +76 -26
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -10,6 +10,36 @@ Part of [**Alfe**](https://alfe.ai) — the operating system for AI agents: buil
|
|
|
10
10
|
npm install @alfe.ai/mcp-server
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
## Profiles
|
|
14
|
+
|
|
15
|
+
The executable defaults to the OpenClaw-safe integrations-only surface:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
alfe-mcp-server
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Claude Code has no Alfe plugins, so its explicit profile adds memory, voice,
|
|
22
|
+
and messaging tools:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
alfe-mcp-server --profile claude-code
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The default profile must remain integrations-only. OpenClaw already receives
|
|
29
|
+
the other capabilities from its native Alfe plugins, and exposing them here as
|
|
30
|
+
well would duplicate its tool surface.
|
|
31
|
+
|
|
32
|
+
The server reads credentials through `@alfe.ai/config`. Library callers may
|
|
33
|
+
inject a pre-bound `AgentApiClient`, its API URL, and an identity into
|
|
34
|
+
`createServer` for tests or alternate hosts. The main client and URL must be
|
|
35
|
+
provided together. A distinct `voiceApiUrl` also requires a `voiceClient`; the
|
|
36
|
+
package never reads the local API key and attaches it to an arbitrary injected
|
|
37
|
+
destination.
|
|
38
|
+
|
|
39
|
+
`main()` attaches a transport and returns the connected server without changing
|
|
40
|
+
process state. The packaged executable owns fatal logging, exit codes, and
|
|
41
|
+
SIGINT/SIGTERM shutdown.
|
|
42
|
+
|
|
13
43
|
## Links
|
|
14
44
|
|
|
15
45
|
- 🌐 Website: <https://alfe.ai>
|
package/dist/bin.cjs
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const require_index = require("./index.cjs");
|
|
3
|
+
//#region src/cli.ts
|
|
4
|
+
const MAX_ERROR_CHARS = 4096;
|
|
5
|
+
/**
|
|
6
|
+
* Own the executable-only process policy around the reusable MCP server.
|
|
7
|
+
* Startup failures set a non-zero exit code after writing a redacted diagnostic;
|
|
8
|
+
* SIGINT/SIGTERM close the transport before terminating the child process.
|
|
9
|
+
*/
|
|
10
|
+
async function runCli(argv, dependencies = {}, runtime = process) {
|
|
11
|
+
const start = dependencies.start ?? require_index.main;
|
|
12
|
+
let server;
|
|
13
|
+
try {
|
|
14
|
+
server = await start({ profile: require_index.parseProfileArg(argv) });
|
|
15
|
+
} catch (error) {
|
|
16
|
+
runtime.stderr.write(`[alfe-mcp-server] failed to start: ${safeErrorMessage(error)}\n`);
|
|
17
|
+
runtime.exitCode = 1;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
let shutdownPromise;
|
|
21
|
+
const shutdown = () => {
|
|
22
|
+
if (shutdownPromise) return;
|
|
23
|
+
runtime.off("SIGINT", shutdown);
|
|
24
|
+
runtime.off("SIGTERM", shutdown);
|
|
25
|
+
shutdownPromise = server.close().catch((error) => {
|
|
26
|
+
runtime.stderr.write(`[alfe-mcp-server] failed to close cleanly: ${safeErrorMessage(error)}\n`);
|
|
27
|
+
}).then(() => {
|
|
28
|
+
runtime.exit(0);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
runtime.once("SIGINT", shutdown);
|
|
32
|
+
runtime.once("SIGTERM", shutdown);
|
|
33
|
+
}
|
|
34
|
+
function safeErrorMessage(error) {
|
|
35
|
+
const redacted = (error instanceof Error ? error.message : String(error)).slice(0, MAX_ERROR_CHARS).replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/giu, "$1 [REDACTED]").replace(/\b(api[_-]?key|access[_-]?token|refresh[_-]?token|secret|password)\s*[=:]\s*[^\s,;]+/giu, "$1=[REDACTED]").replace(/\balfe_[A-Za-z0-9_-]{8,}/gu, "[REDACTED]").replace(/:\/\/[^:/@\s]+:[^@\s]+@/gu, "://[REDACTED]@");
|
|
36
|
+
let flattened = "";
|
|
37
|
+
let previousWasControl = false;
|
|
38
|
+
for (const character of redacted) {
|
|
39
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
40
|
+
const isControl = codePoint < 32 || codePoint === 127;
|
|
41
|
+
if (isControl) {
|
|
42
|
+
if (!previousWasControl) flattened += " ";
|
|
43
|
+
} else flattened += character;
|
|
44
|
+
previousWasControl = isControl;
|
|
45
|
+
}
|
|
46
|
+
return flattened;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
3
49
|
//#region src/bin.ts
|
|
4
|
-
|
|
50
|
+
runCli(process.argv.slice(2));
|
|
5
51
|
//#endregion
|
package/dist/bin.js
CHANGED
|
@@ -1,7 +1,53 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { main, parseProfileArg } from "./index.js";
|
|
3
|
+
//#region src/cli.ts
|
|
4
|
+
const MAX_ERROR_CHARS = 4096;
|
|
5
|
+
/**
|
|
6
|
+
* Own the executable-only process policy around the reusable MCP server.
|
|
7
|
+
* Startup failures set a non-zero exit code after writing a redacted diagnostic;
|
|
8
|
+
* SIGINT/SIGTERM close the transport before terminating the child process.
|
|
9
|
+
*/
|
|
10
|
+
async function runCli(argv, dependencies = {}, runtime = process) {
|
|
11
|
+
const start = dependencies.start ?? main;
|
|
12
|
+
let server;
|
|
13
|
+
try {
|
|
14
|
+
server = await start({ profile: parseProfileArg(argv) });
|
|
15
|
+
} catch (error) {
|
|
16
|
+
runtime.stderr.write(`[alfe-mcp-server] failed to start: ${safeErrorMessage(error)}\n`);
|
|
17
|
+
runtime.exitCode = 1;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
let shutdownPromise;
|
|
21
|
+
const shutdown = () => {
|
|
22
|
+
if (shutdownPromise) return;
|
|
23
|
+
runtime.off("SIGINT", shutdown);
|
|
24
|
+
runtime.off("SIGTERM", shutdown);
|
|
25
|
+
shutdownPromise = server.close().catch((error) => {
|
|
26
|
+
runtime.stderr.write(`[alfe-mcp-server] failed to close cleanly: ${safeErrorMessage(error)}\n`);
|
|
27
|
+
}).then(() => {
|
|
28
|
+
runtime.exit(0);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
runtime.once("SIGINT", shutdown);
|
|
32
|
+
runtime.once("SIGTERM", shutdown);
|
|
33
|
+
}
|
|
34
|
+
function safeErrorMessage(error) {
|
|
35
|
+
const redacted = (error instanceof Error ? error.message : String(error)).slice(0, MAX_ERROR_CHARS).replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/giu, "$1 [REDACTED]").replace(/\b(api[_-]?key|access[_-]?token|refresh[_-]?token|secret|password)\s*[=:]\s*[^\s,;]+/giu, "$1=[REDACTED]").replace(/\balfe_[A-Za-z0-9_-]{8,}/gu, "[REDACTED]").replace(/:\/\/[^:/@\s]+:[^@\s]+@/gu, "://[REDACTED]@");
|
|
36
|
+
let flattened = "";
|
|
37
|
+
let previousWasControl = false;
|
|
38
|
+
for (const character of redacted) {
|
|
39
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
40
|
+
const isControl = codePoint < 32 || codePoint === 127;
|
|
41
|
+
if (isControl) {
|
|
42
|
+
if (!previousWasControl) flattened += " ";
|
|
43
|
+
} else flattened += character;
|
|
44
|
+
previousWasControl = isControl;
|
|
45
|
+
}
|
|
46
|
+
return flattened;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
3
49
|
//#region src/bin.ts
|
|
4
|
-
|
|
50
|
+
runCli(process.argv.slice(2));
|
|
5
51
|
//#endregion
|
|
6
52
|
export {};
|
|
7
53
|
|
package/dist/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bin.js","names":[],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\nimport {
|
|
1
|
+
{"version":3,"file":"bin.js","names":[],"sources":["../src/cli.ts","../src/bin.ts"],"sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { main, parseProfileArg } from './index.js';\n\nconst MAX_ERROR_CHARS = 4096;\n\ninterface CliRuntime {\n stderr: { write(message: string): unknown };\n once(signal: 'SIGINT' | 'SIGTERM', listener: () => void): unknown;\n off(signal: 'SIGINT' | 'SIGTERM', listener: () => void): unknown;\n exit(code?: number): unknown;\n exitCode?: string | number | null;\n}\n\ninterface CliDependencies {\n start?: typeof main;\n}\n\n/**\n * Own the executable-only process policy around the reusable MCP server.\n * Startup failures set a non-zero exit code after writing a redacted diagnostic;\n * SIGINT/SIGTERM close the transport before terminating the child process.\n */\nexport async function runCli(\n argv: string[],\n dependencies: CliDependencies = {},\n runtime: CliRuntime = process,\n): Promise<void> {\n const start = dependencies.start ?? main;\n let server: McpServer;\n try {\n server = await start({ profile: parseProfileArg(argv) });\n } catch (error: unknown) {\n runtime.stderr.write(`[alfe-mcp-server] failed to start: ${safeErrorMessage(error)}\\n`);\n runtime.exitCode = 1;\n return;\n }\n\n let shutdownPromise: Promise<void> | undefined;\n const shutdown = (): void => {\n if (shutdownPromise) return;\n runtime.off('SIGINT', shutdown);\n runtime.off('SIGTERM', shutdown);\n shutdownPromise = server.close()\n .catch((error: unknown) => {\n runtime.stderr.write(`[alfe-mcp-server] failed to close cleanly: ${safeErrorMessage(error)}\\n`);\n })\n .then(() => {\n runtime.exit(0);\n });\n };\n runtime.once('SIGINT', shutdown);\n runtime.once('SIGTERM', shutdown);\n}\n\nexport function safeErrorMessage(error: unknown): string {\n const redacted = (error instanceof Error ? error.message : String(error))\n .slice(0, MAX_ERROR_CHARS)\n .replace(/\\b(Bearer|Basic)\\s+[A-Za-z0-9._~+/=-]+/giu, '$1 [REDACTED]')\n .replace(\n /\\b(api[_-]?key|access[_-]?token|refresh[_-]?token|secret|password)\\s*[=:]\\s*[^\\s,;]+/giu,\n '$1=[REDACTED]',\n )\n .replace(/\\balfe_[A-Za-z0-9_-]{8,}/gu, '[REDACTED]')\n .replace(/:\\/\\/[^:/@\\s]+:[^@\\s]+@/gu, '://[REDACTED]@');\n let flattened = '';\n let previousWasControl = false;\n for (const character of redacted) {\n const codePoint = character.codePointAt(0) ?? 0;\n const isControl = codePoint < 32 || codePoint === 127;\n if (isControl) {\n if (!previousWasControl) flattened += ' ';\n } else {\n flattened += character;\n }\n previousWasControl = isControl;\n }\n return flattened;\n}\n","#!/usr/bin/env node\nimport { runCli } from './cli.js';\n\n// The OpenClaw daemon bundler launches this bin with no args → `default`\n// profile (integrations only, unchanged). The Alfe CLI launches it as\n// `node <bin> --profile claude-code` for a Claude Code session → memory +\n// voice + messaging tools on top.\nvoid runCli(process.argv.slice(2));\n"],"mappings":";;;AAGA,MAAM,kBAAkB;;;;;;AAmBxB,eAAsB,OACpB,MACA,eAAgC,EAAE,EAClC,UAAsB,SACP;CACf,MAAM,QAAQ,aAAa,SAAS;CACpC,IAAI;AACJ,KAAI;AACF,WAAS,MAAM,MAAM,EAAE,SAAS,gBAAgB,KAAK,EAAE,CAAC;UACjD,OAAgB;AACvB,UAAQ,OAAO,MAAM,sCAAsC,iBAAiB,MAAM,CAAC,IAAI;AACvF,UAAQ,WAAW;AACnB;;CAGF,IAAI;CACJ,MAAM,iBAAuB;AAC3B,MAAI,gBAAiB;AACrB,UAAQ,IAAI,UAAU,SAAS;AAC/B,UAAQ,IAAI,WAAW,SAAS;AAChC,oBAAkB,OAAO,OAAO,CAC7B,OAAO,UAAmB;AACzB,WAAQ,OAAO,MAAM,8CAA8C,iBAAiB,MAAM,CAAC,IAAI;IAC/F,CACD,WAAW;AACV,WAAQ,KAAK,EAAE;IACf;;AAEN,SAAQ,KAAK,UAAU,SAAS;AAChC,SAAQ,KAAK,WAAW,SAAS;;AAGnC,SAAgB,iBAAiB,OAAwB;CACvD,MAAM,YAAY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EACrE,MAAM,GAAG,gBAAgB,CACzB,QAAQ,6CAA6C,gBAAgB,CACrE,QACC,2FACA,gBACD,CACA,QAAQ,8BAA8B,aAAa,CACnD,QAAQ,6BAA6B,iBAAiB;CACzD,IAAI,YAAY;CAChB,IAAI,qBAAqB;AACzB,MAAK,MAAM,aAAa,UAAU;EAChC,MAAM,YAAY,UAAU,YAAY,EAAE,IAAI;EAC9C,MAAM,YAAY,YAAY,MAAM,cAAc;AAClD,MAAI;OACE,CAAC,mBAAoB,cAAa;QAEtC,cAAa;AAEf,uBAAqB;;AAEvB,QAAO;;;;ACrEJ,OAAO,QAAQ,KAAK,MAAM,EAAE,CAAC"}
|
package/dist/index.cjs
CHANGED
|
@@ -8,6 +8,8 @@ let _alfe_ai_config = require("@alfe.ai/config");
|
|
|
8
8
|
let _alfe_ai_mcp_tools = require("@alfe.ai/mcp-tools");
|
|
9
9
|
//#region src/index.ts
|
|
10
10
|
const pkg = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href)("../package.json");
|
|
11
|
+
const MAX_IDENTITY_CHARS = 256;
|
|
12
|
+
const MAX_SERVICE_URL_CHARS = 8192;
|
|
11
13
|
/**
|
|
12
14
|
* Wire shape the bundler advertises this server as — must match the
|
|
13
15
|
* key the CLI registers (`alfe-platform`) so namespacing is consistent
|
|
@@ -39,30 +41,44 @@ const SERVER_BIN_PATH = (0, node_url.fileURLToPath)(new URL("./bin.js", require(
|
|
|
39
41
|
* Pure construction — does not connect a transport. Callers (the bin
|
|
40
42
|
* entry, or tests) attach `StdioServerTransport` or any other transport.
|
|
41
43
|
*
|
|
42
|
-
* `resolveConfig()` is only called when
|
|
43
|
-
*
|
|
44
|
-
* `~/.alfe/config.toml`.
|
|
44
|
+
* `resolveConfig()` is only called when the main `client`/`apiUrl` pair is
|
|
45
|
+
* omitted. Tests and alternate hosts can inject a complete pair without
|
|
46
|
+
* touching `~/.alfe/config.toml`.
|
|
45
47
|
*/
|
|
46
48
|
async function createServer(opts = {}) {
|
|
47
|
-
const profile = opts.profile ?? "default";
|
|
48
|
-
|
|
49
|
-
let
|
|
49
|
+
const profile = validateProfile(opts.profile ?? "default");
|
|
50
|
+
if (opts.client !== void 0 !== (opts.apiUrl !== void 0)) throw new Error("client and apiUrl must be provided together.");
|
|
51
|
+
let client;
|
|
52
|
+
let apiUrl;
|
|
50
53
|
let voiceClient = opts.voiceClient;
|
|
51
54
|
let voiceApiUrl = opts.voiceApiUrl;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
let config;
|
|
56
|
+
const getConfig = () => {
|
|
57
|
+
config ??= (0, _alfe_ai_config.resolveConfig)();
|
|
58
|
+
return config;
|
|
59
|
+
};
|
|
60
|
+
if (opts.client !== void 0 && opts.apiUrl !== void 0) {
|
|
61
|
+
client = opts.client;
|
|
62
|
+
apiUrl = validateServiceUrl("apiUrl", opts.apiUrl);
|
|
63
|
+
} else {
|
|
64
|
+
const cfg = getConfig();
|
|
65
|
+
apiUrl = validateServiceUrl("apiUrl", cfg.apiUrl);
|
|
66
|
+
client = new _alfe_ai_agent_api_client.AgentApiClient({
|
|
55
67
|
apiKey: cfg.apiKey,
|
|
56
|
-
apiUrl
|
|
68
|
+
apiUrl
|
|
57
69
|
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
}
|
|
71
|
+
if (profile === "claude-code") {
|
|
72
|
+
const explicitVoiceUrl = voiceApiUrl !== void 0;
|
|
73
|
+
voiceApiUrl = validateServiceUrl("voiceApiUrl", voiceApiUrl ?? config?.voiceServiceUrl ?? apiUrl);
|
|
74
|
+
if (!voiceClient) if (voiceApiUrl === apiUrl) voiceClient = client;
|
|
75
|
+
else if (!explicitVoiceUrl) voiceClient = new _alfe_ai_agent_api_client.AgentApiClient({
|
|
76
|
+
apiKey: getConfig().apiKey,
|
|
77
|
+
apiUrl: voiceApiUrl
|
|
63
78
|
});
|
|
79
|
+
else throw new Error("voiceClient is required when voiceApiUrl differs from apiUrl.");
|
|
64
80
|
}
|
|
65
|
-
const identity = opts.identity ?? await client.whoami();
|
|
81
|
+
const identity = validateIdentity(opts.identity ?? await client.whoami());
|
|
66
82
|
const ctx = {
|
|
67
83
|
client,
|
|
68
84
|
apiUrl,
|
|
@@ -100,23 +116,57 @@ function parseProfileArg(argv) {
|
|
|
100
116
|
return "default";
|
|
101
117
|
}
|
|
102
118
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* handshake.
|
|
119
|
+
* Boot the server and attach its transport. Used by `bin.ts`; tests can inject
|
|
120
|
+
* an in-memory transport. Process policy (logging, exit code, signals) remains
|
|
121
|
+
* in the executable boundary rather than this reusable library function.
|
|
107
122
|
*/
|
|
108
123
|
async function main(opts = {}) {
|
|
124
|
+
const { transport = new _modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport(), ...serverOptions } = opts;
|
|
125
|
+
const server = await createServer(serverOptions);
|
|
109
126
|
try {
|
|
110
|
-
const server = await createServer({ profile: opts.profile });
|
|
111
|
-
const transport = new _modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
|
|
112
127
|
await server.connect(transport);
|
|
128
|
+
return server;
|
|
113
129
|
} catch (err) {
|
|
114
|
-
|
|
115
|
-
|
|
130
|
+
await server.close().catch(() => void 0);
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function validateProfile(value) {
|
|
135
|
+
if (value !== "default" && value !== "claude-code") throw new Error("profile must be \"default\" or \"claude-code\".");
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
function validateIdentity(value) {
|
|
139
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("Agent identity must be an object.");
|
|
140
|
+
const record = value;
|
|
141
|
+
return {
|
|
142
|
+
agentId: validateIdentityPart("agentId", record.agentId),
|
|
143
|
+
tenantId: validateIdentityPart("tenantId", record.tenantId)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function validateIdentityPart(label, value) {
|
|
147
|
+
if (typeof value !== "string" || value.length < 1 || value.length > MAX_IDENTITY_CHARS || hasControlCharacters(value)) throw new Error(`${label} must contain 1 to ${String(MAX_IDENTITY_CHARS)} non-control characters.`);
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
function hasControlCharacters(value) {
|
|
151
|
+
return Array.from(value).some((character) => {
|
|
152
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
153
|
+
return codePoint < 32 || codePoint === 127;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function validateServiceUrl(label, value) {
|
|
157
|
+
if (typeof value !== "string" || value.length < 1 || value.length > MAX_SERVICE_URL_CHARS) throw new Error(`${label} must be a bounded absolute HTTP(S) URL.`);
|
|
158
|
+
let parsed;
|
|
159
|
+
try {
|
|
160
|
+
parsed = new URL(value);
|
|
161
|
+
} catch {
|
|
162
|
+
throw new Error(`${label} must be a bounded absolute HTTP(S) URL.`);
|
|
116
163
|
}
|
|
164
|
+
if (!["http:", "https:"].includes(parsed.protocol) || parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "" || parsed.protocol === "http:" && !isLoopbackHostname(parsed.hostname)) throw new Error(`${label} must use HTTPS (or loopback HTTP) without credentials, query, or fragment.`);
|
|
165
|
+
return parsed.href.replace(/\/$/u, "");
|
|
117
166
|
}
|
|
118
|
-
function
|
|
119
|
-
|
|
167
|
+
function isLoopbackHostname(hostname) {
|
|
168
|
+
const normalized = hostname.replace(/^\[|\]$/gu, "").toLowerCase();
|
|
169
|
+
return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1";
|
|
120
170
|
}
|
|
121
171
|
//#endregion
|
|
122
172
|
exports.SERVER_BIN_PATH = SERVER_BIN_PATH;
|