From 1c5dda1bc63532f8a661df40662946d9387f809e Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Mon, 29 Jun 2026 23:35:27 -0500 Subject: [PATCH] feat(core): register mcp prompt commands --- packages/client/src/generated/types.ts | 1 + packages/core/src/config/plugin/command.ts | 1 + packages/core/src/plugin/command.ts | 2 + packages/core/src/plugin/internal.ts | 7 +++ packages/core/src/plugin/mcp-command.ts | 68 ++++++++++++++++++++++ packages/schema/src/command.ts | 1 + packages/sdk/js/src/v2/gen/types.gen.ts | 1 + 7 files changed, 81 insertions(+) create mode 100644 packages/core/src/plugin/mcp-command.ts diff --git a/packages/client/src/generated/types.ts b/packages/client/src/generated/types.ts index cffc6fac5c..cdf420977e 100644 --- a/packages/client/src/generated/types.ts +++ b/packages/client/src/generated/types.ts @@ -2564,6 +2564,7 @@ export type CommandsListOutput = { readonly description?: string readonly agent?: string readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } + readonly source?: "command" | "mcp" | "skill" readonly subtask?: boolean }> } diff --git a/packages/core/src/config/plugin/command.ts b/packages/core/src/config/plugin/command.ts index f9b31f8e45..56e3169cd2 100644 --- a/packages/core/src/config/plugin/command.ts +++ b/packages/core/src/config/plugin/command.ts @@ -31,6 +31,7 @@ export const Plugin = define({ for (const [name, command] of Object.entries(document.commands ?? {})) { draft.update(name, (item) => { item.template = command.template + item.source = "command" if (command.description !== undefined) item.description = command.description if (command.agent !== undefined) item.agent = command.agent if (command.model !== undefined) { diff --git a/packages/core/src/plugin/command.ts b/packages/core/src/plugin/command.ts index cbafd68b50..6b02591aa3 100644 --- a/packages/core/src/plugin/command.ts +++ b/packages/core/src/plugin/command.ts @@ -14,10 +14,12 @@ export const Plugin = define({ draft.update("init", (command) => { command.template = PROMPT_INITIALIZE.replace("${path}", location.project.directory) command.description = "guided AGENTS.md setup" + command.source = "command" }) draft.update("review", (command) => { command.template = PROMPT_REVIEW.replace("${path}", location.project.directory) command.description = "review changes [commit|branch|pr], defaults to uncommitted" + command.source = "command" command.subtask = true }) }) diff --git a/packages/core/src/plugin/internal.ts b/packages/core/src/plugin/internal.ts index d1262372a9..5e1224840c 100644 --- a/packages/core/src/plugin/internal.ts +++ b/packages/core/src/plugin/internal.ts @@ -21,6 +21,7 @@ import { Global } from "../global" import { Integration } from "../integration" import { Location } from "../location" import { ModelsDev } from "../models-dev" +import { MCP } from "../mcp" import { Npm } from "../npm" import { PluginV2 } from "../plugin" import { Reference } from "../reference" @@ -30,6 +31,7 @@ import { FetchHttpClient, HttpClient } from "effect/unstable/http" import { AgentPlugin } from "./agent" import { CommandPlugin } from "./command" import { ModelsDevPlugin } from "./models-dev" +import { MCPCommandPlugin } from "./mcp-command" import { ProviderPlugins } from "./provider" import { SdkPlugins } from "./sdk" import { SkillPlugin } from "./skill" @@ -48,6 +50,7 @@ export type Requirements = | Integration.Service | Location.Service | ModelsDev.Service + | MCP.Service | Npm.Service | Reference.Service | SkillV2.Service @@ -72,6 +75,7 @@ const layer = Layer.effectDiscard( const config = yield* Config.Service const location = yield* Location.Service const modelsDev = yield* ModelsDev.Service + const mcp = yield* MCP.Service const npm = yield* Npm.Service const events = yield* EventV2.Service const fs = yield* FSUtil.Service @@ -94,6 +98,7 @@ const layer = Layer.effectDiscard( Effect.provideService(Config.Service, config), Effect.provideService(Location.Service, location), Effect.provideService(ModelsDev.Service, modelsDev), + Effect.provideService(MCP.Service, mcp), Effect.provideService(Npm.Service, npm), Effect.provideService(EventV2.Service, events), Effect.provideService(FSUtil.Service, fs), @@ -116,6 +121,7 @@ const layer = Layer.effectDiscard( yield* add(ModelsDevPlugin) yield* add(ConfigAgentPlugin.Plugin) yield* add(ConfigCommandPlugin.Plugin) + yield* add(MCPCommandPlugin.Plugin) yield* add(ConfigSkillPlugin.Plugin) for (const item of ProviderPlugins) yield* add(item) yield* add(ConfigExternalPlugin.Plugin) @@ -147,6 +153,7 @@ export const node = makeLocationNode({ Config.node, Location.node, ModelsDev.node, + MCP.node, Npm.node, EventV2.node, FSUtil.node, diff --git a/packages/core/src/plugin/mcp-command.ts b/packages/core/src/plugin/mcp-command.ts new file mode 100644 index 0000000000..344c6e4a2a --- /dev/null +++ b/packages/core/src/plugin/mcp-command.ts @@ -0,0 +1,68 @@ +export * as MCPCommandPlugin from "./mcp-command" + +import { Effect } from "effect" +import { MCP } from "../mcp" +import { define } from "./internal" + +export const Plugin = define({ + id: "mcp-command", + effect: Effect.fn(function* (ctx) { + const mcp = yield* MCP.Service + yield* Effect.gen(function* () { + const prompts = yield* mcp.prompts() + const commands = yield* Effect.forEach( + prompts, + (prompt) => + mcp + .prompt({ + server: prompt.server, + name: prompt.name, + args: Object.fromEntries(prompt.arguments?.map((argument, index) => [argument.name, `$${index + 1}`]) ?? []), + }) + .pipe( + Effect.map((result) => ({ + name: `${sanitize(prompt.server)}:${sanitize(prompt.name)}`, + description: prompt.description, + template: promptText(result), + })), + ), + { concurrency: "unbounded" }, + ) + yield* ctx.command.transform((draft) => { + for (const command of commands) { + const template = command.template + if (!template || draft.get(command.name)) continue + draft.update(command.name, (item) => { + item.template = template + item.source = "mcp" + if (command.description !== undefined) item.description = command.description + }) + } + }) + }).pipe( + Effect.tapError((error) => + Effect.logWarning("failed to register MCP prompt commands", { + error: error instanceof Error ? error.message : String(error), + }), + ), + Effect.ignore, + Effect.forkScoped, + ) + }), +}) + +function promptText(result: MCP.PromptResult | undefined) { + const text = result?.messages + .flatMap((message) => { + const content = message.content + if (!content || typeof content !== "object") return [] + if (!("type" in content) || content.type !== "text") return [] + if (!("text" in content) || typeof content.text !== "string") return [] + return [content.text] + }) + .join("\n") + .trim() + return text || undefined +} + +const sanitize = (value: string) => value.replace(/[^a-zA-Z0-9_-]/g, "_") diff --git a/packages/schema/src/command.ts b/packages/schema/src/command.ts index 2bdf3a95c5..d689cc7a5b 100644 --- a/packages/schema/src/command.ts +++ b/packages/schema/src/command.ts @@ -11,5 +11,6 @@ export const Info = Schema.Struct({ description: Schema.String.pipe(optional), agent: Schema.String.pipe(optional), model: Model.Ref.pipe(optional), + source: Schema.Literals(["command", "mcp", "skill"]).pipe(optional), subtask: Schema.Boolean.pipe(optional), }).annotate({ identifier: "CommandV2.Info" }) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 9259dad941..60b78a5a8f 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -5145,6 +5145,7 @@ export type CommandV2Info = { description?: string agent?: string model?: ModelRef + source?: "command" | "mcp" | "skill" subtask?: boolean }