@bridge_gpt/mcp-server 0.2.56 → 0.2.58
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 +23 -6
- package/build/codex-bridge-profile.js +155 -0
- package/build/codex-home.js +7 -0
- package/build/codex-launch-scope.js +23 -7
- package/build/commands.generated.js +2 -2
- package/build/cursor-agent-progress.js +72 -0
- package/build/doctor.js +8 -1
- package/build/epic-implementer-bin.js +4 -4
- package/build/index.js +98 -94
- package/build/install-bridge.js +48 -1
- package/build/install-doctor.js +1 -1
- package/build/mcp-host-config.js +4 -0
- package/build/mcp-host-targets.js +3 -0
- package/build/pipelines.generated.js +4 -4
- package/build/readme.generated.js +1 -1
- package/build/start-tickets.js +95 -24
- package/build/version.generated.js +3 -3
- package/package.json +1 -1
- package/pipelines/implement-ticket.json +2 -2
- package/pipelines/review-ticket.json +1 -1
package/README.md
CHANGED
|
@@ -242,6 +242,23 @@ npx -y @bridge_gpt/mcp-server doctor
|
|
|
242
242
|
`doctor` is strictly read-only. It reports what it found — configs, registrations,
|
|
243
243
|
credential availability, prerequisites — and changes nothing.
|
|
244
244
|
|
|
245
|
+
**Codex approval profile.** When selecting Codex during interactive `install`,
|
|
246
|
+
press Enter to accept the default Yes at the profile prompt, or answer `n`/`no`
|
|
247
|
+
to decline. Pass `--tools=codex --codex-profile=on` in a scripted install. Bridge
|
|
248
|
+
creates `$CODEX_HOME/bridge.config.toml` (default `~/.codex/bridge.config.toml`)
|
|
249
|
+
with approval settings for the tools used by its review and implementation
|
|
250
|
+
workflows. Bridge-spawned Codex workers then select it automatically, including
|
|
251
|
+
the fresh implementation session after review. Pass `--codex-profile=off` to
|
|
252
|
+
decline; the installer leaves any existing profile content intact. On package
|
|
253
|
+
upgrade, the next Bridge-spawned worker refreshes an older, unmodified managed
|
|
254
|
+
profile before launch. If a profile
|
|
255
|
+
already contains user-authored or conflicting content, the installer leaves it
|
|
256
|
+
untouched and reports that workers cannot use it. Run `doctor --agent codex` to
|
|
257
|
+
inspect the current status. To use the profile in a Codex session you open
|
|
258
|
+
yourself, run `codex --profile bridge`. Bridge `--auto` controls recipe gates;
|
|
259
|
+
Codex MCP tool approvals are a separate setting. The profile does not change
|
|
260
|
+
global `sandbox_mode` or `approval_policy`.
|
|
261
|
+
|
|
245
262
|
</details>
|
|
246
263
|
|
|
247
264
|
<details>
|
|
@@ -351,7 +368,7 @@ later boot.
|
|
|
351
368
|
"mcpServers": {
|
|
352
369
|
"bridge": {
|
|
353
370
|
"command": "npx",
|
|
354
|
-
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
371
|
+
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.58", "serve"],
|
|
355
372
|
"env": {
|
|
356
373
|
"BAPI_BASE_URL": "https://bridgegpt-api.com",
|
|
357
374
|
"BAPI_REPO_NAME": "your-repo",
|
|
@@ -373,7 +390,7 @@ later boot.
|
|
|
373
390
|
"bridge": {
|
|
374
391
|
"type": "stdio",
|
|
375
392
|
"command": "npx",
|
|
376
|
-
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
393
|
+
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.58", "serve"],
|
|
377
394
|
"env": {
|
|
378
395
|
"BAPI_BASE_URL": "https://bridgegpt-api.com",
|
|
379
396
|
"BAPI_REPO_NAME": "your-repo",
|
|
@@ -395,7 +412,7 @@ later boot.
|
|
|
395
412
|
"bridge": {
|
|
396
413
|
"type": "stdio",
|
|
397
414
|
"command": "npx",
|
|
398
|
-
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
415
|
+
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.58", "serve"],
|
|
399
416
|
"env": {
|
|
400
417
|
"BAPI_BASE_URL": "https://bridgegpt-api.com",
|
|
401
418
|
"BAPI_REPO_NAME": "your-repo",
|
|
@@ -418,7 +435,7 @@ Windsurf only supports global MCP configuration.
|
|
|
418
435
|
"mcpServers": {
|
|
419
436
|
"bridge": {
|
|
420
437
|
"command": "npx",
|
|
421
|
-
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
438
|
+
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.58", "serve"],
|
|
422
439
|
"env": {
|
|
423
440
|
"BAPI_BASE_URL": "https://bridgegpt-api.com",
|
|
424
441
|
"BAPI_REPO_NAME": "your-repo",
|
|
@@ -437,7 +454,7 @@ Windsurf only supports global MCP configuration.
|
|
|
437
454
|
```toml
|
|
438
455
|
[mcp_servers.bridge]
|
|
439
456
|
command = "npx"
|
|
440
|
-
args = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
457
|
+
args = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.58", "serve"]
|
|
441
458
|
|
|
442
459
|
[mcp_servers.bridge.env]
|
|
443
460
|
BAPI_BASE_URL = "https://bridgegpt-api.com"
|
|
@@ -1555,7 +1572,7 @@ The authoritative tool catalog covers **75 tools** (enumerated below). What's ac
|
|
|
1555
1572
|
- **Jira tickets** — `get_tickets`, `get_ticket`, `create_ticket`, `update_ticket_description`, `add_comment`, `get_comments`
|
|
1556
1573
|
- **Attachments** — `attachment` (operations: `upload`, `download`, `list`)
|
|
1557
1574
|
- **AI generation (request/get)** — `request_plan_generation`/`get_plan`, `create_doc`/`get_doc` (design docs by `doc_type`: tdd/architecture/fsd/prd, where `architecture` is an alias of `tdd`), `request_ticket_review` (writes both `get_clarifying_questions` and `get_ticket_critique`), `request_reimplement_context`/`get_reimplement_context`, `request_council`/`get_council`, `request_deep_research`/`get_deep_research`
|
|
1558
|
-
- **Bounded waits** — `wait_for_ticket_review`, `wait_for_ci_checks`. Each
|
|
1575
|
+
- **Bounded waits** — `wait_for_ticket_review`, `wait_for_ci_checks`. Each returns `state: "ready" | "pending" | "error"`; review waits default to 20 s and CI waits default to 240 s (both accept up to 240 s explicitly). `pending` is a normal, resumable result you call again on, never a failure. They exist so an agent can wait without a foreground `sleep` and without binding a long server-side operation to the 900 s MCP client deadline. `wait_for_ci_checks` reports only that checks are **terminal** — deciding pass or fail stays with the caller.
|
|
1559
1576
|
- **Other AI** — `second_opinion`, `generate_image`, `generate_decision_page`, `visual_diff` (deterministic pixel diff of a rendered URL vs a design comp)
|
|
1560
1577
|
- **Ticket lifecycle** — `track_ticket`, `update_ticket_state`, `get_ticket_state`, `get_ticket_state_tree` (live repo-wide lifecycle + dependency tree; read-only, no mutation parameter)
|
|
1561
1578
|
- **Jira status** — `update_jira_status` (pass `status: "auto"` to resolve the configured post-PR status server-side)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/** Secret-free, opt-in approval settings for Bridge-spawned Codex workers. */
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { codexBridgeTableNames } from "./mcp-host-config.js";
|
|
5
|
+
import { MCP_SERVER_NAME } from "./mcp-identity.js";
|
|
6
|
+
export { codexHome } from "./codex-home.js";
|
|
7
|
+
export const BRIDGE_CODEX_PROFILE = "bridge";
|
|
8
|
+
const PROFILE_FILE = "bridge.config.toml";
|
|
9
|
+
const STATE_FILE = "bridge.profile-state.json";
|
|
10
|
+
const MARKER = "# Bridge-managed Codex approval profile (BAPI-1214)";
|
|
11
|
+
/** Tools called by review-and-start, review-and-implement, and implement-ticket. */
|
|
12
|
+
export const BRIDGE_WORKFLOW_TOOLS = [
|
|
13
|
+
"ping", "get_ticket", "get_pipeline_recipe", "request_ticket_review",
|
|
14
|
+
"wait_for_ticket_review", "fresh_base", "generate_decision_page",
|
|
15
|
+
"capture_review_decisions", "request_plan_generation", "get_plan",
|
|
16
|
+
"attachment",
|
|
17
|
+
"get_phase_context", "record_phase_result", "create_pull_request",
|
|
18
|
+
"update_jira_status", "config_field", "resolve_ci_checks", "poll_ci_checks",
|
|
19
|
+
"wait_for_ci_checks",
|
|
20
|
+
];
|
|
21
|
+
export function codexProfilePaths(home) {
|
|
22
|
+
return {
|
|
23
|
+
profile: path.join(home, PROFILE_FILE),
|
|
24
|
+
state: path.join(home, STATE_FILE),
|
|
25
|
+
config: path.join(home, "config.toml"),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function renderBridgeCodexProfile(key) {
|
|
29
|
+
return [
|
|
30
|
+
MARKER,
|
|
31
|
+
`# Applies only when selected with codex --profile ${BRIDGE_CODEX_PROFILE}.`,
|
|
32
|
+
...BRIDGE_WORKFLOW_TOOLS.flatMap((tool) => [
|
|
33
|
+
`[mcp_servers.${key}.tools.${tool}]`,
|
|
34
|
+
'approval_mode = "auto"',
|
|
35
|
+
"",
|
|
36
|
+
]),
|
|
37
|
+
].join("\n") + "\n";
|
|
38
|
+
}
|
|
39
|
+
function fingerprint(value) {
|
|
40
|
+
return createHash("sha256").update(value).digest("hex");
|
|
41
|
+
}
|
|
42
|
+
async function optionalRead(readFile, filePath) {
|
|
43
|
+
try {
|
|
44
|
+
return await readFile(filePath);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return error.code === "ENOENT" ? null : undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Only a matching managed file and explicit enabled state can activate workers. */
|
|
51
|
+
export async function inspectBridgeCodexProfile(home, readFile) {
|
|
52
|
+
const paths = codexProfilePaths(home);
|
|
53
|
+
const [config, profile, state] = await Promise.all([
|
|
54
|
+
optionalRead(readFile, paths.config),
|
|
55
|
+
optionalRead(readFile, paths.profile),
|
|
56
|
+
optionalRead(readFile, paths.state),
|
|
57
|
+
]);
|
|
58
|
+
if (config === undefined || profile === undefined || state === undefined)
|
|
59
|
+
return { status: "unreadable" };
|
|
60
|
+
const names = codexBridgeTableNames(config ?? "");
|
|
61
|
+
if (names.length > 1)
|
|
62
|
+
return { status: "conflict" };
|
|
63
|
+
const key = names[0] ?? MCP_SERVER_NAME;
|
|
64
|
+
if (state === null)
|
|
65
|
+
return { status: profile === null ? "missing" : "conflict", key };
|
|
66
|
+
let parsed;
|
|
67
|
+
try {
|
|
68
|
+
parsed = JSON.parse(state);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return { status: "conflict", key };
|
|
72
|
+
}
|
|
73
|
+
if (!parsed || typeof parsed !== "object" || !("enabled" in parsed) || typeof parsed.enabled !== "boolean") {
|
|
74
|
+
return { status: "conflict", key };
|
|
75
|
+
}
|
|
76
|
+
if (!parsed.enabled)
|
|
77
|
+
return { status: "declined", key };
|
|
78
|
+
if (profile === null || !("hash" in parsed) || typeof parsed.hash !== "string")
|
|
79
|
+
return { status: "conflict", key };
|
|
80
|
+
if (fingerprint(profile) !== parsed.hash)
|
|
81
|
+
return { status: "conflict", key };
|
|
82
|
+
if (profile !== renderBridgeCodexProfile(key)) {
|
|
83
|
+
return { status: profile.startsWith(MARKER) ? "stale" : "conflict", key };
|
|
84
|
+
}
|
|
85
|
+
return { status: "active", key };
|
|
86
|
+
}
|
|
87
|
+
export async function configureBridgeCodexProfile(home, choice, deps) {
|
|
88
|
+
const paths = codexProfilePaths(home);
|
|
89
|
+
const current = await inspectBridgeCodexProfile(home, deps.readFile);
|
|
90
|
+
if (choice === "decline") {
|
|
91
|
+
const oldState = await optionalRead(deps.readFile, paths.state);
|
|
92
|
+
if (oldState === undefined)
|
|
93
|
+
return { status: "unreadable" };
|
|
94
|
+
let existingHash;
|
|
95
|
+
if (oldState !== null) {
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(oldState);
|
|
98
|
+
if (!parsed || typeof parsed.enabled !== "boolean")
|
|
99
|
+
return { status: "conflict" };
|
|
100
|
+
if (typeof parsed.hash === "string")
|
|
101
|
+
existingHash = parsed.hash;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return { status: "conflict" };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
await deps.mkdir(home, { recursive: true });
|
|
108
|
+
await deps.writeFile(paths.state, JSON.stringify({ enabled: false, ...(existingHash ? { hash: existingHash } : {}) }) + "\n");
|
|
109
|
+
return { status: "declined", key: current.key };
|
|
110
|
+
}
|
|
111
|
+
if (current.status === "unreadable")
|
|
112
|
+
return current;
|
|
113
|
+
const config = await optionalRead(deps.readFile, paths.config);
|
|
114
|
+
if (config === undefined)
|
|
115
|
+
return { status: "unreadable" };
|
|
116
|
+
const names = codexBridgeTableNames(config ?? "");
|
|
117
|
+
if (names.length > 1)
|
|
118
|
+
return { status: "conflict" };
|
|
119
|
+
const key = names[0] ?? MCP_SERVER_NAME;
|
|
120
|
+
const existing = await optionalRead(deps.readFile, paths.profile);
|
|
121
|
+
const oldState = await optionalRead(deps.readFile, paths.state);
|
|
122
|
+
const desired = renderBridgeCodexProfile(key);
|
|
123
|
+
if (existing === undefined || oldState === undefined)
|
|
124
|
+
return { status: "unreadable", key };
|
|
125
|
+
if (oldState !== null) {
|
|
126
|
+
try {
|
|
127
|
+
const parsed = JSON.parse(oldState);
|
|
128
|
+
if (!parsed || typeof parsed.enabled !== "boolean" ||
|
|
129
|
+
(parsed.enabled && (typeof parsed.hash !== "string" || existing === null || parsed.hash !== fingerprint(existing)))) {
|
|
130
|
+
return { status: "conflict", key };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return { status: "conflict", key };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Existing user content is never modified or implicitly adopted.
|
|
138
|
+
if (existing !== null && existing !== desired) {
|
|
139
|
+
let managed = false;
|
|
140
|
+
if (oldState !== null && existing.startsWith(MARKER)) {
|
|
141
|
+
try {
|
|
142
|
+
const parsed = JSON.parse(oldState);
|
|
143
|
+
managed = typeof parsed?.enabled === "boolean" && parsed?.hash === fingerprint(existing);
|
|
144
|
+
}
|
|
145
|
+
catch { /* malformed state cannot authorize a rewrite */ }
|
|
146
|
+
}
|
|
147
|
+
if (!managed)
|
|
148
|
+
return { status: "conflict", key };
|
|
149
|
+
}
|
|
150
|
+
await deps.mkdir(home, { recursive: true });
|
|
151
|
+
if (existing !== desired)
|
|
152
|
+
await deps.writeFile(paths.profile, desired);
|
|
153
|
+
await deps.writeFile(paths.state, JSON.stringify({ enabled: true, hash: fingerprint(desired) }) + "\n");
|
|
154
|
+
return { status: "active", key };
|
|
155
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
/** Codex's user config root; only an absolute CODEX_HOME is accepted. */
|
|
4
|
+
export function codexHome(env, homedir = os.homedir()) {
|
|
5
|
+
const override = env.CODEX_HOME?.trim();
|
|
6
|
+
return override && path.isAbsolute(override) ? override : path.join(homedir, ".codex");
|
|
7
|
+
}
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
/** Per-process Codex MCP scope, derived from the same manifest and shim as worktree provisioning. */
|
|
2
2
|
import { readBridgeConfig } from "./bridge-config.js";
|
|
3
|
-
import os from "node:os";
|
|
4
3
|
import { codexBridgeTableNames } from "./mcp-host-config.js";
|
|
5
|
-
import {
|
|
4
|
+
import { codexHome } from "./codex-home.js";
|
|
5
|
+
import { codexProfilePaths, configureBridgeCodexProfile, inspectBridgeCodexProfile, BRIDGE_CODEX_PROFILE } from "./codex-bridge-profile.js";
|
|
6
6
|
import { buildMcpServerEntriesForManifest, normalizeWorktreePathForRegistration, } from "./mcp-provisioning.js";
|
|
7
7
|
import { MCP_SERVER_NAME } from "./mcp-identity.js";
|
|
8
|
-
function tomlEntry(entry) {
|
|
9
|
-
return `{ command = ${JSON.stringify(entry.command)}, args = [${entry.args.map((arg) => JSON.stringify(arg)).join(", ")}] }`;
|
|
10
|
-
}
|
|
11
8
|
/** A missing Bridge target is a launch failure, never a fallback to user-scoped config. */
|
|
12
|
-
export async function resolveCodexLaunchScope(root, deps, codexConfigPath =
|
|
9
|
+
export async function resolveCodexLaunchScope(root, deps, codexConfigPath = codexProfilePaths(codexHome(deps.env ?? process.env, deps.homedir?.())).config) {
|
|
13
10
|
const normalized = normalizeWorktreePathForRegistration(root, deps);
|
|
14
11
|
if (!normalized.ok)
|
|
15
12
|
return { ok: false, error: "Codex MCP scope unavailable: invalid repository path." };
|
|
@@ -41,9 +38,28 @@ export async function resolveCodexLaunchScope(root, deps, codexConfigPath = reso
|
|
|
41
38
|
return { ok: false, error: "Codex MCP scope unavailable: duplicate Bridge registrations in Codex host config." };
|
|
42
39
|
}
|
|
43
40
|
const bridgeKey = registeredNames[0] ?? MCP_SERVER_NAME;
|
|
41
|
+
const home = codexHome(deps.env ?? process.env, deps.homedir?.());
|
|
42
|
+
let profile = await inspectBridgeCodexProfile(home, deps.readFile);
|
|
43
|
+
if (profile.status === "stale") {
|
|
44
|
+
try {
|
|
45
|
+
profile = await configureBridgeCodexProfile(home, "enable", deps);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { ok: false, error: "Codex MCP scope unavailable: could not refresh the managed Bridge approval profile." };
|
|
49
|
+
}
|
|
50
|
+
if (profile.status !== "active") {
|
|
51
|
+
return { ok: false, error: "Codex MCP scope unavailable: managed Bridge approval profile refresh failed." };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
44
54
|
const argv = ["-C", normalized.path];
|
|
55
|
+
if (profile.status === "active" && profile.key === bridgeKey)
|
|
56
|
+
argv.push("--profile", BRIDGE_CODEX_PROFILE);
|
|
45
57
|
for (const [name, entry] of Object.entries(built.entries)) {
|
|
46
|
-
|
|
58
|
+
const key = name === MCP_SERVER_NAME ? bridgeKey : name;
|
|
59
|
+
// Field-level overrides retain the profile's mcp_servers.<key>.tools policy.
|
|
60
|
+
argv.push("-c", `mcp_servers.${key}.command=${JSON.stringify(entry.command)}`);
|
|
61
|
+
argv.push("-c", `mcp_servers.${key}.args=${JSON.stringify(entry.args)}`);
|
|
62
|
+
argv.push("-c", `mcp_servers.${key}.enabled=true`);
|
|
47
63
|
}
|
|
48
64
|
return { ok: true, argv };
|
|
49
65
|
}
|