resolvemcp

package
v1.3.9 Latest Latest
Warning

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

Go to latest
Published: Oct 7, 2026 License: Apache-2.0 Imports: 31 Imported by: 0

README

resolvemcp

Package resolvemcp exposes registered database models to AI clients through a fixed set of Model Context Protocol (MCP) meta tools over SSE or Streamable HTTP. The tool count does not grow with the number of models. It mirrors the resolvespec package — same model registration, filter/sort/pagination/preload options, hook system and security rules.

Every endpoint requires authentication; tools run as the authenticated caller.

Quick Start

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

handler := resolvemcp.NewHandlerWithGORM(db, resolvemcp.Config{
    BaseURL:  "http://localhost:8080",
    BasePath: "/mcp",
    // Read-only by default; uncomment to allow writes:
    // ReadOnly: resolvemcp.Bool(false),
})

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

handler.RegisterModel("public", "users", &User{})
handler.RegisterModel("public", "orders", &Order{})

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

Config

Field Default Purpose
BaseURL request-detected Public base URL sent to SSE clients
BasePath request-detected Mount path (e.g. /mcp)
DefaultLimit 50 Page size when a read gives no limit
MaxLimit 1000 Larger limits are clamped
MaxOffset 100000 Larger offsets are rejected
MaxBatch 100 Rows in one batch insert
MaxPreloadDepth 2 Depth of a preload path (a.b.c)
MaxWriteRows 100 Rows a filter-based update/delete may touch
QueryTimeout 30s One tool call, hooks and queries included
ConfirmTTL 5m Lifetime of a filter-write confirm token
AllowedHosts any Host allowlist for SSE when BaseURL is empty (prefer setting BaseURL)
EnableAnnotations false Registers resolvespec_annotate (opt-in)

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{}).

RegisterModel only adds the model to the registry; it creates no tools. All registered models are visible to list_tables; per-entity rules (see Security) restrict the operations.

Functions
// Go callback
handler.RegisterFunction(resolvemcp.Function{
    Name:        "recalc_totals",
    Description: "Recalculate order totals",
    Params:      []resolvemcp.FunctionParam{{Name: "order_id", Type: resolvemcp.ParamNumber, Required: true}},
    Handler: func(ctx context.Context, tx common.Database, args map[string]any) (any, error) { return nil, nil },
    Authorize: func(ctx context.Context) error { return nil }, // optional per-caller gate
})

// SQL procedure: SELECT * FROM public.my_proc($1, $2::jsonb)
handler.RegisterFunction(resolvemcp.Function{
    Name: "my_proc", Procedure: "public.my_proc",
    Params: []resolvemcp.FunctionParam{{Name: "a", Type: resolvemcp.ParamString, Required: true}},
})

Only registered functions are callable. Arguments are validated against Params; calls run in a transaction (OnTxBegin fired). A function the caller is not authorized for looks identical to an unknown one.


HTTP Transports

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

All Setup*/New* helpers wrap the endpoint in Guard(securityList): a valid OAuth bearer token, session token or API key is required, there is no guest/optional mode, and it fails closed. handler.SSEServer() / handler.StreamableHTTPServer() and the *Unauthenticated variants serve without a guard and log a warning; use them only behind your own authentication.

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, securityList)
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, securityList)
Gin / net/http / Echo
sse := resolvemcp.NewSSEServer(handler, securityList) // guarded

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, securityList)

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

bunrouter
resolvemcp.SetupBunRouterStreamableHTTPRoutes(router, handler, securityList)

Registers GET, POST, DELETE on {BasePath}.

Gin / net/http / Echo
h := resolvemcp.NewStreamableHTTPHandler(handler, securityList) // guarded

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

The underlying security.OAuthServer also supports consent, OpenID Connect, rotating refresh tokens, JWT access tokens, DPoP, PAR, the device grant and token exchange; they are opt-in OAuthServerConfig options described in pkg/security/OAUTH2_SERVER.md. The options of resolvemcp.OAuth2Config are unchanged.

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)
resolvemcp.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, err := security.NewSecurityList(mySecurityProvider)
if err != nil {
    log.Fatal(err)
}
resolvemcp.RegisterSecurityHooks(handler, securityList)

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

Hook Effect
OnTxBegin Stamps transaction-local settings (RLS GUCs) set with SecurityList.SetTxSettings
BeforeHandle Enforces per-entity operation rules (see below); preloads column rules for writes
BeforeRead Loads RLS/CLS rules, then injects a user-scoped WHERE clause
BeforeScan Applies row security to the row an update or delete targets; a row the user cannot see is "not found"
AfterRead Masks/hides columns per column-security rules; writes audit log
BeforeCreate Blocks create if CanCreate is false; drops hidden/masked columns from the payload
BeforeUpdate Blocks update if CanUpdate is false; drops hidden/masked columns from the payload
BeforeDelete Blocks delete if CanDelete is false

Additional hooks: BeforeScan (row pre-read of update/delete/filter writes), BeforeCall/AfterCall (functions) and OnTxBegin. Hooks are mutex-protected and panics in hooks are recovered.

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

Describing the API for agents

Give agents context about what each table is for:

// 1. Explicitly, in code
handler.SetModelDescription("public", "users", modelregistry.ModelInfo{
    Description: "Application accounts",
    Purpose:     "Look up who a person is",
    Tags:        []string{"identity"},
    Columns:     map[string]string{"email": "Login address, unique"},
})

// 2. From an external JSON map (keyed by "schema.entity"); entries here win
n, err := handler.LoadModelDescriptions("docs/model-descriptions.json")

Example docs/model-descriptions.json (every key is optional; unknown keys are rejected):

{
  "public.users": {
    "description": "Application accounts, one row per person who can sign in.",
    "purpose": "Look up who someone is. Use public.orders for what they bought.",
    "tags": ["identity", "pii"],
    "columns": {
      "id": "Internal account id",
      "email": "Login address, unique and lower-cased",
      "created_at": "When the account was created (UTC)"
    }
  },
  "public.orders": {
    "description": "Customer orders.",
    "columns": {
      "status": "One of: pending, paid, shipped, cancelled"
    }
  }
}

Keys are schema.entity names as registered with RegisterModel. Column keys are the JSON column names shown by describe_table. An entry replaces any info set earlier for that table, and columns it leaves out still fall back to field tags.

Fallbacks when nothing is set for a table or column, in order: the model's ModelDescription() string method (table), then field tags (column): comment, note, desc or description tags, then comment: inside the gorm or bun tag.

The text appears in list_tables and describe_table. The server also sends a short usage guide as MCP instructions on connect.

Catalogue file

handler.ExportCatalog(path) writes the usage guide, tools, limits and every table (columns, types, keys, relations, allowed operations, descriptions) to disk, JSON for a .json path and Markdown otherwise. The file is replaced atomically. It lists every table with at least one allowed operation, regardless of caller, so keep it out of public directories. Call it after registering models (for example at startup, or from a go generate step).

Read-only mode

The server is read-only unless you enable writes: Config.ReadOnly is a *bool and an unset (nil) value means on. To allow inserts, updates and deletes:

handler := resolvemcp.NewHandlerWithGORM(db, resolvemcp.Config{ReadOnly: resolvemcp.Bool(false)})

While read-only is on:

  • The insert, update, delete and annotation tools are not registered, so the agent never sees them. list_functions/call_function are off too, because a registered function may change data, unless you set AllowFunctionCalls (below).
  • list_tables and describe_table report only select; describe_table also sets read_only: true and lists no writable columns.
  • The MCP server instructions (and the exported catalogue) say the server is read-only and tell the agent not to attempt writes.
  • A write that reaches a handler anyway is refused with a forbidden error ("this server is read-only: writes are disabled").
Function calls and the allowlist
// Read-only server that may still run two named functions
resolvemcp.Config{
    // ReadOnly is on by default
    AllowFunctionCalls: true, // keep list_functions / call_function on a read-only server
    AllowedFunctions:   []string{"report_totals", "search_customers"},
}
  • AllowFunctionCalls only matters while read-only is on; with writes enabled (ReadOnly: resolvemcp.Bool(false)), functions are always available. Set it only for functions that do not change data.
  • AllowedFunctions works in either mode. When empty, every registered function is allowed. When set, only the named functions are listed and callable; any other is reported as unknown function, so its existence is not revealed. Per-function Authorize still applies on top.

MCP Tools

Fixed set, independent of the models. table is schema.entity. Errors return {"success":false,"error":{"code","message"}} with codes invalid_argument, not_found, forbidden, limit_exceeded, internal (internal details are logged, the client gets a reference id).

Tool Purpose
list_tables Tables the caller may use and the allowed operations
describe_table Columns, PK, relations, writable columns, operations, limits
select_table Read rows (filters, sort, columns, preloads, paging)
insert_into_table Insert one row or a capped batch
update_table Update by id or filters
delete_from_table Delete by id or filters
list_functions Registered functions the caller may call, with parameters
call_function Call a registered function
resolvespec_annotate Only with EnableAnnotations
select_table
Argument Type Description
table string (required) schema.entity
id string Primary key of one row
filters, sort array See Filtering, Sorting
columns, omit_columns array Column selection
preloads array Relations (validated against the model, max depth MaxPreloadDepth)
limit, offset number Clamped to MaxLimit / rejected above MaxOffset
cursor_forward, cursor_backward string PK cursor, requires sort
include_count boolean Also compute totals (slower); otherwise total/filtered are 0

Response: {"success":true,"data":[...],"metadata":{"total","filtered","count","limit","offset"}}

insert_into_table

data is an object or an array (one transaction, max MaxBatch). Unknown, duplicate or read-only keys are rejected; keys are resolved to columns from the model.

update_table / delete_from_table

Either id or filters is required.

Mode Behaviour
id One row, applied immediately. The row is locked and row security applies; an invisible row is "not found".
filters Matching rows are found inside the transaction (row security applied, max MaxWriteRows). The first call returns a preview and a confirm_token; repeat the identical call with confirm_token to apply.
dry_run Report match count and preview ids; change nothing.

The token is single-use, expires after ConfirmTTL, and is bound to user, table, operation and a hash of filters, data and matched ids; it is held in memory (lost on restart, single instance). Update changes only the keys in data; null sets NULL. Filters are strictly parsed (never silently dropped), columns validated, and only the documented operators are accepted.

call_function

name and arguments (object). See Functions.

resolvespec_annotate

Opt-in (Config.EnableAnnotations). Stores/retrieves freeform annotations through resolvespec_set_annotation / resolvespec_get_annotation; runs BeforeHandle hooks (annotate_set / annotate_get) and a transaction.


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
BeforeScan Row pre-read for update/delete/filter writes
BeforeCall / AfterCall Around call_function
OnTxBegin Start of every transaction
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", "delete", "call", "annotate_set" or "annotate_get"
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

The caller's security.UserContext reaches every tool call through the request context. 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 (they sit behind the same guard). Prefer RegisterFunction for database-backed actions:

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)

Breaking changes

  • The server is read-only by default. Writes (insert/update/delete), annotations and function calls need Config{ReadOnly: resolvemcp.Bool(false)} (function calls can also be kept on a read-only server with AllowFunctionCalls).
  • Per-model tools (read_/create_/update_/delete_{schema}_{entity}) and per-model resources are gone; use the meta tools.
  • Setup* / NewSSEServer / NewStreamableHTTPHandler take a *security.SecurityList and require authentication. OptionalAuth* helpers were removed; *Unauthenticated variants exist for explicit opt-out.
  • resolvespec_annotate is opt-in via Config.EnableAnnotations.
  • Handler.Build() is not needed.
  • Update is now a partial update by validated keys; reads are capped by the configured limits.

Documentation

Overview

Package resolvemcp exposes registered database models as Model Context Protocol (MCP) tools over HTTP (SSE and streamable HTTP), so an AI agent can discover, read and change data without a tool per table.

How an agent uses it

The tool set is fixed and does not grow with the models:

list_tables      tables the caller may use, their description and allowed operations
describe_table   columns, types, keys, relations, writable fields, limits
select_table     read rows: filters, sort, columns, preloads, paging, cursors
insert_into_table / update_table / delete_from_table
                 writes; filter-based writes are previewed (dry_run) and need the
                 confirm_token from the preview
list_functions / call_function   registered stored functions
resolvespec_annotate             optional free-text notes (Config.EnableAnnotations)

The same guide is sent to MCP clients as the server instructions.

The server is read-only by default (Config.ReadOnly nil means on); set ReadOnly: resolvemcp.Bool(false) to enable the write tools.

Setting it up

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

r := mux.NewRouter()
resolvemcp.SetupMuxRoutes(r, handler, securityList) // requires an authenticated caller

Describing the API for agents

Models are documented through the model registry (see package modelregistry): SetModelDescription / LoadModelDescriptions on the handler, a ModelDescription() method on the model, or comment tags on its fields (gorm/bun "comment:" or comment/note/desc tags). The descriptions show up in list_tables and describe_table.

ExportCatalog writes the whole picture (guide, tools, limits, tables with columns, relations, operations and descriptions) to a JSON or Markdown file on disk, so agents and developers can learn the API without connecting:

handler.ExportCatalog("docs/mcp-catalog.md")

Security

Routes must be mounted behind Guard(securityList); the *Unauthenticated setup functions exist only for use behind another trusted layer. Per-entity rules come from modelregistry.ModelRules, and BeforeHandle/AfterHandle hooks can veto or audit any call.

Index

Constants

View Source
const (
	CodeInvalidArgument = "invalid_argument"
	CodeNotFound        = "not_found"
	CodeForbidden       = "forbidden"
	CodeLimitExceeded   = "limit_exceeded"
	CodeInternal        = "internal"
)

Stable error codes returned to MCP clients.

View Source
const (
	ParamString  = "string"
	ParamInteger = "integer"
	ParamNumber  = "number"
	ParamBoolean = "boolean"
	ParamObject  = "object"
	ParamArray   = "array"
)

Parameter types a function may declare.

Variables

This section is empty.

Functions

func Bool added in v1.3.7

func Bool(v bool) *bool

Bool returns a pointer to v, for the optional boolean fields of Config.

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 Guard added in v1.2.12

func Guard(securityList *security.SecurityList) func(http.Handler) http.Handler

Guard returns middleware that requires an authenticated caller on every request.

The security list's provider decides which credentials are accepted: build it from a security.ChainAuthenticator over an OAuth bearer token, a session token (header or cookie) and an API key authenticator. The authenticated security.UserContext is placed in the request context, which the MCP transports pass on to every tool call, so rules, row security and OnTxBegin apply to that caller.

Unlike security.NewAuthMiddleware this guard has no guest or optional mode: it ignores security.SkipAuth / security.OptionalAuth markers on the request context, and fails closed (500) when no provider is configured.

func NewClientError added in v1.2.12

func NewClientError(code, message string) error

NewClientError returns an error that reaches the client as {code, message}.

func NewSSEServer

func NewSSEServer(handler *Handler, securityList *security.SecurityList) http.Handler

NewSSEServer returns an http.Handler that serves MCP over SSE behind Guard(securityList). 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, securityList)
http.Handle("/api/mcp/", h)

func NewStreamableHTTPHandler added in v1.0.75

func NewStreamableHTTPHandler(handler *Handler, securityList *security.SecurityList) http.Handler

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

h := resolvemcp.NewStreamableHTTPHandler(handler, securityList)
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, securityList *security.SecurityList)

SetupBunRouterRoutes mounts the MCP HTTP/SSE endpoints on a bunrouter router using the base path from Config.BasePath, behind Guard(securityList).

Routes registered:

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

func SetupBunRouterRoutesUnauthenticated added in v1.2.12

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

SetupBunRouterRoutesUnauthenticated is SetupBunRouterRoutes without the guard. A warning is logged.

func SetupBunRouterStreamableHTTPRoutes added in v1.0.75

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

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

func SetupBunRouterStreamableHTTPRoutesUnauthenticated added in v1.2.12

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

SetupBunRouterStreamableHTTPRoutesUnauthenticated is SetupBunRouterStreamableHTTPRoutes without the guard. A warning is logged.

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, securityList *security.SecurityList)

SetupMuxRoutes mounts the MCP HTTP/SSE endpoints on the given Gorilla Mux router using the base path from Config.BasePath, behind Guard(securityList).

Routes registered:

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

Nothing is mounted (and an error is logged) when securityList has no provider.

func SetupMuxRoutesUnauthenticated added in v1.2.12

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

SetupMuxRoutesUnauthenticated is SetupMuxRoutes without the guard. Every caller reaches every registered model, so use it only behind another trusted layer. A warning is logged.

func SetupMuxStreamableHTTPRoutes added in v1.0.75

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

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

Nothing is mounted (and an error is logged) when securityList has no provider.

func SetupMuxStreamableHTTPRoutesUnauthenticated added in v1.2.12

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

SetupMuxStreamableHTTPRoutesUnauthenticated is SetupMuxStreamableHTTPRoutes without the guard. A warning is logged.

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 Catalog added in v1.3.6

type Catalog struct {
	GeneratedAt time.Time      `json:"generated_at"`
	Server      string         `json:"server"`
	Version     string         `json:"version"`
	ReadOnly    bool           `json:"read_only"`
	Guide       string         `json:"guide"`
	Limits      CatalogLimits  `json:"limits"`
	Tools       []CatalogTool  `json:"tools"`
	Tables      []CatalogTable `json:"tables"`
}

Catalog is a snapshot of what the server offers: the usage guide, the tools, the limits and every table with its columns, relations, allowed operations and descriptions.

func (Catalog) Markdown added in v1.3.6

func (c Catalog) Markdown() string

Markdown renders the catalogue as a Markdown document.

type CatalogColumn added in v1.3.6

type CatalogColumn struct {
	Name        string `json:"name"`
	Type        string `json:"type,omitempty"`
	Nullable    bool   `json:"nullable"`
	PrimaryKey  bool   `json:"primary_key,omitempty"`
	Unique      bool   `json:"unique,omitempty"`
	Writable    bool   `json:"writable"`
	Description string `json:"description,omitempty"`
}

CatalogColumn is one column of a table.

type CatalogLimits added in v1.3.6

type CatalogLimits struct {
	DefaultLimit    int `json:"default_limit"`
	MaxLimit        int `json:"max_limit"`
	MaxOffset       int `json:"max_offset"`
	MaxBatch        int `json:"max_batch"`
	MaxPreloadDepth int `json:"max_preload_depth"`
	MaxWriteRows    int `json:"max_write_rows"`
}

CatalogLimits mirrors the configured server limits.

type CatalogTable added in v1.3.6

type CatalogTable struct {
	Table       string          `json:"table"`
	Description string          `json:"description,omitempty"`
	Purpose     string          `json:"purpose,omitempty"`
	Tags        []string        `json:"tags,omitempty"`
	Operations  []string        `json:"operations"`
	PrimaryKey  string          `json:"primary_key,omitempty"`
	Columns     []CatalogColumn `json:"columns"`
	Relations   []string        `json:"relations,omitempty"`
}

CatalogTable is one table in the catalogue.

type CatalogTool added in v1.3.6

type CatalogTool struct {
	Name        string `json:"name"`
	Description string `json:"description"`
}

CatalogTool is one MCP tool.

type ClientError added in v1.2.12

type ClientError struct {
	Code    string
	Message string
}

ClientError is an error whose code and message are safe to show to an MCP client. Hooks may return one (NewClientError) to give the client a specific reason; every other error is reported as an opaque internal error with a reference that matches the server log.

func (*ClientError) Error added in v1.2.12

func (e *ClientError) Error() string

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

	// DefaultLimit is the page size when a read gives no limit (50).
	DefaultLimit int
	// MaxLimit caps a read's limit; larger values are clamped (1000).
	MaxLimit int
	// MaxOffset rejects a read whose offset is larger (100000).
	MaxOffset int
	// MaxBatch caps the items in one batch create (100).
	MaxBatch int
	// MaxPreloadDepth caps the depth of a preload path such as "a.b.c" (2).
	MaxPreloadDepth int
	// MaxWriteRows caps the rows a filter-based update or delete may touch (100).
	MaxWriteRows int
	// QueryTimeout bounds one tool call, hooks and queries included (30s).
	QueryTimeout time.Duration
	// ConfirmTTL is how long a confirmation token for a filter write stays valid (5m).
	ConfirmTTL time.Duration

	// AllowedHosts restricts the Host header accepted by the SSE transport when BaseURL is
	// empty (the message endpoint URL sent to clients is built from it). Empty accepts any
	// host, with at most 32 distinct base URLs cached; prefer setting BaseURL.
	AllowedHosts []string

	// ReadOnly disables every write and is ON when left nil: set it to Bool(false) to allow
	// writes. When on, the insert, update, delete and annotation tools are not registered,
	// list_tables and describe_table report only the select operation (no writable columns),
	// a write attempted anyway is refused with a "forbidden" error, and the server
	// instructions tell the agent it cannot write. list_functions/call_function are also
	// off, because a registered function may change data, unless AllowFunctionCalls is set.
	ReadOnly *bool

	// AllowFunctionCalls keeps list_functions and call_function available on a ReadOnly
	// server. Only set it for functions that do not change data; pair it with
	// AllowedFunctions to name them. It has no effect when writes are enabled (ReadOnly set to Bool(false)) (functions are
	// always available then).
	AllowFunctionCalls bool

	// AllowedFunctions restricts list_functions and call_function to the named functions.
	// Empty allows every registered function. A function outside the list is reported as
	// unknown, so its existence is not revealed.
	AllowedFunctions []string

	// EnableAnnotations registers the resolvespec_annotate tool. Off by default: annotations
	// are free text that agents read back, so enabling the tool opens a write channel into
	// agent-visible text. When on, every call runs the BeforeHandle hooks (operation
	// "annotate_set" / "annotate_get") and the writes run in a transaction with OnTxBegin.
	EnableAnnotations bool
	// contains filtered or unexported fields
}

Config holds configuration for the resolvemcp handler.

type Function added in v1.2.12

type Function struct {
	Name        string
	Description string
	Params      []FunctionParam

	// Handler is the Go callback.
	Handler FunctionFunc
	// Procedure is the SQL function to call, optionally schema-qualified. Declared params are
	// passed positionally in declaration order; an omitted optional param is NULL. Rows
	// returned are the result.
	Procedure string

	// Authorize, when set, decides per caller whether the function is listed and callable.
	// Return nil to allow.
	Authorize func(ctx context.Context) error
}

Function is a callable registered with Handler.RegisterFunction. Exactly one of Handler (a Go callback) and Procedure (a SQL function called by name) is set.

type FunctionFunc added in v1.2.12

type FunctionFunc func(ctx context.Context, tx common.Database, args map[string]any) (any, error)

FunctionFunc is a Go function callable through call_function. tx is the transaction the call runs in (OnTxBegin already fired on it); args are validated against the declared params.

type FunctionParam added in v1.2.12

type FunctionParam struct {
	Name        string
	Type        string // one of the Param* constants
	Description string
	Required    bool
}

FunctionParam declares one argument of a registered function.

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) BuildCatalog added in v1.3.6

func (h *Handler) BuildCatalog() Catalog

BuildCatalog snapshots the server's tools and tables. Tables with no allowed operation are left out, exactly as list_tables does.

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) ExportCatalog added in v1.3.6

func (h *Handler) ExportCatalog(path string) error

ExportCatalog writes the catalogue to path. A ".json" extension writes JSON; anything else writes Markdown. The file is replaced atomically (written to a temp file in the same directory, then renamed) and created with mode 0600. It lists every table the registry allows any operation on, regardless of caller, so keep it out of public directories.

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) LoadModelDescriptions added in v1.3.6

func (h *Handler) LoadModelDescriptions(path string) (int, error)

LoadModelDescriptions loads an external JSON map of descriptions keyed by "schema.entity" (see modelregistry.LoadModelInfo for the format) and returns how many tables it covered. Loaded entries override the model's own comments.

func (*Handler) MCPServer

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

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

func (*Handler) RegisterFunction added in v1.2.12

func (h *Handler) RegisterFunction(f Function) error

RegisterFunction makes f callable through the call_function meta tool. Only registered functions are callable; there is no way to reach an arbitrary SQL function.

func (*Handler) RegisterModel

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

RegisterModel registers a model. It becomes visible to the fixed meta tools (list_tables, select_table, ...); no per-model tools are created.

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) SetModelDescription added in v1.3.6

func (h *Handler) SetModelDescription(schema, entity string, info modelregistry.ModelInfo) error

SetModelDescription stores documentation for a registered or soon-to-be-registered table. It returns an error when the handler's registry does not keep model info.

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

func (*HookContext) SetTx added in v1.2.7

func (c *HookContext) SetTx(tx common.Database)

SetTx points the context at the transaction in use (common.TxContext).

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"

	// BeforeScan fires on update and delete, with hookCtx.Query set to the select that loads the
	// target row. Hooks that narrow the query (row security) run here; a row the query does
	// not return is reported as not found and never written.
	BeforeScan HookType = "before_scan"

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

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

	// BeforeCall and AfterCall fire inside the transaction of a call_function call.
	// hookCtx.Entity is the function name, Data the validated arguments (BeforeCall may
	// replace them) and Result the function's result (AfterCall).
	BeforeCall HookType = "before_call"
	AfterCall  HookType = "after_call"

	// OnTxBegin fires once, first, inside every transaction the handler opens
	// (including the second short transaction for post-commit work). hookCtx.Tx is
	// the transaction; use it to stamp transaction-local state such as RLS
	// settings. An error or abort rolls the transaction back.
	OnTxBegin HookType = common.TxHookName
)

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