resolvemcp

package
v1.0.83 Latest Latest
Warning

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

Go to latest
Published: Apr 10, 2026 License: Apache-2.0 Imports: 20 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 Transports

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

Two transports are supported: SSE (legacy, two-endpoint) and Streamable HTTP (recommended, single-endpoint).


SSE Transport

Two endpoints: GET {BasePath}/sse (subscribe) + POST {BasePath}/message (send).

Gorilla Mux
resolvemcp.SetupMuxRoutes(r, handler)
Route Method Description
{BasePath}/sse GET SSE connection — clients subscribe here
{BasePath}/message POST JSON-RPC — clients send requests here
bunrouter
resolvemcp.SetupBunRouterRoutes(router, handler)
Gin / net/http / Echo
sse := handler.SSEServer()

engine.Any("/mcp/*path", gin.WrapH(sse))  // Gin
http.Handle("/mcp/", sse)                  // net/http
e.Any("/mcp/*", echo.WrapHandler(sse))     // Echo

Streamable HTTP Transport

Single endpoint at {BasePath}. Handles POST (client→server) and GET (server→client streaming). Preferred for new integrations.

Gorilla Mux
resolvemcp.SetupMuxStreamableHTTPRoutes(r, handler)

Mounts the handler at {BasePath} (all methods).

bunrouter
resolvemcp.SetupBunRouterStreamableHTTPRoutes(router, handler)

Registers GET, POST, DELETE on {BasePath}.

Gin / net/http / Echo
h := handler.StreamableHTTPServer()
// or: h := resolvemcp.NewStreamableHTTPHandler(handler)

engine.Any("/mcp", gin.WrapH(h))      // Gin
http.Handle("/mcp", h)                 // net/http
e.Any("/mcp", echo.WrapHandler(h))     // Echo

OAuth2 Authentication

resolvemcp ships a full MCP-standard OAuth2 authorization server (pkg/security.OAuthServer) that MCP clients (Claude Desktop, Cursor, etc.) can discover and use automatically.

It can operate as:

  • Its own identity provider — shows a login form, validates via DatabaseAuthenticator.Login()
  • An OAuth2 federation layer — delegates to external providers (Google, GitHub, Microsoft, etc.)
  • Both simultaneously
Standard endpoints served
Path Spec Purpose
GET /.well-known/oauth-authorization-server RFC 8414 MCP client auto-discovery
POST /oauth/register RFC 7591 Dynamic client registration
GET /oauth/authorize OAuth 2.1 + PKCE Start login (form or provider redirect)
POST /oauth/authorize Login form submission
POST /oauth/token OAuth 2.1 Auth code → Bearer token exchange
POST /oauth/token (refresh) OAuth 2.1 Refresh token rotation
GET /oauth/provider/callback Internal External provider redirect target

MCP clients send Authorization: Bearer <token> on all subsequent requests.


Mode 1 — Direct login (server as identity provider)
import "github.com/bitechdev/ResolveSpec/pkg/security"

db, _ := sql.Open("postgres", dsn)
auth := security.NewDatabaseAuthenticator(db)

handler := resolvemcp.NewHandlerWithGORM(gormDB, resolvemcp.Config{
    BaseURL:  "https://api.example.com",
    BasePath: "/mcp",
})

// Enable the OAuth2 server — auth enables the login form
handler.EnableOAuthServer(security.OAuthServerConfig{
    Issuer: "https://api.example.com",
}, auth)

provider, _ := security.NewCompositeSecurityProvider(auth, colSec, rowSec)
securityList, _ := security.NewSecurityList(provider)
security.RegisterSecurityHooks(handler, securityList)

http.ListenAndServe(":8080", handler.HTTPHandler(securityList))

MCP client flow:

  1. Discovers server at /.well-known/oauth-authorization-server
  2. Registers itself at /oauth/register
  3. Redirects user to /oauth/authorize → login form appears
  4. On submit, exchanges code at /oauth/token → receives Authorization: Bearer token
  5. Uses token on all MCP tool calls

Mode 2 — External provider (Google, GitHub, etc.)

The RedirectURL in the provider config must point to /oauth/provider/callback on this server.

auth := security.NewDatabaseAuthenticator(db).WithOAuth2(security.OAuth2Config{
    ClientID:     os.Getenv("GOOGLE_CLIENT_ID"),
    ClientSecret: os.Getenv("GOOGLE_CLIENT_SECRET"),
    RedirectURL:  "https://api.example.com/oauth/provider/callback",
    Scopes:       []string{"openid", "profile", "email"},
    AuthURL:      "https://accounts.google.com/o/oauth2/auth",
    TokenURL:     "https://oauth2.googleapis.com/token",
    UserInfoURL:  "https://www.googleapis.com/oauth2/v2/userinfo",
    ProviderName: "google",
})

// Pass `auth` so the OAuth server supports persistence, introspection, and revocation.
// Google handles the end-user authentication flow via redirect.
handler.EnableOAuthServer(security.OAuthServerConfig{
    Issuer: "https://api.example.com",
}, auth)
handler.RegisterOAuth2Provider(auth, "google")

Mode 3 — Both (login form + external providers)
handler.EnableOAuthServer(security.OAuthServerConfig{
    Issuer:     "https://api.example.com",
    LoginTitle: "My App Login",
}, auth) // auth enables the username/password form

handler.RegisterOAuth2Provider(googleAuth, "google")
handler.RegisterOAuth2Provider(githubAuth, "github")

When external providers are registered they take priority; the login form is used as fallback when no providers are configured.


Using security.OAuthServer standalone

The authorization server lives in pkg/security and can be used with any HTTP framework independently of resolvemcp:

oauthSrv := security.NewOAuthServer(security.OAuthServerConfig{
    Issuer: "https://api.example.com",
}, auth)
oauthSrv.RegisterExternalProvider(googleAuth, "google")

mux := http.NewServeMux()
mux.Handle("/", oauthSrv.HTTPHandler())   // mounts all OAuth2 routes
mux.Handle("/mcp/", myMCPHandler)
http.ListenAndServe(":8080", mux)

For simple setups without full MCP OAuth2 compliance, use the legacy helpers that set a session cookie after external provider login:

resolvemcp.SetupMuxOAuth2Routes(r, auth, resolvemcp.OAuth2RouteConfig{
    ProviderName:       "google",
    LoginPath:          "/auth/google/login",
    CallbackPath:       "/auth/google/callback",
    AfterLoginRedirect: "/",
})
resolvemcp.SetupMuxRoutesWithAuth(r, handler, securityList)

Security

resolvemcp integrates with the security package to provide per-entity access control, row-level security, and column-level security — the same system used by resolvespec and restheadspec.

Wiring security hooks
import "github.com/bitechdev/ResolveSpec/pkg/security"

securityList := security.NewSecurityList(mySecurityProvider)
resolvemcp.RegisterSecurityHooks(handler, securityList)

Call RegisterSecurityHooks once, after creating the handler and before registering models. It installs these controls automatically:

Hook Effect
BeforeHandle Enforces per-entity operation rules (see below)
BeforeRead Loads RLS/CLS rules, then injects a user-scoped WHERE clause
AfterRead Masks/hides columns per column-security rules; writes audit log
BeforeUpdate Blocks update if CanUpdate is false
BeforeDelete Blocks delete if CanDelete is false
Per-entity operation rules

Use RegisterModelWithRules instead of RegisterModel to set access rules at registration time:

import "github.com/bitechdev/ResolveSpec/pkg/modelregistry"

// Read-only entity
handler.RegisterModelWithRules("public", "audit_logs", &AuditLog{}, modelregistry.ModelRules{
    CanRead:   true,
    CanCreate: false,
    CanUpdate: false,
    CanDelete: false,
})

// Public read, authenticated write
handler.RegisterModelWithRules("public", "products", &Product{}, modelregistry.ModelRules{
    CanPublicRead: true,
    CanRead:       true,
    CanCreate:     true,
    CanUpdate:     true,
    CanDelete:     false,
})

To update rules for an already-registered model:

handler.SetModelRules("public", "users", modelregistry.ModelRules{
    CanRead:   true,
    CanCreate: true,
    CanUpdate: true,
    CanDelete: false,
})

RegisterModel (no rules) registers with all-allowed defaults (CanRead/Create/Update/Delete = true).

ModelRules fields
Field Default Description
CanPublicRead false Allow unauthenticated reads
CanPublicCreate false Allow unauthenticated creates
CanPublicUpdate false Allow unauthenticated updates
CanPublicDelete false Allow unauthenticated deletes
CanRead true Allow authenticated reads
CanCreate true Allow authenticated creates
CanUpdate true Allow authenticated updates
CanDelete true Allow authenticated deletes
SecurityDisabled false Skip all security checks for this model

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... } }
Annotation Tool — resolvespec_annotate

Store or retrieve freeform annotation records for any tool, model, or entity. Registered automatically on every handler.

Argument Type Description
tool_name string (required) Key to annotate — an MCP tool name (e.g. read_public_users), a model name (e.g. public.users), or any other identifier.
annotations object Annotation data to persist. Omit to retrieve existing annotations instead.

Set annotations (calls resolvespec_set_annotation(tool_name, annotations)):

{ "tool_name": "read_public_users", "annotations": { "description": "Returns active users", "owner": "platform-team" } }

Response:

{ "success": true, "tool_name": "read_public_users", "action": "set" }

Get annotations (calls resolvespec_get_annotation(tool_name)):

{ "tool_name": "read_public_users" }

Response:

{ "success": true, "tool_name": "read_public_users", "action": "get", "annotations": { ... } }

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 NewStreamableHTTPHandler added in v1.0.75

func NewStreamableHTTPHandler(handler *Handler) http.Handler

NewStreamableHTTPHandler returns an http.Handler that serves MCP over the streamable HTTP transport. Mount it at the desired path; that path becomes the MCP endpoint.

h := resolvemcp.NewStreamableHTTPHandler(handler)
http.Handle("/mcp", h)
engine.Any("/mcp", gin.WrapH(h))

func OAuth2CallbackHandler added in v1.0.78

func OAuth2CallbackHandler(auth *security.DatabaseAuthenticator, providerName, afterLoginRedirect string, cookieOpts ...security.SessionCookieOptions) http.HandlerFunc

OAuth2CallbackHandler returns an http.HandlerFunc that handles the OAuth2 provider callback: exchanges the authorization code for a session token, writes the session cookie, then either redirects to afterLoginRedirect or writes the LoginResponse as JSON.

Register it on any router:

mux.Handle("/auth/google/callback", resolvemcp.OAuth2CallbackHandler(auth, "google", "/dashboard"))

func OAuth2LoginHandler added in v1.0.78

func OAuth2LoginHandler(auth *security.DatabaseAuthenticator, providerName string) http.HandlerFunc

OAuth2LoginHandler returns an http.HandlerFunc that redirects the browser to the OAuth2 provider's authorization URL.

Register it on any router:

mux.Handle("/auth/google/login", resolvemcp.OAuth2LoginHandler(auth, "google"))

func RegisterSecurityHooks added in v1.0.74

func RegisterSecurityHooks(handler *Handler, securityList *security.SecurityList)

RegisterSecurityHooks wires the security package's access-control layer into the resolvemcp handler. Call it once after creating the handler, before registering models.

The following controls are applied:

  • Per-entity operation rules (CanRead, CanCreate, CanUpdate, CanDelete, CanPublic*) stored via RegisterModelWithRules / SetModelRules.
  • Row-level security: WHERE clause injected per user from the SecurityList provider.
  • Column-level security: sensitive columns masked/hidden in read results.
  • Audit logging after each read.

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 SetupBunRouterStreamableHTTPRoutes added in v1.0.75

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

SetupBunRouterStreamableHTTPRoutes mounts the MCP streamable HTTP endpoint on a bunrouter router. The streamable HTTP transport uses a single endpoint (Config.BasePath).

func SetupMuxOAuth2Routes added in v1.0.78

func SetupMuxOAuth2Routes(muxRouter *mux.Router, auth *security.DatabaseAuthenticator, cfg OAuth2RouteConfig)

SetupMuxOAuth2Routes registers the OAuth2 login and callback routes on a Gorilla Mux router.

Example:

resolvemcp.SetupMuxOAuth2Routes(r, auth, resolvemcp.OAuth2RouteConfig{
    ProviderName: "google", LoginPath: "/auth/google/login",
    CallbackPath: "/auth/google/callback", AfterLoginRedirect: "/",
})

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 SetupMuxRoutesWithAuth added in v1.0.78

func SetupMuxRoutesWithAuth(muxRouter *mux.Router, handler *Handler, securityList *security.SecurityList)

SetupMuxRoutesWithAuth mounts the MCP SSE endpoints on a Gorilla Mux router with required authentication middleware applied.

func SetupMuxStreamableHTTPRoutes added in v1.0.75

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

SetupMuxStreamableHTTPRoutes mounts the MCP streamable HTTP endpoint on the given Gorilla Mux router. The streamable HTTP transport uses a single endpoint (Config.BasePath) for all communication: POST for client→server messages, GET for server→client streaming.

Example:

resolvemcp.SetupMuxStreamableHTTPRoutes(r, handler) // mounts at Config.BasePath

func SetupMuxStreamableHTTPRoutesWithAuth added in v1.0.78

func SetupMuxStreamableHTTPRoutesWithAuth(muxRouter *mux.Router, handler *Handler, securityList *security.SecurityList)

SetupMuxStreamableHTTPRoutesWithAuth mounts the MCP streamable HTTP endpoint on a Gorilla Mux router with required authentication middleware applied.

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) AuthedSSEServer added in v1.0.78

func (h *Handler) AuthedSSEServer(securityList *security.SecurityList) http.Handler

AuthedSSEServer wraps SSEServer with required authentication middleware from pkg/security. The middleware reads the session cookie / Authorization header and populates the user context into the request context, making it available to BeforeHandle security hooks. Unauthenticated requests receive 401 before reaching any MCP tool.

func (*Handler) AuthedStreamableHTTPServer added in v1.0.78

func (h *Handler) AuthedStreamableHTTPServer(securityList *security.SecurityList) http.Handler

AuthedStreamableHTTPServer wraps StreamableHTTPServer with required authentication middleware.

func (*Handler) EnableOAuthServer added in v1.0.78

func (h *Handler) EnableOAuthServer(cfg security.OAuthServerConfig, auth *security.DatabaseAuthenticator)

EnableOAuthServer activates the MCP-standard OAuth2 authorization server on this Handler.

Pass a DatabaseAuthenticator to enable direct username/password login — the server acts as its own identity provider and renders a login form at /oauth/authorize. Pass nil to use only external providers registered via RegisterOAuth2Provider.

After calling this, HTTPHandler and StreamableHTTPMux serve the full set of RFC-compliant endpoints required by MCP clients alongside the MCP transport:

GET  /.well-known/oauth-authorization-server   RFC 8414 — auto-discovery
POST /oauth/register                            RFC 7591 — dynamic client registration
GET  /oauth/authorize                           OAuth 2.1 + PKCE — start login
POST /oauth/authorize                           Login form submission (password flow)
POST /oauth/token                               Bearer token exchange + refresh
GET  /oauth/provider/callback                   External provider redirect target

func (*Handler) GetDatabase

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

GetDatabase returns the underlying database.

func (*Handler) HTTPHandler added in v1.0.78

func (h *Handler) HTTPHandler(securityList *security.SecurityList) http.Handler

HTTPHandler returns a single http.Handler that serves:

  • MCP OAuth2 authorization server endpoints (when EnableOAuthServer has been called)
  • OAuth2 login + callback routes for every registered provider (legacy cookie flow)
  • The MCP SSE transport wrapped with required authentication middleware

Example:

auth := security.NewGoogleAuthenticator(...)
handler.RegisterOAuth2(auth, cfg)
handler.EnableOAuthServer(security.OAuthServerConfig{Issuer: "https://api.example.com"})
security.RegisterSecurityHooks(handler, securityList)
http.ListenAndServe(":8080", handler.HTTPHandler(securityList))

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) OptionalAuthSSEServer added in v1.0.78

func (h *Handler) OptionalAuthSSEServer(securityList *security.SecurityList) http.Handler

OptionalAuthSSEServer wraps SSEServer with optional authentication middleware. Unauthenticated requests continue as guest rather than returning 401. Use together with RegisterSecurityHooks and per-model CanPublicRead/Write rules to allow mixed public/private access.

func (*Handler) OptionalAuthStreamableHTTPServer added in v1.0.78

func (h *Handler) OptionalAuthStreamableHTTPServer(securityList *security.SecurityList) http.Handler

OptionalAuthStreamableHTTPServer wraps StreamableHTTPServer with optional authentication middleware.

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) RegisterModelWithRules added in v1.0.74

func (h *Handler) RegisterModelWithRules(schema, entity string, model interface{}, rules modelregistry.ModelRules) error

RegisterModelWithRules registers a model and sets per-entity operation rules (CanRead, CanCreate, CanUpdate, CanDelete, CanPublic*, SecurityDisabled). Requires RegisterSecurityHooks to have been called for the rules to be enforced.

func (*Handler) RegisterOAuth2 added in v1.0.78

func (h *Handler) RegisterOAuth2(auth *security.DatabaseAuthenticator, cfg OAuth2RouteConfig)

RegisterOAuth2 attaches an OAuth2 provider to the Handler. The login and callback HTTP routes are served by HTTPHandler / StreamableHTTPMux. Call this once per provider before serving requests.

Example:

auth := security.NewGoogleAuthenticator(clientID, secret, redirectURL, db)
handler.RegisterOAuth2(auth, resolvemcp.OAuth2RouteConfig{
    ProviderName:       "google",
    LoginPath:          "/auth/google/login",
    CallbackPath:       "/auth/google/callback",
    AfterLoginRedirect: "/",
})

func (*Handler) RegisterOAuth2Provider added in v1.0.78

func (h *Handler) RegisterOAuth2Provider(auth *security.DatabaseAuthenticator, providerName string)

RegisterOAuth2Provider adds an external OAuth2 provider to the MCP OAuth2 authorization server. EnableOAuthServer must be called before this. The auth must have been configured with WithOAuth2(providerName, ...) for the given provider name.

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.

func (*Handler) SetModelRules added in v1.0.74

func (h *Handler) SetModelRules(schema, entity string, rules modelregistry.ModelRules) error

SetModelRules updates the operation rules for an already-registered model. Requires RegisterSecurityHooks to have been called for the rules to be enforced.

func (*Handler) StreamableHTTPMux added in v1.0.78

func (h *Handler) StreamableHTTPMux(securityList *security.SecurityList) http.Handler

StreamableHTTPMux returns a single http.Handler that serves:

  • MCP OAuth2 authorization server endpoints (when EnableOAuthServer has been called)
  • OAuth2 login + callback routes for every registered provider (legacy cookie flow)
  • The MCP streamable HTTP transport wrapped with required authentication middleware

Example:

http.ListenAndServe(":8080", handler.StreamableHTTPMux(securityList))

func (*Handler) StreamableHTTPServer added in v1.0.75

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

StreamableHTTPServer returns an http.Handler that serves MCP over the streamable HTTP transport. Unlike SSE (which requires two endpoints), streamable HTTP uses a single endpoint for all client-server communication (POST for requests, GET for server-initiated messages). Mount the returned handler at the desired path; the path itself becomes the MCP endpoint.

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"
)

type OAuth2RouteConfig added in v1.0.78

type OAuth2RouteConfig struct {
	// ProviderName is the OAuth2 provider name as registered with WithOAuth2()
	// (e.g. "google", "github", "microsoft").
	ProviderName string

	// LoginPath is the HTTP path that redirects the browser to the OAuth2 provider
	// (e.g. "/auth/google/login").
	LoginPath string

	// CallbackPath is the HTTP path that the OAuth2 provider redirects back to
	// (e.g. "/auth/google/callback"). Must match the RedirectURL in OAuth2Config.
	CallbackPath string

	// AfterLoginRedirect is the URL to redirect the browser to after a successful
	// login. When empty the LoginResponse JSON is written directly to the response.
	AfterLoginRedirect string

	// CookieOptions customises the session cookie written on successful login.
	// Defaults to HttpOnly, Secure, SameSite=Lax when nil.
	CookieOptions *security.SessionCookieOptions
}

OAuth2RouteConfig configures the OAuth2 HTTP endpoints for a single provider.

Jump to

Keyboard shortcuts

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