1818 uri : uri
1919 usestream : usestream
2020 debug : debug
21+ tplDesc : tplDesc
22+ toolPrefix : toolPrefix
2123 fnsMeta : fnsMeta
2224 fns : fns
2325 help :
3133 desc : (Boolean) If true, returns responses as HTTP SSE stream events.
3234 - name : debug
3335 desc : (Boolean) If true, debug messages will be logged.
36+ - name : tplDesc
37+ desc : (Boolean) If true, all tool description fields in fnsMeta are evaluated as handlebars templates with the current oJob args.
38+ - name : toolPrefix
39+ desc : (String) If defined, prefixes exposed MCP tool names without changing fns/fnsMeta key mapping.
3440 - name : fnsMeta
3541 desc : (Map) Metadata for the functions available in the MCP server.
3642 - name : fns
@@ -42,11 +48,32 @@ jobs:
4248 uri : isString.default("/mcp")
4349 usestream : toBoolean.isBoolean.default(false)
4450 debug : toBoolean.isBoolean.default(false)
51+ tplDesc : toBoolean.isBoolean.default(false)
52+ toolPrefix : isString.default("")
4553 fnsMeta : isMap.default({})
4654 fns : isMap.default({})
4755 deps :
4856 - HTTP Start Server
4957 exec : | # js
58+ var _applyTemplate = (meta, allStrings) => {
59+ if (!args.tplDesc) return meta
60+ var _walk = obj => {
61+ if (isString(obj) && allStrings) return $t(obj, args)
62+ if (isArray(obj)) return obj.map(_walk)
63+ if (isMap(obj)) {
64+ Object.keys(obj).forEach(k => {
65+ if (isString(obj[k]) && (allStrings || k == "description")) {
66+ obj[k] = $t(obj[k], args)
67+ } else {
68+ obj[k] = _walk(obj[k])
69+ }
70+ })
71+ }
72+ return obj
73+ }
74+ return _walk(meta)
75+ }
76+
5077 // Ensures default server info and capabilities
5178 args.description = merge({
5279 protocolVersion: "2024-11-05",
6491 }
6592 }
6693 }, args.description)
94+ args.description = _applyTemplate(clone(args.description), true)
6795
6896 fns = {}
6997
98+ var _applyTemplateToDescriptions = meta => _applyTemplate(meta, false)
99+
100+ var _prefixToolName = n => (isString(args.toolPrefix) && args.toolPrefix.length > 0 ? args.toolPrefix + n : n)
101+ var _resolveToolName = n => {
102+ if (isString(args.toolPrefix) && args.toolPrefix.length > 0 && isString(n) && n.startsWith(args.toolPrefix)) {
103+ return n.substring(args.toolPrefix.length)
104+ }
105+ return n
106+ }
107+ var _toToolMeta = f => {
108+ var _meta
109+ if (isDef(args.fnsMeta[f])) {
110+ _meta = _applyTemplateToDescriptions(clone(args.fnsMeta[f]))
111+ } else {
112+ _meta = {
113+ name : f,
114+ description: f,
115+ inputSchema: {}
116+ }
117+ }
118+ if (!isMap(_meta)) _meta = { name: f, description: String(_meta), inputSchema: {} }
119+ _meta.name = _prefixToolName(f)
120+ return _meta
121+ }
122+
70123 if (args.usestream) {
71124 fns[args.uri] = req => {
72125 try {
@@ -84,10 +137,11 @@ jobs:
84137 "tools/call" : params => {
85138 if (args.debug) log(`tools/call -- ${af.toCSLON(params)}`)
86139 var _res
87- if (isDef(args.fns[params.name])) {
140+ var _toolName = _resolveToolName(params.name)
141+ if (isDef(args.fns[_toolName])) {
88142 try {
89- if (isString(args.fns[params.name ])) {
90- _res = $job(args.fns[params.name ], merge({ _httprequest: req }, params.arguments || params.input || {}))
143+ if (isString(args.fns[_toolName ])) {
144+ _res = $job(args.fns[_toolName ], merge({ _httprequest: req }, params.arguments || params.input || {}))
91145 if (isMap(_res)) {
92146 delete _res.objId
93147 delete _res.execid
@@ -106,7 +160,7 @@ jobs:
106160 }
107161 }
108162 } else {
109- throw "Function not found: " + params.name
163+ throw "Function not found: " + _toolName
110164 }
111165 } catch(e) {
112166 logErr(`Error executing tool '${params.name}': ${e.message}`)
@@ -155,17 +209,7 @@ jobs:
155209 },
156210 "tools/list": params => {
157211 if (args.debug) log(`tools/list -- ${af.toCSLON(params)}`)
158- var _fnsMeta = Object.keys(args.fns).map(f => {
159- if (isDef(args.fnsMeta[f])) {
160- return clone(args.fnsMeta[f])
161- } else {
162- return {
163- name : f,
164- description: f,
165- inputSchema: {}
166- }
167- }
168- })
212+ var _fnsMeta = Object.keys(args.fns).map(f => _toToolMeta(f))
169213 return {
170214 tools: _fnsMeta
171215 }
@@ -256,10 +300,11 @@ jobs:
256300 "tools/call" : params => {
257301 if (args.debug) log(`tools/call -- ${af.toCSLON(params)}`)
258302 var _res
259- if (isDef(args.fns[params.name])) {
303+ var _toolName = _resolveToolName(params.name)
304+ if (isDef(args.fns[_toolName])) {
260305 try {
261- if (isString(args.fns[params.name ])) {
262- _res = $job(args.fns[params.name ], merge({ _httprequest: req }, params.arguments || params.input || {}))
306+ if (isString(args.fns[_toolName ])) {
307+ _res = $job(args.fns[_toolName ], merge({ _httprequest: req }, params.arguments || params.input || {}))
263308 if (isMap(_res)) {
264309 delete _res.objId
265310 delete _res.execid
@@ -278,7 +323,7 @@ jobs:
278323 }
279324 }
280325 } else {
281- throw "Function not found: " + params.name
326+ throw "Function not found: " + _toolName
282327 }
283328 } catch(e) {
284329 logErr(`Error executing tool '${params.name}': ${e.message}`)
@@ -327,17 +372,7 @@ jobs:
327372 },
328373 "tools/list": params => {
329374 if (args.debug) log(`tools/list -- ${af.toCSLON(params)}`)
330- var _fnsMeta = Object.keys(args.fns).map(f => {
331- if (isDef(args.fnsMeta[f])) {
332- return clone(args.fnsMeta[f])
333- } else {
334- return {
335- name : f,
336- description: f,
337- inputSchema: {}
338- }
339- }
340- })
375+ var _fnsMeta = Object.keys(args.fns).map(f => _toToolMeta(f))
341376 return {
342377 tools: _fnsMeta
343378 }
@@ -366,6 +401,8 @@ jobs:
366401 keyArg : description
367402 args :
368403 debug : debug
404+ tplDesc : tplDesc
405+ toolPrefix : toolPrefix
369406 fnsMeta : fnsMeta
370407 fns : fns
371408 help :
@@ -375,15 +412,40 @@ jobs:
375412 desc : (String) If defined it will create a ndjson file with the provided name.
376413 - name : fnsMeta
377414 desc : (Map) Metadata for the functions available in the MCP server stdio.
415+ - name : tplDesc
416+ desc : (Boolean) If true, all tool description fields in fnsMeta are evaluated as handlebars templates with the current oJob args.
417+ - name : toolPrefix
418+ desc : (String) If defined, prefixes exposed MCP tool names without changing fns/fnsMeta key mapping.
378419 - name : fns
379420 desc : (Map) Functions to be executed when called from the MCP server stdio.
380421 check :
381422 in :
382423 description : isMap.default(__)
383424 debug : isString.default(__)
425+ tplDesc : toBoolean.isBoolean.default(false)
426+ toolPrefix : isString.default("")
384427 fnsMeta : isMap.default({})
385428 fns : isMap.default({})
386429 exec : | # js
430+ var _applyTemplate = (meta, allStrings) => {
431+ if (!args.tplDesc) return meta
432+ var _walk = obj => {
433+ if (isString(obj) && allStrings) return $t(obj, args)
434+ if (isArray(obj)) return obj.map(_walk)
435+ if (isMap(obj)) {
436+ Object.keys(obj).forEach(k => {
437+ if (isString(obj[k]) && (allStrings || k == "description")) {
438+ obj[k] = $t(obj[k], args)
439+ } else {
440+ obj[k] = _walk(obj[k])
441+ }
442+ })
443+ }
444+ return obj
445+ }
446+ return _walk(meta)
447+ }
448+
387449 // Ensures default server info and capabilities
388450 args.description = merge({
389451 protocolVersion: "2024-11-05",
@@ -401,11 +463,39 @@ jobs:
401463 }
402464 }
403465 }, args.description)
466+ args.description = _applyTemplate(clone(args.description), true)
404467
405468 var fs = {}
406- Object.keys(args.fns).map(f => {
407- fs[f] = function(params) {
408- var _r = $job(args.fns[f], params)
469+ var _applyTemplateToDescriptions = meta => _applyTemplate(meta, false)
470+
471+ var _prefixToolName = n => (isString(args.toolPrefix) && args.toolPrefix.length > 0 ? args.toolPrefix + n : n)
472+ var _resolveToolName = n => {
473+ if (isString(args.toolPrefix) && args.toolPrefix.length > 0 && isString(n) && n.startsWith(args.toolPrefix)) {
474+ return n.substring(args.toolPrefix.length)
475+ }
476+ return n
477+ }
478+ var _toToolMeta = f => {
479+ var _meta
480+ if (isDef(args.fnsMeta[f])) {
481+ _meta = _applyTemplateToDescriptions(clone(args.fnsMeta[f]))
482+ } else {
483+ _meta = {
484+ name : f,
485+ description: f,
486+ inputSchema: {}
487+ }
488+ }
489+ if (!isMap(_meta)) _meta = { name: f, description: String(_meta), inputSchema: {} }
490+ _meta.name = _prefixToolName(f)
491+ return _meta
492+ }
493+
494+ Object.keys(args.fns).forEach(f => {
495+ var _toolName = _prefixToolName(f)
496+ fs[_toolName] = function(params) {
497+ var _jobName = _resolveToolName(_toolName)
498+ var _r = $job(args.fns[_jobName], params)
409499 if (isMap(_r)) {
410500 delete _r.objId
411501 delete _r.execid
@@ -420,7 +510,7 @@ jobs:
420510 }
421511 })
422512
423- ow.server.mcpStdio(args.description, Object.values (args.fnsMeta ),
513+ ow.server.mcpStdio(args.description, Object.keys (args.fns).map(f => _toToolMeta(f) ),
424514 fs,
425515 (type, msg) => {
426516 if (args.debug) {
0 commit comments