@@ -4,8 +4,10 @@ import {
44 rewrapToolWithBeforeToolCallHook ,
55 wrapToolWithBeforeToolCallHook ,
66} from "../agents/agent-tools.before-tool-call.js" ;
7+ import { copyChannelAgentToolMeta } from "../agents/channel-tools.js" ;
78import type { AnyAgentTool } from "../agents/tools/common.js" ;
89import { formatErrorMessage } from "../infra/errors.js" ;
10+ import { copyPluginToolMeta } from "../plugins/tool-metadata.js" ;
911import { coerceChatContentText } from "../shared/chat-content.js" ;
1012
1113type CallPluginToolParams = {
@@ -21,14 +23,132 @@ function resolveJsonSchemaForTool(tool: AnyAgentTool): Record<string, unknown> {
2123 return { type : "object" , properties : { } } ;
2224}
2325
24- export function createPluginToolsMcpHandlers ( tools : AnyAgentTool [ ] ) {
25- const wrappedTools = tools . map ( ( tool ) => {
26+ function snapshotMcpPluginTool ( tool : AnyAgentTool ) : AnyAgentTool | undefined {
27+ let name : unknown ;
28+ let description : unknown ;
29+ let parameters : unknown ;
30+ let execute : unknown ;
31+ try {
32+ name = tool . name ;
33+ description = tool . description ;
34+ parameters = tool . parameters ;
35+ execute = tool . execute ;
36+ } catch {
37+ return undefined ;
38+ }
39+ if ( typeof name !== "string" || ! name . trim ( ) || typeof execute !== "function" ) {
40+ return undefined ;
41+ }
42+
43+ let prototype : object | null ;
44+ try {
45+ prototype = Reflect . getPrototypeOf ( tool ) ;
46+ } catch {
47+ return undefined ;
48+ }
49+ const descriptors = copyReadableMcpPluginToolDescriptors ( tool ) ;
50+ if ( ! descriptors ) {
51+ return undefined ;
52+ }
53+ descriptors . name = {
54+ configurable : true ,
55+ enumerable : true ,
56+ value : name . trim ( ) ,
57+ writable : true ,
58+ } ;
59+ descriptors . description = {
60+ configurable : true ,
61+ enumerable : true ,
62+ value : typeof description === "string" ? description : "" ,
63+ writable : true ,
64+ } ;
65+ descriptors . parameters = {
66+ configurable : true ,
67+ enumerable : true ,
68+ value : parameters ,
69+ writable : true ,
70+ } ;
71+ descriptors . execute = {
72+ configurable : true ,
73+ enumerable : true ,
74+ value : ( ...args : Parameters < AnyAgentTool [ "execute" ] > ) =>
75+ Reflect . apply ( execute , tool , args ) as ReturnType < AnyAgentTool [ "execute" ] > ,
76+ writable : true ,
77+ } ;
78+
79+ const snapshot = Object . create ( prototype ) as AnyAgentTool ;
80+ Object . defineProperties ( snapshot , descriptors ) ;
81+ copyPluginToolMeta ( tool , snapshot ) ;
82+ copyChannelAgentToolMeta ( tool as never , snapshot as never ) ;
83+ return snapshot ;
84+ }
85+
86+ function copyReadableMcpPluginToolDescriptors (
87+ tool : AnyAgentTool ,
88+ ) : PropertyDescriptorMap | undefined {
89+ let keys : PropertyKey [ ] ;
90+ try {
91+ keys = Reflect . ownKeys ( tool ) ;
92+ } catch {
93+ return undefined ;
94+ }
95+
96+ const descriptors : PropertyDescriptorMap = { } ;
97+ for ( const key of keys ) {
98+ if ( key === "name" || key === "description" || key === "parameters" || key === "execute" ) {
99+ continue ;
100+ }
101+ let descriptor : PropertyDescriptor | undefined ;
102+ try {
103+ descriptor = Reflect . getOwnPropertyDescriptor ( tool , key ) ;
104+ } catch {
105+ return undefined ;
106+ }
107+ if ( ! descriptor ) {
108+ continue ;
109+ }
110+ if ( "value" in descriptor ) {
111+ descriptors [ key ] = {
112+ configurable : true ,
113+ enumerable : descriptor . enumerable ,
114+ value : descriptor . value ,
115+ writable : true ,
116+ } ;
117+ continue ;
118+ }
119+ try {
120+ descriptors [ key ] = {
121+ configurable : true ,
122+ enumerable : descriptor . enumerable ,
123+ value : Reflect . get ( tool , key , tool ) ,
124+ writable : true ,
125+ } ;
126+ } catch {
127+ continue ;
128+ }
129+ }
130+ return descriptors ;
131+ }
132+
133+ function wrapPluginToolForMcp ( tool : AnyAgentTool ) : AnyAgentTool | undefined {
134+ try {
26135 if ( isToolWrappedWithBeforeToolCallHook ( tool ) ) {
27136 return rewrapToolWithBeforeToolCallHook ( tool , undefined , { approvalMode : "report" } ) ;
28137 }
29- // The ACPX MCP bridge should enforce the same pre-execution hook boundary
30- // as the agent and HTTP tool execution paths.
31- return wrapToolWithBeforeToolCallHook ( tool , undefined , { approvalMode : "report" } ) ;
138+ } catch {
139+ return undefined ;
140+ }
141+ const snapshot = snapshotMcpPluginTool ( tool ) ;
142+ if ( ! snapshot ) {
143+ return undefined ;
144+ }
145+ return wrapToolWithBeforeToolCallHook ( snapshot , undefined , { approvalMode : "report" } ) ;
146+ }
147+
148+ export function createPluginToolsMcpHandlers ( tools : AnyAgentTool [ ] ) {
149+ const wrappedTools = tools . flatMap ( ( tool ) => {
150+ const wrapped = wrapPluginToolForMcp ( tool ) ;
151+ return wrapped ? [ wrapped ] : [ ] ;
32152 } ) ;
33153 const toolMap = new Map < string , AnyAgentTool > ( ) ;
34154 for ( const tool of wrappedTools ) {
0 commit comments