mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-10-01 19:20:31 +00:00
docs(resolvemcp): rewrite README for meta tools, guard and limits; update plan status
This commit is contained in:
@@ -277,32 +277,28 @@ ResolveMCP exposes registered models as Model Context Protocol tools so AI model
|
||||
```go
|
||||
import "github.com/bitechdev/ResolveSpec/pkg/resolvemcp"
|
||||
|
||||
// Create handler
|
||||
handler := resolvemcp.NewHandlerWithGORM(db)
|
||||
handler := resolvemcp.NewHandlerWithGORM(db, resolvemcp.Config{BaseURL: "http://localhost:8080", BasePath: "/mcp"})
|
||||
|
||||
securityList, _ := security.NewSecurityList(provider)
|
||||
resolvemcp.RegisterSecurityHooks(handler, securityList)
|
||||
|
||||
// Register models — must be done BEFORE Build()
|
||||
handler.RegisterModel("public", "users", &User{})
|
||||
handler.RegisterModel("public", "posts", &Post{})
|
||||
|
||||
// Finalize: registers MCP tools and resources
|
||||
handler.Build()
|
||||
|
||||
// Mount SSE transport on your existing router
|
||||
// Mount the guarded SSE transport (OAuth bearer, session token or API key required)
|
||||
router := mux.NewRouter()
|
||||
resolvemcp.SetupMuxRoutes(router, handler, "http://localhost:8080")
|
||||
resolvemcp.SetupMuxRoutes(router, handler, securityList)
|
||||
|
||||
// MCP clients connect to:
|
||||
// SSE stream: GET http://localhost:8080/mcp/sse
|
||||
// Messages: POST http://localhost:8080/mcp/message
|
||||
//
|
||||
// Auto-registered tools per model:
|
||||
// read_public_users — filter, sort, paginate, preload
|
||||
// create_public_users — insert a new record
|
||||
// update_public_users — update a record by ID
|
||||
// delete_public_users — delete a record by ID
|
||||
// Fixed meta tools (independent of the number of models):
|
||||
// list_tables, describe_table, select_table, insert_into_table,
|
||||
// update_table, delete_from_table, list_functions, call_function
|
||||
```
|
||||
|
||||
For complete documentation, see [pkg/resolvemcp/README.md](pkg/resolvemcp/README.md) (if present) or the package source.
|
||||
For complete documentation, see [pkg/resolvemcp/README.md](pkg/resolvemcp/README.md) .
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -648,7 +644,7 @@ For documentation, see [pkg/cache/README.md](pkg/cache/README.md).
|
||||
|
||||
Authentication and authorization framework with hooks integration. Database-backed providers use PostgreSQL stored procedures by default, with a direct SQL backend (SQLite, MySQL, SQL Server, or Postgres without the procedures) selected through `lookup.Config`.
|
||||
|
||||
For documentation, see [pkg/security/README.md](pkg/security/README.md) (see "Direct Mode" for the SQLite/portable-SQL path).
|
||||
For documentation, see [pkg/security/README.md](pkg/security/README.md) (see "Database access (lookup)" for the SQLite/portable-SQL path).
|
||||
|
||||
#### Middleware
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# resolvemcp rewrite plan
|
||||
|
||||
Source: `audit/pkg/resolvemcp.audit.md`. Status: plan only, no code changed.
|
||||
Source: `audit/pkg/resolvemcp.audit.md`. Status: items 1-8 and 10 implemented; item 9 (tests) mostly done, see git log.
|
||||
|
||||
## Goal
|
||||
|
||||
@@ -72,7 +72,7 @@ Same rules as resolvespec CRUD, plus guardrails.
|
||||
|
||||
### 1. API key login (`pkg/security`)
|
||||
- Existing: keystore has `ValidateKey` and `KeyStoreAuthenticator`; `Login` needs a password; no key-to-session path.
|
||||
- Add `resolvespec_login_api_key` to `SQLNames` (default + override) and a SQL script beside the existing procedures. Contract: `p_success, p_error, p_data`, input raw key; hashes, validates active/non-expired key, creates session for the key's user.
|
||||
- Add `resolvespec_login_api_key` to `lookup.ProcNames` (default + override; was `SQLNames` before the lookup refactor) and a SQL script beside the existing procedures. Contract: `p_success, p_error, p_data`, input raw key; hashes, validates active/non-expired key, creates session for the key's user.
|
||||
- Add `DatabaseAuthenticator.LoginWithAPIKey(ctx, rawKey)`; procedure first, direct-SQL fallback via `ShouldUseProcedure`.
|
||||
- Hashed lookup; same generic error for unknown, expired or inactive key; no key material in logs.
|
||||
- Expose through the chain/composite authenticators so the middleware can accept it.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
| **Files** | `handler.go` (901), `tools.go` (720), `cursor.go`, `oauth2.go`, `oauth2_server.go`, `annotation.go`, `hooks.go`, `security_hooks.go`, `context.go`, `resolvemcp.go` |
|
||||
| **Tests** | `tools_test.go` (34), `tx_test.go` (207); `go test` passes. No hostile-input tests, no `-race` |
|
||||
| **Audit date** | 2026-09-30 |
|
||||
| **Status** | Rewrite implemented (meta tools, guard, limits, guardrails, function registry); see `audit/mcp_plan.md` |
|
||||
| **Axes** | thread locking/waiting, slowness, security, panic handling & logging, agent usability |
|
||||
| **Threat model** | hostile or confused MCP client (LLM agent, possibly prompt-injected); tool arguments are attacker-controlled |
|
||||
| **Depth** | targeted (request path, security wiring; verified against source) |
|
||||
|
||||
+110
-130
@@ -1,46 +1,53 @@
|
||||
# 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.
|
||||
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
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/bitechdev/ResolveSpec/pkg/resolvemcp"
|
||||
"github.com/bitechdev/ResolveSpec/pkg/security"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// 1. Create a handler
|
||||
handler := resolvemcp.NewHandlerWithGORM(db, resolvemcp.Config{
|
||||
BaseURL: "http://localhost:8080",
|
||||
BaseURL: "http://localhost:8080",
|
||||
BasePath: "/mcp",
|
||||
})
|
||||
|
||||
// 2. Register models
|
||||
securityList, _ := security.NewSecurityList(provider)
|
||||
resolvemcp.RegisterSecurityHooks(handler, securityList)
|
||||
|
||||
handler.RegisterModel("public", "users", &User{})
|
||||
handler.RegisterModel("public", "orders", &Order{})
|
||||
|
||||
// 3. Mount routes
|
||||
r := mux.NewRouter()
|
||||
resolvemcp.SetupMuxRoutes(r, handler)
|
||||
resolvemcp.SetupMuxRoutes(r, handler, securityList) // guarded
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Config
|
||||
|
||||
```go
|
||||
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
|
||||
| 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) |
|
||||
|
||||
// BasePath is the URL path prefix where MCP endpoints are mounted (e.g. "/mcp").
|
||||
// Required.
|
||||
BasePath string
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
## Handler Creation
|
||||
|
||||
@@ -63,14 +70,36 @@ handler.RegisterModel(schema, entity string, model interface{}) error
|
||||
- `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.
|
||||
`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](#security)) restrict the operations.
|
||||
|
||||
### Functions
|
||||
|
||||
```go
|
||||
// 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 required and used for all route registration.
|
||||
`Config.BaseURL` is optional — when empty it is detected from each request.
|
||||
`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).
|
||||
|
||||
@@ -83,7 +112,7 @@ Two endpoints: `GET {BasePath}/sse` (subscribe) + `POST {BasePath}/message` (sen
|
||||
#### Gorilla Mux
|
||||
|
||||
```go
|
||||
resolvemcp.SetupMuxRoutes(r, handler)
|
||||
resolvemcp.SetupMuxRoutes(r, handler, securityList)
|
||||
```
|
||||
|
||||
| Route | Method | Description |
|
||||
@@ -94,13 +123,13 @@ resolvemcp.SetupMuxRoutes(r, handler)
|
||||
#### bunrouter
|
||||
|
||||
```go
|
||||
resolvemcp.SetupBunRouterRoutes(router, handler)
|
||||
resolvemcp.SetupBunRouterRoutes(router, handler, securityList)
|
||||
```
|
||||
|
||||
#### Gin / net/http / Echo
|
||||
|
||||
```go
|
||||
sse := handler.SSEServer()
|
||||
sse := resolvemcp.NewSSEServer(handler, securityList) // guarded
|
||||
|
||||
engine.Any("/mcp/*path", gin.WrapH(sse)) // Gin
|
||||
http.Handle("/mcp/", sse) // net/http
|
||||
@@ -116,7 +145,7 @@ Single endpoint at `{BasePath}`. Handles POST (client→server) and GET (server
|
||||
#### Gorilla Mux
|
||||
|
||||
```go
|
||||
resolvemcp.SetupMuxStreamableHTTPRoutes(r, handler)
|
||||
resolvemcp.SetupMuxStreamableHTTPRoutes(r, handler, securityList)
|
||||
```
|
||||
|
||||
Mounts the handler at `{BasePath}` (all methods).
|
||||
@@ -124,7 +153,7 @@ Mounts the handler at `{BasePath}` (all methods).
|
||||
#### bunrouter
|
||||
|
||||
```go
|
||||
resolvemcp.SetupBunRouterStreamableHTTPRoutes(router, handler)
|
||||
resolvemcp.SetupBunRouterStreamableHTTPRoutes(router, handler, securityList)
|
||||
```
|
||||
|
||||
Registers GET, POST, DELETE on `{BasePath}`.
|
||||
@@ -132,8 +161,7 @@ Registers GET, POST, DELETE on `{BasePath}`.
|
||||
#### Gin / net/http / Echo
|
||||
|
||||
```go
|
||||
h := handler.StreamableHTTPServer()
|
||||
// or: h := resolvemcp.NewStreamableHTTPHandler(handler)
|
||||
h := resolvemcp.NewStreamableHTTPHandler(handler, securityList) // guarded
|
||||
|
||||
engine.Any("/mcp", gin.WrapH(h)) // Gin
|
||||
http.Handle("/mcp", h) // net/http
|
||||
@@ -306,6 +334,8 @@ Call `RegisterSecurityHooks` **once**, after creating the handler and before reg
|
||||
| `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:
|
||||
@@ -362,121 +392,58 @@ handler.SetModelRules("public", "users", modelregistry.ModelRules{
|
||||
|
||||
## MCP Tools
|
||||
|
||||
### Tool Naming
|
||||
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).
|
||||
|
||||
```
|
||||
{operation}_{schema}_{entity} // e.g. read_public_users
|
||||
{operation}_{entity} // e.g. read_users (when schema is empty)
|
||||
```
|
||||
| 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` |
|
||||
|
||||
Operations: `read`, `create`, `update`, `delete`.
|
||||
|
||||
### Read Tool — `read_{schema}_{entity}`
|
||||
|
||||
Fetch one or many records.
|
||||
### `select_table`
|
||||
|
||||
| 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](#filtering)). |
|
||||
| `sort` | array | Sort objects (see [Sorting](#sorting)). |
|
||||
| `preloads` | array | Relation preload objects (see [Preloading](#preloading)). |
|
||||
| `table` | string (required) | `schema.entity` |
|
||||
| `id` | string | Primary key of one row |
|
||||
| `filters`, `sort` | array | See [Filtering](#filtering), [Sorting](#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:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [...],
|
||||
"metadata": {
|
||||
"total": 100,
|
||||
"filtered": 100,
|
||||
"count": 10,
|
||||
"limit": 10,
|
||||
"offset": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
Response: `{"success":true,"data":[...],"metadata":{"total","filtered","count","limit","offset"}}`
|
||||
|
||||
### Create Tool — `create_{schema}_{entity}`
|
||||
### `insert_into_table`
|
||||
|
||||
Insert one or more records.
|
||||
`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.
|
||||
|
||||
| Argument | Type | Description |
|
||||
|---|---|---|
|
||||
| `data` | object \| array | Single object or array of objects to insert. |
|
||||
### `update_table` / `delete_from_table`
|
||||
|
||||
Array input runs inside a single transaction — all succeed or all fail.
|
||||
Either `id` or `filters` is required.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{ "success": true, "data": { ... } }
|
||||
```
|
||||
| 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. |
|
||||
|
||||
### Update Tool — `update_{schema}_{entity}`
|
||||
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.
|
||||
|
||||
Partially update an existing record. Only non-null, non-empty fields in `data` are applied; existing values are preserved for omitted fields.
|
||||
### `call_function`
|
||||
|
||||
| Argument | Type | Description |
|
||||
|---|---|---|
|
||||
| `id` | string | Primary key of the record. Can also be included inside `data`. |
|
||||
| `data` | object (required) | Fields to update. |
|
||||
`name` and `arguments` (object). See [Functions](#functions).
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{ "success": true, "data": { ...merged record... } }
|
||||
```
|
||||
### `resolvespec_annotate`
|
||||
|
||||
### 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:**
|
||||
```json
|
||||
{ "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)`):
|
||||
```json
|
||||
{ "tool_name": "read_public_users", "annotations": { "description": "Returns active users", "owner": "platform-team" } }
|
||||
```
|
||||
**Response:**
|
||||
```json
|
||||
{ "success": true, "tool_name": "read_public_users", "action": "set" }
|
||||
```
|
||||
|
||||
**Get annotations** (calls `resolvespec_get_annotation(tool_name)`):
|
||||
```json
|
||||
{ "tool_name": "read_public_users" }
|
||||
```
|
||||
**Response:**
|
||||
```json
|
||||
{ "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`.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -576,6 +543,9 @@ Hooks let you intercept and modify CRUD operations at well-defined lifecycle poi
|
||||
| `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
|
||||
|
||||
@@ -605,7 +575,7 @@ handler.Hooks().RegisterMultiple(
|
||||
| `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"` |
|
||||
| `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) |
|
||||
@@ -639,7 +609,7 @@ registry.ClearAll() // remove all hooks
|
||||
|
||||
## Context Helpers
|
||||
|
||||
Request metadata is threaded through `context.Context` during handler execution. Hooks and custom tools can read it:
|
||||
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:
|
||||
|
||||
```go
|
||||
schema := resolvemcp.GetSchema(ctx)
|
||||
@@ -659,7 +629,7 @@ ctx = resolvemcp.WithSchema(ctx, "tenant_a")
|
||||
|
||||
## Adding Custom MCP Tools
|
||||
|
||||
Access the underlying `*server.MCPServer` to register additional tools:
|
||||
Access the underlying `*server.MCPServer` to register additional tools (they sit behind the same guard). Prefer `RegisterFunction` for database-backed actions:
|
||||
|
||||
```go
|
||||
mcpServer := handler.MCPServer()
|
||||
@@ -675,3 +645,13 @@ 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
|
||||
|
||||
- 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.
|
||||
|
||||
Reference in New Issue
Block a user