Skip to content

Commit 2361d48

Browse files
committed
feat(workflows): output schema MCP
1 parent 634ef13 commit 2361d48

5 files changed

Lines changed: 346 additions & 2 deletions

File tree

apps/code/src/main/services/agent/service.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ describe("AgentService", () => {
349349
service.recordActivity("run-1");
350350
const secondDeadline = getIdleTimeouts(service).get("run-1")?.deadline;
351351

352-
expect(secondDeadline).toBeGreaterThan(firstDeadline!);
352+
expect(secondDeadline).toBeGreaterThan(firstDeadline as number);
353353
});
354354

355355
it("kills idle session after timeout expires", () => {

packages/agent/src/server/agent-server.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { AsyncMutex } from "../utils/async-mutex";
3131
import { getLlmGatewayUrl } from "../utils/gateway";
3232
import { Logger } from "../utils/logger";
3333
import { type JwtPayload, JwtValidationError, validateJwt } from "./jwt";
34+
import { ResultMcpServer } from "./result-mcp-server.js";
3435
import { jsonRpcRequestSchema, validateCommandParams } from "./schemas";
3536
import type { AgentServerConfig } from "./types";
3637

@@ -162,6 +163,7 @@ export class AgentServer {
162163
private questionRelayedToSlack = false;
163164
private detectedPrUrl: string | null = null;
164165
private resumeState: ResumeState | null = null;
166+
private resultMcpServer: ResultMcpServer | null = null;
165167

166168
private emitConsoleLog = (
167169
level: LogLevel,
@@ -379,6 +381,17 @@ export class AgentServer {
379381
);
380382
});
381383

384+
if (this.config.outputSchema) {
385+
this.resultMcpServer = new ResultMcpServer({
386+
port: this.config.port + 1,
387+
outputSchema: this.config.outputSchema,
388+
taskId: this.config.taskId,
389+
runId: this.config.runId,
390+
posthogAPI: this.posthogAPI,
391+
});
392+
await this.resultMcpServer.start();
393+
}
394+
382395
await this.autoInitializeSession();
383396
}
384397

@@ -435,6 +448,11 @@ export class AgentServer {
435448
await this.cleanupSession();
436449
}
437450

451+
if (this.resultMcpServer) {
452+
await this.resultMcpServer.stop();
453+
this.resultMcpServer = null;
454+
}
455+
438456
if (this.server) {
439457
this.server.close();
440458
this.server = null;
@@ -633,9 +651,19 @@ export class AgentServer {
633651
this.detectedPrUrl = prUrl;
634652
}
635653

654+
const mcpServers = [...(this.config.mcpServers ?? [])];
655+
if (this.resultMcpServer) {
656+
mcpServers.push({
657+
type: "http",
658+
name: "posthog-result",
659+
url: this.resultMcpServer.url,
660+
headers: [],
661+
});
662+
}
663+
636664
const sessionResponse = await clientConnection.newSession({
637665
cwd: this.config.repositoryPath ?? "/tmp/workspace",
638-
mcpServers: this.config.mcpServers ?? [],
666+
mcpServers,
639667
_meta: {
640668
sessionId: payload.run_id,
641669
taskRunId: payload.run_id,
@@ -777,6 +805,17 @@ export class AgentServer {
777805
if (result.stopReason === "end_turn") {
778806
await this.relayAgentResponse(payload);
779807
}
808+
809+
// If structured output was submitted, flush logs first then mark completed
810+
if (this.resultMcpServer?.isResultSubmitted) {
811+
if (this.session) {
812+
await this.session.logWriter.flushAll();
813+
}
814+
await this.posthogAPI.updateTaskRun(payload.task_id, payload.run_id, {
815+
status: "completed",
816+
});
817+
this.logger.info("Task marked completed after submit_result");
818+
}
780819
} catch (error) {
781820
this.logger.error("Failed to send initial task message", error);
782821
if (this.session) {
@@ -945,6 +984,27 @@ export class AgentServer {
945984
}
946985

947986
private buildCloudSystemPrompt(prUrl?: string | null): string {
987+
// When outputSchema is provided, the agent's job is to gather information
988+
// and submit structured results — not create PRs or branches.
989+
if (this.config.outputSchema) {
990+
return `
991+
# Structured Output Task
992+
993+
You have access to a tool called \`submit_result\` (from the "posthog-result" MCP server).
994+
Your job is to complete the task described in the user message, then call \`submit_result\` with the result data.
995+
996+
The output schema is:
997+
\`\`\`json
998+
${JSON.stringify(this.config.outputSchema, null, 2)}
999+
\`\`\`
1000+
1001+
Instructions:
1002+
1. Read the task carefully and gather all the information needed.
1003+
2. Once you have the answer, call the \`submit_result\` tool with a \`data\` argument that conforms to the schema above.
1004+
3. You MUST call \`submit_result\` exactly once before finishing.
1005+
`;
1006+
}
1007+
9481008
if (prUrl) {
9491009
return `
9501010
# Cloud Task Execution

packages/agent/src/server/bin.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ program
5151
"MCP servers config as JSON array (ACP McpServer[] format)",
5252
)
5353
.option("--baseBranch <branch>", "Base branch for PR creation")
54+
.option(
55+
"--outputSchema <json>",
56+
"JSON Schema for structured output (enables submit_result MCP tool)",
57+
)
5458
.action(async (options) => {
5559
const envResult = envSchema.safeParse(process.env);
5660

@@ -89,6 +93,16 @@ program
8993
mcpServers = result.data;
9094
}
9195

96+
let outputSchema: Record<string, unknown> | undefined;
97+
if (options.outputSchema) {
98+
try {
99+
outputSchema = JSON.parse(options.outputSchema);
100+
} catch {
101+
program.error("--outputSchema must be valid JSON");
102+
return;
103+
}
104+
}
105+
92106
const server = new AgentServer({
93107
port: parseInt(options.port, 10),
94108
jwtPublicKey: env.JWT_PUBLIC_KEY,
@@ -101,6 +115,7 @@ program
101115
runId: options.runId,
102116
mcpServers,
103117
baseBranch: options.baseBranch,
118+
outputSchema,
104119
});
105120

106121
process.on("SIGINT", async () => {

0 commit comments

Comments
 (0)