Skip to content

Commit 7155493

Browse files
authored
Merge pull request #63 from OpenAF/codex/add-handlebars-template-flag-to-ojobmcp.yaml
MCP: add templated tool descriptions and tool name prefixing for HTTP/STDIO servers
2 parents cc647cc + c328114 commit 7155493

2 files changed

Lines changed: 126 additions & 36 deletions

File tree

.package.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ name: oJob-common
3737
main: ''
3838
mainJob: ''
3939
license: Apache 2.0
40-
version: '20260228'
40+
version: '20260309'
4141
dependencies:
4242
openaf: '>=20251115'
4343
files:
@@ -83,7 +83,7 @@ filesHash:
8383
oJobHBS.yaml: ab15b0efd31f95ee770c77dace146035e0781661
8484
oJobHTTPd.yaml: ca7b876513bf380be043528adc972c9974394486
8585
oJobIO.yaml: 8108313099abf9d1115a3d428b68a203b8d5c2e2
86-
oJobMCP.yaml: 65340bcd98481e228cd638e55f588132254c8ac6
86+
oJobMCP.yaml: 107a8c48850c1e33888b1b636b100e256bd4a7bc
8787
oJobManager.yaml: 5ad9ecf276bbee8b6503d0e3b40b007f2724507d
8888
oJobNet.yaml: 4413085d38724249587f6a790d7e1952a4d9c138
8989
oJobOPack.yaml: b4e78c90de2a72bfe1bca21e2143de4715431e87

oJobMCP.yaml

Lines changed: 124 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ jobs:
1818
uri : uri
1919
usestream : usestream
2020
debug : debug
21+
tplDesc : tplDesc
22+
toolPrefix : toolPrefix
2123
fnsMeta : fnsMeta
2224
fns : fns
2325
help :
@@ -31,6 +33,10 @@ jobs:
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",
@@ -64,9 +91,35 @@ jobs:
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

Comments
 (0)