From 7537458bf50d642f518d1d527a59945f9721131f Mon Sep 17 00:00:00 2001 From: yao <63141491+yaonyan@users.noreply.github.com> Date: Sun, 2 Nov 2025 17:29:59 +0800 Subject: [PATCH] feat: implement agentic and workflow execution modes with corresponding plugins fix: wrong placeholder handling of planning prompt --- packages/core/mod.ts | 12 +++++ packages/core/src/compose.ts | 52 +++++++++---------- .../src/executors/agentic/agentic-executor.ts | 2 + packages/core/src/plugin-types.ts | 41 +++++++++++++-- packages/core/src/plugin-utils.ts | 5 +- packages/core/src/plugins/built-in/index.ts | 6 +++ .../plugins/built-in/mode-agentic-plugin.ts | 30 +++++++++++ .../plugins/built-in/mode-workflow-plugin.ts | 33 ++++++++++++ packages/core/src/set-up-mcp-compose.ts | 3 +- packages/core/src/utils/plugin-manager.ts | 39 ++++++++++++++ 10 files changed, 188 insertions(+), 35 deletions(-) create mode 100644 packages/core/src/plugins/built-in/mode-agentic-plugin.ts create mode 100644 packages/core/src/plugins/built-in/mode-workflow-plugin.ts diff --git a/packages/core/mod.ts b/packages/core/mod.ts index 269f091..9343a47 100644 --- a/packages/core/mod.ts +++ b/packages/core/mod.ts @@ -50,6 +50,18 @@ export * from "./src/compose.ts"; +// Plugin system +export type { + AgentToolRegistrationContext, + ComposedTool, + ComposeEndContext, + ComposeStartContext, + FinalizeContext, + ToolConfig, + ToolPlugin, + TransformContext, +} from "./src/plugin-types.ts"; + export * from "./src/utils/common/env.ts"; export * from "./src/utils/common/json.ts"; export * from "./src/utils/common/mcp.ts"; diff --git a/packages/core/src/compose.ts b/packages/core/src/compose.ts index faa4b14..fe1e25d 100644 --- a/packages/core/src/compose.ts +++ b/packages/core/src/compose.ts @@ -17,14 +17,13 @@ import { parseTags } from "@mcpc/utils"; import { composeMcpDepTools } from "./utils/common/mcp.ts"; import type { ComposeDefinition } from "./set-up-mcp-compose.ts"; import type { JSONSchema, ToolCallback } from "./types.ts"; -import { registerAgenticTool } from "./executors/agentic/agentic-tool-registrar.ts"; -import { registerAgenticWorkflowTool } from "./executors/workflow/workflow-tool-registrar.ts"; import { processToolTags } from "./utils/common/tool-tag-processor.ts"; import { getBuiltInPlugins } from "./plugins/built-in/index.ts"; import { createLogger } from "./utils/logger.ts"; // Import plugin types and utilities import type { ComposedTool, ToolConfig, ToolPlugin } from "./plugin-types.ts"; +import type { ExecutionMode } from "./prompts/types.ts"; import { sortPluginsByOrder, validatePlugins } from "./plugin-utils.ts"; // Import new manager modules @@ -343,7 +342,7 @@ export class ComposableMCPServer extends Server { */ private async processToolsWithPlugins( externalTools: Record, - mode: "agentic" | "agentic_workflow", + mode: ExecutionMode, ): Promise { const { processToolsWithPlugins: processTools } = await import( "./utils/compose-helpers.ts" @@ -595,30 +594,29 @@ export class ComposableMCPServer extends Server { this, ); - switch (options.mode ?? "agentic") { - case "agentic": - registerAgenticTool(this, { - description, - name, - allToolNames, - depGroups, - toolNameToDetailList, - sampling: options.sampling, - }); - break; - case "agentic_workflow": - registerAgenticWorkflowTool(this, { - description, - name, - allToolNames, - depGroups, - toolNameToDetailList, - predefinedSteps: options.steps, - sampling: options.sampling, - ensureStepActions: options.ensureStepActions, - toolNameToIdMapping, - }); - break; + const mode = options.mode ?? "agentic"; + const context = { + server: this, + name, + description, + mode, + allToolNames, + toolNameToDetailList, + depGroups, + toolNameToIdMapping, + publicToolNames, + hiddenToolNames, + options, + }; + + // Trigger registerAgentTool hook - last plugin wins + const handled = await this.pluginManager.triggerRegisterAgentTool(context); + + if (!handled) { + throw new Error( + `No plugin registered to handle execution mode "${mode}". ` + + `Did you override the default mode plugin, but in the wrong way?`, + ); } } } diff --git a/packages/core/src/executors/agentic/agentic-executor.ts b/packages/core/src/executors/agentic/agentic-executor.ts index bd78007..53c7666 100644 --- a/packages/core/src/executors/agentic/agentic-executor.ts +++ b/packages/core/src/executors/agentic/agentic-executor.ts @@ -154,6 +154,7 @@ export class AgenticExecutor { type: "text", text: CompiledPrompts.planningPrompt({ currentAction: actionName, + toolName: this.name, }), }); } @@ -209,6 +210,7 @@ export class AgenticExecutor { type: "text", text: CompiledPrompts.planningPrompt({ currentAction: actionName, + toolName: this.name, }), }); } diff --git a/packages/core/src/plugin-types.ts b/packages/core/src/plugin-types.ts index 0d2a5d1..44e5971 100644 --- a/packages/core/src/plugin-types.ts +++ b/packages/core/src/plugin-types.ts @@ -6,6 +6,7 @@ import type { Tool } from "@modelcontextprotocol/sdk/types.js"; import type { ToolCallback } from "./types.ts"; import type { ComposableMCPServer } from "./compose.ts"; +import type { ExecutionMode } from "./prompts/types.ts"; export interface ComposedTool extends Tool { execute: ToolCallback; @@ -24,7 +25,7 @@ export interface ToolPlugin { enforce?: "pre" | "post"; /** Apply plugin conditionally based on mode */ - apply?: "agentic" | "workflow" | ((mode: string) => boolean); + apply?: ExecutionMode | ((mode: string) => boolean); /** Plugin dependencies - names of plugins that must be loaded first */ dependencies?: string[]; @@ -49,6 +50,15 @@ export interface ToolPlugin { context: FinalizeContext, ) => void | Promise; + /** + * Called to register the final agent tool (execution mode). + * If multiple plugins implement this, the last one wins. + * Use this to implement custom execution modes. + */ + registerAgentTool?: ( + context: AgentToolRegistrationContext, + ) => void | Promise; + /** Called after composition is complete - for logging and cleanup */ composeEnd?: (result: ComposeEndContext) => void | Promise; @@ -84,7 +94,7 @@ export interface CompositionInfo { export interface ComposeStartContext { serverName: string; description: string; - mode: "agentic" | "agentic_workflow"; + mode: ExecutionMode; server: ComposableMCPServer; /** All available tool names before composition */ availableTools: string[]; @@ -94,7 +104,7 @@ export interface ComposeStartContext { export interface TransformContext { toolName: string; server: ComposableMCPServer; - mode: "agentic" | "agentic_workflow"; + mode: ExecutionMode; /** Original tool definition before any transformations */ originalTool: ComposedTool; /** Index of current transformation (how many plugins have processed this tool) */ @@ -104,17 +114,38 @@ export interface TransformContext { /** Context for finalizeComposition hook */ export interface FinalizeContext { serverName: string; - mode: "agentic" | "agentic_workflow"; + mode: ExecutionMode; server: ComposableMCPServer; /** Names of all composed tools */ toolNames: string[]; } +/** Context for registerAgentTool hook - implements custom execution modes */ +export interface AgentToolRegistrationContext { + server: ComposableMCPServer; + name: string; + description: string; + mode: ExecutionMode | string; + allToolNames: string[]; + toolNameToDetailList: [string, ComposedTool][]; + depGroups: Record; + toolNameToIdMapping?: Map; + publicToolNames: string[]; + hiddenToolNames: string[]; + options: { + mode?: string; + sampling?: boolean | { maxIterations?: number; summarize?: boolean }; + steps?: Array<{ description: string; actions: string[] }>; + ensureStepActions?: string[]; + [key: string]: unknown; + }; +} + /** Context for composeEnd hook */ export interface ComposeEndContext { toolName: string | null; pluginNames: string[]; - mode: "agentic" | "agentic_workflow"; + mode: ExecutionMode; server: ComposableMCPServer; /** Composition statistics - simplified */ stats: { diff --git a/packages/core/src/plugin-utils.ts b/packages/core/src/plugin-utils.ts index 3332c77..1e621ed 100644 --- a/packages/core/src/plugin-utils.ts +++ b/packages/core/src/plugin-utils.ts @@ -15,12 +15,12 @@ const pluginCache = new Map(); */ export function shouldApplyPlugin( plugin: ToolPlugin, - mode: "agentic" | "agentic_workflow", + mode: string, ): boolean { if (!plugin.apply) return true; if (typeof plugin.apply === "string") { - return mode.includes(plugin.apply); + return mode === plugin.apply; } if (typeof plugin.apply === "function") { @@ -112,6 +112,7 @@ export function isValidPlugin(plugin: unknown): plugin is ToolPlugin { typeof p.composeStart === "function" || typeof p.transformTool === "function" || typeof p.finalizeComposition === "function" || + typeof p.registerAgentTool === "function" || typeof p.composeEnd === "function" || typeof p.transformInput === "function" || typeof p.transformOutput === "function" || diff --git a/packages/core/src/plugins/built-in/index.ts b/packages/core/src/plugins/built-in/index.ts index e4a7c8e..f79602e 100644 --- a/packages/core/src/plugins/built-in/index.ts +++ b/packages/core/src/plugins/built-in/index.ts @@ -6,11 +6,15 @@ export { createConfigPlugin } from "./config-plugin.ts"; export { createToolNameMappingPlugin } from "./tool-name-mapping-plugin.ts"; export { createLoggingPlugin } from "./logging-plugin.ts"; +export { createAgenticModePlugin } from "./mode-agentic-plugin.ts"; +export { createWorkflowModePlugin } from "./mode-workflow-plugin.ts"; // Import default instances import configPlugin from "./config-plugin.ts"; import toolNameMappingPlugin from "./tool-name-mapping-plugin.ts"; import loggingPlugin from "./logging-plugin.ts"; +import agenticModePlugin from "./mode-agentic-plugin.ts"; +import workflowModePlugin from "./mode-workflow-plugin.ts"; /** * Get all built-in plugins in the correct order @@ -19,6 +23,8 @@ export function getBuiltInPlugins() { return [ toolNameMappingPlugin, // First: establish name mappings configPlugin, // Second: apply configurations + agenticModePlugin, // Third: agentic mode handler + workflowModePlugin, // Fourth: workflow mode handler loggingPlugin, // Last: logging ]; } diff --git a/packages/core/src/plugins/built-in/mode-agentic-plugin.ts b/packages/core/src/plugins/built-in/mode-agentic-plugin.ts new file mode 100644 index 0000000..f50666f --- /dev/null +++ b/packages/core/src/plugins/built-in/mode-agentic-plugin.ts @@ -0,0 +1,30 @@ +/** + * Agentic Mode Plugin + * Implements the "agentic" execution mode as a built-in plugin + */ + +import type { ToolPlugin } from "../../plugin-types.ts"; +import { registerAgenticTool } from "../../executors/agentic/agentic-tool-registrar.ts"; + +export const createAgenticModePlugin = (): ToolPlugin => ({ + name: "mode-agentic", + version: "1.0.0", + + // Only apply to agentic mode + apply: "agentic", + + // Register the agent tool + registerAgentTool: (context) => { + registerAgenticTool(context.server, { + description: context.description, + name: context.name, + allToolNames: context.allToolNames, + depGroups: context.depGroups, + toolNameToDetailList: context.toolNameToDetailList, + sampling: context.options.sampling, + }); + }, +}); + +// Export default instance for auto-loading +export default createAgenticModePlugin(); diff --git a/packages/core/src/plugins/built-in/mode-workflow-plugin.ts b/packages/core/src/plugins/built-in/mode-workflow-plugin.ts new file mode 100644 index 0000000..753585a --- /dev/null +++ b/packages/core/src/plugins/built-in/mode-workflow-plugin.ts @@ -0,0 +1,33 @@ +/** + * Workflow Mode Plugin + * Implements the "agentic_workflow" execution mode as a built-in plugin + */ + +import type { ToolPlugin } from "../../plugin-types.ts"; +import { registerAgenticWorkflowTool } from "../../executors/workflow/workflow-tool-registrar.ts"; + +export const createWorkflowModePlugin = (): ToolPlugin => ({ + name: "mode-workflow", + version: "1.0.0", + + // Only apply to workflow mode + apply: "agentic_workflow", + + // Register the agent tool + registerAgentTool: (context) => { + registerAgenticWorkflowTool(context.server, { + description: context.description, + name: context.name, + allToolNames: context.allToolNames, + depGroups: context.depGroups, + toolNameToDetailList: context.toolNameToDetailList, + predefinedSteps: context.options.steps, + sampling: context.options.sampling, + ensureStepActions: context.options.ensureStepActions, + toolNameToIdMapping: context.toolNameToIdMapping, + }); + }, +}); + +// Export default instance for auto-loading +export default createWorkflowModePlugin(); diff --git a/packages/core/src/set-up-mcp-compose.ts b/packages/core/src/set-up-mcp-compose.ts index 4a9c05d..c6c4eab 100644 --- a/packages/core/src/set-up-mcp-compose.ts +++ b/packages/core/src/set-up-mcp-compose.ts @@ -5,6 +5,7 @@ import type { MCPSetting } from "./service/tools.ts"; import type { SamplingConfig } from "./types.ts"; import type { ToolPlugin } from "./plugin-types.ts"; import type { ToolRefXml } from "./types.ts"; +import type { ExecutionMode } from "./prompts/types.ts"; export interface ComposeDefinition { /** @@ -39,7 +40,7 @@ export interface ComposeDefinition { * - "agentic_workflow": Agent workflow mode that can either generate steps at runtime or use predefined steps * @default "agentic" */ - mode?: "agentic" | "agentic_workflow"; + mode?: ExecutionMode; /** * Enable MCP sampling-based autonomous execution capability diff --git a/packages/core/src/utils/plugin-manager.ts b/packages/core/src/utils/plugin-manager.ts index 355f5a2..6076ff2 100644 --- a/packages/core/src/utils/plugin-manager.ts +++ b/packages/core/src/utils/plugin-manager.ts @@ -4,6 +4,7 @@ */ import type { + AgentToolRegistrationContext, ComposedTool, ComposeEndContext, ComposeStartContext, @@ -234,6 +235,44 @@ export class PluginManager { } } + /** + * Trigger registerAgentTool hook - allows plugins to register the main agent tool + * Returns true if any plugin handled the registration + */ + async triggerRegisterAgentTool( + context: AgentToolRegistrationContext, + ): Promise { + const registerPlugins = this.plugins.filter( + (p) => p.registerAgentTool && shouldApplyPlugin(p, context.mode), + ); + + if (registerPlugins.length === 0) { + return false; + } + + // Sort plugins - last one wins (reverse order) + const sortedPlugins = sortPluginsByOrder(registerPlugins).reverse(); + + for (const plugin of sortedPlugins) { + if (plugin.registerAgentTool) { + try { + await plugin.registerAgentTool(context); + // First successful registration wins + return true; + } catch (error) { + const errorMsg = error instanceof Error + ? error.message + : String(error); + await this.logger.error( + `Plugin "${plugin.name}" registerAgentTool failed: ${errorMsg}`, + ); + } + } + } + + return false; + } + /** * Dispose all plugins and cleanup resources */