resolvemcp

package
v1.0.71 Latest Latest
Warning

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

Go to latest
Published: Mar 27, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

README

resolvemcp

Package resolvemcp exposes registered database models as Model Context Protocol (MCP) tools and resources over HTTP/SSE transport. It mirrors the resolvespec package patterns — same model registration API, same filter/sort/pagination/preload options, same lifecycle hook system.

Quick Start

import (
    "github.com/bitechdev/ResolveSpec/pkg/resolvemcp"
    "github.com/gorilla/mux"
)

// 1. Create a handler
handler := resolvemcp.NewHandlerWithGORM(db, resolvemcp.Config{
    BaseURL: "http://localhost:8080",
})

// 2. Register models
handler.RegisterModel("public", "users", &User{})
handler.RegisterModel("public", "orders", &Order{})

// 3. Mount routes
r := mux.NewRouter()
resolvemcp.SetupMuxRoutes(r, handler)

Config

type Config struct {
    // BaseURL is the public-facing base URL of the server (e.g. "http://localhost:8080").
    // Sent to MCP clients during the SSE handshake so they know where to POST messages.
    // If empty, it is detected from each incoming request using the Host header and
    // TLS state (X-Forwarded-Proto is honoured for reverse-proxy deployments).
    BaseURL string

    // BasePath is the URL path prefix where MCP endpoints are mounted (e.g. "/mcp").
    // Required.
    BasePath string
}

Handler Creation

Function Description
NewHandlerWithGORM(db *gorm.DB, cfg Config) *Handler Backed by GORM
NewHandlerWithBun(db *bun.DB, cfg Config) *Handler Backed by Bun
NewHandlerWithDB(db common.Database, cfg Config) *Handler Backed by any common.Database
NewHandler(db common.Database, registry common.ModelRegistry, cfg Config) *Handler Full control over registry

Registering Models

handler.RegisterModel(schema, entity string, model interface{}) error
  • schema — database schema name (e.g. "public"), or empty string for no schema prefix.
  • entity — table/entity name (e.g. "users").
  • model — a pointer to a struct (e.g. &User{}).

Each call immediately creates four MCP tools and one MCP resource for the model.


HTTP / SSE Transport

The *server.SSEServer returned by any of the helpers below implements http.Handler, so it works with every Go HTTP framework.

Config.BasePath is required and used for all route registration. Config.BaseURL is optional — when empty it is detected from each request.

Gorilla Mux
resolvemcp.SetupMuxRoutes(r, handler)

Registers:

Route Method Description
{BasePath}/sse GET SSE connection — clients subscribe here
{BasePath}/message POST JSON-RPC — clients send requests here
{BasePath}/* any Full SSE server (convenience prefix)
bunrouter
resolvemcp.SetupBunRouterRoutes(router, handler)

Registers GET {BasePath}/sse and POST {BasePath}/message on the provided *bunrouter.Router.

Gin (or any http.Handler-compatible framework)

Use handler.SSEServer() to get an http.Handler and wrap it with the framework's adapter:

sse := handler.SSEServer()

// Gin
engine.Any("/mcp/*path", gin.WrapH(sse))

// net/http
http.Handle("/mcp/", sse)

// Echo
e.Any("/mcp/*", echo.WrapHandler(sse))
Authentication

Add middleware before the MCP routes. The handler itself has no auth layer.


MCP Tools

Tool Naming
{operation}_{schema}_{entity}    // e.g. read_public_users
{operation}_{entity}             // e.g. read_users  (when schema is empty)

Operations: read, create, update, delete.

Read Tool — read_{schema}_{entity}

Fetch one or many records.

Argument Type Description
id string Primary key value. Omit to return multiple records.
limit number Max records per page (recommended: 10–100).
offset number Records to skip (offset-based pagination).
cursor_forward string PK of the last record on the current page (next-page cursor).
cursor_backward string PK of the first record on the current page (prev-page cursor).
columns array Column names to include. Omit for all columns.
omit_columns array Column names to exclude.
filters array Filter objects (see Filtering).
sort array Sort objects (see Sorting).
preloads array Relation preload objects (see Preloading).

Response:

{
  "success": true,
  "data": [...],
  "metadata": {
    "total": 100,
    "filtered": 100,
    "count": 10,
    "limit": 10,
    "offset": 0
  }
}
Create Tool — create_{schema}_{entity}

Insert one or more records.

Argument Type Description
data object | array Single object or array of objects to insert.

Array input runs inside a single transaction — all succeed or all fail.

Response:

{ "success": true, "data": { ... } }
Update Tool — update_{schema}_{entity}

Partially update an existing record. Only non-null, non-empty fields in data are applied; existing values are preserved for omitted fields.

Argument Type Description
id string Primary key of the record. Can also be included inside data.
data object (required) Fields to update.

Response:

{ "success": true, "data": { ...merged record... } }
Delete Tool — delete_{schema}_{entity}

Delete a record by primary key. Irreversible.

Argument Type Description
id string (required) Primary key of the record to delete.

Response:

{ "success": true, "data": { ...deleted record... } }
Resource — {schema}.{entity}

Each model is also registered as an MCP resource with URI schema.entity (or just entity when schema is empty). Reading the resource returns up to 100 records as application/json.


Filtering

Pass an array of filter objects to the filters argument:

[
  { "column": "status", "operator": "=", "value": "active" },
  { "column": "age", "operator": ">", "value": 18, "logic_operator": "AND" },
  { "column": "role", "operator": "in", "value": ["admin", "editor"], "logic_operator": "OR" }
]
Supported Operators
Operator Aliases Description
= eq Equal
!= neq, <> Not equal
> gt Greater than
>= gte Greater than or equal
< lt Less than
<= lte Less than or equal
like SQL LIKE (case-sensitive)
ilike SQL ILIKE (case-insensitive)
in Value in list
is_null Column IS NULL
is_not_null Column IS NOT NULL
Logic Operators
  • "logic_operator": "AND" (default) — filter is AND-chained with the previous condition.
  • "logic_operator": "OR" — filter is OR-grouped with the previous condition.

Consecutive OR filters are grouped into a single (cond1 OR cond2 OR ...) clause.


Sorting

[
  { "column": "created_at", "direction": "desc" },
  { "column": "name", "direction": "asc" }
]

Pagination

Offset-Based
{ "limit": 20, "offset": 40 }
Cursor-Based

Cursor pagination uses a SQL EXISTS subquery for stable, efficient paging. Always pair with a sort argument.

// Next page: pass the PK of the last record on the current page
{ "cursor_forward": "42", "limit": 20, "sort": [{"column": "id", "direction": "asc"}] }

// Previous page: pass the PK of the first record on the current page
{ "cursor_backward": "23", "limit": 20, "sort": [{"column": "id", "direction": "asc"}] }

Preloading Relations

[
  { "relation": "Profile" },
  { "relation": "Orders" }
]

Available relations are listed in each tool's description. Only relations defined on the model struct are valid.


Hook System

Hooks let you intercept and modify CRUD operations at well-defined lifecycle points.

Hook Types
Constant Fires
BeforeHandle After model resolution, before operation dispatch (all CRUD)
BeforeRead / AfterRead Around read queries
BeforeCreate / AfterCreate Around insert
BeforeUpdate / AfterUpdate Around update
BeforeDelete / AfterDelete Around delete
Registering Hooks
handler.Hooks().Register(resolvemcp.BeforeCreate, func(ctx *resolvemcp.HookContext) error {
    // Inject a timestamp before insert
    if data, ok := ctx.Data.(map[string]interface{}); ok {
        data["created_at"] = time.Now()
    }
    return nil
})

// Register the same hook for multiple events
handler.Hooks().RegisterMultiple(
    []resolvemcp.HookType{resolvemcp.BeforeCreate, resolvemcp.BeforeUpdate},
    auditHook,
)
HookContext Fields
Field Type Description
Context context.Context Request context
Handler *Handler The resolvemcp handler
Schema string Database schema name
Entity string Entity/table name
Model interface{} Registered model instance
Options common.RequestOptions Parsed request options (read operations)
Operation string "read", "create", "update", or "delete"
ID string Primary key from request (read/update/delete)
Data interface{} Input data (create/update — modifiable)
Result interface{} Output data (set by After hooks)
Error error Operation error, if any
Query common.SelectQuery Live query object (available in BeforeRead)
Tx common.Database Database/transaction handle
Abort bool Set to true to abort the operation
AbortMessage string Error message returned when aborting
AbortCode int Optional status code for the abort
Aborting an Operation
handler.Hooks().Register(resolvemcp.BeforeDelete, func(ctx *resolvemcp.HookContext) error {
    ctx.Abort = true
    ctx.AbortMessage = "deletion is disabled"
    return nil
})
Managing Hooks
registry := handler.Hooks()
registry.HasHooks(resolvemcp.BeforeCreate)   // bool
registry.Clear(resolvemcp.BeforeCreate)      // remove hooks for one type
registry.ClearAll()                          // remove all hooks

Context Helpers

Request metadata is threaded through context.Context during handler execution. Hooks and custom tools can read it:

schema    := resolvemcp.GetSchema(ctx)
entity    := resolvemcp.GetEntity(ctx)
tableName := resolvemcp.GetTableName(ctx)
model     := resolvemcp.GetModel(ctx)
modelPtr  := resolvemcp.GetModelPtr(ctx)

You can also set values manually (e.g. in middleware):

ctx = resolvemcp.WithSchema(ctx, "tenant_a")

Adding Custom MCP Tools

Access the underlying *server.MCPServer to register additional tools:

mcpServer := handler.MCPServer()
mcpServer.AddTool(myTool, myHandler)

Table Name Resolution

The handler resolves table names in priority order:

  1. TableNameProvider interface — TableName() string (can return "schema.table")
  2. SchemaProvider interface — SchemaName() string (combined with entity name)
  3. Fallback: schema.entity (or schema_entity for SQLite)

Documentation

Overview

Package resolvemcp exposes registered database models as Model Context Protocol (MCP) tools and resources over HTTP/SSE transport.

It mirrors the resolvespec package patterns:

  • Same model registration API
  • Same filter, sort, cursor pagination, preload options
  • Same lifecycle hook system

Usage:

handler := resolvemcp.NewHandlerWithGORM(db, resolvemcp.Config{BaseURL: "http://localhost:8080"})
handler.RegisterModel("public", "users", &User{})

r := mux.NewRouter()
resolvemcp.SetupMuxRoutes(r, handler)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetEntity

func GetEntity(ctx context.Context) string

func GetModel

func GetModel(ctx context.Context) interface{}

func GetModelPtr

func GetModelPtr(ctx context.Context) interface{}

func GetSchema

func GetSchema(ctx context.Context) string

func GetTableName

func GetTableName(ctx context.Context) string

func NewSSEServer

func NewSSEServer(handler *Handler) http.Handler

NewSSEServer returns an http.Handler that serves MCP over SSE. If Config.BasePath is set it is used directly; otherwise the base path is detected from each incoming request (by stripping the "/sse" or "/message" suffix).

h := resolvemcp.NewSSEServer(handler)
http.Handle("/api/mcp/", h)

func SetupBunRouterRoutes added in v1.0.69

func SetupBunRouterRoutes(router *bunrouter.Router, handler *Handler)

SetupBunRouterRoutes mounts the MCP HTTP/SSE endpoints on a bunrouter router using the base path from Config.BasePath.

Two routes are registered:

  • GET {basePath}/sse — SSE connection endpoint
  • POST {basePath}/message — JSON-RPC message endpoint

func SetupMuxRoutes

func SetupMuxRoutes(muxRouter *mux.Router, handler *Handler)

SetupMuxRoutes mounts the MCP HTTP/SSE endpoints on the given Gorilla Mux router using the base path from Config.BasePath (falls back to "/mcp" if empty).

Two routes are registered:

  • GET {basePath}/sse — SSE connection endpoint (client subscribes here)
  • POST {basePath}/message — JSON-RPC message endpoint (client sends requests here)

To protect these routes with authentication, wrap the mux router or apply middleware before calling SetupMuxRoutes.

func WithEntity

func WithEntity(ctx context.Context, entity string) context.Context

func WithModel

func WithModel(ctx context.Context, model interface{}) context.Context

func WithModelPtr

func WithModelPtr(ctx context.Context, modelPtr interface{}) context.Context

func WithSchema

func WithSchema(ctx context.Context, schema string) context.Context

func WithTableName

func WithTableName(ctx context.Context, tableName string) context.Context

Types

type Config added in v1.0.70

type Config struct {
	// BaseURL is the public-facing base URL of the server (e.g. "http://localhost:8080").
	// It is sent to MCP clients during the SSE handshake so they know where to POST messages.
	BaseURL string

	// BasePath is the URL path prefix where the MCP endpoints are mounted (e.g. "/mcp").
	// If empty, the path is detected from each incoming request automatically.
	BasePath string
}

Config holds configuration for the resolvemcp handler.

type Handler

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

Handler exposes registered database models as MCP tools and resources.

func NewHandler

func NewHandler(db common.Database, registry common.ModelRegistry, cfg Config) *Handler

NewHandler creates a Handler with the given database, model registry, and config.

func NewHandlerWithBun

func NewHandlerWithBun(db *bun.DB, cfg Config) *Handler

NewHandlerWithBun creates a Handler backed by a Bun database connection.

func NewHandlerWithDB

func NewHandlerWithDB(db common.Database, cfg Config) *Handler

NewHandlerWithDB creates a Handler using an existing common.Database and a new registry.

func NewHandlerWithGORM

func NewHandlerWithGORM(db *gorm.DB, cfg Config) *Handler

NewHandlerWithGORM creates a Handler backed by a GORM database connection.

func (*Handler) GetDatabase

func (h *Handler) GetDatabase() common.Database

GetDatabase returns the underlying database.

func (*Handler) Hooks

func (h *Handler) Hooks() *HookRegistry

Hooks returns the hook registry.

func (*Handler) MCPServer

func (h *Handler) MCPServer() *server.MCPServer

MCPServer returns the underlying MCP server, e.g. to add custom tools.

func (*Handler) RegisterModel

func (h *Handler) RegisterModel(schema, entity string, model interface{}) error

RegisterModel registers a model and immediately exposes it as MCP tools and a resource.

func (*Handler) SSEServer added in v1.0.69

func (h *Handler) SSEServer() http.Handler

SSEServer returns an http.Handler that serves MCP over SSE. Config.BasePath must be set. Config.BaseURL is used when set; if empty it is detected automatically from each incoming request.

type HookContext

type HookContext struct {
	Context      context.Context
	Handler      *Handler
	Schema       string
	Entity       string
	Model        interface{}
	Options      common.RequestOptions
	Operation    string
	ID           string
	Data         interface{}
	Result       interface{}
	Error        error
	Query        common.SelectQuery
	Abort        bool
	AbortMessage string
	AbortCode    int
	Tx           common.Database
}

HookContext contains all the data available to a hook

type HookFunc

type HookFunc func(*HookContext) error

HookFunc is the signature for hook functions

type HookRegistry

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

HookRegistry manages all registered hooks

func NewHookRegistry

func NewHookRegistry() *HookRegistry

func (*HookRegistry) Clear

func (r *HookRegistry) Clear(hookType HookType)

func (*HookRegistry) ClearAll

func (r *HookRegistry) ClearAll()

func (*HookRegistry) Execute

func (r *HookRegistry) Execute(hookType HookType, ctx *HookContext) error

func (*HookRegistry) HasHooks

func (r *HookRegistry) HasHooks(hookType HookType) bool

func (*HookRegistry) Register

func (r *HookRegistry) Register(hookType HookType, hook HookFunc)

func (*HookRegistry) RegisterMultiple

func (r *HookRegistry) RegisterMultiple(hookTypes []HookType, hook HookFunc)

type HookType

type HookType string

HookType defines the type of hook to execute

const (
	// BeforeHandle fires after model resolution, before operation dispatch.
	BeforeHandle HookType = "before_handle"

	BeforeRead HookType = "before_read"
	AfterRead  HookType = "after_read"

	BeforeCreate HookType = "before_create"
	AfterCreate  HookType = "after_create"

	BeforeUpdate HookType = "before_update"
	AfterUpdate  HookType = "after_update"

	BeforeDelete HookType = "before_delete"
	AfterDelete  HookType = "after_delete"
)

Jump to

Keyboard shortcuts

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