@wyrd-company/async-codex-mcp 0.1.0 → 0.2.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/{plugins/async-codex-mcp/.claude-plugin → .claude-plugin}/plugin.json +1 -1
- package/{plugins/async-codex-mcp/.mcp.json → .mcp.json} +2 -3
- package/README.md +29 -1
- package/bin/claude-channels-wrapper.sh +18 -0
- package/dist/bundle/callback-cli.js +31021 -0
- package/dist/bundle/cli.js +35530 -0
- package/dist/src/callback-cli.js +1 -1
- package/dist/src/codex-client.d.ts +1 -0
- package/dist/src/codex-client.js +8 -3
- package/dist/src/config.d.ts +6 -2
- package/dist/src/config.js +6 -3
- package/dist/src/server.js +20 -3
- package/package.json +9 -4
package/dist/src/callback-cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
const options = parseArgs(process.argv.slice(2));
|
|
6
|
-
const server = new McpServer({ name: "async-codex-mcp-callback", version: "0.
|
|
6
|
+
const server = new McpServer({ name: "async-codex-mcp-callback", version: "0.2.0" }, {
|
|
7
7
|
instructions: "Use async_codex_ask_user only when you need a user answer before continuing. Use async_codex_notify_user for non-blocking progress updates or FYIs.",
|
|
8
8
|
});
|
|
9
9
|
server.tool("async_codex_ask_user", "Ask the user a blocking question. Codex waits until the user responds to the async session.", {
|
|
@@ -17,6 +17,7 @@ export declare class CodexMcpClient implements CodexClientLike {
|
|
|
17
17
|
constructor(config: AsyncCodexConfig);
|
|
18
18
|
callCodex(profile: ToolProfile, args: CodexToolArguments): Promise<CallToolResult>;
|
|
19
19
|
continueSession(sessionId: string, prompt: string, cwd?: string): Promise<CallToolResult>;
|
|
20
|
+
private requestOptions;
|
|
20
21
|
close(): Promise<void>;
|
|
21
22
|
private getClient;
|
|
22
23
|
}
|
package/dist/src/codex-client.js
CHANGED
|
@@ -27,14 +27,19 @@ export class CodexMcpClient {
|
|
|
27
27
|
codexArgs["developer-instructions"] = profile.developerInstructions;
|
|
28
28
|
if (Object.keys(profile.config).length > 0)
|
|
29
29
|
codexArgs.config = profile.config;
|
|
30
|
-
return client.callTool({ name: "codex", arguments: codexArgs });
|
|
30
|
+
return client.callTool({ name: "codex", arguments: codexArgs }, undefined, this.requestOptions());
|
|
31
31
|
}
|
|
32
32
|
async continueSession(sessionId, prompt, cwd) {
|
|
33
33
|
const client = await this.getClient();
|
|
34
34
|
const args = { threadId: sessionId, prompt };
|
|
35
35
|
if (cwd)
|
|
36
36
|
args.cwd = cwd;
|
|
37
|
-
return client.callTool({ name: "codex-reply", arguments: args });
|
|
37
|
+
return client.callTool({ name: "codex-reply", arguments: args }, undefined, this.requestOptions());
|
|
38
|
+
}
|
|
39
|
+
// The SDK default request timeout is 60s, which aborts any Codex run
|
|
40
|
+
// longer than a minute regardless of callback state.
|
|
41
|
+
requestOptions() {
|
|
42
|
+
return { timeout: this.config.codex.requestTimeoutSec * 1000, resetTimeoutOnProgress: true };
|
|
38
43
|
}
|
|
39
44
|
async close() {
|
|
40
45
|
await this.client?.close();
|
|
@@ -45,7 +50,7 @@ export class CodexMcpClient {
|
|
|
45
50
|
async getClient() {
|
|
46
51
|
if (this.client)
|
|
47
52
|
return this.client;
|
|
48
|
-
this.client = new Client({ name: "async-codex-mcp-client", version: "0.
|
|
53
|
+
this.client = new Client({ name: "async-codex-mcp-client", version: "0.2.0" });
|
|
49
54
|
this.transport = new StdioClientTransport({
|
|
50
55
|
command: this.config.codex.command,
|
|
51
56
|
args: this.config.codex.args,
|
package/dist/src/config.d.ts
CHANGED
|
@@ -9,7 +9,8 @@ declare const profileSchema: z.ZodObject<{
|
|
|
9
9
|
developerInstructions: z.ZodOptional<z.ZodString>;
|
|
10
10
|
config: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
11
11
|
callbacks: z.ZodOptional<z.ZodObject<{
|
|
12
|
-
enabled: z.ZodBoolean
|
|
12
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
13
|
+
askTimeoutSec: z.ZodOptional<z.ZodNumber>;
|
|
13
14
|
}, z.core.$strict>>;
|
|
14
15
|
}, z.core.$strict>;
|
|
15
16
|
declare const configSchema: z.ZodObject<{
|
|
@@ -18,9 +19,11 @@ declare const configSchema: z.ZodObject<{
|
|
|
18
19
|
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
19
20
|
env: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
20
21
|
cwd: z.ZodOptional<z.ZodString>;
|
|
22
|
+
requestTimeoutSec: z.ZodDefault<z.ZodNumber>;
|
|
21
23
|
}, z.core.$strip>>;
|
|
22
24
|
callbacks: z.ZodDefault<z.ZodObject<{
|
|
23
25
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
26
|
+
askTimeoutSec: z.ZodDefault<z.ZodNumber>;
|
|
24
27
|
}, z.core.$strict>>;
|
|
25
28
|
tools: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
26
29
|
description: z.ZodOptional<z.ZodString>;
|
|
@@ -32,7 +35,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
32
35
|
developerInstructions: z.ZodOptional<z.ZodString>;
|
|
33
36
|
config: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
34
37
|
callbacks: z.ZodOptional<z.ZodObject<{
|
|
35
|
-
enabled: z.ZodBoolean
|
|
38
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
39
|
+
askTimeoutSec: z.ZodOptional<z.ZodNumber>;
|
|
36
40
|
}, z.core.$strict>>;
|
|
37
41
|
}, z.core.$strict>>>;
|
|
38
42
|
}, z.core.$strip>;
|
package/dist/src/config.js
CHANGED
|
@@ -9,12 +9,15 @@ const codexServerSchema = z.object({
|
|
|
9
9
|
args: z.array(z.string()).default(["mcp-server"]),
|
|
10
10
|
env: stringRecordSchema.default({}),
|
|
11
11
|
cwd: z.string().optional(),
|
|
12
|
+
requestTimeoutSec: z.number().int().positive().default(86400),
|
|
12
13
|
});
|
|
13
14
|
const callbacksSchema = z.object({
|
|
14
15
|
enabled: z.boolean().default(true),
|
|
16
|
+
askTimeoutSec: z.number().int().positive().default(3600),
|
|
15
17
|
}).strict();
|
|
16
18
|
const profileCallbacksSchema = z.object({
|
|
17
|
-
enabled: z.boolean(),
|
|
19
|
+
enabled: z.boolean().optional(),
|
|
20
|
+
askTimeoutSec: z.number().int().positive().optional(),
|
|
18
21
|
}).strict();
|
|
19
22
|
const profileSchema = z.object({
|
|
20
23
|
description: z.string().optional(),
|
|
@@ -28,8 +31,8 @@ const profileSchema = z.object({
|
|
|
28
31
|
callbacks: profileCallbacksSchema.optional(),
|
|
29
32
|
}).strict();
|
|
30
33
|
const configSchema = z.object({
|
|
31
|
-
codex: codexServerSchema.default({ command: "codex", args: ["mcp-server"], env: {} }),
|
|
32
|
-
callbacks: callbacksSchema.default({ enabled: true }),
|
|
34
|
+
codex: codexServerSchema.default({ command: "codex", args: ["mcp-server"], env: {}, requestTimeoutSec: 86400 }),
|
|
35
|
+
callbacks: callbacksSchema.default({ enabled: true, askTimeoutSec: 3600 }),
|
|
33
36
|
tools: z.record(z.string(), profileSchema).default({
|
|
34
37
|
codex: {
|
|
35
38
|
description: "Run Codex asynchronously with danger-full-access sandboxing.",
|
package/dist/src/server.js
CHANGED
|
@@ -20,9 +20,12 @@ const answerShape = {
|
|
|
20
20
|
message: z.string().min(1).describe("User response to return to Codex."),
|
|
21
21
|
};
|
|
22
22
|
export function createServer(config, options = {}) {
|
|
23
|
-
const server = new McpServer({ name: "async-codex-mcp", version: "0.
|
|
24
|
-
capabilities: { logging: {} },
|
|
25
|
-
instructions: "Starts Codex sub-agent sessions asynchronously. Profile tools return immediately with an async session id; use continue-session after completion to resume."
|
|
23
|
+
const server = new McpServer({ name: "async-codex-mcp", version: "0.2.0" }, {
|
|
24
|
+
capabilities: { logging: {}, experimental: { "claude/channel": {} } },
|
|
25
|
+
instructions: "Starts Codex sub-agent sessions asynchronously. Profile tools return immediately with an async session id; use continue-session after completion to resume. " +
|
|
26
|
+
'When this server runs as a Claude Code channel, session events arrive as <channel source="async-codex-mcp" session_id="..." kind="...">. ' +
|
|
27
|
+
"kind=ask means Codex is blocked waiting for input: call answer-session with the session_id from the tag. " +
|
|
28
|
+
"kind=notify is a non-blocking progress update. kind=completed or kind=failed means the session finished; use session-status or continue-session.",
|
|
26
29
|
});
|
|
27
30
|
const client = options.client ?? new CodexMcpClient(config);
|
|
28
31
|
const store = options.store ?? new SessionStore();
|
|
@@ -155,12 +158,22 @@ function errorMessageFromResult(result) {
|
|
|
155
158
|
.trim();
|
|
156
159
|
return text || "Codex returned an error result.";
|
|
157
160
|
}
|
|
161
|
+
async function sendChannelNotification(server, content, meta) {
|
|
162
|
+
const cleanMeta = Object.fromEntries(Object.entries(meta).filter((entry) => typeof entry[1] === "string"));
|
|
163
|
+
await server.server.notification({
|
|
164
|
+
method: "notifications/claude/channel",
|
|
165
|
+
params: { content, meta: cleanMeta },
|
|
166
|
+
});
|
|
167
|
+
}
|
|
158
168
|
async function sendSessionNotification(server, sessionId, status, codexSessionId, error) {
|
|
159
169
|
await server.server.sendLoggingMessage({
|
|
160
170
|
level: status === "completed" ? "notice" : "error",
|
|
161
171
|
logger: "async-codex-mcp",
|
|
162
172
|
data: { session_id: sessionId, status, codex_session_id: codexSessionId, error },
|
|
163
173
|
});
|
|
174
|
+
await sendChannelNotification(server, status === "completed"
|
|
175
|
+
? `Async Codex session ${sessionId} completed. Use session-status to read the result or continue-session to resume.`
|
|
176
|
+
: `Async Codex session ${sessionId} failed: ${error ?? "unknown error"}`, { session_id: sessionId, kind: status, codex_session_id: codexSessionId });
|
|
164
177
|
}
|
|
165
178
|
async function sendCallbackNotification(server, sessionId, type, message, extra) {
|
|
166
179
|
await server.server.sendLoggingMessage({
|
|
@@ -168,6 +181,7 @@ async function sendCallbackNotification(server, sessionId, type, message, extra)
|
|
|
168
181
|
logger: "async-codex-mcp",
|
|
169
182
|
data: { session_id: sessionId, type, message, ...extra },
|
|
170
183
|
});
|
|
184
|
+
await sendChannelNotification(server, extra.context ? `${message}\n\nContext: ${extra.context}` : message, { session_id: sessionId, kind: type, topic: extra.topic });
|
|
171
185
|
}
|
|
172
186
|
async function prepareProfile(config, profile, sessionId, callbackHub) {
|
|
173
187
|
if (!callbacksEnabled(config, profile)) {
|
|
@@ -192,6 +206,9 @@ async function prepareProfile(config, profile, sessionId, callbackHub) {
|
|
|
192
206
|
"--session-id",
|
|
193
207
|
sessionId,
|
|
194
208
|
],
|
|
209
|
+
// Codex aborts blocked ask_user calls at its default MCP tool
|
|
210
|
+
// timeout (60s); a human answer routinely takes longer.
|
|
211
|
+
tool_timeout_sec: profile.callbacks?.askTimeoutSec ?? config.callbacks.askTimeoutSec,
|
|
195
212
|
},
|
|
196
213
|
},
|
|
197
214
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wyrd-company/async-codex-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Async MCP proxy for Codex MCP server profiles",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,14 +12,18 @@
|
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc -p tsconfig.json",
|
|
15
|
-
"
|
|
15
|
+
"build:bundle": "esbuild src/cli.ts src/callback-cli.ts --bundle --platform=node --target=node20 --format=esm --outdir=dist/bundle --banner:js=\"import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);\"",
|
|
16
|
+
"prepack": "npm run build && npm run build:bundle",
|
|
16
17
|
"test": "vitest run",
|
|
17
18
|
"start": "node dist/src/cli.js"
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
21
|
+
".claude-plugin",
|
|
22
|
+
".mcp.json",
|
|
23
|
+
"bin",
|
|
20
24
|
"dist/src",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
25
|
+
"dist/bundle",
|
|
26
|
+
"fixtures"
|
|
23
27
|
],
|
|
24
28
|
"repository": {
|
|
25
29
|
"type": "git",
|
|
@@ -38,6 +42,7 @@
|
|
|
38
42
|
"@openai/codex": "^0.135.0",
|
|
39
43
|
"@types/js-yaml": "^4.0.9",
|
|
40
44
|
"@types/node": "^22.15.3",
|
|
45
|
+
"esbuild": "^0.25.0",
|
|
41
46
|
"mcp-testing-kit": "github:thoughtspot/mcp-testing-kit",
|
|
42
47
|
"typescript": "^5.8.3",
|
|
43
48
|
"vitest": "^3.1.2"
|