agy-mcp-bridge 0.1.21 → 0.1.22
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 -4
- package/dist/scripts/install-skill.js +69 -36
- package/dist/src/cli.js +3 -3
- package/dist/src/http.js +3 -3
- package/dist/src/index.js +2 -2
- package/dist/src/missions/missions.js +413 -0
- package/dist/src/onboarding.js +27 -17
- package/dist/src/runtime.js +11 -3
- package/dist/src/stdio.js +2 -2
- package/dist/src/tools/register.js +69 -1
- package/dist/src/ui/routes.js +23 -0
- package/docs/MULTI-AGENT-PLAN.md +61 -8
- package/package.json +1 -1
- package/skills/known-versions.json +75 -67
- package/skills/mission-orchestrator/SKILL.md +82 -0
- package/ui/app.css +11 -0
- package/ui/app.js +103 -2
package/README.md
CHANGED
|
@@ -79,6 +79,23 @@ Then call `antigravity_environment` from a fresh session — that is the proof;
|
|
|
79
79
|
|
|
80
80
|
Two switches control delegation itself: `agy-mcp-bridge delegation off` blocks new tasks immediately without a restart, and `BRIDGE_DELEGATION=off` pins it at startup. Tasks still refuse to be delegated by policy the skill describes (security, auth, secrets, data deletion, deploy, CI and public API changes are not delegated).
|
|
81
81
|
|
|
82
|
+
## Missions
|
|
83
|
+
|
|
84
|
+
A mission is a plan the bridge **records and gates but never runs**: up to 12 steps in one workspace, each carrying its own `delegate` arguments and a list of `depends_on`. An MCP client writes the plan and dispatches it one step at a time; the bridge refuses to start a step whose dependencies did not all complete, so no agent is sent at a step whose inputs do not exist yet. Nothing ever starts by itself — a restart restores every plan and re-dispatches nothing.
|
|
85
|
+
|
|
86
|
+
| Tool | What it does |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `mission_create` | record the plan and return `mission_id`, progress and the `ready` step ids (the receipt does not echo the plan back) |
|
|
89
|
+
| `mission_extend` | append correction or follow-up steps: the plan is append-only, so a retry is always a new step |
|
|
90
|
+
| `mission_dispatch` | start one ready step as a normal supervised task; refused while a dependency is not `completed`, if the step already ran, or once the mission is canceled |
|
|
91
|
+
| `mission_status` | cheap poll: per-step state, `blocked_by`, `ready` — no task text |
|
|
92
|
+
| `mission_plan` | the whole recorded plan with each step's exact `delegate` arguments, for a client that lost its own copy |
|
|
93
|
+
| `mission_cancel` | close the plan so nothing further can be dispatched; running agents are left alone unless `cancel_running_tasks: true` |
|
|
94
|
+
|
|
95
|
+
A dispatched step **is** an ordinary task, so everything in [Delegate](#delegate) still applies to it: worktree isolation, `owns`, timeouts, redaction, process-tree release, `review_required`, and no auto-merge. Step state is derived from the task records on every read rather than cached, so a record that retention dropped shows as `lost` — which never unlocks the step waiting for it. Replies are deliberately small (`dispatch` returns five fields plus a `next_step` hint) because an orchestrator polls them; the **Nhiệm vụ** page is the human view. There is no `mission_list` — keep the `mission_id` you were given, or read it from that page.
|
|
96
|
+
|
|
97
|
+
The bundled `mission-orchestrator` skill teaches a client how to write such a plan (and when not to).
|
|
98
|
+
|
|
82
99
|
## Lifecycle and limits
|
|
83
100
|
|
|
84
101
|
Task state belongs to the HTTP server, not to the MCP session, and all clients share one TaskManager. `none`-lane tasks take a cross-process lock (`.bridge-state/execution.lock`); `worktree`-lane tasks rely on their own branch. Results persist to `.bridge-state/tasks/<task_id>.json` (0600, secret-redacted) and reload at startup, so you can recover an answer after a reconnect — this is history, not durable task recovery. Ctrl+C once: stop listening, cancel running tasks and reap their process trees, drain sockets for up to 3 s, print `[bridge] Stopped`. It does not revert files.
|
|
@@ -99,7 +116,7 @@ Redaction recognises common secret shapes; it does not catch everything. Keep se
|
|
|
99
116
|
|
|
100
117
|
## Web UI
|
|
101
118
|
|
|
102
|
-
`http://127.0.0.1:3000/ui` on the same machine, no login (there is no auth to log into). It reads the same task store as the tools, so it costs your other agents no tokens. Pages: **Tasks** (bridge state, ready agents, delegation switch, per-task request/criteria/response, `denied_actions`, stdout/stderr, full prompt, worktree/branch and `owns` verdict, workspace changes with per-file line counts) and **Settings** (delegation, MCP endpoint, per-agent probe results, allowed workspaces, permission level, model, effort, wait cadence, soft deadline, step progress, usage, shutdown button). Regions patch in place, so selecting log text survives an update.
|
|
119
|
+
`http://127.0.0.1:3000/ui` on the same machine, no login (there is no auth to log into). It reads the same task store as the tools, so it costs your other agents no tokens. Pages: **Tasks** (bridge state, ready agents, delegation switch, per-task request/criteria/response, `denied_actions`, stdout/stderr, full prompt, worktree/branch and `owns` verdict, workspace changes with per-file line counts), **Nhiệm vụ** (every recorded plan: steps, states, which dependency blocks which step, the exact `delegate` arguments per step, and a **Dừng kế hoạch** button that closes the plan and never touches a running agent — the page reads and stops missions, it cannot dispatch one), and **Settings** (delegation, MCP endpoint, per-agent probe results, allowed workspaces, permission level, model, effort, wait cadence, soft deadline, step progress, usage, shutdown button). Regions patch in place, so selecting log text survives an update.
|
|
103
120
|
|
|
104
121
|
Long explanations, unverified notes and rarely-changed numbers live behind **More** blocks; decisions-worthy state stays visible. The no-auth warning is always visible, and the "allowlist is not a sandbox" warning is always visible at `auto`/`full`.
|
|
105
122
|
|
|
@@ -114,7 +131,7 @@ npm test # build + unit/integration with fake executables; no AI calls
|
|
|
114
131
|
npm run test:live # REAL agent calls, scratch workspace, disconnect/reconnect, cancel, shutdown
|
|
115
132
|
```
|
|
116
133
|
|
|
117
|
-
`npm test` runs serially (`--test-concurrency=1`, ~158 s): most cases start a real loopback server and time its output, so a parallel failure reflects machine load, not code. CI runs Windows and Linux on Node 20/22. Source map: `src/index.ts` entry, `src/http.ts` routing/shutdown, `src/runtime.ts` shared CLI + TaskManager, `src/agents` adapters and registry, `src/isolation` worktrees and `owns`, `src/tasks` lifecycle/lock/output, `src/security` validation and redaction, `src/tools` tool definitions, `src/antigravity` child process, `ui/` plain HTML/CSS/JS with `src/ui/` behind it.
|
|
134
|
+
`npm test` runs serially (`--test-concurrency=1`, ~158 s): most cases start a real loopback server and time its output, so a parallel failure reflects machine load, not code. CI runs Windows and Linux on Node 20/22. Source map: `src/index.ts` entry, `src/http.ts` routing/shutdown, `src/runtime.ts` shared CLI + TaskManager, `src/agents` adapters and registry, `src/missions` the plan store and its dispatch gates, `src/isolation` worktrees and `owns`, `src/tasks` lifecycle/lock/output, `src/security` validation and redaction, `src/tools` tool definitions, `src/antigravity` child process, `ui/` plain HTML/CSS/JS with `src/ui/` behind it.
|
|
118
135
|
|
|
119
136
|
Pushing or merging to `main` runs CI and, when it passes, publishes to npm automatically: the [publish workflow](.github/workflows/publish.yml) bumps the patch version, tags, pushes and publishes through OIDC Trusted Publishing — no npm token in secrets, no manual version bump, nothing published if CI fails. PRs run [CI](.github/workflows/ci.yml) only. Set up once under the package's **Trusted Publisher** settings: user `thanhdo257205`, repository `MCP-bridge`, workflow `publish.yml`, empty environment, allow direct npm publish.
|
|
120
137
|
|
|
@@ -125,8 +142,8 @@ Pushing or merging to `main` runs CI and, when it passes, publishes to npm autom
|
|
|
125
142
|
- [docs/MULTI-AGENT-PLAN.md](docs/MULTI-AGENT-PLAN.md) — the multi-agent roadmap and per-phase status.
|
|
126
143
|
- [docs/HANDOFF.md](docs/HANDOFF.md) — open work (Job Object, POSIX E2E, worktree TTL) with mutation-test evidence.
|
|
127
144
|
- [docs/PROMPTS.md](docs/PROMPTS.md) — review and follow-up task prompts.
|
|
128
|
-
- [skills/antigravity-bridge/SKILL.md](skills/antigravity-bridge/SKILL.md) and [docs/SKILL-EVAL.md](docs/SKILL-EVAL.md) — the client-side contract and manual evaluation cases.
|
|
145
|
+
- [skills/antigravity-bridge/SKILL.md](skills/antigravity-bridge/SKILL.md), [skills/mission-orchestrator/SKILL.md](skills/mission-orchestrator/SKILL.md) and [docs/SKILL-EVAL.md](docs/SKILL-EVAL.md) — the client-side contract and manual evaluation cases.
|
|
129
146
|
|
|
130
|
-
|
|
147
|
+
Two skills ship with the bridge: `antigravity-bridge` teaches a client the real tool schemas, task-id recovery, timeouts, permission denials and diff review; `mission-orchestrator` teaches it how to write and drive a plan — when a mission is the right shape, how to keep it cheap, and what must never be put in one. First interactive run offers to install them (`agy-mcp-bridge skill install --client codex|claude|both [--skill <name>] [--update]` writes `~/.agents/skills/…` or `~/.claude/skills/…`); a `SKILL.md` you edited yourself is never overwritten, and one customized skill never blocks the other. Installing a skill does not connect the server or grant any permission.
|
|
131
148
|
|
|
132
149
|
MIT licensed; see `LICENSE`.
|
|
@@ -1,24 +1,37 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { readFile, mkdir, writeFile, stat, realpath, rename, rm } from 'node:fs/promises';
|
|
2
|
+
import { readFile, mkdir, writeFile, stat, readdir, realpath, rename, rm } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
6
|
import { paths } from '../src/paths.js';
|
|
7
|
-
const skillSource = () => path.join(
|
|
8
|
-
const skillFile = (root, client) => path.join(root, client === 'codex' ? '.agents' : '.claude', 'skills',
|
|
7
|
+
const skillSource = (name, directory = paths.skillsDirectory) => path.join(directory, name, 'SKILL.md');
|
|
8
|
+
const skillFile = (root, client, name) => path.join(root, client === 'codex' ? '.agents' : '.claude', 'skills', name, 'SKILL.md');
|
|
9
|
+
const readIfPresent = (file) => readFile(file, 'utf8').catch(error => { if (error.code === 'ENOENT')
|
|
10
|
+
return null; throw error; });
|
|
11
|
+
/** One folder per skill under `skills/`, each holding its SKILL.md, so a new skill needs no change here. */
|
|
12
|
+
export async function packagedSkills(directory = paths.skillsDirectory) {
|
|
13
|
+
const names = (await readdir(directory, { withFileTypes: true }).catch(() => []))
|
|
14
|
+
.filter(entry => entry.isDirectory()).map(entry => entry.name);
|
|
15
|
+
const present = await Promise.all(names.map(async (name) => (await readIfPresent(skillSource(name, directory)) === null ? null : name)));
|
|
16
|
+
return present.filter((name) => name !== null).sort();
|
|
17
|
+
}
|
|
9
18
|
const normalize = (text) => text.replace(/\r\n/g, '\n');
|
|
10
19
|
/** Identity of a skill file's content, independent of line endings (a Windows checkout may turn LF into CRLF). */
|
|
11
20
|
export const skillHash = (text) => createHash('sha256').update(normalize(text)).digest('hex');
|
|
12
|
-
/** sha256 of every SKILL.md this project has shipped (skills/known-versions.json). A file that matches one is an old packaged copy, not the owner's edit. */
|
|
21
|
+
/** sha256 of every SKILL.md this project has shipped, listed per skill (skills/known-versions.json). A file that matches one is an old packaged copy, not the owner's edit. */
|
|
13
22
|
export async function knownSkillHashes(file = path.join(paths.skillsDirectory, 'known-versions.json')) {
|
|
14
23
|
try {
|
|
15
24
|
const parsed = JSON.parse(await readFile(file, 'utf8'));
|
|
16
|
-
|
|
25
|
+
const known = {};
|
|
26
|
+
for (const [name, entries] of Object.entries(parsed.skills ?? {}))
|
|
27
|
+
known[name] = new Set((entries ?? []).map(entry => entry.sha256).filter((value) => typeof value === 'string' && /^[0-9a-f]{64}$/.test(value)));
|
|
28
|
+
return known;
|
|
17
29
|
}
|
|
18
30
|
catch {
|
|
19
|
-
return
|
|
31
|
+
return {};
|
|
20
32
|
}
|
|
21
33
|
}
|
|
34
|
+
const hashesFor = (known, skill) => known[skill] ?? new Set();
|
|
22
35
|
function classify(existing, content, known) {
|
|
23
36
|
if (existing === null)
|
|
24
37
|
return 'missing';
|
|
@@ -27,27 +40,35 @@ function classify(existing, content, known) {
|
|
|
27
40
|
return 'installed';
|
|
28
41
|
return known.has(hash) ? 'outdated' : 'different';
|
|
29
42
|
}
|
|
30
|
-
|
|
31
|
-
|
|
43
|
+
async function skillSources(skills) {
|
|
44
|
+
const names = skills ?? await packagedSkills();
|
|
45
|
+
if (!names.length)
|
|
46
|
+
throw new Error('No skill was selected and this bridge ships none (expected skills/<name>/SKILL.md)');
|
|
47
|
+
return Promise.all(names.map(async (name) => ({ name, content: await readFile(skillSource(name), 'utf8') })));
|
|
48
|
+
}
|
|
49
|
+
/** One fingerprint for the packaged set, so "a newer skill ships" means any skill in it changed. */
|
|
50
|
+
const setHash = (sources) => skillHash(sources.map(source => `${source.name}:${skillHash(source.content)}`).join('\n'));
|
|
32
51
|
/**
|
|
33
|
-
* Where
|
|
34
|
-
* (an older version this project shipped, safe to replace), `different` (not any shipped version: the owner's edit) or `missing`. Reads only.
|
|
52
|
+
* Where every packaged skill is (or would be) installed for the current user and what is there: `installed` (this version),
|
|
53
|
+
* `outdated` (an older version this project shipped, safe to replace), `different` (not any shipped version: the owner's edit) or `missing`. Reads only.
|
|
35
54
|
*/
|
|
36
55
|
export async function skillStatus(options = {}) {
|
|
37
|
-
const
|
|
56
|
+
const sources = await skillSources(options.skills);
|
|
38
57
|
const known = options.known ?? await knownSkillHashes();
|
|
39
58
|
const root = options.userHome ?? homedir();
|
|
40
|
-
const result = { currentHash:
|
|
59
|
+
const result = { currentHash: setHash(sources), codex: [], claude: [] };
|
|
41
60
|
for (const client of ['codex', 'claude']) {
|
|
42
|
-
const
|
|
43
|
-
|
|
61
|
+
for (const source of sources) {
|
|
62
|
+
const file = skillFile(root, client, source.name);
|
|
63
|
+
result[client].push({ skill: source.name, file, state: classify(await readIfPresent(file), source.content, hashesFor(known, source.name)) });
|
|
64
|
+
}
|
|
44
65
|
}
|
|
45
66
|
return result;
|
|
46
67
|
}
|
|
47
68
|
export async function installSkill(options) {
|
|
48
69
|
if (!['codex', 'claude', 'both'].includes(options.client))
|
|
49
70
|
throw new Error('--client must be codex, claude or both');
|
|
50
|
-
const
|
|
71
|
+
const sources = await skillSources(options.skills);
|
|
51
72
|
const known = options.known ?? await knownSkillHashes();
|
|
52
73
|
let root = options.userHome ?? homedir();
|
|
53
74
|
if (options.project !== undefined) {
|
|
@@ -58,35 +79,39 @@ export async function installSkill(options) {
|
|
|
58
79
|
throw new Error('--project must be a directory');
|
|
59
80
|
}
|
|
60
81
|
const clients = options.client === 'both' ? ['codex', 'claude'] : [options.client];
|
|
61
|
-
const destinations = clients.map(client => skillFile(root, client));
|
|
62
82
|
const planned = [];
|
|
83
|
+
const writes = [];
|
|
63
84
|
// Decide for every destination before writing any. A file that is not one of our shipped versions is never overwritten.
|
|
64
|
-
for (const
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
85
|
+
for (const client of clients) {
|
|
86
|
+
for (const source of sources) {
|
|
87
|
+
const filename = skillFile(root, client, source.name);
|
|
88
|
+
const state = classify(await readIfPresent(filename), source.content, hashesFor(known, source.name));
|
|
89
|
+
if (state === 'installed')
|
|
90
|
+
planned.push({ skill: source.name, filename, unchanged: true });
|
|
91
|
+
else if (state === 'missing') {
|
|
92
|
+
planned.push({ skill: source.name, filename, unchanged: false });
|
|
93
|
+
writes.push({ filename, content: source.content, replace: false });
|
|
94
|
+
}
|
|
95
|
+
else if (state === 'outdated') {
|
|
96
|
+
if (!options.update)
|
|
97
|
+
throw new Error(`Existing skill is an older packaged version; run again with --update to replace it: ${filename}`);
|
|
98
|
+
planned.push({ skill: source.name, filename, unchanged: false, updated: true });
|
|
99
|
+
writes.push({ filename, content: source.content, replace: true });
|
|
100
|
+
}
|
|
101
|
+
else
|
|
102
|
+
throw new Error(`Existing skill differs from every packaged version (customized); review it or remove it first: ${filename}`);
|
|
74
103
|
}
|
|
75
|
-
else
|
|
76
|
-
throw new Error(`Existing skill differs from every packaged version (customized); review it or remove it first: ${filename}`);
|
|
77
104
|
}
|
|
78
|
-
for (const item of
|
|
79
|
-
if (item.unchanged)
|
|
80
|
-
continue;
|
|
105
|
+
for (const item of writes) {
|
|
81
106
|
await mkdir(path.dirname(item.filename), { recursive: true });
|
|
82
|
-
if (!item.
|
|
83
|
-
await writeFile(item.filename, content, { flag: 'wx' });
|
|
107
|
+
if (!item.replace) {
|
|
108
|
+
await writeFile(item.filename, item.content, { flag: 'wx' });
|
|
84
109
|
continue;
|
|
85
110
|
}
|
|
86
111
|
// Replace atomically so a crash never leaves a half-written skill.
|
|
87
112
|
const temporary = `${item.filename}.${process.pid}.tmp`;
|
|
88
113
|
try {
|
|
89
|
-
await writeFile(temporary, content, { flag: 'wx' });
|
|
114
|
+
await writeFile(temporary, item.content, { flag: 'wx' });
|
|
90
115
|
await rename(temporary, item.filename);
|
|
91
116
|
}
|
|
92
117
|
finally {
|
|
@@ -96,8 +121,10 @@ export async function installSkill(options) {
|
|
|
96
121
|
return planned;
|
|
97
122
|
}
|
|
98
123
|
async function main(args) {
|
|
124
|
+
const skills = await packagedSkills();
|
|
99
125
|
if (args.includes('--help')) {
|
|
100
|
-
console.log('npm run skill:install -- --client codex|claude|both [--project ABSOLUTE_PROJECT_PATH] [--update]');
|
|
126
|
+
console.log('npm run skill:install -- --client codex|claude|both [--skill <name>] [--project ABSOLUTE_PROJECT_PATH] [--update]');
|
|
127
|
+
console.log(`Skills packaged with this bridge: ${skills.join(', ') || '(none)'}. Without --skill, every one of them is installed.`);
|
|
101
128
|
console.log('Without --project, installs for the current user. Does not change MCP configuration or permissions.');
|
|
102
129
|
console.log('--update replaces an installed copy only if it is an older version shipped by this project; a customized file is never overwritten.');
|
|
103
130
|
return;
|
|
@@ -105,12 +132,15 @@ async function main(args) {
|
|
|
105
132
|
let client;
|
|
106
133
|
let project;
|
|
107
134
|
let update = false;
|
|
135
|
+
const selected = [];
|
|
108
136
|
for (let index = 0; index < args.length; index++) {
|
|
109
137
|
const flag = args[index];
|
|
110
138
|
if (flag === '--client' && client === undefined && args[index + 1])
|
|
111
139
|
client = args[++index];
|
|
112
140
|
else if (flag === '--project' && project === undefined && args[index + 1])
|
|
113
141
|
project = args[++index];
|
|
142
|
+
else if (flag === '--skill' && !selected.includes(args[index + 1] ?? '') && args[index + 1])
|
|
143
|
+
selected.push(args[++index]);
|
|
114
144
|
else if (flag === '--update' && !update)
|
|
115
145
|
update = true;
|
|
116
146
|
else
|
|
@@ -118,7 +148,10 @@ async function main(args) {
|
|
|
118
148
|
}
|
|
119
149
|
if (!client)
|
|
120
150
|
throw new Error('Choose --client codex, claude or both');
|
|
121
|
-
|
|
151
|
+
for (const name of selected)
|
|
152
|
+
if (!skills.includes(name))
|
|
153
|
+
throw new Error(`--skill must be one of the packaged skills: ${skills.join(', ')}`);
|
|
154
|
+
const installed = await installSkill({ client, project, update, skills: selected.length ? selected : undefined });
|
|
122
155
|
for (const item of installed)
|
|
123
156
|
console.log(`[skill] ${item.unchanged ? 'Already installed' : item.updated ? 'Updated' : 'Installed'}: ${item.filename}`);
|
|
124
157
|
console.log('[skill] Reopen the client if the skill does not appear. MCP connection and workspace permissions are configured separately.');
|
package/dist/src/cli.js
CHANGED
|
@@ -32,9 +32,9 @@ Usage: ${BIN} [command] [options]
|
|
|
32
32
|
Manage the directories agents may edit (restart the server afterwards)
|
|
33
33
|
delegation on|off|status
|
|
34
34
|
Switch delegation to Antigravity on or off immediately (no restart)
|
|
35
|
-
skill install --client codex|claude|both [--project <dir>] [--update]
|
|
36
|
-
Install the
|
|
37
|
-
packaged copy
|
|
35
|
+
skill install --client codex|claude|both [--skill <name>] [--project <dir>] [--update]
|
|
36
|
+
Install the skills this bridge ships (first run also offers them once). Repeat --skill for one of
|
|
37
|
+
them; --update replaces an older packaged copy, and a file you edited yourself is never overwritten
|
|
38
38
|
setup codex|claude|both [--port N] [--shell powershell|cmd|bash]
|
|
39
39
|
Print the exact client configuration (changes nothing)
|
|
40
40
|
setup codex|both --apply [--yes] [--tool-timeout N] [--port N]
|
package/dist/src/http.js
CHANGED
|
@@ -15,8 +15,8 @@ export function configuredPort(value = process.env.PORT) {
|
|
|
15
15
|
export async function startHttpBridge(options) {
|
|
16
16
|
let stopping = false;
|
|
17
17
|
let baseUrl = '';
|
|
18
|
-
const ui = { manager: options.manager, environment: options.environment, mcpUrl: () => `${baseUrl}/mcp`, startedAt: Date.now(), requestShutdown: options.requestShutdown };
|
|
19
|
-
const handler = createMcpHandler(() => createServer(options.manager, options.environment), { maxRequestBodySize: 131072, maxSubscriptions: 16 });
|
|
18
|
+
const ui = { manager: options.manager, missions: options.missions, environment: options.environment, mcpUrl: () => `${baseUrl}/mcp`, startedAt: Date.now(), requestShutdown: options.requestShutdown };
|
|
19
|
+
const handler = createMcpHandler(() => createServer(options.manager, options.environment, options.missions), { maxRequestBodySize: 131072, maxSubscriptions: 16 });
|
|
20
20
|
const mcp = toNodeHandler(handler, { maxRequestBodySize: 131072 });
|
|
21
21
|
const server = createHttpServer((req, res) => {
|
|
22
22
|
const reply = (code, body) => {
|
|
@@ -49,7 +49,7 @@ export async function startHttpBridge(options) {
|
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
51
|
if (req.url === '/health' && req.method === 'GET') {
|
|
52
|
-
reply(200, { status: 'ok', transport: 'streamable-http', mcp_endpoint: `${baseUrl}/mcp`, ...options.manager.overview() });
|
|
52
|
+
reply(200, { status: 'ok', transport: 'streamable-http', mcp_endpoint: `${baseUrl}/mcp`, ...options.manager.overview(), missions: options.missions.overview() });
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
if (req.url !== '/mcp') {
|
package/dist/src/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createRuntime } from './runtime.js';
|
|
|
5
5
|
import { configuredPort, startHttpBridge } from './http.js';
|
|
6
6
|
async function main() {
|
|
7
7
|
const port = configuredPort();
|
|
8
|
-
const { manager, environment } = await createRuntime();
|
|
8
|
+
const { manager, missions, environment } = await createRuntime();
|
|
9
9
|
let stopping = false;
|
|
10
10
|
let closeBridge;
|
|
11
11
|
const shutdown = async () => {
|
|
@@ -26,7 +26,7 @@ async function main() {
|
|
|
26
26
|
process.exitCode = 1;
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
|
-
const bridge = await startHttpBridge({ manager, environment, port, requestShutdown: shutdown });
|
|
29
|
+
const bridge = await startHttpBridge({ manager, missions, environment, port, requestShutdown: shutdown });
|
|
30
30
|
closeBridge = bridge.close;
|
|
31
31
|
console.error(`[bridge] HTTP server: ${bridge.baseUrl}`);
|
|
32
32
|
console.error(`[bridge] MCP endpoint: ${bridge.mcpUrl}`);
|