Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion plugins/codex/scripts/lib/app-server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function createProtocolError(message, data) {
return error;
}

class AppServerClientBase {
export class AppServerClientBase {
constructor(cwd, options = {}) {
this.cwd = cwd;
this.options = options;
Expand Down Expand Up @@ -154,6 +154,20 @@ class AppServerClientBase {
}

handleServerRequest(message) {
// MCP servers (e.g. ChatGPT connectors surfaced as `codex_apps`) request the
// operator's consent via an elicitation, which app-server delivers as a
// server->client request. This client runs Codex non-interactively, so there
// is no human to answer it; blanket-rejecting every server request with
// -32601 makes those tool calls fail ("user rejected MCP tool call") on the
// background runner and hang on `codex exec` / `codex mcp-server`. Accept the
// elicitation so connectors the operator has already enabled can run.
if (message.method === "mcpServer/elicitation/request") {
this.sendMessage({
id: message.id,
result: { action: "accept", content: null, _meta: null }
});
return;
}
this.sendMessage({
id: message.id,
error: buildJsonRpcError(-32601, `Unsupported server request: ${message.method}`)
Expand Down
36 changes: 36 additions & 0 deletions tests/app-server.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { test } from "node:test";
import assert from "node:assert/strict";

import { AppServerClientBase } from "../plugins/codex/scripts/lib/app-server.mjs";

/** Minimal client that records the JSON-RPC messages it would send. */
class CapturingClient extends AppServerClientBase {
constructor() {
super(process.cwd());
this.sent = [];
}
sendMessage(message) {
this.sent.push(message);
}
}

test("handleServerRequest accepts MCP elicitation requests instead of rejecting them", () => {
const client = new CapturingClient();
client.handleServerRequest({
id: 7,
method: "mcpServer/elicitation/request",
params: { threadId: "t1" }
});
assert.deepEqual(client.sent, [
{ id: 7, result: { action: "accept", content: null, _meta: null } }
]);
});

test("handleServerRequest still rejects unknown server requests with -32601", () => {
const client = new CapturingClient();
client.handleServerRequest({ id: 8, method: "some/unknown/request", params: {} });
assert.equal(client.sent.length, 1);
assert.equal(client.sent[0].id, 8);
assert.equal(client.sent[0].result, undefined);
assert.equal(client.sent[0].error.code, -32601);
});