@@ -39,11 +39,15 @@ interface RegisteredTool {
3939 execute : ( ...args : unknown [ ] ) => Promise < unknown > ;
4040}
4141
42- function createTool ( name : string , description = `Tool ${ name } ` ) {
42+ function createTool (
43+ name : string ,
44+ description = `Tool ${ name } ` ,
45+ inputSchema : Record < string , unknown > = { type : "object" , properties : { } }
46+ ) {
4347 return {
4448 name,
4549 description,
46- inputSchema : { type : "object" , properties : { } } ,
50+ inputSchema,
4751 } ;
4852}
4953
@@ -282,6 +286,113 @@ describe("AiToEarn OpenClaw Plugin", () => {
282286 } ) ;
283287 } ) ;
284288
289+ it ( "should sanitize non-required placeholder values before calling MCP" , async ( ) => {
290+ toolDiscoveryMock . loadToolDefinitionsSync . mockReturnValue ( {
291+ source : "remote" ,
292+ tools : [
293+ createTool ( "test_tool" , "Tool test_tool" , {
294+ type : "object" ,
295+ required : [ "workLink" ] ,
296+ properties : {
297+ workLink : { type : "string" } ,
298+ imgUrlList : {
299+ type : "array" ,
300+ items : { type : "string" } ,
301+ } ,
302+ shippingAddress : {
303+ type : "object" ,
304+ properties : {
305+ address1 : { type : "string" } ,
306+ city : { type : "string" } ,
307+ } ,
308+ } ,
309+ zero : { type : "number" } ,
310+ flag : { type : "boolean" } ,
311+ } ,
312+ } ) ,
313+ ] ,
314+ logs : [ ] ,
315+ } ) ;
316+
317+ await pluginEntry . register ( mockApi as any ) ;
318+
319+ await getRegisteredTool ( "test_tool" ) . execute ( "test-call-id" , {
320+ workLink : "https://real.example.com/work" ,
321+ imgUrlList : [
322+ "https://placeholder.invalid/remove-me" ,
323+ "https://cdn.example.com/image.jpg" ,
324+ ] ,
325+ shippingAddress : {
326+ address1 : " " ,
327+ city : " " ,
328+ } ,
329+ zero : 0 ,
330+ flag : false ,
331+ note : "placeholder" ,
332+ } ) ;
333+
334+ expect ( sharedMcpClientMock . callMcpTool ) . toHaveBeenCalledWith (
335+ "test-api-key" ,
336+ "https://test.aitoearn.ai/api" ,
337+ "test_tool" ,
338+ {
339+ workLink : "https://real.example.com/work" ,
340+ imgUrlList : [ "https://cdn.example.com/image.jpg" ] ,
341+ zero : 0 ,
342+ flag : false ,
343+ }
344+ ) ;
345+ } ) ;
346+
347+ it ( "should preserve placeholder values for required fields" , async ( ) => {
348+ toolDiscoveryMock . loadToolDefinitionsSync . mockReturnValue ( {
349+ source : "remote" ,
350+ tools : [
351+ createTool ( "test_tool" , "Tool test_tool" , {
352+ type : "object" ,
353+ required : [ "imgUrlList" , "payload" ] ,
354+ properties : {
355+ imgUrlList : {
356+ type : "array" ,
357+ items : { type : "string" } ,
358+ } ,
359+ payload : {
360+ type : "object" ,
361+ required : [ "workLink" ] ,
362+ properties : {
363+ workLink : { type : "string" } ,
364+ caption : { type : "string" } ,
365+ } ,
366+ } ,
367+ } ,
368+ } ) ,
369+ ] ,
370+ logs : [ ] ,
371+ } ) ;
372+
373+ await pluginEntry . register ( mockApi as any ) ;
374+
375+ await getRegisteredTool ( "test_tool" ) . execute ( "test-call-id" , {
376+ imgUrlList : [ "https://placeholder.invalid/remove-me" ] ,
377+ payload : {
378+ workLink : "https://placeholder.invalid/remove-me" ,
379+ caption : " " ,
380+ } ,
381+ } ) ;
382+
383+ expect ( sharedMcpClientMock . callMcpTool ) . toHaveBeenCalledWith (
384+ "test-api-key" ,
385+ "https://test.aitoearn.ai/api" ,
386+ "test_tool" ,
387+ {
388+ imgUrlList : [ "https://placeholder.invalid/remove-me" ] ,
389+ payload : {
390+ workLink : "https://placeholder.invalid/remove-me" ,
391+ } ,
392+ }
393+ ) ;
394+ } ) ;
395+
285396 it ( "should keep only China publish tools for China environment" , async ( ) => {
286397 setDiscoveredTools ( [
287398 "test_tool" ,
0 commit comments