Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/server/src/cli/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ const EnvServerConfig = Config.all({
Config.map(Option.getOrUndefined),
),
managedDevPc: Config.boolean("T3CODE_MANAGED_DEVPC").pipe(Config.withDefault(false)),
managedGatewayToken: Config.string("WORKSPACE_GATEWAY_TOKEN").pipe(
Config.option,
Config.map(Option.getOrUndefined),
),
bootstrapFd: Config.int("T3CODE_BOOTSTRAP_FD").pipe(
Config.option,
Config.map(Option.getOrUndefined),
Expand Down Expand Up @@ -366,6 +370,7 @@ export const resolveServerConfig = (
devUrl,
noBrowser,
managedDevPc: env.managedDevPc,
managedGatewayToken: env.managedGatewayToken,
startupPresentation,
desktopBootstrapToken,
autoBootstrapProjectFromCwd,
Expand Down
5 changes: 5 additions & 0 deletions apps/server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ export class ServerConfig extends Context.Service<
readonly devUrl: URL | undefined;
readonly noBrowser: boolean;
readonly managedDevPc: boolean;
/**
* Shared only with Aldo's loopback workspace gateway. It authenticates
* managed automation routes that are never exposed by standalone T3 Code.
*/
readonly managedGatewayToken?: string | undefined;
readonly startupPresentation: StartupPresentation;
readonly desktopBootstrapToken: string | undefined;
readonly autoBootstrapProjectFromCwd: boolean;
Expand Down
11 changes: 10 additions & 1 deletion apps/server/src/mcp/McpHttpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
PreviewSnapshotToolkit,
PreviewStandardToolkit,
} from "./toolkits/preview/tools.ts";
import { GrokReviewToolkitHandlersLive } from "./toolkits/review/handlers.ts";
import { GrokReviewToolkit } from "./toolkits/review/tools.ts";

const unauthorized = HttpServerResponse.jsonUnsafe(
{
Expand Down Expand Up @@ -208,10 +210,17 @@ export const PreviewToolkitRegistrationLive = Layer.mergeAll(
PreviewSnapshotRegistrationLive,
);

export const GrokReviewToolkitRegistrationLive = McpServer.toolkit(GrokReviewToolkit).pipe(
Layer.provide(GrokReviewToolkitHandlersLive),
);

const McpTransportLive = McpServer.layerHttp({
name: "T3 Code",
version: packageJson.version,
path: "/mcp",
}).pipe(Layer.provide(McpAuthMiddlewareLive));

export const layer = PreviewToolkitRegistrationLive.pipe(Layer.provideMerge(McpTransportLive));
export const layer = Layer.mergeAll(
PreviewToolkitRegistrationLive,
GrokReviewToolkitRegistrationLive,
).pipe(Layer.provideMerge(McpTransportLive));
19 changes: 17 additions & 2 deletions apps/server/src/mcp/McpInvocationContext.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import {
type EnvironmentId,
GrokReviewUnavailableError,
PreviewAutomationUnavailableError,
type ProviderInstanceId,
type ThreadId,
} from "@t3tools/contracts";
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";

export type McpCapability = "preview";
export type McpCapability = "preview" | "review";

export interface McpInvocationScope {
readonly environmentId: EnvironmentId;
Expand All @@ -25,7 +26,7 @@ export class McpInvocationContext extends Context.Service<
>()("t3/mcp/McpInvocationContext") {}

export const requireMcpCapability = Effect.fn("mcp.requireCapability")(function* (
capability: McpCapability,
capability: "preview",
) {
const invocation = yield* McpInvocationContext;
if (!invocation.capabilities.has(capability)) {
Expand All @@ -39,3 +40,17 @@ export const requireMcpCapability = Effect.fn("mcp.requireCapability")(function*
}
return invocation;
});

export const requireMcpReviewCapability = Effect.fn("mcp.requireReviewCapability")(function* () {
const invocation = yield* McpInvocationContext;
if (!invocation.capabilities.has("review")) {
return yield* new GrokReviewUnavailableError({
capability: "review",
environmentId: invocation.environmentId,
threadId: invocation.threadId,
providerSessionId: invocation.providerSessionId,
providerInstanceId: invocation.providerInstanceId,
});
}
return invocation;
});
15 changes: 15 additions & 0 deletions apps/server/src/mcp/McpSessionRegistry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ it.effect("stores only a token hash, resolves the bearer token, and revokes by t

const resolved = yield* registry.resolve(token);
expect(resolved?.threadId).toBe(threadId);
expect(resolved?.capabilities).toEqual(new Set(["preview"]));

yield* registry.revokeThread(threadId);
expect(yield* registry.resolve(token)).toBeUndefined();
Expand All @@ -55,6 +56,20 @@ it.effect("stores only a token hash, resolves the bearer token, and revokes by t
}),
);

it.effect("grants review only when explicitly requested", () =>
Effect.gen(function* () {
const registry = yield* makeRegistry(() => 1_000);
const issued = yield* registry.issue({
threadId: ThreadId.make("thread-review"),
providerInstanceId: ProviderInstanceId.make("grok"),
grantReview: true,
});
const token = issued.config.authorizationHeader.replace(/^Bearer\s+/, "");

expect((yield* registry.resolve(token))?.capabilities).toEqual(new Set(["preview", "review"]));
}),
);

it.effect("builds MCP endpoints from the bound server host", () =>
Effect.gen(function* () {
const cases = [
Expand Down
6 changes: 5 additions & 1 deletion apps/server/src/mcp/McpSessionRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as McpProviderSession from "./McpProviderSession.ts";
export interface McpCredentialRequest {
readonly threadId: ThreadId;
readonly providerInstanceId: ProviderInstanceId;
readonly grantReview?: boolean;
}

export interface McpIssuedCredential {
Expand Down Expand Up @@ -114,7 +115,10 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (
threadId: ThreadId.make(request.threadId),
providerSessionId,
providerInstanceId: ProviderInstanceId.make(request.providerInstanceId),
capabilities: new Set(["preview"]),
capabilities: new Set<McpInvocationContext.McpCapability>([
"preview",
...(request.grantReview ? (["review"] as const) : []),
]),
issuedAt,
expiresAt,
};
Expand Down
14 changes: 14 additions & 0 deletions apps/server/src/mcp/toolkits/review/handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as Effect from "effect/Effect";

import * as McpInvocationContext from "../../McpInvocationContext.ts";
import * as GrokReviewService from "../../../review/GrokReviewService.ts";
import { GrokReviewToolkit } from "./tools.ts";

export const GrokReviewToolkitHandlersLive = GrokReviewToolkit.toLayer({
grok_review: (input) =>
Effect.gen(function* () {
yield* McpInvocationContext.requireMcpReviewCapability();
const review = yield* GrokReviewService.GrokReviewService;
return yield* review.run(input);
}),
});
113 changes: 113 additions & 0 deletions apps/server/src/mcp/toolkits/review/tools.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { expect, it } from "@effect/vitest";
import {
EnvironmentId,
ProviderInstanceId,
ThreadId,
type GrokReviewReport,
} from "@t3tools/contracts";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import { McpSchema, McpServer } from "effect/unstable/ai";

import * as McpInvocationContext from "../../McpInvocationContext.ts";
import * as GrokReviewService from "../../../review/GrokReviewService.ts";
import { GrokReviewToolkitHandlersLive } from "./handlers.ts";
import { GrokReviewToolkit } from "./tools.ts";

const report: GrokReviewReport = {
schemaVersion: 1,
runId: "review-run",
target: {
kind: "working-tree",
baseRef: "HEAD",
headRef: null,
diffHash: "working-hash",
},
status: "pass",
resolvedModel: "grok-4.5",
grokBuildVersion: "0.2.112",
reasoningEffort: "medium",
escalatedToHigh: false,
summary: "No actionable findings.",
findings: [],
coverage: ["Diff"],
limitations: [],
usage: { agentRuns: 5, mediumEffortRuns: 5, highEffortRuns: 0 },
markdown: "## Aldo Grok review",
};

const invocation = {
environmentId: EnvironmentId.make("environment-review-test"),
threadId: ThreadId.make("thread-review-test"),
providerSessionId: "provider-session-review-test",
providerInstanceId: ProviderInstanceId.make("codex"),
capabilities: new Set(["review"] as const),
issuedAt: 1,
expiresAt: Number.MAX_SAFE_INTEGER,
};

const client = McpSchema.McpServerClient.of({
clientId: 1,
initializePayload: {
protocolVersion: "2025-03-26",
capabilities: {},
clientInfo: { name: "mcp-review-test", version: "1.0.0" },
},
getClient: Effect.die("unused"),
});

const TestLayer = McpServer.toolkit(GrokReviewToolkit).pipe(
Layer.provide(GrokReviewToolkitHandlersLive),
Layer.provideMerge(McpServer.McpServer.layer),
Layer.provide(
Layer.mock(GrokReviewService.GrokReviewService)({
run: () => Effect.succeed(report),
}),
),
);

it.effect("registers a read-only grok_review tool and returns the canonical report", () =>
Effect.gen(function* () {
const server = yield* McpServer.McpServer;
const registered = server.tools.find(({ tool }) => tool.name === "grok_review");
expect(registered?.tool.annotations?.readOnlyHint).toBe(true);
expect(registered?.tool.annotations?.destructiveHint).toBe(false);

const result = yield* server
.callTool({
name: "grok_review",
arguments: { cwd: "/workspace/project", target: "working-tree" },
})
.pipe(
Effect.provideService(McpInvocationContext.McpInvocationContext, invocation),
Effect.provideService(McpSchema.McpServerClient, client),
);

expect(result.isError).toBe(false);
expect(result.structuredContent).toMatchObject({
runId: "review-run",
resolvedModel: "grok-4.5",
reasoningEffort: "medium",
});
}).pipe(Effect.provide(TestLayer)),
);
Comment thread
logancsack marked this conversation as resolved.

it.effect("rejects a session without the review capability", () =>
Effect.gen(function* () {
const server = yield* McpServer.McpServer;
const result = yield* server
.callTool({
name: "grok_review",
arguments: { cwd: "/workspace/project", target: "working-tree" },
})
.pipe(
Effect.provideService(McpInvocationContext.McpInvocationContext, {
...invocation,
capabilities: new Set(["preview"] as const),
}),
Effect.provideService(McpSchema.McpServerClient, client),
);

expect(result.isError).toBe(true);
}).pipe(Effect.provide(TestLayer)),
);
21 changes: 21 additions & 0 deletions apps/server/src/mcp/toolkits/review/tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { GrokReviewInput, GrokReviewReport, GrokReviewRunError } from "@t3tools/contracts";
import { Tool, Toolkit } from "effect/unstable/ai";

import * as McpInvocationContext from "../../McpInvocationContext.ts";
import * as GrokReviewService from "../../../review/GrokReviewService.ts";

export const GrokReviewTool = Tool.make("grok_review", {
description:
"Run Aldo's bounded Grok 4.5 review swarm against a working tree or branch range. The swarm uses medium reasoning by default, escalates ambiguous severe findings to high reasoning, and returns the same canonical report used for pull-request reviews.",
Comment thread
logancsack marked this conversation as resolved.
parameters: GrokReviewInput,
success: GrokReviewReport,
failure: GrokReviewRunError,
dependencies: [McpInvocationContext.McpInvocationContext, GrokReviewService.GrokReviewService],
})
.annotate(Tool.Title, "Run Grok review swarm")
.annotate(Tool.Readonly, true)
.annotate(Tool.Destructive, false)
.annotate(Tool.Idempotent, false)
.annotate(Tool.OpenWorld, false);

export const GrokReviewToolkit = Toolkit.make(GrokReviewTool);
5 changes: 5 additions & 0 deletions apps/server/src/provider/Drivers/GrokDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { HttpClient } from "effect/unstable/http";
import { ChildProcessSpawner } from "effect/unstable/process";

import { ServerConfig } from "../../config.ts";
import { makeGrokCodeReview } from "../../review/GrokCodeReview.ts";
import { ServerSettingsService } from "../../serverSettings.ts";
import { makeGrokTextGeneration } from "../../textGeneration/GrokTextGeneration.ts";
import { ProviderDriverError } from "../Errors.ts";
Expand Down Expand Up @@ -112,6 +113,9 @@ export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv> = {
instanceId,
});
const textGeneration = yield* makeGrokTextGeneration(effectiveConfig, processEnv);
const codeReview = enabled
? yield* makeGrokCodeReview(effectiveConfig, processEnv)
: undefined;

const checkProvider = checkGrokProviderStatus(effectiveConfig, processEnv).pipe(
Effect.map(stampIdentity),
Expand Down Expand Up @@ -159,6 +163,7 @@ export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv> = {
snapshot,
adapter,
textGeneration,
...(codeReview ? { codeReview } : {}),
} satisfies ProviderInstance;
}),
};
6 changes: 5 additions & 1 deletion apps/server/src/provider/Layers/ProviderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,11 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (
const runtimeEventPubSub = yield* PubSub.unbounded<ProviderRuntimeEvent>();
const nowIso = Effect.map(DateTime.now, DateTime.formatIso);
const prepareMcpSession = (threadId: ThreadId, providerInstanceId: ProviderInstanceId) =>
McpSessionRegistry.issueActiveMcpCredential({ threadId, providerInstanceId }).pipe(
McpSessionRegistry.issueActiveMcpCredential({
threadId,
providerInstanceId,
grantReview: true,
}).pipe(
Effect.tap((credential) =>
credential
? Effect.sync(() => McpProviderSession.setMcpProviderSession(credential.config))
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/provider/ProviderDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type * as Schema from "effect/Schema";
import type * as Scope from "effect/Scope";

import type * as TextGeneration from "../textGeneration/TextGeneration.ts";
import type { CodeReviewRunner } from "../review/CodeReviewRunner.ts";
import type { ProviderAdapterError, ProviderDriverError } from "./Errors.ts";
import type { ProviderAdapterShape } from "./Services/ProviderAdapter.ts";
import type { ServerProviderShape } from "./Services/ServerProvider.ts";
Expand Down Expand Up @@ -71,6 +72,7 @@ export interface ProviderInstance {
readonly snapshot: ServerProviderShape;
readonly adapter: ProviderAdapterShape<ProviderAdapterError>;
readonly textGeneration: TextGeneration.TextGeneration["Service"];
readonly codeReview?: CodeReviewRunner | undefined;
}

export interface ProviderContinuationIdentity {
Expand Down
14 changes: 14 additions & 0 deletions apps/server/src/review/CodeReviewRunner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type {
GrokReviewError,
GrokReviewInput,
GrokReviewReport,
ReviewDiffPreviewSource,
} from "@t3tools/contracts";
import type * as Effect from "effect/Effect";

export interface CodeReviewRunner {
readonly run: (input: {
readonly request: GrokReviewInput;
readonly source: ReviewDiffPreviewSource;
}) => Effect.Effect<GrokReviewReport, GrokReviewError>;
}
19 changes: 19 additions & 0 deletions apps/server/src/review/GrokCodeReview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { GrokSettings } from "@t3tools/contracts";
import * as Crypto from "effect/Crypto";
import * as Effect from "effect/Effect";

import type { CodeReviewRunner } from "./CodeReviewRunner.ts";
import { makeGrokReviewAgent } from "./GrokReviewAgent.ts";
import { runGrokReviewSwarm } from "./GrokReviewSwarm.ts";

export const makeGrokCodeReview = Effect.fn("makeGrokCodeReview")(function* (
grokSettings: GrokSettings,
environment: NodeJS.ProcessEnv = process.env,
) {
const crypto = yield* Crypto.Crypto;
const agent = yield* makeGrokReviewAgent(grokSettings, environment);
return {
run: (input) =>
runGrokReviewSwarm({ ...input, agent }).pipe(Effect.provideService(Crypto.Crypto, crypto)),
} satisfies CodeReviewRunner;
});
Loading
Loading