| author | |
| committer | |
| log | 8d4a8b4f3179a054b545b252a1b4c005e9da7875 |
| tree | bc5c2434a791fc432a8781f0956c0974f56c5b93 |
| parent | 23ab75e84ab0680f6e71bf08dfb4b832d4bc4c87 |
| signature | Signed by PGP key B5690EEEBB952194 |
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 @@ |
| 1 | import { expect, it } from "@effect/vitest"; | |
| 2 | import { | |
| 3 | EnvironmentId, | |
| 4 | PreviewAutomationUnavailableError, | |
| 5 | ProviderInstanceId, | |
| 6 | ThreadId, | |
| 7 | } from "@t3tools/contracts"; | |
| 8 | import * as Effect from "effect/Effect"; | |
| 9 | ||
| 10 | import * as McpInvocationContext from "./McpInvocationContext.ts"; | |
| 11 | ||
| 12 | it.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 | import type { EnvironmentId, ProviderInstanceId, ThreadId } from "@t3tools/contracts"; | |
| 2 | import { PreviewAutomationUnavailableError } from "@t3tools/contracts"; | |
| 1 | import { | |
| 2 | type EnvironmentId, | |
| 3 | PreviewAutomationUnavailableError, | |
| 4 | type ProviderInstanceId, | |
| 5 | type ThreadId, | |
| 6 | } from "@t3tools/contracts"; | |
| 3 | 7 | import * as Context from "effect/Context"; |
| 4 | 8 | import * as Effect from "effect/Effect"; |
| 5 | 9 | |
| ... | ... | @@ -26,7 +30,11 @@ export const requireMcpCapability = Effect.fn("mcp.requireCapability")(function* |
| 26 | 30 | const invocation = yield* McpInvocationContext; |
| 27 | 31 | if (!invocation.capabilities.has(capability)) { |
| 28 | 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 | 40 | return invocation; |
packages/contracts/src/previewAutomation.ts+12-2| ... | ... | @@ -453,8 +453,18 @@ export type PreviewAutomationResponse = typeof PreviewAutomationResponse.Type; |
| 453 | 453 | |
| 454 | 454 | export class PreviewAutomationUnavailableError extends Schema.TaggedErrorClass<PreviewAutomationUnavailableError>()( |
| 455 | 455 | "PreviewAutomationUnavailableError", |
| 456 | { message: Schema.String }, | |
| 457 | ) {} | |
| 456 | { | |
| 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 | } | |
| 458 | 468 | |
| 459 | 469 | const PreviewAutomationScopeErrorFields = { |
| 460 | 470 | operation: PreviewAutomationOperation, |