1import * as NodeAssert from "node:assert/strict";
2
3import { describe, it } from "vite-plus/test";
4import type * as EffectCodexSchema from "effect-codex-app-server/schema";
5
6import { describeMcpElicitation, toMcpElicitationResponse } from "./CodexMcpElicitation.ts";
7
8describe("Codex MCP elicitation approvals", () => {
9 const request = {
10 mode: "form",
11 message: "Allow ChatGPT to use Safari?",
12 serverName: "computer-use",
13 threadId: "provider-thread-1",
14 turnId: "turn-1",
15 _meta: {
16 app_name: "Safari",
17 persist: ["session", "always"],
18 },
19 requestedSchema: {
20 type: "object",
21 properties: {
22 approval: {
23 type: "string",
24 oneOf: [
25 { const: "once", title: "Allow once" },
26 { const: "session", title: "Allow for this session" },
27 { const: "always", title: "Always allow Safari" },
28 ],
29 },
30 },
31 required: ["approval"],
32 },
33 } satisfies EffectCodexSchema.McpServerElicitationRequestParams;
34
35 it("preserves the app name and advertised persistence choices", () => {
36 NodeAssert.deepStrictEqual(describeMcpElicitation(request), {
37 appName: "Safari",
38 options: [
39 { decision: "cancel", label: "Cancel" },
40 { decision: "decline", label: "Decline" },
41 { decision: "acceptForSession", label: "Allow for this session" },
42 { decision: "acceptAlways", label: "Always allow Safari" },
43 { decision: "accept", label: "Approve" },
44 ],
45 });
46 });
47
48 it("extracts the app name from a Computer Use request without metadata", () => {
49 const { _meta, ...requestWithoutMetadata } = request;
50
51 NodeAssert.equal(describeMcpElicitation(requestWithoutMetadata).appName, "Safari");
52 });
53
54 it("returns the accepted form option to Codex", () => {
55 NodeAssert.deepStrictEqual(toMcpElicitationResponse(request, "accept"), {
56 action: "accept",
57 content: { approval: "once" },
58 });
59 });
60
61 it("returns session-scoped approval in the MCP response", () => {
62 NodeAssert.deepStrictEqual(toMcpElicitationResponse(request, "acceptForSession"), {
63 action: "accept",
64 _meta: { persist: "session" },
65 content: { approval: "session" },
66 });
67 });
68
69 it("returns persistent approval in the MCP response", () => {
70 NodeAssert.deepStrictEqual(toMcpElicitationResponse(request, "acceptAlways"), {
71 action: "accept",
72 _meta: { persist: "always" },
73 content: { approval: "always" },
74 });
75 });
76
77 it("returns rejection without form content", () => {
78 NodeAssert.deepStrictEqual(toMcpElicitationResponse(request, "decline"), {
79 action: "decline",
80 });
81 });
82
83 it("returns cancellation without form content", () => {
84 NodeAssert.deepStrictEqual(toMcpElicitationResponse(request, "cancel"), {
85 action: "cancel",
86 });
87 });
88
89 it("supports boolean permanent-approval fields", () => {
90 const booleanRequest = {
91 ...request,
92 _meta: { app_name: "Safari" },
93 requestedSchema: {
94 type: "object",
95 properties: {
96 always: { type: "boolean", title: "Always allow Safari" },
97 },
98 },
99 } satisfies EffectCodexSchema.McpServerElicitationRequestParams;
100
101 NodeAssert.ok(
102 describeMcpElicitation(booleanRequest).options.some(
103 (option) => option.decision === "acceptAlways",
104 ),
105 );
106 NodeAssert.deepStrictEqual(toMcpElicitationResponse(booleanRequest, "acceptAlways"), {
107 action: "accept",
108 _meta: { persist: "always" },
109 content: { always: true },
110 });
111 });
112
113 it("preserves valid nullable MCP form fields and persistence choices", () => {
114 const nullableRequest = {
115 ...request,
116 _meta: {
117 app_name: null,
118 appName: "Safari",
119 connector_name: null,
120 persist: null,
121 target: null,
122 tool_params: null,
123 },
124 requestedSchema: {
125 type: "object",
126 properties: {
127 approval: {
128 type: "string",
129 title: null,
130 description: null,
131 default: null,
132 enum: ["once", "always"],
133 enumNames: null,
134 },
135 },
136 required: ["approval"],
137 },
138 } satisfies EffectCodexSchema.McpServerElicitationRequestParams;
139
140 NodeAssert.equal(describeMcpElicitation(nullableRequest).appName, "Safari");
141 NodeAssert.ok(
142 describeMcpElicitation(nullableRequest).options.some(
143 (option) => option.decision === "acceptAlways",
144 ),
145 );
146 NodeAssert.deepStrictEqual(toMcpElicitationResponse(nullableRequest, "acceptAlways"), {
147 action: "accept",
148 _meta: { persist: "always" },
149 content: { approval: "always" },
150 });
151 });
152
153 it("declines required form fields that an approval prompt cannot collect", () => {
154 const inputRequest = {
155 ...request,
156 requestedSchema: {
157 type: "object",
158 properties: {
159 email: { type: "string", format: "email" },
160 },
161 required: ["email"],
162 },
163 } satisfies EffectCodexSchema.McpServerElicitationRequestParams;
164
165 NodeAssert.deepStrictEqual(toMcpElicitationResponse(inputRequest, "accept"), {
166 action: "decline",
167 });
168 });
169
170 it("does not approve URL elicitations without opening their requested URL", () => {
171 const urlRequest = {
172 mode: "url",
173 message: "Finish signing in to continue.",
174 serverName: "computer-use",
175 threadId: "provider-thread-1",
176 turnId: "turn-1",
177 elicitationId: "sign-in-1",
178 url: "https://example.com/authorize",
179 } satisfies EffectCodexSchema.McpServerElicitationRequestParams;
180
181 NodeAssert.deepStrictEqual(toMcpElicitationResponse(urlRequest, "accept"), {
182 action: "decline",
183 });
184 });
185
186 it("omits persistence choices that cannot satisfy required form fields", () => {
187 const onceOnlyRequest = {
188 ...request,
189 _meta: { app_name: "Safari", persist: ["session", "always"] },
190 requestedSchema: {
191 type: "object",
192 properties: {
193 approval: {
194 type: "string",
195 enum: ["once"],
196 },
197 },
198 required: ["approval"],
199 },
200 } satisfies EffectCodexSchema.McpServerElicitationRequestParams;
201
202 NodeAssert.deepStrictEqual(describeMcpElicitation(onceOnlyRequest).options, [
203 { decision: "cancel", label: "Cancel" },
204 { decision: "decline", label: "Decline" },
205 { decision: "accept", label: "Approve" },
206 ]);
207 });
208});