What it is
MCP (Model Context Protocol) is a standard way for an LLM application to discover and call tools exposed by an external server. In practice, it lets you treat remote capabilities (APIs, databases, CLIs, internal services) as tools that your agent can call.
Phero’s mcp package adapts an MCP client session into Phero’s llm.Tool values, so the
agent loop can call MCP tools the same way it calls in-process tools.
How it’s used
In practice, you connect to an MCP server, list its tools, convert them to agent tools, and then run the agent. The examples/mcp example shows the full flow.
// From examples/mcp (edited for brevity)
ctx := context.Background()
client := gomcp.NewClient(&gomcp.Implementation{Name: "myclient", Version: "1.0.0"}, nil)
// Launch MCP server as a subprocess and connect over stdio
command := "./examples/mcp/server/server"
cmd := exec.CommandContext(ctx, command)
transport := &gomcp.CommandTransport{Command: cmd}
session, err := client.Connect(ctx, transport, nil)
if err != nil {
panic(err)
}
defer session.Close()
mcpServer := mcp.New(session)
tools, err := mcpServer.AsTools(ctx, nil)
if err != nil {
panic(err)
}
llmClient := /* any llm.LLM */
a, _ := agent.New(llmClient, "MCP Agent", "An agent that uses tools from an MCP server.")
for _, tool := range tools {
_ = a.AddTool(tool)
}
res, _ := a.Run(ctx, llm.Text("Give me a random quote."))
fmt.Println(res.TextContent())
Example: run an MCP server over stdio
The repo includes an end-to-end example under
examples/mcp.
The server exposes a single tool: get_random_quote.
1) Build the server
# from repo root
make -C ./examples/mcp/server build
# binary created at:
# ./examples/mcp/server/server
2) Run the client
# IMPORTANT: run from the repo root (client uses a relative path)
go run ./examples/mcp
Client side (connect + expose tools to an agent)
ctx := context.Background()
client := gomcp.NewClient(&gomcp.Implementation{Name: "myclient", Version: "1.0.0"}, nil)
// Launch MCP server as a subprocess and connect over stdio
command := "./examples/mcp/server/server"
cmd := exec.CommandContext(ctx, command)
transport := &gomcp.CommandTransport{Command: cmd}
session, err := client.Connect(ctx, transport, nil)
if err != nil {
panic(err)
}
defer session.Close()
mcpServer := mcp.New(session)
tools, err := mcpServer.AsTools(ctx, nil)
if err != nil {
panic(err)
}
a, _ := agent.New(llmClient, "MCP Agent", "An agent that uses tools from an MCP server.")
for _, tool := range tools {
_ = a.AddTool(tool)
}
res, _ := a.Run(ctx, llm.Text("Give me a random quote."))
fmt.Println(res.TextContent())
Server side (define and run a tool)
server := mcp.NewServer(&mcp.Implementation{Name: "random_quote", Version: "v1.0.0"}, nil)
mcp.AddTool(
server,
&mcp.Tool{Name: "get_random_quote", Description: "Fetches a random inspirational quote"},
getRandomQuoteHandler,
)
// Run over stdin/stdout until client disconnects
_ = server.Run(context.Background(), &mcp.StdioTransport{})
Filtering tools
If your MCP server exposes many tools, you can selectively expose them to an agent by passing a filter:
tools, err := mcpServer.AsTools(ctx, func(name string) bool {
// allowlist only a subset
return name == "get_random_quote" || strings.HasPrefix(name, "search_")
})
When an agent uses several MCP servers, two of them may offer a tool with the same name. Give each server
a prefix with mcp.WithToolPrefix; the MCP server is still called with the tool's own name:
github := mcp.New(githubSession, mcp.WithToolPrefix("github_"))
browser := mcp.New(playwrightSession, mcp.WithToolPrefix("browser_"))
Tool names, with the prefix, are made ones every provider accepts: characters other than
[a-zA-Z0-9_-] become _ and names are cut at 64 characters
(llm.SanitizeToolName). When two names become the same, the later gets a numbered suffix
(files_read_2). The filter still gets the MCP tool's own name.
Tool results
- Text content is joined into the tool's text result.
- Image content, such as a Playwright screenshot, is passed to the model as an image part.
- A result with
IsErrorset returns an*mcp.ToolResultErrorholding the result's text, so the agent reports a failed tool call to the model. mcp.WithMaxOutputChars(n)caps the text of each result, error text included, atnbytes, the closing[output cut at n characters]note included, so one call cannot fill a small context window. Images are kept. There is no cap by default.
server := mcp.New(session, mcp.WithMaxOutputChars(30_000))
Notes & troubleshooting
- The example server calls
https://zenquotes.io/api/random(needs outbound HTTP). - Run the MCP client from the repo root, or update the server command path.
- Bring your own
llm.LLMbackend (see llm).