toolify

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 6, 2026 License: MIT Imports: 4 Imported by: 0

README

mcp-toolify

English | 简体中文

Go Reference MCP Stars

Turn a plain Go function into an MCP tool with one comment — // mcp:tool, go generate, done.

mcp-toolify is a code generator plus runtime for building Model Context Protocol servers in Go. Annotate an ordinary function's godoc with // mcp:tool, run go generate, and it becomes a fully-typed MCP tool — input struct, JSON schema, and registration all generated for you. No hand-written wrappers, no runtime reflection. The runtime adds the things real deployments need: connection-level authorization, automatic spill of oversized results, and call auditing.

Write the function, tag it, generate. Your tool's schema, description, argument docs, and risk metadata all come straight from the code you already wrote.

Built in Go, official MCP SDK, stdio + Streamable HTTP. Drop it into Cursor, Claude, Comate, or any MCP client — or embed it into a host server you already run.


Why mcp-toolify

Most ways to expose Go logic as MCP tools mean hand-writing a wrapper per function: an input struct, a JSON schema, argument descriptions, a handler that unpacks args and packs results, plus registration boilerplate. It drifts from the real function the moment you touch it, and it says nothing about risk, size, or auth. mcp-toolify nails five things:

  • 1. Annotation-driven, zero boilerplate — the code is the spec. Add // mcp:tool to a function's godoc and a standalone generator (cmd/mcpgen) emits a typed wrapper: input struct from the parameters, JSON-schema descriptions from param: lines, the tool description from the doc comment. Generated code calls your function directly — no runtime reflection, and the tool can never silently drift from the signature.
  • 2. Oversized results spill to disk — the model context never blows up. Every tool return is measured; anything over a configurable token budget is written to a temp file and returned as an MCP resource link + short summary instead of a giant payload. A built-in companion tool, spill_explore (read / grep / schema / jq over json/jsonl/text), lets the agent explore the blob on demand and pull only the lines it needs. Cross-machine deployments get a direct download URL for free.
  • 3. Connection-level authorization, two layers. Per-token read/write risk ceilings decide what a connection may ever do (and filter tools/list accordingly); tools/call additionally checks the caller identity against a per-risk allow-list. One agent can share a connection across many users, each still gated by who they are. Tools declare risk with mcp:risk=low|medium|high and write-intent with the write tag.
  • 4. Standalone or embedded — share one server. Run it over stdio or HTTP as its own process, or get two http.Handlers and mount onto an HTTP server you already have, sharing the port and lifecycle. External, hand-written tools can be registered onto the same server alongside the generated ones (just register their risk metadata).
  • 5. No proprietary dependencies — clean, portable, auditable. Only the official Go MCP SDK, jsonschema-go, BurntSushi/toml, and (for the built-in spill tool) gojq. The generator is its own module with just golang.org/x/tools + yaml.v3. Nothing else to trust.

Plus the machinery that makes the above reliable:

  • Honest schemas for tricky types. interface{} parameters get an explicit half-restricted JSON schema (a type union) instead of the SDK's unconstrained empty node, so the model still gets type hints. Multiple return values are packed into a stable, named JSON object.
  • Interface parameters, handled. A parameter the model can't build from JSON (an interface) is bound to a concrete type with mcp:bind=param:Type; mcp:import=<path> pulls in an external package for that type. Fully generic — no framework-specific special-casing.
  • Pluggable auditing. Inject a Logger to record every call (user, tool, args, result, cost) as structured fields; with none set it falls back to the standard library.
  • Bounded, self-cleaning spill store. Spill files live under a temp dir with per-category TTLs and a background GC; nothing accumulates forever.

Quick start

Requires Go 1.25+.

1. Annotate a function.

package greeter

// Greet builds a greeting.
//
// param: name — the name to greet
// param: excited — add an exclamation mark
//
// mcp:tool
// mcp:tags=read
func Greet(name string, excited bool) (string, error) {
	if name == "" {
		return "", fmt.Errorf("name is required")
	}
	msg := "Hello, " + name
	if excited {
		msg += "!"
	}
	return msg, nil
}

2. List your packages in mcpgen.yaml.

output:
  dir: ./tools
packages:
  - github.com/you/yourmod/greeter
  # optional: expose the built-in large-result explorer
  - github.com/fzxbl/mcp-toolify/spillexplore

3. Generate the wrappers.

//go:generate go run github.com/fzxbl/mcp-toolify/cmd/mcpgen -config ./mcpgen.yaml
go generate ./...

4. Start a server.

package main

import (
	"context"

	toolify "github.com/fzxbl/mcp-toolify"
	"github.com/you/yourmod/tools" // generated
)

func main() {
	_ = toolify.Start(context.Background(),
		toolify.Config{Transport: "stdio"}, tools.RegisterAll)
}

A runnable end-to-end sample lives in example/. Inspect the exposed tools (name + description + schema) with:

go run ./cmd/listtools -short

Annotation markers

All live in the function's godoc comment:

  • mcp:tool — expose this function (required).
  • mcp:name=<n> — override the tool name (default <pkg>.<snake_case_func>).
  • mcp:tags=a,b — tags for start-up filtering; the write tag marks a write operation for authz.
  • mcp:risk=low|medium|high — risk level (default none), enforced by HTTP authz.
  • mcp:bind=<param>:<Type> — bind an interface parameter (not JSON-constructible) to a concrete input type.
  • mcp:import=<path> — import path for a package referenced by a mcp:bind type outside the source package.

param: <name> — <desc> lines become per-argument JSON-schema descriptions.

Run over HTTP / mount onto an existing server

toolify.Start with Config{Transport: "http", Addr: ":8080"} runs a standalone HTTP server. To mount onto a server you already have:

mcpH, spillH, err := toolify.Handlers(cfg, tools.RegisterAll)
mux.Handle("/mcp", mcpH)      // MCP endpoint (Streamable HTTP)
mux.Handle("/spill/", spillH) // large-result download endpoint

Authorization

Set Config.AuthzEnabled = true (HTTP only) and point Config.ConfigPath at a TOML file:

identity_headers = ["X-MCP-User"]   # ordered headers for caller identity; first non-empty wins (top-level; omit => ["X-MCP-User"])

[spill]
max_result_tokens = 4000   # results above this estimate spill to disk; -1 disables

[[tokens]]
token = "..."          # Authorization: Bearer <token>
name = "readonly-agent"
applicant = "you"
read = "medium"        # max read risk; omit to disallow reads
# write = "low"        # max write risk; omit to disallow writes

[risk_allowlist]
high = ["alice"]       # identity values allowed to run high-risk tools
medium = ["bob"]
  • Connection + tools/list are filtered by the token's ceilings only (what this connection can ever do).
  • tools/call additionally checks the caller identity (resolved from identity_headers front-to-back, first non-empty wins; default X-MCP-User) against risk_allowlist for medium/high tools.

Audit headers

Point Config.ConfigPath at the same TOML file to record chosen request headers into the audit log. This is audit-only and independent of AuthzEnabled:

[audit]
headers = ["X-Tenant-Id", "X-Client-Id"]

Each listed header becomes its own audit field, keyed by the lowercased header name (e.g. X-Tenant-Idx-tenant-id); a missing header is logged as -. Omit the section (or leave it empty) to record no extra headers. Values are trusted as-is, so only enable this behind a trusted gateway and avoid listing credential headers (Authorization, Cookie).

Request id & access log

Every HTTP request carries a logid for cross-log correlation:

[log]
logid_header = "X-Log-Id"   # header to read the incoming logid from; omit => "X-Log-Id"

HTTPLogID takes the logid from that header (or generates one when absent), injects it into the request context, echoes it back in the same response header, and records it as the logid field of every audit record. When you run the built-in standalone server (Start/Run over HTTP) it also emits a per-request access log (logid, method, path, status, cost, user) through the same Logger, so a request can be traced across the access log and the audit log by its logid. When you mount the handlers into your own server (Handlers), no access log is added — the logid is still injected and echoed so your host's access log can correlate.

Layout

  • toolify.go — public entry points: Start, Handlers, Config, Logger, SetAuditLogger, RegisterToolMeta.
  • runtime/ — server wiring, authz, spill store, audit logging.
  • spillexplore/ — the built-in spill_explore tool.
  • cmd/mcpgen/ — the code generator (its own module).
  • cmd/listtools/ — dev helper to dump exposed tools + schemas.
  • example/ — a minimal, runnable end-to-end sample.

Generated *_gen.go files are normally not checked in (regenerated on build); this repo commits example/tools/ only so the sample builds out of the box.

Security

When served over HTTP without AuthzEnabled, the MCP endpoint is unauthenticated — calling a tool runs your Go code. Bind to loopback or front it with an authenticating proxy, or enable authz. Note that X-MCP-User is trusted as-is, so it must be injected by a trusted gateway, never accepted from clients directly.

License

MIT — see LICENSE. Contributions and stars welcome.

Documentation

Overview

Package toolify 把带 `// mcp:tool` 标记的 Go 函数暴露为 MCP(Model Context Protocol)工具。生成器(cmd/mcpgen)扫描注解生成 wrapper 代码,本包提供把这些 wrapper 挂到 MCP server 上运行的入口(stdio / http),并附带连接级鉴权、 大返回结果落盘(spill)与调用审计等运行时能力。

典型用法:

//go:generate go run github.com/fzxbl/mcp-toolify/cmd/mcpgen -config ./mcpgen.yaml
func main() {
    _ = toolify.Start(context.Background(), toolify.Config{Transport: "stdio"}, tools.RegisterAll)
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Handlers

func Handlers(cfg Config, registrar Registrar, extra ...func(*mcp.Server)) (mcpHandler, spillHandler http.Handler, err error)

Handlers 构造用于挂载到既有 HTTP server 的两个 http.Handler:

  • mcpHandler:MCP 协议端点(Streamable HTTP),可挂在任意子路径(如 /mcp)。
  • spillHandler:大返回结果的临时下载端点(默认 /spill/<id>)。

与 Start 不同,Handlers 不自己监听端口,而是把 handler 交给调用方挂载到已有的 HTTP server 上,从而与主服务共用同一端口与生命周期。

extra 为可选的额外工具注册器:除生成的 registrar 外,把外部模块的工具注册到 同一个 server。注意:外部工具需另行调用 RegisterToolMeta 登记风险等级, 否则在 authz 中按“未登记默认放行”处理。

若 cfg.PublicBaseURL 非空,会设置 spill 下载端点的对外基础地址(跨机部署时 agent 直连下载用)。

func RegisterToolMeta

func RegisterToolMeta(name string, write bool, risk string)

RegisterToolMeta 为“非注解生成、运行时注册”的外部工具登记鉴权元数据(能力+风险)。 write=true 表示写操作(ReadWrite),否则只读;risk 取 none|low|medium|high。 必须在处理请求前调用,否则该工具在 authz 里按“未登记默认放行”处理。

func SetAuditLogger

func SetAuditLogger(l Logger)

SetAuditLogger 注入调用审计 logger。应在启动/挂载 server 前调用。

func Start

func Start(ctx context.Context, cfg Config, registrar Registrar) error

Start 用给定 registrar 启动 MCP server,阻塞直到 ctx 取消或 server 退出。

Types

type Config

type Config = runtime.Config

Config 是 server 启动配置(runtime.Config 的别名,外部只需 import 本包)。

type Logger

type Logger = runtime.Logger

Logger 是 MCP 调用审计日志接口;不注入时回退到标准 log。

type RegisterOptions

type RegisterOptions = runtime.RegisterOptions

RegisterOptions 控制启用哪些生成的工具(按包名/标签过滤)。

type Registrar

type Registrar = runtime.Registrar

Registrar 是生成代码暴露的注册函数类型(通常是生成的 tools.RegisterAll)。

Directories

Path Synopsis
cmd
listtools command
listtools 用 in-memory transport 启动 mcp server,验证暴露给 MCP 客户端 (Cherry Studio / Claude Desktop 等)的完整元数据。
listtools 用 in-memory transport 启动 mcp server,验证暴露给 MCP 客户端 (Cherry Studio / Claude Desktop 等)的完整元数据。
mcpgen module
example
cmd/server command
Command server is a minimal mcp-toolify server exposing the example tools.
Command server is a minimal mcp-toolify server exposing the example tools.
greeter
Package greeter is a tiny example of tools exposed via mcp-toolify.
Package greeter is a tiny example of tools exposed via mcp-toolify.
Package runtime provides the hand-written runtime substrate for the auto-generated MCP tool registrations under mcp/tools.
Package runtime provides the hand-written runtime substrate for the auto-generated MCP tool registrations under mcp/tools.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL