feat(tools): add execution wrapper support to tool registration
This commit is contained in:
+37
-3
@@ -65,20 +65,54 @@ export interface ToolDefinition<
|
||||
execute: (args: TArgs) => Promise<McpToolResponse>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for registering an individual tool on an McpServer instance.
|
||||
*/
|
||||
export interface RegisterToolOptions<
|
||||
TShape extends ZodRawShape = ZodRawShape,
|
||||
TArgs = Record<string, unknown>,
|
||||
> {
|
||||
/**
|
||||
* Optional wrapper function to intercept tool execution, enabling
|
||||
* context injection (runWithContext) or execution guards (e.g. read-only checks).
|
||||
*/
|
||||
wrapExecute?: (
|
||||
fn: () => Promise<McpToolResponse>,
|
||||
tool: ToolDefinition<TShape, TArgs>,
|
||||
args: TArgs
|
||||
) => Promise<McpToolResponse>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a ToolDefinition instance on an McpServer instance.
|
||||
*
|
||||
* @param server The McpServer instance to register the tool on.
|
||||
* @param tool The ToolDefinition describing the tool metadata, parameters, and handler.
|
||||
* @param options Optional registration options, such as an execution wrapper.
|
||||
*/
|
||||
export function registerTool<
|
||||
TShape extends ZodRawShape = ZodRawShape,
|
||||
TArgs = Record<string, unknown>,
|
||||
>(server: McpServer, tool: ToolDefinition<TShape, TArgs>): void {
|
||||
>(
|
||||
server: McpServer,
|
||||
tool: ToolDefinition<TShape, TArgs>,
|
||||
options?: RegisterToolOptions<TShape, TArgs>
|
||||
): void {
|
||||
const executeFn = (args: TArgs) => {
|
||||
if (options?.wrapExecute) {
|
||||
return options.wrapExecute(() => tool.execute(args), tool, args);
|
||||
}
|
||||
return tool.execute(args);
|
||||
};
|
||||
|
||||
if (tool.parameters) {
|
||||
server.tool(tool.name, tool.description, tool.parameters, async (args) => {
|
||||
return tool.execute(args as unknown as TArgs);
|
||||
return executeFn(args as unknown as TArgs);
|
||||
});
|
||||
} else {
|
||||
server.tool(tool.name, tool.description, async () => {
|
||||
return tool.execute({} as unknown as TArgs);
|
||||
return executeFn({} as unknown as TArgs);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+26
-2
@@ -3,7 +3,12 @@
|
||||
*/
|
||||
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { registerTool, type ToolDefinition } from "./common";
|
||||
import {
|
||||
registerTool,
|
||||
type ToolDefinition,
|
||||
type RegisterToolOptions,
|
||||
type McpToolResponse,
|
||||
} from "./common";
|
||||
import { projectTools } from "./projects";
|
||||
import { workPackageTools } from "./work-packages";
|
||||
import { queryTools } from "./queries";
|
||||
@@ -31,11 +36,17 @@ export const allTools: AnyToolDefinition[] = [
|
||||
export interface RegisterToolsOptions {
|
||||
readOnly?: boolean;
|
||||
tools?: AnyToolDefinition[];
|
||||
wrapExecute?: (
|
||||
fn: () => Promise<McpToolResponse>,
|
||||
tool: AnyToolDefinition,
|
||||
args: Record<string, unknown>
|
||||
) => Promise<McpToolResponse>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all OpenProject tools with the given McpServer instance.
|
||||
* If options.readOnly is true, tools not marked as readOnly are skipped.
|
||||
* If options.wrapExecute is provided, tool execution is intercepted through the wrapper.
|
||||
*/
|
||||
export function registerAllTools(
|
||||
server: McpServer,
|
||||
@@ -49,6 +60,19 @@ export function registerAllTools(
|
||||
if (isReadOnly && !tool.readOnly) {
|
||||
continue;
|
||||
}
|
||||
registerTool(server, tool as unknown as ToolDefinition);
|
||||
|
||||
const toolOptions: RegisterToolOptions | undefined = options?.wrapExecute
|
||||
? {
|
||||
wrapExecute: (fn, t, args) =>
|
||||
options.wrapExecute!(
|
||||
fn,
|
||||
t as unknown as AnyToolDefinition,
|
||||
args as Record<string, unknown>
|
||||
),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
registerTool(server, tool as unknown as ToolDefinition, toolOptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user