From 660644f7cb32d95fb8c7945c18d0a63e5004e715 Mon Sep 17 00:00:00 2001 From: dario-nunez Date: Tue, 14 Jul 2026 08:53:03 +0100 Subject: [PATCH] Add report_dreaming_artifact tool to engine-tools MCP server Adds a report_dreaming_artifact MCP tool so dream jobs can hand a generated artifact back to the platform over the existing job-progress channel, without exposing the engine token to the model. - client.ts: DreamingArtifact type, DREAMING_* progress constants, and PlatformClient.sendDreamingArtifact() which posts on the first-party "dreaming" progress namespace (not the default sessions-v2 namespace). - mcp-server.ts: register report_dreaming_artifact, mirroring reply_to_comment; guards on platformClient and enforces a MAX_DREAMING_ARTIFACT_CONTENT_BYTES size cap before sending. - index.ts: export the new public symbols. Tool name, payload schema, and size limit are provisional M0 defaults (github/agents issue 1476) and may change once finalized. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/client.ts | 40 +++++++++++++++++++++ src/index.ts | 9 +++-- src/mcp-server.ts | 91 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 137 insertions(+), 3 deletions(-) diff --git a/src/client.ts b/src/client.ts index 7a5000f..40ba456 100644 --- a/src/client.ts +++ b/src/client.ts @@ -57,6 +57,29 @@ export interface ProgressPayload { content: string; } +/** Progress namespace used for Dreaming artifacts. Downstream consumers filter on this. */ +export const DREAMING_PROGRESS_NAMESPACE = "dreaming"; +/** Progress kind used for Dreaming artifacts. */ +export const DREAMING_ARTIFACT_KIND = "artifact"; +/** Progress payload version for Dreaming artifacts. */ +export const DREAMING_ARTIFACT_VERSION = 0; + +/** + * A Dreaming artifact produced by a dream job and reported back to the platform. + * + * NOTE: This shape is provisional (see github/agents issue 1476). The fields + * below are a reasonable M0 default and may change once the artifact schema is + * finalized. + */ +export interface DreamingArtifact { + /** Short human-readable title for the artifact. */ + title: string; + /** Markdown summary describing what the dream produced. */ + summary: string; + /** Optional structured payload for machine consumers. */ + data?: Record; +} + /** * Response from the progress API (may contain user messages for mid-session steering). */ @@ -370,6 +393,23 @@ export class PlatformClient { } } + /** + * Sends a Dreaming artifact to the platform via the job-progress callback. + * + * Uses the first-party "dreaming" progress namespace (not this.namespace) so + * downstream consumers can filter for it. The platform token and nonce stay + * inside this trusted process; the artifact travels as the progress payload + * content. + */ + async sendDreamingArtifact(artifact: DreamingArtifact): Promise { + return this.sendRawProgress({ + namespace: DREAMING_PROGRESS_NAMESPACE, + kind: DREAMING_ARTIFACT_KIND, + version: DREAMING_ARTIFACT_VERSION, + content: JSON.stringify(artifact), + }); + } + // ========================================================================= // Generic Progress API // ========================================================================= diff --git a/src/index.ts b/src/index.ts index f827fb8..081e888 100644 --- a/src/index.ts +++ b/src/index.ts @@ -91,9 +91,9 @@ export type { // Platform Client // ============================================================================= -export { PlatformClient, resolveSelectedModel, isModelSelectionEnabled } from "./client.js"; +export { PlatformClient, resolveSelectedModel, isModelSelectionEnabled, DREAMING_PROGRESS_NAMESPACE, DREAMING_ARTIFACT_KIND, DREAMING_ARTIFACT_VERSION } from "./client.js"; -export type { PlatformClientConfig, ProgressPayload, ProgressRecord, ProgressResponse, SendResult, JobDetails, ProblemStatement, ResolveSelectedModelOptions } from "./client.js"; +export type { PlatformClientConfig, ProgressPayload, ProgressRecord, ProgressResponse, SendResult, JobDetails, ProblemStatement, ResolveSelectedModelOptions, DreamingArtifact } from "./client.js"; // ============================================================================= // MCP Server @@ -105,11 +105,16 @@ export { REPORT_PROGRESS_TOOL_NAME, reportProgressToolDescription, reportProgressInputSchema, + REPORT_DREAMING_ARTIFACT_TOOL_NAME, + reportDreamingArtifactToolDescription, + reportDreamingArtifactInputSchema, + MAX_DREAMING_ARTIFACT_CONTENT_BYTES, } from "./mcp-server.js"; export type { EngineMcpServerConfig, ReportProgressInput, + ReportDreamingArtifactInput, } from "./mcp-server.js"; // ============================================================================= diff --git a/src/mcp-server.ts b/src/mcp-server.ts index c5f7ec1..cb36354 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -10,6 +10,8 @@ * * Tools provided: * - report_progress: Commits changes and reports progress to the platform + * - reply_to_comment: Posts a reply to a PR comment + * - report_dreaming_artifact: Reports a Dreaming artifact back to the platform * * Usage: * // Start as standalone server @@ -26,7 +28,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { commitAndPush } from "./git.js"; -import type { PlatformClient } from "./client.js"; +import type { PlatformClient, DreamingArtifact } from "./client.js"; // ============================================================================= // Debug Logging (writes to file since stdout is used for MCP protocol) @@ -100,6 +102,30 @@ export const replyToCommentToolDescription = `Reply to a pull request comment or * Use this to respond to reviewer feedback or questions * The reply will be visible on the pull request as a comment from the agent`; +export const REPORT_DREAMING_ARTIFACT_TOOL_NAME = "report_dreaming_artifact"; + +/** + * Maximum size (in bytes) of the serialized artifact content. Progress payloads + * are forwarded onto a hydro topic with a bounded message size, so the tool + * rejects oversized artifacts rather than failing downstream. Provisional value + * (see github/agents issue 1476). + */ +export const MAX_DREAMING_ARTIFACT_CONTENT_BYTES = 256 * 1024; + +export const reportDreamingArtifactInputSchema = z.object({ + title: z.string().describe("Short human-readable title for the artifact"), + summary: z.string().describe("Markdown summary describing what the dream produced"), + data: z.record(z.string(), z.unknown()).optional().describe("Optional structured payload for machine consumers"), +}); + +export type ReportDreamingArtifactInput = z.infer; + +export const reportDreamingArtifactToolDescription = `Report a Dreaming artifact produced by this job back to the platform. +* Call this when the dream has produced a result worth persisting (e.g. a summary of findings or decisions) +* Provide a short title and a markdown summary; include structured details in data when useful +* The artifact is sent to the platform over the job-progress channel; do not include secrets +* Keep the payload small; oversized artifacts are rejected`; + // ============================================================================= // Tool Implementation // ============================================================================= @@ -267,6 +293,69 @@ export function createEngineMcpServer(config: EngineMcpServerConfig): McpServer } ); + // Register the report_dreaming_artifact tool + server.tool( + REPORT_DREAMING_ARTIFACT_TOOL_NAME, + reportDreamingArtifactToolDescription, + reportDreamingArtifactInputSchema.shape, + async (params: ReportDreamingArtifactInput) => { + log("report_dreaming_artifact called", { title: params.title }); + + if (!config.platformClient) { + log("report_dreaming_artifact: no platform client available"); + return { + content: [{ type: "text" as const, text: "Cannot report artifact: platform client not configured" }], + isError: true, + }; + } + + const artifact: DreamingArtifact = { + title: params.title, + summary: params.summary, + ...(params.data !== undefined ? { data: params.data } : {}), + }; + + const contentBytes = Buffer.byteLength(JSON.stringify(artifact), "utf8"); + if (contentBytes > MAX_DREAMING_ARTIFACT_CONTENT_BYTES) { + log("report_dreaming_artifact: payload too large", { contentBytes }); + return { + content: [ + { + type: "text" as const, + text: `Artifact too large: ${contentBytes} bytes exceeds the ${MAX_DREAMING_ARTIFACT_CONTENT_BYTES} byte limit. Shorten the summary or move detail into a smaller data payload.`, + }, + ], + isError: true, + }; + } + + try { + const result = await config.platformClient.sendDreamingArtifact(artifact); + + if (result.success) { + log("report_dreaming_artifact: sent successfully"); + return { + content: [{ type: "text" as const, text: "Artifact reported successfully." }], + isError: false, + }; + } + + log("report_dreaming_artifact: failed", { error: result.error?.message }); + return { + content: [{ type: "text" as const, text: `Failed to report artifact: ${result.error?.message}` }], + isError: true, + }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + log("report_dreaming_artifact: error", { error: errorMessage }); + return { + content: [{ type: "text" as const, text: `Error reporting artifact: ${errorMessage}` }], + isError: true, + }; + } + } + ); + return server; }