Skip to content

Commit 2d0e44d

Browse files
authored
feat(fxmcpserver): Added type and structured tools support (#378)
1 parent 65ec792 commit 2d0e44d

File tree

10 files changed

+240
-9
lines changed

10 files changed

+240
-9
lines changed

.github/workflows/common-ci.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ on:
1111
required: false
1212
type: string
1313
default: "1.20.0"
14+
linter_version:
15+
required: false
16+
type: string
17+
default: "1.55.2"
1418

1519
permissions:
1620
contents: read
@@ -63,7 +67,7 @@ jobs:
6367
go-version: ${{ inputs.go_version }}
6468
- name: Install lint
6569
working-directory: ${{ inputs.module }}
66-
run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2
70+
run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v${{ inputs.linter_version }}
6771
- name: Run lint
6872
working-directory: ${{ inputs.module }}
6973
run: golangci-lint run

.github/workflows/fxmcpserver-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ jobs:
3030
with:
3131
module: "fxmcpserver"
3232
go_version: "1.23"
33+
linter_version: "1.64.8"

docs/modules/fxmcpserver.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,11 @@ modules:
524524
tools: true # to expose MCP tools (disabled by default)
525525
```
526526
527+
Note: you can also create `typed` and `structured` tools, for [MCP tools with structured content](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content):
528+
529+
- see [typed tool example](https://github.com/ankorstore/yokai/blob/main/fxmcpserver/testdata/tool/typed.go)
530+
- see [structured tool example](https://github.com/ankorstore/yokai/blob/main/fxmcpserver/testdata/tool/structured.go)
531+
527532
## Hooks
528533

529534
This module provides hooking mechanisms for the `StreamableHTTP` and `SSE` servers requests handling.

fxmcpserver/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,12 @@ modules:
562562
capabilities:
563563
tools: true # to expose MCP tools (disabled by default)
564564
```
565+
566+
Note: you can also create `typed` and `structured` tools, for [MCP tools with structured content](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content):
567+
568+
- see [typed tool example](testdata/tool/typed.go)
569+
- see [structured tool example](testdata/tool/structured.go)
570+
565571
### Hooks
566572

567573
This module provides hooking mechanisms for the `StreamableHTTP` and `SSE` servers requests handling.

fxmcpserver/go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ require (
1414
github.com/ankorstore/yokai/healthcheck v1.1.0
1515
github.com/ankorstore/yokai/log v1.2.0
1616
github.com/ankorstore/yokai/trace v1.4.0
17-
github.com/mark3labs/mcp-go v0.31.0
17+
github.com/mark3labs/mcp-go v0.41.1
1818
github.com/prometheus/client_golang v1.22.0
1919
github.com/stretchr/testify v1.10.0
2020
go.opencensus.io v0.24.0
@@ -25,7 +25,9 @@ require (
2525
)
2626

2727
require (
28+
github.com/bahlo/generic-list-go v0.2.0 // indirect
2829
github.com/beorn7/perks v1.0.1 // indirect
30+
github.com/buger/jsonparser v1.1.1 // indirect
2931
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
3032
github.com/cespare/xxhash/v2 v2.3.0 // indirect
3133
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
@@ -37,8 +39,10 @@ require (
3739
github.com/google/uuid v1.6.0 // indirect
3840
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
3941
github.com/hashicorp/hcl v1.0.0 // indirect
42+
github.com/invopop/jsonschema v0.13.0 // indirect
4043
github.com/kylelemons/godebug v1.1.0 // indirect
4144
github.com/magiconair/properties v1.8.7 // indirect
45+
github.com/mailru/easyjson v0.7.7 // indirect
4246
github.com/mattn/go-colorable v0.1.13 // indirect
4347
github.com/mattn/go-isatty v0.0.20 // indirect
4448
github.com/mitchellh/mapstructure v1.5.0 // indirect
@@ -59,6 +63,7 @@ require (
5963
github.com/spf13/viper v1.19.0 // indirect
6064
github.com/stretchr/objx v0.5.2 // indirect
6165
github.com/subosito/gotenv v1.6.0 // indirect
66+
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
6267
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
6368
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
6469
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect

fxmcpserver/go.sum

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@ github.com/ankorstore/yokai/log v1.2.0 h1:jiuDiC0dtqIGIOsFQslUHYoFJ1qjI+rOMa6dI1
2222
github.com/ankorstore/yokai/log v1.2.0/go.mod h1:MVvUcms1AYGo0BT6l88B9KJdvtK6/qGKdgyKVXfbmyc=
2323
github.com/ankorstore/yokai/trace v1.4.0 h1:AdEQs/4TEuqOJ9p/EfsQmrtmkSG3pcmE7r/l+FQFxY8=
2424
github.com/ankorstore/yokai/trace v1.4.0/go.mod h1:m7EL2MRBilgCtrly5gA4F0jkGSXR2EbG6LsotbTJ4nA=
25+
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
26+
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
2527
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
2628
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
29+
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
30+
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
2731
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
2832
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
2933
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -81,6 +85,9 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0Q
8185
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
8286
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
8387
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
88+
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
89+
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
90+
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
8491
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
8592
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
8693
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -89,8 +96,10 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
8996
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
9097
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
9198
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
92-
github.com/mark3labs/mcp-go v0.31.0 h1:4UxSV8aM770OPmTvaVe/b1rA2oZAjBMhGBfUgOGut+4=
93-
github.com/mark3labs/mcp-go v0.31.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
99+
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
100+
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
101+
github.com/mark3labs/mcp-go v0.41.1 h1:w78eWfiQam2i8ICL7AL0WFiq7KHNJQ6UB53ZVtH4KGA=
102+
github.com/mark3labs/mcp-go v0.41.1/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g=
94103
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
95104
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
96105
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -149,6 +158,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
149158
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
150159
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
151160
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
161+
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
162+
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
152163
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
153164
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
154165
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=

fxmcpserver/module_test.go

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ func TestMCPServerModule(t *testing.T) {
5656
fxhealthcheck.FxHealthcheckModule,
5757
fxmcpserver.FxMCPServerModule,
5858
fx.Options(
59-
fxmcpserver.AsMCPServerTools(tool.NewSimpleTestTool, tool.NewAdvancedTestTool),
59+
fxmcpserver.AsMCPServerTools(
60+
tool.NewSimpleTestTool,
61+
tool.NewAdvancedTestTool,
62+
tool.NewTypedTestTool,
63+
tool.NewStructuredTestTool,
64+
),
6065
fxmcpserver.AsMCPServerPrompts(prompt.NewSimpleTestPrompt),
6166
fxmcpserver.AsMCPServerResources(resource.NewSimpleTestResource),
6267
fxmcpserver.AsMCPServerResourceTemplates(resourcetemplate.NewSimpleTestResourceTemplate),
@@ -187,7 +192,7 @@ func TestMCPServerModule(t *testing.T) {
187192

188193
_, err = tt.client.CallTool(ctx, callToolRequest)
189194
assert.Error(t, err)
190-
assert.Equal(t, "advanced tool test failure", err.Error())
195+
assert.Contains(t, err.Error(), "advanced tool test failure")
191196

192197
logtest.AssertHasLogRecord(t, logBuffer, map[string]any{
193198
"level": "error",
@@ -222,6 +227,104 @@ func TestMCPServerModule(t *testing.T) {
222227
)
223228
assert.NoError(t, err)
224229

230+
// send success typed tools/call request
231+
expectedRequest = `{"method":"tools/call","params":{"name":"typed-test-tool","arguments":{"input":"test-value"}}}`
232+
expectedResponse = `{"content":[{"type":"text","text":"input: test-value"}]}`
233+
234+
callToolRequest = mcp.CallToolRequest{}
235+
callToolRequest.Params.Name = "typed-test-tool"
236+
callToolRequest.Params.Arguments = map[string]interface{}{
237+
"input": "test-value",
238+
}
239+
240+
callToolResult, err = tt.client.CallTool(ctx, callToolRequest)
241+
assert.NoError(t, err)
242+
assert.False(t, callToolResult.IsError)
243+
assert.Equal(t, "input: test-value", callToolResult.Content[0].(mcp.TextContent).Text)
244+
245+
logtest.AssertHasLogRecord(t, logBuffer, map[string]any{
246+
"level": "info",
247+
"mcpMethod": "tools/call",
248+
"mcpTool": "typed-test-tool",
249+
"mcpRequest": expectedRequest,
250+
"mcpResponse": expectedResponse,
251+
"mcpTransport": tt.transport,
252+
"message": "MCP request success",
253+
})
254+
255+
tracetest.AssertHasTraceSpan(
256+
t,
257+
traceExporter,
258+
"MCP tools/call typed-test-tool",
259+
attribute.String("mcp.method", "tools/call"),
260+
attribute.String("mcp.tool", "typed-test-tool"),
261+
attribute.String("mcp.request", expectedRequest),
262+
attribute.String("mcp.response", expectedResponse),
263+
attribute.String("mcp.transport", tt.transport),
264+
)
265+
266+
expectedMetric = `
267+
# HELP foo_bar_mcp_server_requests_total Number of processed MCP requests
268+
# TYPE foo_bar_mcp_server_requests_total counter
269+
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="advanced-test-tool"} 1
270+
foo_bar_mcp_server_requests_total{method="tools/call",status="error",target="advanced-test-tool"} 1
271+
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="typed-test-tool"} 1
272+
`
273+
err = testutil.GatherAndCompare(
274+
metricsRegistry,
275+
strings.NewReader(expectedMetric),
276+
"foo_bar_mcp_server_requests_total",
277+
)
278+
assert.NoError(t, err)
279+
280+
// send success structured tools/call request
281+
expectedRequest = `{"method":"tools/call","params":{"name":"structured-test-tool","arguments":{"input":"structured-value"}}}`
282+
283+
callToolRequest = mcp.CallToolRequest{}
284+
callToolRequest.Params.Name = "structured-test-tool"
285+
callToolRequest.Params.Arguments = map[string]interface{}{
286+
"input": "structured-value",
287+
}
288+
289+
callToolResult, err = tt.client.CallTool(ctx, callToolRequest)
290+
assert.NoError(t, err)
291+
assert.False(t, callToolResult.IsError)
292+
assert.Equal(t, "{\"output\":\"input: structured-value\"}", callToolResult.Content[0].(mcp.TextContent).Text)
293+
294+
logtest.AssertHasLogRecord(t, logBuffer, map[string]any{
295+
"level": "info",
296+
"mcpMethod": "tools/call",
297+
"mcpTool": "structured-test-tool",
298+
"mcpRequest": expectedRequest,
299+
"mcpTransport": tt.transport,
300+
"message": "MCP request success",
301+
})
302+
303+
tracetest.AssertHasTraceSpan(
304+
t,
305+
traceExporter,
306+
"MCP tools/call structured-test-tool",
307+
attribute.String("mcp.method", "tools/call"),
308+
attribute.String("mcp.tool", "structured-test-tool"),
309+
attribute.String("mcp.request", expectedRequest),
310+
attribute.String("mcp.transport", tt.transport),
311+
)
312+
313+
expectedMetric = `
314+
# HELP foo_bar_mcp_server_requests_total Number of processed MCP requests
315+
# TYPE foo_bar_mcp_server_requests_total counter
316+
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="advanced-test-tool"} 1
317+
foo_bar_mcp_server_requests_total{method="tools/call",status="error",target="advanced-test-tool"} 1
318+
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="structured-test-tool"} 1
319+
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="typed-test-tool"} 1
320+
`
321+
err = testutil.GatherAndCompare(
322+
metricsRegistry,
323+
strings.NewReader(expectedMetric),
324+
"foo_bar_mcp_server_requests_total",
325+
)
326+
assert.NoError(t, err)
327+
225328
// send success prompts/get request
226329
expectedRequest = `{"method":"prompts/get","params":{"name":"simple-test-prompt"}}`
227330
expectedResponse = `{"description":"ok","messages":[{"role":"assistant","content":{"type":"text","text":"context hook value: bar"}}]}`
@@ -261,6 +364,8 @@ func TestMCPServerModule(t *testing.T) {
261364
foo_bar_mcp_server_requests_total{method="prompts/get",status="success",target="simple-test-prompt"} 1
262365
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="advanced-test-tool"} 1
263366
foo_bar_mcp_server_requests_total{method="tools/call",status="error",target="advanced-test-tool"} 1
367+
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="structured-test-tool"} 1
368+
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="typed-test-tool"} 1
264369
`
265370
err = testutil.GatherAndCompare(
266371
metricsRegistry,
@@ -277,7 +382,7 @@ func TestMCPServerModule(t *testing.T) {
277382

278383
_, err = tt.client.GetPrompt(ctx, getPromptRequest)
279384
assert.Error(t, err)
280-
assert.Equal(t, "prompt 'invalid-test-prompt' not found: prompt not found", err.Error())
385+
assert.Contains(t, err.Error(), "prompt 'invalid-test-prompt' not found: prompt not found")
281386

282387
logtest.AssertHasLogRecord(t, logBuffer, map[string]any{
283388
"level": "error",
@@ -306,6 +411,8 @@ func TestMCPServerModule(t *testing.T) {
306411
foo_bar_mcp_server_requests_total{method="prompts/get",status="success",target="simple-test-prompt"} 1
307412
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="advanced-test-tool"} 1
308413
foo_bar_mcp_server_requests_total{method="tools/call",status="error",target="advanced-test-tool"} 1
414+
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="structured-test-tool"} 1
415+
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="typed-test-tool"} 1
309416
`
310417
err = testutil.GatherAndCompare(
311418
metricsRegistry,
@@ -354,6 +461,8 @@ func TestMCPServerModule(t *testing.T) {
354461
foo_bar_mcp_server_requests_total{method="resources/read",status="success",target="simple-test://resources"} 1
355462
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="advanced-test-tool"} 1
356463
foo_bar_mcp_server_requests_total{method="tools/call",status="error",target="advanced-test-tool"} 1
464+
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="structured-test-tool"} 1
465+
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="typed-test-tool"} 1
357466
`
358467
err = testutil.GatherAndCompare(
359468
metricsRegistry,
@@ -370,7 +479,7 @@ func TestMCPServerModule(t *testing.T) {
370479

371480
_, err = tt.client.ReadResource(ctx, readResourceRequest)
372481
assert.Error(t, err)
373-
assert.Equal(t, "handler not found for resource URI 'simple-test://invalid': resource not found", err.Error())
482+
assert.Contains(t, err.Error(), "handler not found for resource URI 'simple-test://invalid': resource not found")
374483

375484
logtest.AssertHasLogRecord(t, logBuffer, map[string]any{
376485
"level": "error",
@@ -401,6 +510,8 @@ func TestMCPServerModule(t *testing.T) {
401510
foo_bar_mcp_server_requests_total{method="resources/read",status="success",target="simple-test://resources"} 1
402511
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="advanced-test-tool"} 1
403512
foo_bar_mcp_server_requests_total{method="tools/call",status="error",target="advanced-test-tool"} 1
513+
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="structured-test-tool"} 1
514+
foo_bar_mcp_server_requests_total{method="tools/call",status="success",target="typed-test-tool"} 1
404515
`
405516
err = testutil.GatherAndCompare(
406517
metricsRegistry,

fxmcpserver/server/registry_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ func TestMCPServerRegistry_Info(t *testing.T) {
2424
cfg,
2525
[]server.MCPServerTool{
2626
tool.NewSimpleTestTool(),
27+
tool.NewTypedTestTool(),
28+
tool.NewStructuredTestTool(),
2729
},
2830
[]server.MCPServerPrompt{
2931
prompt.NewSimpleTestPrompt(),
@@ -53,7 +55,9 @@ func TestMCPServerRegistry_Info(t *testing.T) {
5355
ResourceTemplates map[string]string
5456
}{
5557
Tools: map[string]string{
56-
"simple-test-tool": "github.com/ankorstore/yokai/fxmcpserver/testdata/tool.(*SimpleTestTool).Handle.func1",
58+
"simple-test-tool": "github.com/ankorstore/yokai/fxmcpserver/testdata/tool.(*SimpleTestTool).Handle.func1",
59+
"typed-test-tool": "github.com/ankorstore/yokai/fxmcpserver/testdata/tool.(*TypedTestTool).Handle.NewTypedToolHandler[...].func2",
60+
"structured-test-tool": "github.com/ankorstore/yokai/fxmcpserver/testdata/tool.(*StructuredTestTool).Handle.NewStructuredToolHandler[...].func2",
5761
},
5862
Prompts: map[string]string{
5963
"simple-test-prompt": "github.com/ankorstore/yokai/fxmcpserver/testdata/prompt.(*SimpleTestPrompt).Handle.func1",
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package tool
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/mark3labs/mcp-go/mcp"
8+
"github.com/mark3labs/mcp-go/server"
9+
)
10+
11+
type StructuredTestTool struct{}
12+
13+
func NewStructuredTestTool() *StructuredTestTool {
14+
return &StructuredTestTool{}
15+
}
16+
17+
func (t *StructuredTestTool) Name() string {
18+
return "structured-test-tool"
19+
}
20+
21+
func (t *StructuredTestTool) Options() []mcp.ToolOption {
22+
return []mcp.ToolOption{
23+
mcp.WithDescription("Structured test tool."),
24+
mcp.WithInputSchema[StructuredTestToolRequest](),
25+
mcp.WithOutputSchema[StructuredTestToolResult](),
26+
}
27+
}
28+
29+
type StructuredTestToolRequest struct {
30+
Input string `json:"input" jsonschema_description:"Test input" jsonschema:"required"`
31+
}
32+
33+
type StructuredTestToolResult struct {
34+
Output string `json:"output" jsonschema_description:"Test output"`
35+
}
36+
37+
func (t *StructuredTestTool) Handle() server.ToolHandlerFunc {
38+
return mcp.NewStructuredToolHandler(
39+
func(ctx context.Context, request mcp.CallToolRequest, args StructuredTestToolRequest) (StructuredTestToolResult, error) {
40+
output := fmt.Sprintf("input: %s", args.Input)
41+
42+
return StructuredTestToolResult{
43+
Output: output,
44+
}, nil
45+
},
46+
)
47+
}

0 commit comments

Comments
 (0)