Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 12 additions & 0 deletions packages/core/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
52 changes: 25 additions & 27 deletions packages/core/src/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -343,7 +342,7 @@ export class ComposableMCPServer extends Server {
*/
private async processToolsWithPlugins(
externalTools: Record<string, ComposedTool>,
mode: "agentic" | "agentic_workflow",
mode: ExecutionMode,
): Promise<void> {
const { processToolsWithPlugins: processTools } = await import(
"./utils/compose-helpers.ts"
Expand Down Expand Up @@ -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?`,
);
}
}
}
2 changes: 2 additions & 0 deletions packages/core/src/executors/agentic/agentic-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export class AgenticExecutor {
type: "text",
text: CompiledPrompts.planningPrompt({
currentAction: actionName,
toolName: this.name,
}),
});
}
Expand Down Expand Up @@ -209,6 +210,7 @@ export class AgenticExecutor {
type: "text",
text: CompiledPrompts.planningPrompt({
currentAction: actionName,
toolName: this.name,
}),
});
}
Expand Down
41 changes: 36 additions & 5 deletions packages/core/src/plugin-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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[];
Expand All @@ -49,6 +50,15 @@ export interface ToolPlugin {
context: FinalizeContext,
) => void | Promise<void>;

/**
* 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<void>;

/** Called after composition is complete - for logging and cleanup */
composeEnd?: (result: ComposeEndContext) => void | Promise<void>;

Expand Down Expand Up @@ -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[];
Expand All @@ -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) */
Expand All @@ -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<string, unknown>;
toolNameToIdMapping?: Map<string, string>;
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: {
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/plugin-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ const pluginCache = new Map<string, ToolPlugin>();
*/
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") {
Expand Down Expand Up @@ -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" ||
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/plugins/built-in/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
];
}
30 changes: 30 additions & 0 deletions packages/core/src/plugins/built-in/mode-agentic-plugin.ts
Original file line number Diff line number Diff line change
@@ -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();
33 changes: 33 additions & 0 deletions packages/core/src/plugins/built-in/mode-workflow-plugin.ts
Original file line number Diff line number Diff line change
@@ -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();
3 changes: 2 additions & 1 deletion packages/core/src/set-up-mcp-compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand Down Expand Up @@ -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
Expand Down
39 changes: 39 additions & 0 deletions packages/core/src/utils/plugin-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import type {
AgentToolRegistrationContext,
ComposedTool,
ComposeEndContext,
ComposeStartContext,
Expand Down Expand Up @@ -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<boolean> {
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
*/
Expand Down