authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-20 18:19:12-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-20 18:19:12-07:00
log8d4a8b4f3179a054b545b252a1b4c005e9da7875
treebc5c2434a791fc432a8781f0956c0974f56c5b93
parent23ab75e84ab0680f6e71bf08dfb4b832d4bc4c87
signature Signed by PGP key B5690EEEBB952194

[codex] Structure preview capability errors (#3454)

Co-authored-by: codex <codex@users.noreply.github.com>

3 files changed, 62 insertions(+), 5 deletions(-)

apps/server/src/mcp/McpInvocationContext.test.ts created+39
...@@ -0,0 +1,39 @@
1import { expect, it } from "@effect/vitest";
2import {
3 EnvironmentId,
4 PreviewAutomationUnavailableError,
5 ProviderInstanceId,
6 ThreadId,
7} from "@t3tools/contracts";
8import * as Effect from "effect/Effect";
9
10import * as McpInvocationContext from "./McpInvocationContext.ts";
11
12it.effect("reports the scoped credential context when preview capability is unavailable", () => {
13 const invocation: McpInvocationContext.McpInvocationScope = {
14 environmentId: EnvironmentId.make("environment-1"),
15 threadId: ThreadId.make("thread-1"),
16 providerSessionId: "provider-session-1",
17 providerInstanceId: ProviderInstanceId.make("codex"),
18 capabilities: new Set(),
19 issuedAt: 1,
20 expiresAt: 2,
21 };
22
23 return Effect.gen(function* () {
24 const error = yield* McpInvocationContext.requireMcpCapability("preview").pipe(
25 Effect.provideService(McpInvocationContext.McpInvocationContext, invocation),
26 Effect.flip,
27 );
28
29 expect(error).toBeInstanceOf(PreviewAutomationUnavailableError);
30 expect(error).toMatchObject({
31 capability: "preview",
32 environmentId: invocation.environmentId,
33 threadId: invocation.threadId,
34 providerSessionId: invocation.providerSessionId,
35 providerInstanceId: invocation.providerInstanceId,
36 });
37 expect(error.message).toBe("MCP credential does not grant the preview capability.");
38 });
39});
apps/server/src/mcp/McpInvocationContext.ts+11-3
...@@ -1,5 +1,9 @@...@@ -1,5 +1,9 @@
1import type { EnvironmentId, ProviderInstanceId, ThreadId } from "@t3tools/contracts";1import {
2import { PreviewAutomationUnavailableError } from "@t3tools/contracts";2 type EnvironmentId,
3 PreviewAutomationUnavailableError,
4 type ProviderInstanceId,
5 type ThreadId,
6} from "@t3tools/contracts";
3import * as Context from "effect/Context";7import * as Context from "effect/Context";
4import * as Effect from "effect/Effect";8import * as Effect from "effect/Effect";
59
...@@ -26,7 +30,11 @@ export const requireMcpCapability = Effect.fn("mcp.requireCapability")(function*...@@ -26,7 +30,11 @@ export const requireMcpCapability = Effect.fn("mcp.requireCapability")(function*
26 const invocation = yield* McpInvocationContext;30 const invocation = yield* McpInvocationContext;
27 if (!invocation.capabilities.has(capability)) {31 if (!invocation.capabilities.has(capability)) {
28 return yield* new PreviewAutomationUnavailableError({32 return yield* new PreviewAutomationUnavailableError({
29 message: `MCP credential does not grant the ${capability} capability.`,33 capability,
34 environmentId: invocation.environmentId,
35 threadId: invocation.threadId,
36 providerSessionId: invocation.providerSessionId,
37 providerInstanceId: invocation.providerInstanceId,
30 });38 });
31 }39 }
32 return invocation;40 return invocation;
packages/contracts/src/previewAutomation.ts+12-2
...@@ -453,8 +453,18 @@ export type PreviewAutomationResponse = typeof PreviewAutomationResponse.Type;...@@ -453,8 +453,18 @@ export type PreviewAutomationResponse = typeof PreviewAutomationResponse.Type;
453453
454export class PreviewAutomationUnavailableError extends Schema.TaggedErrorClass<PreviewAutomationUnavailableError>()(454export class PreviewAutomationUnavailableError extends Schema.TaggedErrorClass<PreviewAutomationUnavailableError>()(
455 "PreviewAutomationUnavailableError",455 "PreviewAutomationUnavailableError",
456 { message: Schema.String },456 {
457) {}457 capability: Schema.Literal("preview"),
458 environmentId: EnvironmentId,
459 threadId: ThreadId,
460 providerSessionId: TrimmedNonEmptyString,
461 providerInstanceId: ProviderInstanceId,
462 },
463) {
464 override get message(): string {
465 return `MCP credential does not grant the ${this.capability} capability.`;
466 }
467}
458468
459const PreviewAutomationScopeErrorFields = {469const PreviewAutomationScopeErrorFields = {
460 operation: PreviewAutomationOperation,470 operation: PreviewAutomationOperation,