Skip to content
Source
Sign inSign up
Open History to see the last commit

CodexMcpElicitation.test.ts

191 lines5.8 KB

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("returns the accepted form option to Codex", () => {
49 NodeAssert.deepStrictEqual(toMcpElicitationResponse(request, "accept"), {
50 action: "accept",
51 content: { approval: "once" },
52 });
53 });
54​
55 it("returns session-scoped approval in the MCP response", () => {
56 NodeAssert.deepStrictEqual(toMcpElicitationResponse(request, "acceptForSession"), {
57 action: "accept",
58 _meta: { persist: "session" },
59 content: { approval: "session" },
60 });
61 });
62​
63 // regression: #8058 show Codex app access approval prompts
64 it("returns persistent approval in the MCP response", () => {
65 NodeAssert.deepStrictEqual(toMcpElicitationResponse(request, "acceptAlways"), {
66 action: "accept",
67 _meta: { persist: "always" },
68 content: { approval: "always" },
69 });
70 });
71​
72 it("supports boolean permanent-approval fields", () => {
73 const booleanRequest = {
74 ...request,
75 _meta: { app_name: "Safari" },
76 requestedSchema: {
77 type: "object",
78 properties: {
79 always: { type: "boolean", title: "Always allow Safari" },
80 },
81 },
82 } satisfies EffectCodexSchema.McpServerElicitationRequestParams;
83​
84 NodeAssert.ok(
85 describeMcpElicitation(booleanRequest).options.some(
86 (option) => option.decision === "acceptAlways",
87 ),
88 );
89 NodeAssert.deepStrictEqual(toMcpElicitationResponse(booleanRequest, "acceptAlways"), {
90 action: "accept",
91 _meta: { persist: "always" },
92 content: { always: true },
93 });
94 });
95​
96 it("preserves valid nullable MCP form fields and persistence choices", () => {
97 const nullableRequest = {
98 ...request,
99 _meta: {
100 app_name: null,
101 appName: "Safari",
102 connector_name: null,
103 persist: null,
104 target: null,
105 tool_params: null,
106 },
107 requestedSchema: {
108 type: "object",
109 properties: {
110 approval: {
111 type: "string",
112 title: null,
113 description: null,
114 default: null,
115 enum: ["once", "always"],
116 enumNames: null,
117 },
118 },
119 required: ["approval"],
120 },
121 } satisfies EffectCodexSchema.McpServerElicitationRequestParams;
122​
123 NodeAssert.equal(describeMcpElicitation(nullableRequest).appName, "Safari");
124 NodeAssert.ok(
125 describeMcpElicitation(nullableRequest).options.some(
126 (option) => option.decision === "acceptAlways",
127 ),
128 );
129 NodeAssert.deepStrictEqual(toMcpElicitationResponse(nullableRequest, "acceptAlways"), {
130 action: "accept",
131 _meta: { persist: "always" },
132 content: { approval: "always" },
133 });
134 });
135​
136 it("declines required form fields that an approval prompt cannot collect", () => {
137 const inputRequest = {
138 ...request,
139 requestedSchema: {
140 type: "object",
141 properties: {
142 email: { type: "string", format: "email" },
143 },
144 required: ["email"],
145 },
146 } satisfies EffectCodexSchema.McpServerElicitationRequestParams;
147​
148 NodeAssert.deepStrictEqual(toMcpElicitationResponse(inputRequest, "accept"), {
149 action: "decline",
150 });
151 });
152​
153 it("does not approve URL elicitations without opening their requested URL", () => {
154 const urlRequest = {
155 mode: "url",
156 message: "Finish signing in to continue.",
157 serverName: "computer-use",
158 threadId: "provider-thread-1",
159 turnId: "turn-1",
160 elicitationId: "sign-in-1",
161 url: "https://example.com/authorize",
162 } satisfies EffectCodexSchema.McpServerElicitationRequestParams;
163​
164 NodeAssert.deepStrictEqual(toMcpElicitationResponse(urlRequest, "accept"), {
165 action: "decline",
166 });
167 });
168​
169 it("omits persistence choices that cannot satisfy required form fields", () => {
170 const onceOnlyRequest = {
171 ...request,
172 _meta: { app_name: "Safari", persist: ["session", "always"] },
173 requestedSchema: {
174 type: "object",
175 properties: {
176 approval: {
177 type: "string",
178 enum: ["once"],
179 },
180 },
181 required: ["approval"],
182 },
183 } satisfies EffectCodexSchema.McpServerElicitationRequestParams;
184​
185 NodeAssert.deepStrictEqual(describeMcpElicitation(onceOnlyRequest).options, [
186 { decision: "cancel", label: "Cancel" },
187 { decision: "decline", label: "Decline" },
188 { decision: "accept", label: "Approve" },
189 ]);
190 });
191});