claude-codex-bridge 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.
- package/README.md +146 -0
- package/dist/claude-server.d.mts +1 -0
- package/dist/claude-server.mjs +250 -0
- package/dist/codex-server.d.mts +1 -0
- package/dist/codex-server.mjs +264 -0
- package/dist/prompt-builder-CQ2IGDTq.mjs +159 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# claude-codex-bridge
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/claude-codex-bridge)
|
|
4
|
+
[](https://www.npmjs.com/package/claude-codex-bridge)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
|
|
8
|
+
Bidirectional [MCP](https://modelcontextprotocol.io/) server bridge between **Claude Code** and **OpenAI Codex CLI**.
|
|
9
|
+
|
|
10
|
+
Let Claude and Codex work as partners — each can ask the other for help, review code, explain logic, and plan performance improvements.
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
┌──────────────┐ MCP ┌──────────────┐
|
|
14
|
+
│ │ ◄──────────────────► │ │
|
|
15
|
+
│ Claude Code │ claude-codex │ Codex CLI │
|
|
16
|
+
│ │ ◄───── bridge ─────► │ │
|
|
17
|
+
└──────────────┘ └──────────────┘
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Prerequisites
|
|
21
|
+
|
|
22
|
+
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) — installed and authenticated
|
|
23
|
+
- [Codex CLI](https://developers.openai.com/codex/cli/) — installed and authenticated
|
|
24
|
+
- [Node.js](https://nodejs.org) >= 18
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
No install needed — use directly via `npx`:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx -p claude-codex-bridge ccb-codex # Start the Codex server
|
|
32
|
+
npx -p claude-codex-bridge ccb-claude # Start the Claude server
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Setup
|
|
36
|
+
|
|
37
|
+
### Automatic (recommended)
|
|
38
|
+
|
|
39
|
+
Run `/setup` inside Claude Code to automatically configure both directions:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
/setup # Set up both Claude Code and Codex
|
|
43
|
+
/setup claude # Only set up Claude Code → Codex
|
|
44
|
+
/setup codex # Only set up Codex → Claude
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or, if you don't have the repo cloned, just ask Claude Code:
|
|
48
|
+
|
|
49
|
+
> Please help me set up claude-codex-bridge MCP for both Claude Code and Codex according to https://github.com/Dunqing/claude-codex-bridge/blob/main/.claude/skills/setup/SKILL.md
|
|
50
|
+
|
|
51
|
+
### Manual
|
|
52
|
+
|
|
53
|
+
<details>
|
|
54
|
+
<summary><strong>Claude Code → Codex</strong> (let Claude call Codex)</summary>
|
|
55
|
+
|
|
56
|
+
Add to your Claude Code MCP config:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
claude mcp add codex -- npx -p claude-codex-bridge ccb-codex
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Or add to `.mcp.json` in your project:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"codex": {
|
|
68
|
+
"type": "stdio",
|
|
69
|
+
"command": "npx",
|
|
70
|
+
"args": ["-p", "claude-codex-bridge", "ccb-codex"]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
</details>
|
|
77
|
+
|
|
78
|
+
<details>
|
|
79
|
+
<summary><strong>Codex → Claude</strong> (let Codex call Claude)</summary>
|
|
80
|
+
|
|
81
|
+
Add to `~/.codex/config.toml`:
|
|
82
|
+
|
|
83
|
+
```toml
|
|
84
|
+
[mcp_servers.claude]
|
|
85
|
+
command = "npx"
|
|
86
|
+
args = ["-p", "claude-codex-bridge", "ccb-claude"]
|
|
87
|
+
tool_timeout_sec = 300
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
</details>
|
|
91
|
+
|
|
92
|
+
## Tools
|
|
93
|
+
|
|
94
|
+
### `ccb-codex` — Claude calls Codex
|
|
95
|
+
|
|
96
|
+
| Tool | Description |
|
|
97
|
+
|------|-------------|
|
|
98
|
+
| `codex_query` | Ask Codex a question or give it a task |
|
|
99
|
+
| `codex_review_code` | Ask Codex to review code changes |
|
|
100
|
+
| `codex_review_plan` | Ask Codex to critique an implementation plan |
|
|
101
|
+
| `codex_explain_code` | Ask Codex to explain code / logic / architecture |
|
|
102
|
+
| `codex_plan_perf` | Ask Codex to plan performance improvements |
|
|
103
|
+
| `codex_implement` | Ask Codex to write or modify code |
|
|
104
|
+
|
|
105
|
+
### `ccb-claude` — Codex calls Claude
|
|
106
|
+
|
|
107
|
+
| Tool | Description |
|
|
108
|
+
|------|-------------|
|
|
109
|
+
| `claude_query` | Ask Claude a question or give it a task |
|
|
110
|
+
| `claude_review_code` | Ask Claude to review code changes |
|
|
111
|
+
| `claude_review_plan` | Ask Claude to critique an implementation plan |
|
|
112
|
+
| `claude_explain_code` | Ask Claude to explain code / logic / architecture |
|
|
113
|
+
| `claude_plan_perf` | Ask Claude to plan performance improvements |
|
|
114
|
+
| `claude_implement` | Ask Claude to write or modify code |
|
|
115
|
+
|
|
116
|
+
## Configuration
|
|
117
|
+
|
|
118
|
+
| Variable | Description | Default |
|
|
119
|
+
|----------|-------------|---------|
|
|
120
|
+
| `BRIDGE_TIMEOUT_MS` | Subprocess timeout in milliseconds | `300000` (5 min) |
|
|
121
|
+
| `BRIDGE_DEBUG` | Enable debug logging to stderr | — |
|
|
122
|
+
| `BRIDGE_DEPTH` | Current recursion depth (set automatically) | `0` |
|
|
123
|
+
|
|
124
|
+
### Anti-Recursion Guard
|
|
125
|
+
|
|
126
|
+
The bridge automatically prevents infinite loops. If Claude calls Codex which tries to call Claude again, the second call is blocked (`BRIDGE_DEPTH >= 2`).
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
git clone https://github.com/Dunqing/claude-codex-bridge.git
|
|
132
|
+
cd claude-codex-bridge
|
|
133
|
+
pnpm install
|
|
134
|
+
|
|
135
|
+
pnpm build # Compile to dist/
|
|
136
|
+
pnpm test # Run tests
|
|
137
|
+
pnpm lint # Lint and type check
|
|
138
|
+
|
|
139
|
+
# Dev mode (no build needed)
|
|
140
|
+
pnpm dev:codex-server
|
|
141
|
+
pnpm dev:claude-server
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { i as logger, n as buildPlanPerfPrompt, r as execCommand, t as buildExplainCodePrompt } from "./prompt-builder-CQ2IGDTq.mjs";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
//#region src/lib/claude-output-parser.ts
|
|
8
|
+
/**
|
|
9
|
+
* Parses the JSON output from `claude -p --output-format json`.
|
|
10
|
+
* The output is a single JSON object with a `result` field containing
|
|
11
|
+
* the response content.
|
|
12
|
+
*/
|
|
13
|
+
function parseClaudeOutput(jsonOutput) {
|
|
14
|
+
const result = {
|
|
15
|
+
resultText: "",
|
|
16
|
+
sessionId: null,
|
|
17
|
+
costUsd: null,
|
|
18
|
+
errors: []
|
|
19
|
+
};
|
|
20
|
+
const trimmed = jsonOutput.trim();
|
|
21
|
+
if (!trimmed) {
|
|
22
|
+
result.errors.push("Empty output from Claude CLI");
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = JSON.parse(trimmed);
|
|
28
|
+
} catch {
|
|
29
|
+
logger.debug("Failed to parse Claude output as JSON, using raw text");
|
|
30
|
+
result.resultText = trimmed;
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
const resultField = parsed["result"];
|
|
34
|
+
if (typeof resultField === "string") result.resultText = resultField;
|
|
35
|
+
else if (resultField && typeof resultField === "object") {
|
|
36
|
+
const content = resultField["content"];
|
|
37
|
+
if (Array.isArray(content)) result.resultText = content.filter((c) => c["type"] === "text").map((c) => c["text"]).join("\n");
|
|
38
|
+
}
|
|
39
|
+
if (!result.resultText) {
|
|
40
|
+
const message = parsed["message"];
|
|
41
|
+
const text = parsed["text"];
|
|
42
|
+
const output = parsed["output"];
|
|
43
|
+
result.resultText = message ?? text ?? output ?? "";
|
|
44
|
+
}
|
|
45
|
+
if (!result.resultText && Object.keys(parsed).length > 0) result.resultText = JSON.stringify(parsed, null, 2);
|
|
46
|
+
result.sessionId = parsed["session_id"] ?? parsed["sessionId"] ?? null;
|
|
47
|
+
result.costUsd = parsed["cost_usd"] ?? parsed["costUsd"] ?? null;
|
|
48
|
+
const error = parsed["error"];
|
|
49
|
+
if (error) {
|
|
50
|
+
const msg = typeof error === "string" ? error : error["message"] ?? JSON.stringify(error);
|
|
51
|
+
result.errors.push(msg);
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/claude-server.ts
|
|
58
|
+
const server = new McpServer({
|
|
59
|
+
name: "claude-bridge",
|
|
60
|
+
version: "0.1.0"
|
|
61
|
+
});
|
|
62
|
+
async function runClaude(prompt, options = {}) {
|
|
63
|
+
const args = [
|
|
64
|
+
"-p",
|
|
65
|
+
"--output-format",
|
|
66
|
+
"json"
|
|
67
|
+
];
|
|
68
|
+
if (options.model) args.push("--model", options.model);
|
|
69
|
+
if (options.maxTurns) args.push("--max-turns", String(options.maxTurns));
|
|
70
|
+
if (options.allowedTools && options.allowedTools.length > 0) for (const tool of options.allowedTools) args.push("--allowedTools", tool);
|
|
71
|
+
args.push(prompt);
|
|
72
|
+
const result = await execCommand({
|
|
73
|
+
command: "claude",
|
|
74
|
+
args,
|
|
75
|
+
cwd: options.workingDirectory
|
|
76
|
+
});
|
|
77
|
+
if (result.timedOut) return {
|
|
78
|
+
resultText: "",
|
|
79
|
+
sessionId: null,
|
|
80
|
+
costUsd: null,
|
|
81
|
+
errors: ["Claude timed out. Increase BRIDGE_TIMEOUT_MS if needed."]
|
|
82
|
+
};
|
|
83
|
+
const parsed = parseClaudeOutput(result.stdout);
|
|
84
|
+
if (result.exitCode !== 0 && !parsed.resultText) {
|
|
85
|
+
const stderr = result.stderr.toLowerCase();
|
|
86
|
+
if (stderr.includes("api key") || stderr.includes("authentication") || stderr.includes("unauthorized")) parsed.errors.push("Claude API key issue. Ensure ANTHROPIC_API_KEY is set.");
|
|
87
|
+
else if (result.stderr.trim()) parsed.errors.push(result.stderr.trim());
|
|
88
|
+
}
|
|
89
|
+
return parsed;
|
|
90
|
+
}
|
|
91
|
+
function formatClaudeResponse(parsed) {
|
|
92
|
+
if (parsed.errors.length > 0 && !parsed.resultText) return {
|
|
93
|
+
content: [{
|
|
94
|
+
type: "text",
|
|
95
|
+
text: `Error: ${parsed.errors.join("; ")}`
|
|
96
|
+
}],
|
|
97
|
+
isError: true
|
|
98
|
+
};
|
|
99
|
+
return { content: [{
|
|
100
|
+
type: "text",
|
|
101
|
+
text: parsed.resultText
|
|
102
|
+
}] };
|
|
103
|
+
}
|
|
104
|
+
const READ_ONLY_TOOLS = [
|
|
105
|
+
"Read",
|
|
106
|
+
"Grep",
|
|
107
|
+
"Glob",
|
|
108
|
+
"Bash(git diff *)",
|
|
109
|
+
"Bash(git log *)",
|
|
110
|
+
"Bash(git show *)"
|
|
111
|
+
];
|
|
112
|
+
server.registerTool("claude_query", {
|
|
113
|
+
title: "Ask Claude",
|
|
114
|
+
description: "Ask Claude Code a question or give it a task. Claude will use its full toolset (file reading, code search, web search, etc.) to answer.",
|
|
115
|
+
inputSchema: {
|
|
116
|
+
prompt: z.string().describe("The question or task for Claude"),
|
|
117
|
+
workingDirectory: z.string().optional().describe("Working directory (defaults to server cwd)"),
|
|
118
|
+
model: z.string().optional().describe("Model alias: sonnet, opus, or haiku"),
|
|
119
|
+
maxTurns: z.number().optional().default(10).describe("Maximum agentic turns (limits runtime)"),
|
|
120
|
+
allowedTools: z.array(z.string()).optional().describe("Restrict tools (e.g., [\"Read\", \"Grep\", \"Glob\"])")
|
|
121
|
+
}
|
|
122
|
+
}, async ({ prompt, workingDirectory, model, maxTurns, allowedTools }) => {
|
|
123
|
+
return formatClaudeResponse(await runClaude(prompt, {
|
|
124
|
+
workingDirectory,
|
|
125
|
+
model,
|
|
126
|
+
maxTurns,
|
|
127
|
+
allowedTools
|
|
128
|
+
}));
|
|
129
|
+
});
|
|
130
|
+
server.registerTool("claude_review_code", {
|
|
131
|
+
title: "Claude Code Review",
|
|
132
|
+
description: "Ask Claude to review code for quality, bugs, security issues, and best practices. Provide a git diff range, file paths, or code snippet.",
|
|
133
|
+
inputSchema: {
|
|
134
|
+
target: z.string().describe("What to review: git diff range, file paths, or code snippet"),
|
|
135
|
+
focusAreas: z.string().optional().describe("Focus on: bugs, performance, style, security, etc."),
|
|
136
|
+
context: z.string().optional().describe("Additional context about the changes"),
|
|
137
|
+
workingDirectory: z.string().optional(),
|
|
138
|
+
maxTurns: z.number().optional().default(5)
|
|
139
|
+
}
|
|
140
|
+
}, async ({ target, focusAreas, context, workingDirectory, maxTurns }) => {
|
|
141
|
+
let prompt = `Review the following code changes. Provide specific, actionable feedback with line references.\n\nTarget: ${target}`;
|
|
142
|
+
if (focusAreas) prompt += `\n\nFocus areas: ${focusAreas}`;
|
|
143
|
+
if (context) prompt += `\n\nContext: ${context}`;
|
|
144
|
+
return formatClaudeResponse(await runClaude(prompt, {
|
|
145
|
+
workingDirectory,
|
|
146
|
+
maxTurns,
|
|
147
|
+
allowedTools: READ_ONLY_TOOLS
|
|
148
|
+
}));
|
|
149
|
+
});
|
|
150
|
+
server.registerTool("claude_review_plan", {
|
|
151
|
+
title: "Claude Plan Review",
|
|
152
|
+
description: "Ask Claude to critique an implementation plan. Claude will examine the actual codebase to validate feasibility and consistency with existing patterns.",
|
|
153
|
+
inputSchema: {
|
|
154
|
+
plan: z.string().describe("The implementation plan to review"),
|
|
155
|
+
codebasePath: z.string().optional().describe("Path to relevant codebase for context"),
|
|
156
|
+
constraints: z.string().optional().describe("Known constraints"),
|
|
157
|
+
workingDirectory: z.string().optional(),
|
|
158
|
+
maxTurns: z.number().optional().default(8)
|
|
159
|
+
}
|
|
160
|
+
}, async ({ plan, codebasePath, constraints, workingDirectory, maxTurns }) => {
|
|
161
|
+
let prompt = `Critique this implementation plan. Evaluate feasibility against the actual codebase, check consistency with existing patterns, identify gaps and risks.\n\nPlan:\n${plan}`;
|
|
162
|
+
if (codebasePath) prompt += `\n\nRelevant codebase: ${codebasePath}`;
|
|
163
|
+
if (constraints) prompt += `\n\nConstraints: ${constraints}`;
|
|
164
|
+
return formatClaudeResponse(await runClaude(prompt, {
|
|
165
|
+
workingDirectory,
|
|
166
|
+
maxTurns,
|
|
167
|
+
allowedTools: READ_ONLY_TOOLS
|
|
168
|
+
}));
|
|
169
|
+
});
|
|
170
|
+
server.registerTool("claude_explain_code", {
|
|
171
|
+
title: "Claude Explain Code",
|
|
172
|
+
description: "Ask Claude to deeply explain code, logic, or architecture. Claude will read the actual source files to give grounded explanations.",
|
|
173
|
+
inputSchema: {
|
|
174
|
+
target: z.string().describe("What to explain: file path, function name, module, or code snippet"),
|
|
175
|
+
depth: z.enum([
|
|
176
|
+
"overview",
|
|
177
|
+
"detailed",
|
|
178
|
+
"trace"
|
|
179
|
+
]).optional().default("detailed").describe("Depth: overview, detailed, or full execution trace"),
|
|
180
|
+
context: z.string().optional().describe("Additional context about the codebase"),
|
|
181
|
+
workingDirectory: z.string().optional(),
|
|
182
|
+
maxTurns: z.number().optional().default(8)
|
|
183
|
+
}
|
|
184
|
+
}, async ({ target, depth, context, workingDirectory, maxTurns }) => {
|
|
185
|
+
return formatClaudeResponse(await runClaude(buildExplainCodePrompt({
|
|
186
|
+
target,
|
|
187
|
+
depth,
|
|
188
|
+
context
|
|
189
|
+
}), {
|
|
190
|
+
workingDirectory,
|
|
191
|
+
maxTurns,
|
|
192
|
+
allowedTools: READ_ONLY_TOOLS
|
|
193
|
+
}));
|
|
194
|
+
});
|
|
195
|
+
server.registerTool("claude_plan_perf", {
|
|
196
|
+
title: "Claude Performance Plan",
|
|
197
|
+
description: "Ask Claude to analyze performance and create an improvement plan. Claude reads the actual code to identify bottlenecks and propose optimizations.",
|
|
198
|
+
inputSchema: {
|
|
199
|
+
target: z.string().describe("What to optimize: function, module, or pipeline path"),
|
|
200
|
+
metrics: z.array(z.enum([
|
|
201
|
+
"latency",
|
|
202
|
+
"throughput",
|
|
203
|
+
"memory",
|
|
204
|
+
"binary-size"
|
|
205
|
+
])).optional().describe("Performance metrics to focus on"),
|
|
206
|
+
constraints: z.string().optional().describe("Constraints"),
|
|
207
|
+
context: z.string().optional().describe("Additional context about usage patterns"),
|
|
208
|
+
workingDirectory: z.string().optional(),
|
|
209
|
+
maxTurns: z.number().optional().default(10)
|
|
210
|
+
}
|
|
211
|
+
}, async ({ target, metrics, constraints, context, workingDirectory, maxTurns }) => {
|
|
212
|
+
return formatClaudeResponse(await runClaude(buildPlanPerfPrompt({
|
|
213
|
+
target,
|
|
214
|
+
metrics,
|
|
215
|
+
constraints,
|
|
216
|
+
context
|
|
217
|
+
}), {
|
|
218
|
+
workingDirectory,
|
|
219
|
+
maxTurns,
|
|
220
|
+
allowedTools: READ_ONLY_TOOLS
|
|
221
|
+
}));
|
|
222
|
+
});
|
|
223
|
+
server.registerTool("claude_implement", {
|
|
224
|
+
title: "Claude Implement",
|
|
225
|
+
description: "Ask Claude to implement a feature, fix a bug, or make code changes. WARNING: This modifies your codebase.",
|
|
226
|
+
inputSchema: {
|
|
227
|
+
task: z.string().describe("What to implement or fix"),
|
|
228
|
+
workingDirectory: z.string().optional(),
|
|
229
|
+
model: z.string().optional(),
|
|
230
|
+
maxTurns: z.number().optional().default(15)
|
|
231
|
+
}
|
|
232
|
+
}, async ({ task, workingDirectory, model, maxTurns }) => {
|
|
233
|
+
return formatClaudeResponse(await runClaude(task, {
|
|
234
|
+
workingDirectory,
|
|
235
|
+
model,
|
|
236
|
+
maxTurns
|
|
237
|
+
}));
|
|
238
|
+
});
|
|
239
|
+
async function main() {
|
|
240
|
+
const transport = new StdioServerTransport();
|
|
241
|
+
await server.connect(transport);
|
|
242
|
+
logger.info("claude-bridge MCP server started on stdio");
|
|
243
|
+
}
|
|
244
|
+
main().catch((err) => {
|
|
245
|
+
logger.error("Failed to start claude-bridge:", err);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
//#endregion
|
|
250
|
+
export { };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { i as logger, n as buildPlanPerfPrompt, r as execCommand, t as buildExplainCodePrompt } from "./prompt-builder-CQ2IGDTq.mjs";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
//#region src/lib/codex-output-parser.ts
|
|
8
|
+
/**
|
|
9
|
+
* Parses the JSONL output from `codex exec --json`.
|
|
10
|
+
* Each line is a JSON event with a `type` field.
|
|
11
|
+
* We extract the final agent message, file changes, commands, and usage.
|
|
12
|
+
*/
|
|
13
|
+
function parseCodexOutput(jsonlOutput) {
|
|
14
|
+
const result = {
|
|
15
|
+
threadId: null,
|
|
16
|
+
agentMessage: "",
|
|
17
|
+
fileChanges: [],
|
|
18
|
+
commandsExecuted: [],
|
|
19
|
+
usage: null,
|
|
20
|
+
errors: []
|
|
21
|
+
};
|
|
22
|
+
const lines = jsonlOutput.split("\n").filter((l) => l.trim().length > 0);
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
let event;
|
|
25
|
+
try {
|
|
26
|
+
event = JSON.parse(line);
|
|
27
|
+
} catch {
|
|
28
|
+
logger.debug(`Skipping non-JSON line: ${line.slice(0, 100)}`);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const type = event["type"];
|
|
32
|
+
if (!type) continue;
|
|
33
|
+
switch (type) {
|
|
34
|
+
case "thread.started":
|
|
35
|
+
result.threadId = event["threadId"] ?? null;
|
|
36
|
+
break;
|
|
37
|
+
case "item.completed": {
|
|
38
|
+
const itemType = event["itemType"];
|
|
39
|
+
if (itemType === "agent_message" || itemType === "message") {
|
|
40
|
+
const text = event["text"] ?? event["content"] ?? "";
|
|
41
|
+
if (text) result.agentMessage = text;
|
|
42
|
+
} else if (itemType === "file_change") {
|
|
43
|
+
const path = event["path"] ?? "";
|
|
44
|
+
const kind = event["kind"] ?? "update";
|
|
45
|
+
if (path) result.fileChanges.push({
|
|
46
|
+
path,
|
|
47
|
+
kind
|
|
48
|
+
});
|
|
49
|
+
} else if (itemType === "command_execution") result.commandsExecuted.push({
|
|
50
|
+
command: event["command"] ?? "",
|
|
51
|
+
exitCode: event["exitCode"] ?? null,
|
|
52
|
+
output: event["output"] ?? ""
|
|
53
|
+
});
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case "turn.completed": {
|
|
57
|
+
const usage = event["usage"];
|
|
58
|
+
if (usage) result.usage = {
|
|
59
|
+
inputTokens: usage["input_tokens"] ?? usage["inputTokens"] ?? 0,
|
|
60
|
+
outputTokens: usage["output_tokens"] ?? usage["outputTokens"] ?? 0
|
|
61
|
+
};
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case "turn.failed":
|
|
65
|
+
case "error": {
|
|
66
|
+
const msg = event["error"] ?? event["message"] ?? JSON.stringify(event);
|
|
67
|
+
result.errors.push(msg);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!result.agentMessage && jsonlOutput.trim()) result.agentMessage = jsonlOutput.trim();
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/codex-server.ts
|
|
78
|
+
const server = new McpServer({
|
|
79
|
+
name: "codex-bridge",
|
|
80
|
+
version: "0.1.0"
|
|
81
|
+
});
|
|
82
|
+
async function runCodex(prompt, options = {}) {
|
|
83
|
+
const args = ["exec", "--json"];
|
|
84
|
+
if (options.model) args.push("--model", options.model);
|
|
85
|
+
if (options.sandbox) args.push("--sandbox", options.sandbox);
|
|
86
|
+
if (options.fullAuto) args.push("--full-auto");
|
|
87
|
+
args.push(prompt);
|
|
88
|
+
const result = await execCommand({
|
|
89
|
+
command: "codex",
|
|
90
|
+
args,
|
|
91
|
+
cwd: options.workingDirectory
|
|
92
|
+
});
|
|
93
|
+
if (result.timedOut) return {
|
|
94
|
+
threadId: null,
|
|
95
|
+
agentMessage: "",
|
|
96
|
+
fileChanges: [],
|
|
97
|
+
commandsExecuted: [],
|
|
98
|
+
usage: null,
|
|
99
|
+
errors: ["Codex timed out. Increase BRIDGE_TIMEOUT_MS if needed."]
|
|
100
|
+
};
|
|
101
|
+
const parsed = parseCodexOutput(result.stdout);
|
|
102
|
+
if (result.exitCode !== 0 && !parsed.agentMessage) {
|
|
103
|
+
const stderr = result.stderr.toLowerCase();
|
|
104
|
+
if (stderr.includes("api key") || stderr.includes("authentication") || stderr.includes("unauthorized")) parsed.errors.push("Codex API key issue. Ensure OPENAI_API_KEY is set.");
|
|
105
|
+
else if (result.stderr.trim()) parsed.errors.push(result.stderr.trim());
|
|
106
|
+
}
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
function formatCodexResponse(parsed) {
|
|
110
|
+
if (parsed.errors.length > 0 && !parsed.agentMessage) return {
|
|
111
|
+
content: [{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: `Error: ${parsed.errors.join("; ")}`
|
|
114
|
+
}],
|
|
115
|
+
isError: true
|
|
116
|
+
};
|
|
117
|
+
let text = parsed.agentMessage;
|
|
118
|
+
if (parsed.fileChanges.length > 0) {
|
|
119
|
+
text += "\n\n**Files changed:**\n";
|
|
120
|
+
for (const fc of parsed.fileChanges) text += `- ${fc.kind}: ${fc.path}\n`;
|
|
121
|
+
}
|
|
122
|
+
if (parsed.commandsExecuted.length > 0) {
|
|
123
|
+
text += "\n\n**Commands executed:**\n";
|
|
124
|
+
for (const cmd of parsed.commandsExecuted) text += `- \`${cmd.command}\` (exit: ${cmd.exitCode})\n`;
|
|
125
|
+
}
|
|
126
|
+
return { content: [{
|
|
127
|
+
type: "text",
|
|
128
|
+
text
|
|
129
|
+
}] };
|
|
130
|
+
}
|
|
131
|
+
server.registerTool("codex_query", {
|
|
132
|
+
title: "Ask Codex",
|
|
133
|
+
description: "Ask OpenAI Codex a question or give it a task. Use for getting a second opinion, exploring unfamiliar code, or tasks that benefit from a different model's perspective.",
|
|
134
|
+
inputSchema: {
|
|
135
|
+
prompt: z.string().describe("The question or task for Codex"),
|
|
136
|
+
workingDirectory: z.string().optional().describe("Working directory (defaults to server cwd)"),
|
|
137
|
+
model: z.string().optional().describe("Override the Codex model (e.g., o4-mini, o3)"),
|
|
138
|
+
sandbox: z.enum([
|
|
139
|
+
"read-only",
|
|
140
|
+
"workspace-write",
|
|
141
|
+
"danger-full-access"
|
|
142
|
+
]).optional().default("read-only").describe("Sandbox level controlling what Codex can modify")
|
|
143
|
+
}
|
|
144
|
+
}, async ({ prompt, workingDirectory, model, sandbox }) => {
|
|
145
|
+
return formatCodexResponse(await runCodex(prompt, {
|
|
146
|
+
workingDirectory,
|
|
147
|
+
model,
|
|
148
|
+
sandbox
|
|
149
|
+
}));
|
|
150
|
+
});
|
|
151
|
+
server.registerTool("codex_review_code", {
|
|
152
|
+
title: "Codex Code Review",
|
|
153
|
+
description: "Ask Codex to review code. Provide a git diff range, file paths, or a code snippet. Returns specific, actionable feedback.",
|
|
154
|
+
inputSchema: {
|
|
155
|
+
target: z.string().describe("What to review: git diff range (e.g., \"HEAD~3..HEAD\"), file paths, or code snippet"),
|
|
156
|
+
focusAreas: z.string().optional().describe("Focus on: bugs, performance, style, security, etc."),
|
|
157
|
+
context: z.string().optional().describe("Additional context about the codebase or changes"),
|
|
158
|
+
workingDirectory: z.string().optional()
|
|
159
|
+
}
|
|
160
|
+
}, async ({ target, focusAreas, context, workingDirectory }) => {
|
|
161
|
+
let prompt = `Review the following code changes. Provide specific, actionable feedback with line references.\n\nTarget: ${target}`;
|
|
162
|
+
if (focusAreas) prompt += `\n\nFocus areas: ${focusAreas}`;
|
|
163
|
+
if (context) prompt += `\n\nContext: ${context}`;
|
|
164
|
+
return formatCodexResponse(await runCodex(prompt, {
|
|
165
|
+
workingDirectory,
|
|
166
|
+
sandbox: "read-only"
|
|
167
|
+
}));
|
|
168
|
+
});
|
|
169
|
+
server.registerTool("codex_review_plan", {
|
|
170
|
+
title: "Codex Plan Review",
|
|
171
|
+
description: "Ask Codex to critique an implementation plan. Identifies gaps, risks, missing edge cases, and suggests improvements.",
|
|
172
|
+
inputSchema: {
|
|
173
|
+
plan: z.string().describe("The implementation plan or design to review"),
|
|
174
|
+
codebasePath: z.string().optional().describe("Path to relevant codebase for context"),
|
|
175
|
+
constraints: z.string().optional().describe("Known constraints: timeline, tech stack, compatibility"),
|
|
176
|
+
workingDirectory: z.string().optional()
|
|
177
|
+
}
|
|
178
|
+
}, async ({ plan, codebasePath, constraints, workingDirectory }) => {
|
|
179
|
+
let prompt = `Critique this implementation plan. Identify gaps, risks, missing edge cases, and suggest improvements.\n\nPlan:\n${plan}`;
|
|
180
|
+
if (codebasePath) prompt += `\n\nRelevant codebase: ${codebasePath}`;
|
|
181
|
+
if (constraints) prompt += `\n\nConstraints: ${constraints}`;
|
|
182
|
+
return formatCodexResponse(await runCodex(prompt, {
|
|
183
|
+
workingDirectory,
|
|
184
|
+
sandbox: "read-only"
|
|
185
|
+
}));
|
|
186
|
+
});
|
|
187
|
+
server.registerTool("codex_explain_code", {
|
|
188
|
+
title: "Codex Explain Code",
|
|
189
|
+
description: "Ask Codex to deeply explain code, logic, or architecture. Useful for understanding unfamiliar code, onboarding, or documenting complex systems.",
|
|
190
|
+
inputSchema: {
|
|
191
|
+
target: z.string().describe("What to explain: file path, function name, module, or code snippet"),
|
|
192
|
+
depth: z.enum([
|
|
193
|
+
"overview",
|
|
194
|
+
"detailed",
|
|
195
|
+
"trace"
|
|
196
|
+
]).optional().default("detailed").describe("Depth of explanation: overview, detailed, or full execution trace"),
|
|
197
|
+
context: z.string().optional().describe("Additional context about the codebase"),
|
|
198
|
+
workingDirectory: z.string().optional()
|
|
199
|
+
}
|
|
200
|
+
}, async ({ target, depth, context, workingDirectory }) => {
|
|
201
|
+
return formatCodexResponse(await runCodex(buildExplainCodePrompt({
|
|
202
|
+
target,
|
|
203
|
+
depth,
|
|
204
|
+
context
|
|
205
|
+
}), {
|
|
206
|
+
workingDirectory,
|
|
207
|
+
sandbox: "read-only"
|
|
208
|
+
}));
|
|
209
|
+
});
|
|
210
|
+
server.registerTool("codex_plan_perf", {
|
|
211
|
+
title: "Codex Performance Plan",
|
|
212
|
+
description: "Ask Codex to analyze performance and create an improvement plan. Identifies bottlenecks, proposes ranked optimizations with expected impact.",
|
|
213
|
+
inputSchema: {
|
|
214
|
+
target: z.string().describe("What to optimize: function, module, or pipeline path"),
|
|
215
|
+
metrics: z.array(z.enum([
|
|
216
|
+
"latency",
|
|
217
|
+
"throughput",
|
|
218
|
+
"memory",
|
|
219
|
+
"binary-size"
|
|
220
|
+
])).optional().describe("Performance metrics to focus on"),
|
|
221
|
+
constraints: z.string().optional().describe("Constraints: must not increase binary size, etc."),
|
|
222
|
+
context: z.string().optional().describe("Additional context about usage patterns"),
|
|
223
|
+
workingDirectory: z.string().optional()
|
|
224
|
+
}
|
|
225
|
+
}, async ({ target, metrics, constraints, context, workingDirectory }) => {
|
|
226
|
+
return formatCodexResponse(await runCodex(buildPlanPerfPrompt({
|
|
227
|
+
target,
|
|
228
|
+
metrics,
|
|
229
|
+
constraints,
|
|
230
|
+
context
|
|
231
|
+
}), {
|
|
232
|
+
workingDirectory,
|
|
233
|
+
sandbox: "read-only"
|
|
234
|
+
}));
|
|
235
|
+
});
|
|
236
|
+
server.registerTool("codex_implement", {
|
|
237
|
+
title: "Codex Implement",
|
|
238
|
+
description: "Ask Codex to implement a feature, fix a bug, or make code changes. WARNING: This modifies your codebase. Returns a summary of what was changed.",
|
|
239
|
+
inputSchema: {
|
|
240
|
+
task: z.string().describe("What to implement or fix"),
|
|
241
|
+
workingDirectory: z.string().optional(),
|
|
242
|
+
model: z.string().optional(),
|
|
243
|
+
sandbox: z.enum(["workspace-write", "danger-full-access"]).optional().default("workspace-write").describe("Sandbox level (must allow writes)")
|
|
244
|
+
}
|
|
245
|
+
}, async ({ task, workingDirectory, model, sandbox }) => {
|
|
246
|
+
return formatCodexResponse(await runCodex(task, {
|
|
247
|
+
workingDirectory,
|
|
248
|
+
model,
|
|
249
|
+
sandbox,
|
|
250
|
+
fullAuto: true
|
|
251
|
+
}));
|
|
252
|
+
});
|
|
253
|
+
async function main() {
|
|
254
|
+
const transport = new StdioServerTransport();
|
|
255
|
+
await server.connect(transport);
|
|
256
|
+
logger.info("codex-bridge MCP server started on stdio");
|
|
257
|
+
}
|
|
258
|
+
main().catch((err) => {
|
|
259
|
+
logger.error("Failed to start codex-bridge:", err);
|
|
260
|
+
process.exit(1);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
//#endregion
|
|
264
|
+
export { };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/errors.ts
|
|
4
|
+
var BridgeError = class extends Error {
|
|
5
|
+
constructor(message, code, details) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.details = details;
|
|
9
|
+
this.name = "BridgeError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/lib/logger.ts
|
|
15
|
+
const isDebug = !!process.env["BRIDGE_DEBUG"];
|
|
16
|
+
const logger = {
|
|
17
|
+
info(msg, ...args) {
|
|
18
|
+
console.error(`[INFO] ${msg}`, ...args);
|
|
19
|
+
},
|
|
20
|
+
warn(msg, ...args) {
|
|
21
|
+
console.error(`[WARN] ${msg}`, ...args);
|
|
22
|
+
},
|
|
23
|
+
error(msg, ...args) {
|
|
24
|
+
console.error(`[ERROR] ${msg}`, ...args);
|
|
25
|
+
},
|
|
26
|
+
debug(msg, ...args) {
|
|
27
|
+
if (isDebug) console.error(`[DEBUG] ${msg}`, ...args);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/lib/exec-runner.ts
|
|
33
|
+
const DEFAULT_TIMEOUT_MS = 3e5;
|
|
34
|
+
const MAX_BRIDGE_DEPTH = 2;
|
|
35
|
+
function getTimeoutMs() {
|
|
36
|
+
const env = process.env["BRIDGE_TIMEOUT_MS"];
|
|
37
|
+
if (env) {
|
|
38
|
+
const parsed = parseInt(env, 10);
|
|
39
|
+
if (!Number.isNaN(parsed) && parsed > 0) return parsed;
|
|
40
|
+
}
|
|
41
|
+
return DEFAULT_TIMEOUT_MS;
|
|
42
|
+
}
|
|
43
|
+
function checkRecursionDepth() {
|
|
44
|
+
const depth = parseInt(process.env["BRIDGE_DEPTH"] ?? "0", 10);
|
|
45
|
+
if (depth >= MAX_BRIDGE_DEPTH) throw new BridgeError(`Maximum bridge nesting depth reached (${depth} >= ${MAX_BRIDGE_DEPTH}). This prevents infinite recursion between Claude and Codex.`, "RECURSION_LIMIT");
|
|
46
|
+
}
|
|
47
|
+
function execCommand(options) {
|
|
48
|
+
checkRecursionDepth();
|
|
49
|
+
const timeoutMs = options.timeoutMs ?? getTimeoutMs();
|
|
50
|
+
const currentDepth = parseInt(process.env["BRIDGE_DEPTH"] ?? "0", 10);
|
|
51
|
+
const env = {
|
|
52
|
+
...process.env,
|
|
53
|
+
...options.env,
|
|
54
|
+
BRIDGE_DEPTH: String(currentDepth + 1)
|
|
55
|
+
};
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
logger.debug(`exec: ${options.command} ${options.args.join(" ")} (cwd: ${options.cwd ?? process.cwd()}, timeout: ${timeoutMs}ms)`);
|
|
58
|
+
let child;
|
|
59
|
+
try {
|
|
60
|
+
child = spawn(options.command, options.args, {
|
|
61
|
+
cwd: options.cwd ?? process.cwd(),
|
|
62
|
+
env,
|
|
63
|
+
stdio: [
|
|
64
|
+
"ignore",
|
|
65
|
+
"pipe",
|
|
66
|
+
"pipe"
|
|
67
|
+
]
|
|
68
|
+
});
|
|
69
|
+
} catch (err) {
|
|
70
|
+
reject(new BridgeError(`Failed to spawn "${options.command}": ${err instanceof Error ? err.message : String(err)}`, "CLI_NOT_FOUND"));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const stdoutChunks = [];
|
|
74
|
+
const stderrChunks = [];
|
|
75
|
+
let timedOut = false;
|
|
76
|
+
let settled = false;
|
|
77
|
+
child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
78
|
+
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
79
|
+
const timer = setTimeout(() => {
|
|
80
|
+
timedOut = true;
|
|
81
|
+
logger.warn(`Process timed out after ${timeoutMs}ms, sending SIGTERM`);
|
|
82
|
+
child.kill("SIGTERM");
|
|
83
|
+
setTimeout(() => {
|
|
84
|
+
if (!settled) {
|
|
85
|
+
logger.warn("Process did not exit after SIGTERM, sending SIGKILL");
|
|
86
|
+
child.kill("SIGKILL");
|
|
87
|
+
}
|
|
88
|
+
}, 5e3);
|
|
89
|
+
}, timeoutMs);
|
|
90
|
+
child.on("error", (err) => {
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
settled = true;
|
|
93
|
+
if (err.code === "ENOENT") reject(new BridgeError(`"${options.command}" not found. Is it installed and on your PATH?`, "CLI_NOT_FOUND"));
|
|
94
|
+
else reject(new BridgeError(`Process error: ${err.message}`, "PROCESS_ERROR", err.code));
|
|
95
|
+
});
|
|
96
|
+
child.on("close", (code) => {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
settled = true;
|
|
99
|
+
resolve({
|
|
100
|
+
exitCode: code ?? 1,
|
|
101
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
102
|
+
stderr: Buffer.concat(stderrChunks).toString("utf-8"),
|
|
103
|
+
timedOut
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/lib/prompt-builder.ts
|
|
111
|
+
/**
|
|
112
|
+
* Builds a prompt for deep code/logic explanation.
|
|
113
|
+
*/
|
|
114
|
+
function buildExplainCodePrompt(options) {
|
|
115
|
+
const depth = options.depth ?? "detailed";
|
|
116
|
+
let prompt = `Explain the following code/module/function in depth.\n\nTarget: ${options.target}\n\n${{
|
|
117
|
+
overview: "Provide a high-level overview: what the code does, its role in the system, and key abstractions. Keep it concise.",
|
|
118
|
+
detailed: "Provide a detailed explanation: purpose, control flow, data flow, key design decisions, edge cases, and how it interacts with surrounding code.",
|
|
119
|
+
trace: "Provide a full execution trace: step through the code path, explain each branch, data transformation, and side effect. Include call chains and state mutations."
|
|
120
|
+
}[depth]}`;
|
|
121
|
+
if (options.context) prompt += `\n\nAdditional context: ${options.context}`;
|
|
122
|
+
prompt += `\n\nStructure your response with:
|
|
123
|
+
1. **Purpose** - What this code does and why it exists
|
|
124
|
+
2. **Key Components** - Main functions, types, data structures
|
|
125
|
+
3. **Control Flow** - How execution proceeds
|
|
126
|
+
4. **Data Flow** - How data is transformed and passed
|
|
127
|
+
5. **Design Decisions** - Why it's structured this way
|
|
128
|
+
6. **Dependencies** - What it depends on and what depends on it`;
|
|
129
|
+
return prompt;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Builds a prompt for planning performance improvements.
|
|
133
|
+
*/
|
|
134
|
+
function buildPlanPerfPrompt(options) {
|
|
135
|
+
const metricsList = (options.metrics ?? ["latency", "memory"]).join(", ");
|
|
136
|
+
let prompt = `Analyze the performance of the following code and create a concrete improvement plan.
|
|
137
|
+
|
|
138
|
+
Target: ${options.target}
|
|
139
|
+
|
|
140
|
+
Focus metrics: ${metricsList}
|
|
141
|
+
|
|
142
|
+
Perform the following analysis:
|
|
143
|
+
1. **Current State** - Read and understand the target code
|
|
144
|
+
2. **Hot Path Analysis** - Identify the critical execution path and where time/memory is spent
|
|
145
|
+
3. **Bottleneck Identification** - List specific bottlenecks with evidence (e.g., unnecessary allocations, redundant computations, cache misses, algorithmic complexity)
|
|
146
|
+
4. **Optimization Plan** - For each bottleneck, propose a ranked optimization with:
|
|
147
|
+
- Description of the change
|
|
148
|
+
- Expected impact (quantified if possible)
|
|
149
|
+
- Implementation difficulty (low/medium/high)
|
|
150
|
+
- Any correctness risks or trade-offs
|
|
151
|
+
5. **Implementation Order** - Recommend which optimizations to apply first (highest impact, lowest risk)
|
|
152
|
+
6. **Measurement Plan** - How to verify each optimization works (benchmarks, profiling commands)`;
|
|
153
|
+
if (options.constraints) prompt += `\n\nConstraints: ${options.constraints}`;
|
|
154
|
+
if (options.context) prompt += `\n\nAdditional context: ${options.context}`;
|
|
155
|
+
return prompt;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
//#endregion
|
|
159
|
+
export { logger as i, buildPlanPerfPrompt as n, execCommand as r, buildExplainCodePrompt as t };
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-codex-bridge",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Bidirectional MCP server bridge between Claude Code and Codex CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ccb-codex": "./dist/codex-server.mjs",
|
|
8
|
+
"ccb-claude": "./dist/claude-server.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"claude",
|
|
16
|
+
"codex",
|
|
17
|
+
"bridge",
|
|
18
|
+
"ai"
|
|
19
|
+
],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
26
|
+
"zod": "^4.3.6"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^25.2.3",
|
|
30
|
+
"bumpp": "^10.4.1",
|
|
31
|
+
"husky": "^9.1.7",
|
|
32
|
+
"oxfmt": "^0.28.0",
|
|
33
|
+
"oxlint": "^1.43.0",
|
|
34
|
+
"publint": "^0.3.17",
|
|
35
|
+
"tsdown": "^0.20.3",
|
|
36
|
+
"tsx": "^4.21.0",
|
|
37
|
+
"typescript": "^5.9.3",
|
|
38
|
+
"vitest": "^4.0.18"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsdown",
|
|
42
|
+
"dev:codex-server": "tsx src/codex-server.ts",
|
|
43
|
+
"dev:claude-server": "tsx src/claude-server.ts",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:watch": "vitest",
|
|
46
|
+
"lint": "oxlint src/ test/ --type-check",
|
|
47
|
+
"fmt": "oxfmt --write src/ test/",
|
|
48
|
+
"fmt:check": "oxfmt --check src/ test/",
|
|
49
|
+
"publint": "publint",
|
|
50
|
+
"release": "bumpp"
|
|
51
|
+
}
|
|
52
|
+
}
|