u-foo 2.4.4 → 2.4.6

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 CHANGED
@@ -27,6 +27,7 @@ Package: [u-foo on npm](https://www.npmjs.com/package/u-foo)
27
27
  - Launch modes for internal, tmux, host, Terminal.app, and iTerm2 workflows.
28
28
  - Built-in group templates for launching and orchestrating multi-agent workflows.
29
29
  - `ucode`, a native ufoo coding-agent runtime.
30
+ - `ufoo mcp`, a local global MCP bridge for external MCP-capable agents.
30
31
 
31
32
  ## Requirements
32
33
 
@@ -56,7 +57,7 @@ Installed binaries:
56
57
 
57
58
  | Binary | Purpose |
58
59
  |---|---|
59
- | `ufoo` | Main CLI, chat dashboard, daemon, groups, bus, context, memory, reports, and online helpers. |
60
+ | `ufoo` | Main CLI, chat dashboard, daemon, local global MCP bridge, groups, bus, context, memory, reports, and online helpers. |
60
61
  | `uclaude` | Claude Code wrapper with ufoo bootstrap and bus identity. |
61
62
  | `ucodex` | Codex wrapper with ufoo bootstrap and bus identity. |
62
63
  | `uagy` | Antigravity wrapper with ufoo bootstrap and bus identity. |
@@ -96,6 +97,16 @@ Use global chat mode to switch between registered projects:
96
97
  ufoo -g
97
98
  ```
98
99
 
100
+ For MCP-capable clients, configure the global stdio bridge once:
101
+
102
+ ```bash
103
+ ufoo mcp
104
+ ```
105
+
106
+ The MCP bridge connects to the home-scoped global controller daemon and routes
107
+ project-scoped tools through the global project registry. It is not a separate
108
+ per-project MCP server mode.
109
+
99
110
  ## Runtime Model
100
111
 
101
112
  ```text
@@ -107,6 +118,11 @@ ufoo / ufoo chat
107
118
  -> agents launch/providers/internal/controller/activity
108
119
  -> coordination bus/context/memory/history/report/state/status
109
120
  -> shared controller/worker tools and native ucode tools
121
+
122
+ ufoo mcp
123
+ -> home-scoped global controller daemon
124
+ -> ~/.ufoo/projects/runtime
125
+ -> selected project daemon for bus/report/activity state
110
126
  ```
111
127
 
112
128
  Chat is a UI client. The daemon owns project runtime state. Agents communicate
@@ -306,8 +322,7 @@ src/
306
322
  online/ relay client/server/runner/token helpers
307
323
  ```
308
324
 
309
- See [PROJECT.md](PROJECT.md) for the maintainer-facing map and
310
- [docs/source-structure.md](docs/source-structure.md) for detailed package
325
+ See [PROJECT.md](PROJECT.md) for the maintainer-facing map and detailed package
311
326
  ownership.
312
327
 
313
328
  ## Development
@@ -324,7 +339,6 @@ Useful checks:
324
339
  ```bash
325
340
  npm run test:watch
326
341
  npm run test:coverage
327
- npm run bench:global-switch
328
342
  ```
329
343
 
330
344
  The repository is CommonJS, targets Node.js 18+, and has no build step.
package/README.zh-CN.md CHANGED
@@ -25,6 +25,7 @@ npm 包:[u-foo](https://www.npmjs.com/package/u-foo)
25
25
  - 支持 internal、tmux、host、Terminal.app、iTerm2 等启动模式。
26
26
  - 内置 group 模板,用于启动和编排多 Agent 工作流。
27
27
  - 提供原生 ufoo coding-agent 运行时 `ucode`。
28
+ - 提供 `ufoo mcp` 本机 global MCP bridge,供支持 MCP 的外部 Agent 接入。
28
29
 
29
30
  ## 环境要求
30
31
 
@@ -54,7 +55,7 @@ npm link
54
55
 
55
56
  | 命令 | 用途 |
56
57
  |---|---|
57
- | `ufoo` | 主 CLI、chat 仪表盘、daemon、group、bus、context、memory、report 和 online helper。 |
58
+ | `ufoo` | 主 CLI、chat 仪表盘、daemon、本机 global MCP bridge、group、bus、context、memory、report 和 online helper。 |
58
59
  | `uclaude` | Claude Code 包装器,注入 ufoo bootstrap 和 bus 身份。 |
59
60
  | `ucodex` | Codex 包装器,注入 ufoo bootstrap 和 bus 身份。 |
60
61
  | `uagy` | Antigravity 包装器,注入 ufoo bootstrap 和 bus 身份。 |
@@ -94,6 +95,15 @@ ucode
94
95
  ufoo -g
95
96
  ```
96
97
 
98
+ 给支持 MCP 的客户端使用时,只需要配置一次全局 stdio bridge:
99
+
100
+ ```bash
101
+ ufoo mcp
102
+ ```
103
+
104
+ MCP bridge 会连接 home 级 global controller daemon,并通过全局项目 registry
105
+ 把项目级工具路由到对应项目 daemon。它不是每个项目单独部署一个 MCP server 的模式。
106
+
97
107
  ## 运行模型
98
108
 
99
109
  ```text
@@ -105,6 +115,11 @@ ufoo / ufoo chat
105
115
  -> agents launch/providers/internal/controller/activity
106
116
  -> coordination bus/context/memory/history/report/state/status
107
117
  -> shared controller/worker tools and native ucode tools
118
+
119
+ ufoo mcp
120
+ -> home-scoped global controller daemon
121
+ -> ~/.ufoo/projects/runtime
122
+ -> selected project daemon for bus/report/activity state
108
123
  ```
109
124
 
110
125
  Chat 是 UI client。daemon 拥有项目运行态。Agent 通过 bus queue、prompt
package/bin/ufoo.js CHANGED
@@ -2,6 +2,7 @@
2
2
  /* eslint-disable no-console */
3
3
  const { runCli } = require("../src/app/cli/run");
4
4
  const { runDaemonCli } = require("../src/runtime/daemon/run");
5
+ const { runMcpServer } = require("../src/runtime/daemon/mcpServer");
5
6
  const { runChat } = require("../src/app/chat");
6
7
  const { runInternalRunner } = require("../src/agents/internal/internalRunner");
7
8
  const { resolveGlobalControllerProjectRoot } = require("../src/runtime/projects");
@@ -26,6 +27,12 @@ async function main() {
26
27
  runDaemonCli(["daemon", ...argv.slice(1)]);
27
28
  return;
28
29
  }
30
+ if (cmd === "mcp") {
31
+ await runMcpServer({
32
+ autoStart: !argv.includes("--no-auto-start"),
33
+ });
34
+ return;
35
+ }
29
36
  if (cmd === "agent-runner") {
30
37
  const agentType = argv[1] || "codex";
31
38
  const extraArgs = argv.slice(2);
@@ -38,7 +38,7 @@ Versioning is optional but recommended for auditability.
38
38
  ```
39
39
  context/ # This repo
40
40
  ├── README.md # This file
41
- ├── SKILLS/uctx/SKILL.md # Canonical decision format + workflow
41
+ ├── ../../SKILLS/uctx/SKILL.md # Canonical decision format + workflow
42
42
  └── .ufoo/context/ # Local project context for this repo (ignored; not part of protocol distribution)
43
43
  ```
44
44
 
@@ -47,7 +47,7 @@ context/ # This repo
47
47
  1. Read installed module from `~/.ufoo/modules/context/`
48
48
  2. Read/write decisions in `<project>/.ufoo/context/decisions/`
49
49
  3. **Never write to global** — only to project
50
- 4. Follow the decision format in `SKILLS/uctx/SKILL.md`
50
+ 4. Follow the decision format in the package-level `SKILLS/uctx/SKILL.md`
51
51
 
52
52
  ## Validate
53
53
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u-foo",
3
- "version": "2.4.4",
3
+ "version": "2.4.6",
4
4
  "description": "Multi-Agent Workspace Protocol. Just add u. claude → uclaude, codex → ucodex.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://ufoo.dev",
@@ -20,8 +20,7 @@
20
20
  "uclaude": "bin/uclaude.js",
21
21
  "ucodex": "bin/ucodex.js",
22
22
  "uagy": "bin/uagy.js",
23
- "ucode": "bin/ucode.js",
24
- "ucode-core": "bin/ucode-core.js"
23
+ "ucode": "bin/ucode.js"
25
24
  },
26
25
  "files": [
27
26
  "bin/",
@@ -42,10 +41,7 @@
42
41
  "postinstall": "node scripts/postinstall.js",
43
42
  "test": "jest",
44
43
  "test:watch": "jest --watch",
45
- "test:coverage": "jest --coverage",
46
- "ink:demo": "node scripts/ink-demo.js",
47
- "ink:smoke": "node scripts/ink-smoke.js",
48
- "bench:global-switch": "node scripts/global-chat-switch-benchmark.js"
44
+ "test:coverage": "jest --coverage"
49
45
  },
50
46
  "dependencies": {
51
47
  "@anthropic-ai/claude-agent-sdk": "^0.2.138",
@@ -30,11 +30,9 @@ for (const platform of platforms) {
30
30
  }
31
31
  }
32
32
 
33
- // Collect all skill sources from package
33
+ // Collect all skill sources from the package-level SKILLS directory.
34
34
  function collectSkillSources(pkgRoot) {
35
35
  const sources = [];
36
-
37
- // Top-level SKILLS/
38
36
  const topSkills = path.join(pkgRoot, "SKILLS");
39
37
  if (fs.existsSync(topSkills)) {
40
38
  for (const entry of fs.readdirSync(topSkills, { withFileTypes: true })) {
@@ -46,24 +44,6 @@ function collectSkillSources(pkgRoot) {
46
44
  }
47
45
  }
48
46
  }
49
-
50
- // modules/*/SKILLS/
51
- const modulesDir = path.join(pkgRoot, "modules");
52
- if (fs.existsSync(modulesDir)) {
53
- for (const mod of fs.readdirSync(modulesDir, { withFileTypes: true })) {
54
- if (!mod.isDirectory()) continue;
55
- const modSkills = path.join(modulesDir, mod.name, "SKILLS");
56
- if (!fs.existsSync(modSkills)) continue;
57
- for (const entry of fs.readdirSync(modSkills, { withFileTypes: true })) {
58
- if (!entry.isDirectory()) continue;
59
- const skillMd = path.join(modSkills, entry.name, "SKILL.md");
60
- if (fs.existsSync(skillMd)) {
61
- sources.push({ name: entry.name, dir: path.join(modSkills, entry.name), md: skillMd });
62
- }
63
- }
64
- }
65
- }
66
-
67
47
  return sources;
68
48
  }
69
49
 
@@ -19,6 +19,7 @@ const { parseIntervalMs, formatIntervalMs } = require("./cronScheduler");
19
19
  const { isGlobalControllerProjectRoot, resolveGlobalControllerUfooDir } = require("../../runtime/projects");
20
20
  const { loadPromptProfileRegistry } = require("../../orchestration/groups/promptProfiles");
21
21
  const { resolveSoloAgentType } = require("../../orchestration/solo/commands");
22
+ const { restartDaemonLifecycle } = require("../../runtime/daemon/restart");
22
23
  const {
23
24
  inspectDirectAuthStatus,
24
25
  formatDirectAuthStatus,
@@ -269,15 +270,19 @@ function createCommandExecutor(options = {}) {
269
270
  }
270
271
 
271
272
  statusMsg("{gray-fg}⚙{/gray-fg} Restarting daemon...");
272
- stopDaemon(targetRoot, { source: "chat-command:/daemon restart" });
273
- await sleep(500);
274
- if (isDaemonRunning(targetRoot)) {
273
+ const result = await restartDaemonLifecycle({
274
+ projectRoot: targetRoot,
275
+ isRunning: isDaemonRunning,
276
+ stopDaemon,
277
+ startDaemon,
278
+ stopOptions: { source: "chat-command:/daemon restart" },
279
+ sleep,
280
+ });
281
+ if (result.error === "failed_to_stop") {
275
282
  statusMsg("{gray-fg}✗{/gray-fg} Failed to stop daemon");
276
283
  return;
277
284
  }
278
- startDaemon(targetRoot);
279
- await sleep(1000);
280
- if (isDaemonRunning(targetRoot)) {
285
+ if (result.ok) {
281
286
  statusMsg("{gray-fg}✓{/gray-fg} Daemon restarted");
282
287
  } else {
283
288
  statusMsg("{gray-fg}✗{/gray-fg} Failed to restart daemon");
@@ -12,8 +12,10 @@ function createDaemonCoordinator(options = {}) {
12
12
  logMessage,
13
13
  stopDaemon,
14
14
  startDaemon,
15
+ isDaemonRunning,
15
16
  daemonConnection,
16
17
  restartDaemon,
18
+ sleep,
17
19
  } = options;
18
20
 
19
21
  const connectClientFn = connectClient
@@ -37,9 +39,11 @@ function createDaemonCoordinator(options = {}) {
37
39
  projectRoot,
38
40
  stopDaemon,
39
41
  startDaemon,
42
+ isDaemonRunning,
40
43
  daemonConnection: connection,
41
44
  logMessage,
42
45
  resolveStatusLine,
46
+ sleep,
43
47
  });
44
48
  let switchProjectChain = Promise.resolve();
45
49
 
@@ -1,4 +1,5 @@
1
1
  const { restartLocks } = require("./daemonTransport");
2
+ const { restartDaemonLifecycle } = require("../../runtime/daemon/restart");
2
3
 
3
4
  function resolveDaemonConnection(daemonConnection) {
4
5
  return typeof daemonConnection === "function" ? daemonConnection() : daemonConnection;
@@ -9,9 +10,11 @@ function restartDaemonFlow(options = {}) {
9
10
  projectRoot,
10
11
  stopDaemon,
11
12
  startDaemon,
13
+ isDaemonRunning,
12
14
  daemonConnection,
13
15
  logMessage,
14
16
  resolveStatusLine = null,
17
+ sleep,
15
18
  } = options;
16
19
 
17
20
  const statusMsg = resolveStatusLine || ((text) => logMessage("status", text));
@@ -26,14 +29,21 @@ function restartDaemonFlow(options = {}) {
26
29
  if (connection) {
27
30
  connection.close();
28
31
  }
29
- stopDaemon(projectRoot);
30
- startDaemon(projectRoot);
31
- const connected = connection ? await connection.connect() : false;
32
- if (connected) {
33
- if (typeof connection.requestStatus === "function") {
34
- connection.requestStatus();
35
- }
32
+ const result = await restartDaemonLifecycle({
33
+ projectRoot,
34
+ isRunning: isDaemonRunning,
35
+ stopDaemon,
36
+ startDaemon,
37
+ connect: connection ? () => connection.connect() : null,
38
+ requestStatus: connection && typeof connection.requestStatus === "function"
39
+ ? () => connection.requestStatus()
40
+ : null,
41
+ sleep,
42
+ });
43
+ if (result.ok) {
36
44
  statusMsg("{gray-fg}✓{/gray-fg} Daemon reconnected");
45
+ } else if (result.error === "failed_to_stop") {
46
+ statusMsg("{gray-fg}✗{/gray-fg} Failed to stop daemon");
37
47
  } else {
38
48
  statusMsg("{gray-fg}✗{/gray-fg} Failed to reconnect to daemon");
39
49
  }
@@ -15,25 +15,10 @@ class SkillsManager {
15
15
  */
16
16
  findSkillRoots() {
17
17
  const roots = [];
18
-
19
- // 检查 SKILLS 目录
20
18
  const mainSkills = path.join(this.repoRoot, "SKILLS");
21
19
  if (fs.existsSync(mainSkills)) {
22
20
  roots.push(mainSkills);
23
21
  }
24
-
25
- // 检查 modules 中的 SKILLS
26
- const modulesDir = path.join(this.repoRoot, "modules");
27
- if (fs.existsSync(modulesDir)) {
28
- const modules = fs.readdirSync(modulesDir);
29
- for (const module of modules) {
30
- const moduleSkills = path.join(modulesDir, module, "SKILLS");
31
- if (fs.existsSync(moduleSkills)) {
32
- roots.push(moduleSkills);
33
- }
34
- }
35
- }
36
-
37
22
  return roots;
38
23
  }
39
24
 
@@ -585,6 +585,16 @@ async function runCli(argv) {
585
585
  if (opts.global === true) args.push("-g");
586
586
  run(process.execPath, [path.join(repoRoot, "bin", "ufoo.js"), ...args]);
587
587
  });
588
+ program
589
+ .command("mcp")
590
+ .description("Run the local global ufoo MCP bridge over stdio")
591
+ .option("--no-auto-start", "Do not auto-start the home-scoped global controller daemon")
592
+ .action((opts) => {
593
+ const repoRoot = getPackageRoot();
594
+ const args = ["mcp"];
595
+ if (opts.autoStart === false) args.push("--no-auto-start");
596
+ run(process.execPath, [path.join(repoRoot, "bin", "ufoo.js"), ...args]);
597
+ });
588
598
  const project = program.command("project").description("Project runtime commands");
589
599
  project
590
600
  .command("list")
@@ -1665,7 +1675,7 @@ async function runCli(argv) {
1665
1675
  program.addHelpText(
1666
1676
  "after",
1667
1677
  `\nNotes:\n - If 'ufoo' isn't in PATH, run it via ${chalk.cyan(
1668
- "./bin/ufoo"
1678
+ "./bin/ufoo.js"
1669
1679
  )} (repo) or install globally via npm.\n - For bus notifications inside Codex, prefer ${chalk.cyan(
1670
1680
  "ufoo bus alert"
1671
1681
  )} / ${chalk.cyan("ufoo bus listen")} (no IME issues).\n`
@@ -1771,6 +1781,12 @@ async function runCli(argv) {
1771
1781
  run(resolveNodeExecutable(), [path.join(repoRoot, "bin", "ufoo.js"), ...chatArgs]);
1772
1782
  return;
1773
1783
  }
1784
+ if (cmd === "mcp") {
1785
+ const mcpArgs = ["mcp"];
1786
+ if (rest.includes("--no-auto-start")) mcpArgs.push("--no-auto-start");
1787
+ run(resolveNodeExecutable(), [path.join(repoRoot, "bin", "ufoo.js"), ...mcpArgs]);
1788
+ return;
1789
+ }
1774
1790
  if (cmd === "project") {
1775
1791
  const sub = String(rest[0] || "list").trim().toLowerCase();
1776
1792
  const outputJson = rest.includes("--json");
@@ -15,7 +15,7 @@
15
15
  - `ucode> ...` free-form task input routes to model-backed planner path
16
16
  - `tool/run` commands stay available as deterministic fallback
17
17
  - Dispatcher: `runToolCall({ tool, args }, { workspaceRoot })`
18
- - Queue runtime:
19
- - `ucode-core submit`
20
- - `ucode-core run-once`
21
- - `ucode-core list`
18
+ - Queue runtime internals:
19
+ - `submitTask`
20
+ - `runOnce`
21
+ - `listResults`
package/src/code/cli.js CHANGED
@@ -78,7 +78,7 @@ function parseArgs(argv = []) {
78
78
 
79
79
  function usage() {
80
80
  return [
81
- "ucode-core native runtime CLI",
81
+ "ucode native runtime internals",
82
82
  "",
83
83
  "Commands:",
84
84
  " submit --tool <read|write|edit|bash> --args-json <json> [--workspace <path>] [--task-id <id>]",
@@ -206,13 +206,11 @@ function isLikelyPiCoreCommand(command = "", args = []) {
206
206
  const cmdText = String(command || "").trim();
207
207
  const cmdBase = path.basename(cmdText).toLowerCase();
208
208
  if (cmdBase === "ucode" || cmdBase === "ucode.exe") return true;
209
- if (cmdBase === "ucode-core" || cmdBase === "ucode-core.exe") return true;
210
209
 
211
210
  const joined = [cmdText, ...(Array.isArray(args) ? args : [])]
212
211
  .map((part) => String(part || "").toLowerCase())
213
212
  .join(" ");
214
213
  if (!joined) return false;
215
- if (joined.includes("ucode-core")) return true;
216
214
  if (joined.includes("/src/code/agent.js")) return true;
217
215
  if (joined.includes("\\src\\code\\agent.js")) return true;
218
216
  return false;
@@ -287,55 +285,30 @@ function resolveCandidateCoreRoot({
287
285
  }
288
286
 
289
287
  function resolveNativeFallbackCommand({ env = process.env } = {}) {
290
- const candidates = [
291
- path.resolve(__dirname, "..", "agent.js"),
292
- path.resolve(__dirname, "..", "..", "..", "bin", "ucode-core.js"),
293
- ];
294
- for (const entry of candidates) {
295
- try {
296
- if (isReadableFile(entry)) {
297
- if (entry.endsWith("agent.js")) {
298
- return {
299
- command: process.execPath,
300
- args: [entry],
301
- root: path.resolve(__dirname, ".."),
302
- kind: "native",
303
- available: true,
304
- resolvedPath: entry,
305
- };
306
- }
307
- return {
308
- command: process.execPath,
309
- args: [entry, "agent"],
310
- root: path.resolve(__dirname, ".."),
311
- kind: "native",
312
- available: true,
313
- resolvedPath: entry,
314
- };
315
- }
316
- } catch {
317
- // ignore
288
+ void env;
289
+ const entry = path.resolve(__dirname, "..", "agent.js");
290
+ try {
291
+ if (isReadableFile(entry)) {
292
+ return {
293
+ command: process.execPath,
294
+ args: [entry],
295
+ root: path.resolve(__dirname, ".."),
296
+ kind: "native",
297
+ available: true,
298
+ resolvedPath: entry,
299
+ };
318
300
  }
319
- }
320
- const resolvedCommand = resolveExecutableFromPath("ucode-core", env);
321
- if (resolvedCommand) {
322
- return {
323
- command: "ucode-core",
324
- args: ["agent"],
325
- root: "",
326
- kind: "native",
327
- available: true,
328
- resolvedPath: resolvedCommand,
329
- };
301
+ } catch {
302
+ // ignore
330
303
  }
331
304
  return {
332
- command: "ucode-core",
333
- args: ["agent"],
334
- root: "",
305
+ command: process.execPath,
306
+ args: [entry],
307
+ root: path.resolve(__dirname, ".."),
335
308
  kind: "native",
336
309
  available: false,
337
310
  resolvedPath: "",
338
- missingReason: "src/code/agent.js not found and ucode-core is not available on PATH",
311
+ missingReason: "src/code/agent.js not found",
339
312
  };
340
313
  }
341
314
 
@@ -63,19 +63,6 @@ function defaultSkillRoots({
63
63
 
64
64
  const root = path.resolve(String(repoRoot || repoRootFromHere()));
65
65
  roots.push({ path: path.join(root, "SKILLS"), scope: "builtin", source: "ufoo" });
66
- const modulesDir = path.join(root, "modules");
67
- try {
68
- for (const entry of fs.readdirSync(modulesDir, { withFileTypes: true })) {
69
- if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
70
- roots.push({
71
- path: path.join(modulesDir, entry.name, "SKILLS"),
72
- scope: "builtin",
73
- source: `ufoo-module:${entry.name}`,
74
- });
75
- }
76
- } catch {
77
- // Modules are optional in tests and local installs.
78
- }
79
66
 
80
67
  const seen = new Set();
81
68
  return roots
@@ -87,6 +87,7 @@ class ContextDoctor {
87
87
  */
88
88
  lintProtocol() {
89
89
  const moduleRoot = path.join(this.projectRoot, "modules", "context");
90
+ const repoSkill = path.join(this.projectRoot, "SKILLS", "uctx", "SKILL.md");
90
91
 
91
92
  if (!fs.existsSync(moduleRoot)) {
92
93
  console.log("No protocol module found (skipping protocol lint)");
@@ -97,10 +98,7 @@ class ContextDoctor {
97
98
 
98
99
  // Check minimal module files
99
100
  this.checkFile(path.join(moduleRoot, "README.md"), "README.md");
100
- this.checkFile(
101
- path.join(moduleRoot, "SKILLS", "uctx", "SKILL.md"),
102
- "SKILLS/uctx/SKILL.md"
103
- );
101
+ this.checkFile(repoSkill, "SKILLS/uctx/SKILL.md");
104
102
 
105
103
  return !this.failed;
106
104
  }
@@ -10,7 +10,7 @@ const WebSocket = require("ws");
10
10
  * ufoo-online (Phase 1)
11
11
  *
12
12
  * Minimal WebSocket relay implementing hello/auth + join/leave + event routing.
13
- * Intended WebSocket path: /ufoo/online (see docs/ufoo-online/PROTOCOL.md)
13
+ * Intended WebSocket path: /ufoo/online.
14
14
  */
15
15
  class OnlineServer extends EventEmitter {
16
16
  constructor(options = {}) {
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ const MCP_PROTOCOL_VERSION = "2024-11-05";
4
+
5
+ const MCP_ERROR_CODES = Object.freeze({
6
+ PARSE_ERROR: -32700,
7
+ INVALID_REQUEST: -32600,
8
+ METHOD_NOT_FOUND: -32601,
9
+ INVALID_PARAMS: -32602,
10
+ INTERNAL_ERROR: -32603,
11
+ });
12
+
13
+ function createJsonRpcResult(id, result) {
14
+ return {
15
+ jsonrpc: "2.0",
16
+ id,
17
+ result,
18
+ };
19
+ }
20
+
21
+ function createJsonRpcError(id, code, message, data = undefined) {
22
+ const error = {
23
+ code,
24
+ message: String(message || "MCP request failed"),
25
+ };
26
+ if (data !== undefined) error.data = data;
27
+ return {
28
+ jsonrpc: "2.0",
29
+ id: id === undefined ? null : id,
30
+ error,
31
+ };
32
+ }
33
+
34
+ module.exports = {
35
+ MCP_PROTOCOL_VERSION,
36
+ MCP_ERROR_CODES,
37
+ createJsonRpcResult,
38
+ createJsonRpcError,
39
+ };