step23 · Local MCP (wrapping remote tools)
This lesson teaches how to connect to a remote MCP server and decorate the tools it exposes with your own local behavior before an agent uses them.
What this lesson demonstrates
“Local MCP” is about where the tool objects live, not where the server lives. You open a session to a remote MCP server (Microsoft Learn’s public endpoint), list the tools it publishes, and get them back as ordinary tool.Tool values in your process. Because they’re just Go values, you can wrap each one. Here a decorator prints a line every time a tool is invoked, then delegates to the real MCP tool — proving that MCP tools compose like any other tool.FuncTool.
A decorator is embedding plus one override
loggingFuncTool embeds a tool.FuncTool, inheriting Name, Description, Schema and ReturnSchema unchanged; only Call is overridden to print, then delegate:
type loggingFuncTool struct {
tool.FuncTool
}
func (t loggingFuncTool) Call(ctx context.Context, args string) (any, error) {
fmt.Printf(" >> [LOCAL MCP] Invoking tool %q locally...\n", t.Name())
return t.FuncTool.Call(ctx, args)
}
wrapTools applies it to every FuncTool in the slice and passes non-FuncTools through untouched.
What to notice / the gotcha
- Delegation is what makes it transparent. Because
Callends int.FuncTool.Call(...), the agent gets the same result the underlying tool would have returned — the decorator adds behavior without changing outcomes. Swap the print for metrics, caching, or argument validation and nothing else moves. - Type-assert to
tool.FuncToolbefore wrapping.mcptool.ListToolsreturns[]tool.Tool; only the ones that assert totool.FuncToolare callable and decoratable.wrapToolsskips the rest, preserving order. wrapToolsis pure and returns a fresh slice, leaving its input untouched — which is exactly why the offline test can feed it fakes and assert the decoration without any session or model.
How it maps to the Agent Framework
This is the natural extension of step09 (MCP client as tools). There you handed the remote tools straight to the agent; here you interpose a local decorator first. The Go SDK’s mcptool returns tools as first-class tool.Tool values, so the same composition tricks you’d use on any function tool — wrapping, timing, guarding — apply unchanged to tools resolved over the wire.
Run it
go run ./tutorial/02-agents/providers/foundry/step23_local_mcp
The live run needs az login, FOUNDRY_PROJECT_ENDPOINT, and outbound network to learn.microsoft.com. Offline tests wrap a fake tool and assert the decorator forwards args, returns the inner result, propagates errors, and wraps only FuncTools — no session, no model; the end-to-end run is gated behind AF_LIVE=1.
Next: providers/gemini · The Same Agent, Backed by Google Gemini