History

fix(mcp): clarify launch admission failures

Julius Marminge committed 2 months ago

Commit 561d3d51 parent f9307fe3 files · +58 −9Browse files

Unified · Split

3 files
  1. apps/server/src/git/GitWorkflowService.test.ts +39 −1
  2. apps/server/src/mcp/OrchestratorMcpService.ts +2 −2
  3. apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts +17 −6
apps/server/src/git/GitWorkflowService.test.ts+39−1
@@ -1,14 +1,52 @@
1import { assert, describe, expect, it, vi } from "@effect/vitest";1import { assert, describe, expect, it, vi } from "@effect/vitest";
2import * as DateTime from "effect/DateTime";
2import * as Effect from "effect/Effect";3import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";4import * as Layer from "effect/Layer";
5import * as Option from "effect/Option";
4 6
5import { GitCommandError, VcsRepositoryDetectionError } from "@t3tools/contracts";7import { GitCommandError, VcsRepositoryDetectionError } from "@t3tools/contracts";
6 8
7import * as GitManager from "./GitManager.ts";9import * as GitManager from "./GitManager.ts";
8import * as GitWorkflowService from "./GitWorkflowService.ts";10import * as GitWorkflowService from "./GitWorkflowService.ts";
9import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";11import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
12import * as VcsDriver from "../vcs/VcsDriver.ts";
10import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";13import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
11 14
15const unusedVcsDriver = VcsDriver.VcsDriver.of({
16 capabilities: {
17 kind: "git",
18 supportsWorktrees: true,
19 supportsBookmarks: false,
20 supportsAtomicSnapshot: false,
21 supportsPushDefaultRemote: true,
22 ignoreClassifier: "native",
23 },
24 execute: () => Effect.die("unused VCS driver execute"),
25 detectRepository: () => Effect.die("unused VCS repository detection"),
26 isInsideWorkTree: () => Effect.die("unused VCS worktree lookup"),
27 listWorkspaceFiles: () => Effect.die("unused VCS workspace listing"),
28 listRemotes: () => Effect.die("unused VCS remote listing"),
29 filterIgnoredPaths: () => Effect.die("unused VCS ignore classification"),
30 initRepository: () => Effect.die("unused VCS repository initialization"),
31});
32
33function gitHandle(cwd: string): VcsDriverRegistry.VcsDriverHandle {
34 return {
35 kind: "git",
36 repository: {
37 kind: "git",
38 rootPath: cwd,
39 metadataPath: `${cwd}/.git`,
40 freshness: {
41 source: "live-local",
42 observedAt: DateTime.makeUnsafe("2026-08-30T00:00:00.000Z"),
43 expiresAt: Option.none(),
44 },
45 },
46 driver: unusedVcsDriver,
47 };
48}
49
12function makeLayer(input: {50function makeLayer(input: {
13 readonly detect: VcsDriverRegistry.VcsDriverRegistry["Service"]["detect"];51 readonly detect: VcsDriverRegistry.VcsDriverRegistry["Service"]["detect"];
14 readonly resolve?: VcsDriverRegistry.VcsDriverRegistry["Service"]["resolve"];52 readonly resolve?: VcsDriverRegistry.VcsDriverRegistry["Service"]["resolve"];
@@ -221,7 +259,7 @@ describe("GitWorkflowService", () => { · 206 unmodified lines
221 Effect.provide(259 Effect.provide(
222 makeLayer({260 makeLayer({
223 detect: () => Effect.succeed(null),261 detect: () => Effect.succeed(null),
224 resolve: () => Effect.succeed({ kind: "git" } as VcsDriverRegistry.VcsDriverHandle),262 resolve: ({ cwd }) => Effect.succeed(gitHandle(cwd)),
225 execute: (input) => Effect.fail(input.cwd === "/detached" ? detached : corrupt),263 execute: (input) => Effect.fail(input.cwd === "/detached" ? detached : corrupt),
226 }),264 }),
227 ),265 ),
apps/server/src/mcp/OrchestratorMcpService.ts+2−2
@@ -202,13 +202,13 @@ function threadLaunchPolicyFailure( · 201 unmodified lines
202 if (isRuntimeModeCeilingError(cause)) {202 if (isRuntimeModeCeilingError(cause)) {
203 return failure(203 return failure(
204 "runtime_mode_escalation_denied",204 "runtime_mode_escalation_denied",
205 `Child runtime mode ${cause.targetMode} is broader than the caller ceiling ${cause.capturedCallerMode}.`,205 `Child runtime mode ${cause.targetMode} exceeds the caller ceiling (captured ${cause.capturedCallerMode}; current ${cause.currentCallerMode}).`,
206 );206 );
207 }207 }
208 if (isInteractionModeCeilingError(cause)) {208 if (isInteractionModeCeilingError(cause)) {
209 return failure(209 return failure(
210 "interaction_mode_escalation_denied",210 "interaction_mode_escalation_denied",
211 `Child interaction mode ${cause.targetMode} is broader than the caller ceiling ${cause.capturedCallerMode}.`,211 `Child interaction mode ${cause.targetMode} exceeds the caller ceiling (captured ${cause.capturedCallerMode}; current ${cause.currentCallerMode}).`,
212 );212 );
213 }213 }
214 return null;214 return null;
apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts+17−6
@@ -636,12 +636,19 @@ describe("orchestrator MCP toolkit", () => { · 635 unmodified lines
636 ...actual,636 ...actual,
637 withProjectCreationAdmission: (input, effect) =>637 withProjectCreationAdmission: (input, effect) =>
638 actual.withProjectCreationAdmission(input, (receipt) =>638 actual.withProjectCreationAdmission(input, (receipt) =>
639 input.commandId === deletionRaceCommandId && Option.isNone(receipt)639 Effect.gen(function* () {
640 ? Deferred.succeed(creationAdmissionEntered, undefined).pipe(640 if (input.commandId === deletionRaceCommandId && Option.isNone(receipt)) {
641 Effect.andThen(Deferred.await(allowCreationAdmission)),641 yield* Deferred.succeed(creationAdmissionEntered, undefined);
642 Effect.andThen(effect(receipt)),642 yield* Deferred.await(allowCreationAdmission);
643 )643 }
644 : effect(receipt),644 const gate = launchReceiptReadGates.get(input.commandId);
645 if (gate !== undefined) {
646 yield* Deferred.succeed(gate.entered, undefined);
647 yield* Deferred.await(gate.release);
648 launchReceiptReadGates.delete(input.commandId);
649 }
650 return yield* effect(receipt);
651 }),
645 ),652 ),
646 });653 });
647 }),654 }),
@@ -2287,6 +2294,8 @@ describe("orchestrator MCP toolkit", () => { · 1639 unmodified lines
2287 const preCreateCall = yield* Fiber.join(preCreateFiber);2294 const preCreateCall = yield* Fiber.join(preCreateFiber);
2288 expect(preCreateCall.structuredContent).toMatchObject({2295 expect(preCreateCall.structuredContent).toMatchObject({
2289 code: "interaction_mode_escalation_denied",2296 code: "interaction_mode_escalation_denied",
2297 message:
2298 "Child interaction mode default exceeds the caller ceiling (captured default; current plan).",
2290 });2299 });
2291 const preCreateThreadId = ThreadId.make(2300 const preCreateThreadId = ThreadId.make(
2292 `thread:mcp:mcp-provider-session-parent:${preCreateKey}:0`,2301 `thread:mcp:mcp-provider-session-parent:${preCreateKey}:0`,
@@ -2339,6 +2348,8 @@ describe("orchestrator MCP toolkit", () => { · 46 unmodified lines
2339 const betweenCreateAndMessageCall = yield* Fiber.join(betweenCreateAndMessageFiber);2348 const betweenCreateAndMessageCall = yield* Fiber.join(betweenCreateAndMessageFiber);
2340 expect(betweenCreateAndMessageCall.structuredContent).toMatchObject({2349 expect(betweenCreateAndMessageCall.structuredContent).toMatchObject({
2341 code: "runtime_mode_escalation_denied",2350 code: "runtime_mode_escalation_denied",
2351 message:
2352 "Child runtime mode full-access exceeds the caller ceiling (captured full-access; current approval-required).",
2342 });2353 });
2343 const partialProjection = yield* orchestrator.getThreadProjection(2354 const partialProjection = yield* orchestrator.getThreadProjection(
2344 betweenCreateAndMessageThreadId,2355 betweenCreateAndMessageThreadId,

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t