mcprt

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Mar 1, 2026 License: MIT Imports: 15 Imported by: 0

README

mcprt — dynamic MCP tool registry

mcprt stores MCP tool definitions in SQLite and hot-reloads them via PRAGMA data_version. Tools can execute SQL queries, SQL scripts, or Go functions — all defined in database rows.

┌──────────────────────┐       ┌───────────────┐
│  mcp_tools_registry  │──────►│   Registry    │──► MCP Server
│  (SQLite table)      │ watch │   (in-memory) │    (via Bridge)
└──────────────────────┘       └───────────────┘

Quick start

reg := mcprt.NewRegistry(db)
reg.Init()
reg.RegisterGoFunc("compute_stats", statsFunc)
go reg.RunWatcher(ctx)

// Bridge registers all active tools into an MCP server.
mcprt.Bridge(mcpServer, reg)

Handler types

Type Config Description
sql_query {"query": "SELECT ..."} Execute SELECT, return JSON array
sql_script {"statements": [...]} Execute multiple statements, optional transaction
go_function Call a pre-registered Go function
SQL query example
{
  "tool_name": "list_users",
  "handler_type": "sql_query",
  "handler_config": "{\"query\": \"SELECT id, name FROM users WHERE role = ?\", \"params\": [\"role\"]}",
  "input_schema": "{\"type\": \"object\", \"properties\": {\"role\": {\"type\": \"string\"}}}"
}
Template parameters

SQL handlers support template expressions: {{uuid()}}, {{now()}}, and {{param_name}} for parameter references.

Schema

CREATE TABLE mcp_tools_registry (
    tool_name      TEXT PRIMARY KEY,
    tool_category  TEXT,
    description    TEXT,
    input_schema   TEXT,         -- JSON Schema
    handler_type   TEXT NOT NULL, -- sql_query, sql_script, go_function
    handler_config TEXT,         -- JSON
    is_active      TEXT NOT NULL DEFAULT 'true',
    created_at     INTEGER,
    updated_at     INTEGER,
    created_by     TEXT,
    version        INTEGER DEFAULT 1
);

CREATE TABLE mcp_tools_history (
    history_id   INTEGER PRIMARY KEY AUTOINCREMENT,
    tool_name    TEXT, version INTEGER,
    changed_by   TEXT, changed_at INTEGER, change_reason TEXT
);

Exported API

Symbol Description
Registry Tool store with hot-reload
NewRegistry(db, opts) Create registry
DynamicTool Tool definition loaded from DB
RegisterGoFunc(name, fn) Register a Go handler
Bridge(srv, reg) Register all tools into MCP server

Documentation

Index

Constants

View Source
const (
	ModeReadonly  = "readonly"
	ModeReadWrite = "readwrite"
)

ModeReadonly and ModeReadWrite are the valid values for DynamicTool.Mode.

View Source
const (
	HandlerSQLQuery   = "sql_query"
	HandlerSQLScript  = "sql_script"
	HandlerGoFunction = "go_function"
)
View Source
const Schema = `` /* 2802-byte string literal not displayed */

Variables

This section is empty.

Functions

func Bridge

func Bridge(srv *mcp.Server, reg *Registry, opts ...BridgeOption)

Bridge registers all dynamic tools from the registry into an MCP server.

Migration note (feb 2026): uses official SDK's low-level ToolHandler (not the generic AddTool). Arguments arrive as json.RawMessage in req.Params.Arguments (not pre-decoded map[string]any like mcp-go did). InputSchema is set as json.RawMessage on mcp.Tool — must be valid JSON with "type":"object" or the SDK may reject it. Returning a non-nil error from the handler = JSON-RPC protocol error. For tool errors, use result.SetError(err) and return (result, nil).

func GetGroupSession

func GetGroupSession(ctx context.Context) string

GetGroupSession returns the group session tag from context, or empty string.

func WithGroupSession

func WithGroupSession(ctx context.Context, group string) context.Context

WithGroupSession returns a context tagged with the group from the first tool called in this session. Subsequent calls in the same context are checked against this initial group for isolation.

Types

type AuditFunc

type AuditFunc func(ctx context.Context, toolName string, toolVersion int, params map[string]any, result string, err error, duration time.Duration)

AuditFunc records a tool execution for observability. toolVersion captures which version of the tool definition ran.

type BridgeOption

type BridgeOption func(*bridgeConfig)

BridgeOption configures Bridge behavior.

func WithAudit

func WithAudit(fn AuditFunc) BridgeOption

WithAudit adds an audit hook called after each tool execution.

func WithGroupIsolation

func WithGroupIsolation(groups map[string][]string) BridgeOption

WithGroupIsolation configures group isolation on the Bridge. The groups map defines which tools belong to which group. Tools in incompatible groups cannot be called in the same session.

Example:

mcprt.WithGroupIsolation(map[string][]string{
    "sensitive": {"vault_read", "secrets_list"},
    "public":    {"search_docs", "list_items"},
})

In this configuration, if the first tool called is in "sensitive", subsequent calls to "public" tools are rejected (and vice versa). Tools in the same group or in "default" are always compatible.

func WithPolicy

func WithPolicy(fn PolicyFunc) BridgeOption

WithPolicy adds a policy check before each tool execution.

func WithSanitizer

func WithSanitizer(san *Sanitizer) BridgeOption

WithSanitizer returns a BridgeOption that applies a Sanitizer to all tools before registering them with the MCP server.

func WithTimeoutFromDB

func WithTimeoutFromDB() BridgeOption

WithTimeoutFromDB enables per-tool timeouts read from the timeout_ms column in mcp_tools_registry. Each tool execution is wrapped in a context.WithTimeout derived from the stored value. A value of 0 in the database means no timeout is applied.

type DBPolicy

type DBPolicy struct {
	// contains filtered or unexported fields
}

DBPolicy evaluates per-tool access rules stored in mcp_tool_policy.

Evaluation logic:

  • If any DENY rule matches the caller's role → deny.
  • If ALLOW rules exist for the tool but none match → deny.
  • If no rules exist for the tool → allow (default open, backwards compatible).

func (*DBPolicy) Evaluate

func (p *DBPolicy) Evaluate(ctx context.Context, toolName string) error

Evaluate checks whether the current caller (identified by role in context) is allowed to execute the named tool.

type DynamicTool

type DynamicTool struct {
	Name          string
	Category      string
	Description   string
	InputSchema   map[string]any
	HandlerType   string
	HandlerConfig map[string]any
	Mode          string // "readonly" or "readwrite"
	Version       int
	IsActive      bool
	GroupTag      string // group isolation tag (default: "default")
	TimeoutMs     int    // per-tool timeout in milliseconds (0 = use default)
}

DynamicTool is a tool loaded from the mcp_tools_registry table.

type GoFunc

type GoFunc func(ctx context.Context, params map[string]any) (string, error)

GoFunc is a pre-registered Go function callable from a dynamic tool.

type PolicyFunc

type PolicyFunc func(ctx context.Context, toolName string) error

PolicyFunc decides whether a tool call is allowed. Return nil to allow, non-nil error to deny.

func NewDBPolicy

func NewDBPolicy(db *sql.DB) PolicyFunc

NewDBPolicy creates a PolicyFunc backed by the mcp_tool_policy table.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry holds loaded tools in memory with a watcher for hot reload.

func NewRegistry

func NewRegistry(db *sql.DB, opts ...RegistryOption) *Registry

func (*Registry) ExecuteTool

func (r *Registry) ExecuteTool(ctx context.Context, toolName string, params map[string]any) (string, error)

func (*Registry) GetTool

func (r *Registry) GetTool(name string) (*DynamicTool, bool)

func (*Registry) Init

func (r *Registry) Init() error

Init creates the registry tables and applies migrations for existing databases.

func (*Registry) ListTools

func (r *Registry) ListTools() []*DynamicTool

func (*Registry) LoadTools

func (r *Registry) LoadTools(ctx context.Context) error

LoadTools loads all active tools from the mcp_tools_registry table.

func (*Registry) RegisterGoFunc

func (r *Registry) RegisterGoFunc(name string, fn GoFunc)

RegisterGoFunc registers a Go function callable by dynamic tools with handler_type="go_function".

func (*Registry) RunWatcher

func (r *Registry) RunWatcher(ctx context.Context)

RunWatcher polls for database changes and reloads tools automatically. It uses watch.Watcher with PRAGMA data_version detection.

type RegistryOption

type RegistryOption func(*Registry)

RegistryOption configures a Registry.

func WithRegistryIDGenerator

func WithRegistryIDGenerator(gen idgen.Generator) RegistryOption

WithRegistryIDGenerator sets a custom ID generator for template expansions.

type SQLQueryHandler

type SQLQueryHandler struct{ DB *sql.DB }

SQLQueryHandler executes a SELECT and returns results as JSON.

func (*SQLQueryHandler) Execute

func (h *SQLQueryHandler) Execute(ctx context.Context, tool *DynamicTool, params map[string]any) (string, error)

type SQLScriptHandler

type SQLScriptHandler struct {
	DB    *sql.DB
	NewID idgen.Generator
}

SQLScriptHandler executes multi-statement transactions.

func (*SQLScriptHandler) Execute

func (h *SQLScriptHandler) Execute(ctx context.Context, tool *DynamicTool, params map[string]any) (string, error)

type Sanitizer

type Sanitizer struct {
	// contains filtered or unexported fields
}

Sanitizer cleans tool descriptions and names before exposing them to an LLM via tools/list. Protects against prompt injection via tool metadata from downstream MCP servers.

func DefaultSanitizer

func DefaultSanitizer(opts ...SanitizerOption) *Sanitizer

DefaultSanitizer creates a Sanitizer with all protections enabled.

func (*Sanitizer) SanitizeDescription

func (s *Sanitizer) SanitizeDescription(desc string) string

SanitizeDescription cleans a tool description applying all configured filters.

func (*Sanitizer) SanitizeName

func (s *Sanitizer) SanitizeName(name string) string

SanitizeName cleans a tool name: strips control characters and normalizes unicode.

func (*Sanitizer) SanitizeTool

func (s *Sanitizer) SanitizeTool(t *DynamicTool)

SanitizeTool applies sanitization to a DynamicTool's Name and Description in place.

type SanitizerOption

type SanitizerOption func(*Sanitizer)

SanitizerOption configures a Sanitizer.

func WithCustomFilter

func WithCustomFilter(fn func(string) string) SanitizerOption

WithCustomFilter adds a custom string filter applied after built-in filters.

func WithMaxDescriptionLength

func WithMaxDescriptionLength(n int) SanitizerOption

WithMaxDescriptionLength sets the maximum description length (default 1024).

type ToolHandler

type ToolHandler interface {
	Execute(ctx context.Context, tool *DynamicTool, params map[string]any) (string, error)
}

ToolHandler executes a dynamic tool with the given parameters.

Jump to

Keyboard shortcuts

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