A Go library that executes dynamically generated Go snippets against MCP (Model Context Protocol) tools using the Yaegi interpreter.
codemode bridges LLM-generated Go code with MCP tools at runtime. It provides
two layers:
CodeModeMCP— executes arbitrary Go snippets inside a sandboxed Yaegi interpreter, with an injectedcodemodehelper that can call MCP tools.CodeModeOrchestrator— wrapsCodeModeMCPwith an LLM-driven pipeline that decides whether tools are needed, selects them, generates a snippet, and executes it.
go get github.com/Protocol-Lattice/codemodeclient := yourMCPClient // implements codemode.MCPClient
cm := codemode.NewCodeModeMCP(client)
result, err := cm.Execute(ctx, codemode.CodeModeArgs{
Code: `__out = codemode.Sprintf("hello %s", "world")`,
Timeout: 5000, // ms — defaults to 30000 if <= 0
})
// result.Value → "hello world"
// result.Stdout → captured stdout
// result.Stderr → captured stderrSnippets run inside a func run() any wrapper. Assign the return value to
__out. No imports or package declarations — just Go statements.
| Helper | Signature | Description |
|---|---|---|
codemode.CallTool |
(name string, args map[string]any) (any, error) |
Call an MCP tool by name |
codemode.ListTools |
() ([]mcp.Tool, error) |
List all available MCP tools |
codemode.Sprintf |
(format string, a ...any) string |
fmt.Sprintf |
codemode.Errorf |
(format string, a ...any) error |
fmt.Errorf |
CallTool returns the tool's StructuredContent when present; otherwise it
returns the concatenated TextContent blocks as a single string. Tool errors
(IsError == true) are surfaced as Go errors.
cm := &codemode.CodeModeOrchestrator{
CodeModeMCP: codemode.NewCodeModeMCP(mcpClient),
// model: your Model implementation (calls an LLM)
// cache: your ToolSelectionCache (optional)
}
used, result, err := cm.CallTool(ctx, "search for files modified today")CallTool runs the full pipeline:
- Decide — asks the LLM whether any tool is needed for the prompt.
- Select — asks the LLM to choose matching tools from the available set.
- Generate — asks the LLM to produce a Go snippet using those tools.
- Execute — runs the snippet via Yaegi and returns the result.
Returns (toolsUsed bool, result any, err error). If the LLM decides no tools
are needed (or selects none), toolsUsed is false and result is nil.
Snippet execution uses a fixed 20-second timeout.
// Implement to provide an MCP server connection.
type MCPClient interface {
ListTools(ctx context.Context, request mcp.ListToolsRequest) (*mcp.ListToolsResult, error)
CallTool(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error)
}
// Implement to provide an LLM for orchestration. The returned value is
// stringified via fmt.Sprint; a JSON object is then extracted and parsed.
type Model interface {
Generate(ctx context.Context, prompt string) (any, error)
}
// Optional caching layer for tool specs and tool selections.
type ToolSelectionCache interface {
GetToolSpecs() []mcp.Tool
SetToolSpecs([]mcp.Tool)
GetSelectedTools(query, tools string) []string
SetSelectedTools(query, tools string, selected []string)
InvalidateToolSpecs()
InvalidateSelections()
InvalidateAll()
Stats() CacheStats
StartCleanupRoutine(ctx context.Context, interval time.Duration)
}CodeModeOrchestrator accepts an optional ToolSelectionCache to avoid
redundant LLM round-trips. The cache stores:
- Tool specs — the merged, deduplicated list returned by
ToolSpecs(). - Tool selections — keyed by
(query, rendered tool list).
Provide your own implementation, or leave cache nil to disable caching.
cm.InvalidateToolSpecsCache() // clear cached tool specs
cm.InvalidateSelectionsCache() // clear cached tool selections
cm.InvalidateAllCaches() // clear everything
stats := cm.CacheStats() // Hits / Misses
cm.StartCacheCleanup(ctx, 5*time.Minute) // run periodic evictionThe Decide step is not cached; Select and ToolSpecs are.
Snippets are evaluated by Yaegi with a deliberately minimal stdlib surface —
only context, fmt, and reflect are imported — plus the injected
codemode helpers. There is no os, no filesystem, and no network access
from inside a snippet. All side effects must go through MCP tool calls.
Execution runs in a goroutine guarded by context.WithTimeout. Interpreter
panics are recovered and returned as errors, with captured stdout / stderr
preserved on the CodeModeResult.