Documentation
¶
Overview ¶
Package tool defines the DML tool components and their registry. Each tool implements the Tool interface; Enabled(flags) decides whether it is registered with the MCP server (invariant I8). CostGated marks tools whose execution must pass the cost gate (invariant I4). Tools construct relalg.IR, which codegen renders; they never build SQL by hand, keeping queries inject-proof.
Index ¶
- Constants
- Variables
- func DecisionIDFromContext(ctx context.Context) string
- func NewDecisionID() string
- func ProcedureToolName(e entity.Entity) string
- func UsableBy(ctx context.Context, authz rbac.Authorizer, role string, ...) func(entity.Entity) bool
- func VisibleKeys(e entity.Entity, visible []string) [][]string
- func WithDecisionID(ctx context.Context, id string) context.Context
- func WrapDBError(err error) error
- type AggregateTool
- type AmbiguousEntityError
- type AmbiguousFieldScopeError
- type BeginTransactionTool
- type CacheTarget
- type Caller
- type CommitTransactionTool
- type ConstraintViolationError
- type Context
- type CostGated
- type CreateTool
- type DataSource
- type DeleteTool
- type Denial
- type DescribeTool
- type ExecuteTool
- type Info
- type Limits
- type ProcedureTool
- type ReadTool
- type Registry
- type Result
- type ResultMemory
- type RollbackTransactionTool
- type Services
- type Tool
- type TransactionEndedError
- type TransactionManager
- func (m *TransactionManager) Begin(ctx context.Context, beginner store.TxBeginner, caller Caller, ...) (string, error)
- func (m *TransactionManager) Close()
- func (m *TransactionManager) Commit(ctx context.Context, token string, caller Caller) ([]CacheTarget, error)
- func (m *TransactionManager) Configuration() (time.Duration, int)
- func (m *TransactionManager) DB(ctx context.Context, token string, caller Caller, datasource string, ...) (store.DB, error)
- func (m *TransactionManager) MarkDirty(ctx context.Context, token string, caller Caller, datasource string, ...) error
- func (m *TransactionManager) Rollback(ctx context.Context, token string, caller Caller) error
- func (m *TransactionManager) RollbackSession(session string)
- func (m *TransactionManager) SetConnectionLimit(n int)
- func (m *TransactionManager) UpdateLimits(ttl time.Duration, maxOpen int)
- func (m *TransactionManager) UseBus(b *bus.Bus)
- func (m *TransactionManager) Validate(ctx context.Context, token string, caller Caller) error
- type UpdateTool
- type WriteTargets
- type WriteTracker
Constants ¶
const ( CodeEntityNotFound = "ENTITY_NOT_FOUND" CodeInvalidInput = "INVALID_INPUT" CodeDMLToolsDisabled = "DML_TOOLS_DISABLED" CodeUnsafeWrite = "UNSAFE_WRITE" CodeDatabaseError = "DATABASE_ERROR" CodeCostExceeded = "COST_EXCEEDED" CodeBudgetExceeded = "BUDGET_EXCEEDED" CodeTransactionNotFound = "TRANSACTION_NOT_FOUND" CodeTransactionScope = "TRANSACTION_SCOPE" CodeTransactionCapacity = "TRANSACTION_CAPACITY" CodeTransactionStale = "TRANSACTION_STALE" CodeAmbiguousFieldScope = "AMBIGUOUS_FIELD_SCOPE" CodeConstraintViolation = "CONSTRAINT_VIOLATION" CodeDatasourceForbidden = "DATASOURCE_FORBIDDEN" CodeAmbiguousEntity = "AMBIGUOUS_ENTITY" CodeOutcomeUnknown = "OUTCOME_UNKNOWN" )
Stable machine codes for business-level rejections. Retryable means a revised request could succeed under the caller's current privileges; hints may only tighten or equivalently rewrite the request, never widen it.
const ( OutcomeCommitted = "committed" OutcomeRolledBack = "rolledBack" OutcomeExpired = "expired" // OutcomeUnknown is a commit whose connection broke or whose deadline // passed while it ran: the transaction may or may not have committed. OutcomeUnknown = "unknown" )
How an explicit transaction ended, as its tombstone records it.
Variables ¶
var ( // ErrEntityNotFound is returned when the requested entity is unknown. ErrEntityNotFound = errors.New("tool: entity not found") ErrUnauthorized = errors.New("tool: unauthorized") // ErrInvalidInput is returned for malformed tool input. ErrInvalidInput = errors.New("tool: invalid input") // ErrDMLToolsDisabled is returned when an entity is not exposed to the // generic entity tools. ErrDMLToolsDisabled = errors.New("tool: entity DML tools disabled") // ErrUnsafeWrite is returned when an update/delete lacks a PK-scoped filter. ErrUnsafeWrite = errors.New("tool: unsafe write without primary-key filter") // ErrDuplicateTool is returned by NewRegistry for duplicate tool names. ErrDuplicateTool = errors.New("tool: duplicate name") // ErrDatabase identifies errors returned by the database driver. ErrDatabase = errors.New("tool: database error") // ErrConstraintViolation is a write rejected by a database constraint. ErrConstraintViolation = errors.New("tool: constraint violation") // ErrDatasourceForbidden is a statement the datasource's account may not // run: a missing database privilege or a read-only server. ErrDatasourceForbidden = errors.New("tool: the datasource connection is not permitted to do this") )
Sentinel errors. Business errors (Unauthorized/NotFound/UnsafeWrite/Cost) are mapped by x/mcpserver to IsError results; protocol errors to JSON-RPC.
var ( ErrTransactionNotFound = errors.New("tool: transaction not found") ErrTransactionScope = errors.New("tool: transaction scope mismatch") ErrTransactionCapacity = errors.New("tool: transaction capacity reached") // ErrTransactionStale rejects statements in a transaction whose datasource // a reload has since pointed at another connection: the configuration they // would be planned with no longer describes the database it runs on. ErrTransactionStale = errors.New("tool: transaction datasource was reconfigured; roll it back and begin again") )
var ErrOutcomeUnknown = errors.New("tool: the database may or may not have carried out the write")
ErrOutcomeUnknown marks a write or a commit the database may or may not have carried out: its connection broke or its deadline passed while it ran. Retrying it blindly may apply it twice.
Functions ¶
func DecisionIDFromContext ¶ added in v0.1.5
DecisionIDFromContext returns the decision ID attached by RunTool, or "".
func NewDecisionID ¶ added in v0.1.5
func NewDecisionID() string
NewDecisionID returns a 128-bit random identifier correlating one tool call's MCP response, audit event, and trace span.
func ProcedureToolName ¶
ProcedureToolName returns a stable MCP-safe name for a procedure entity: its local name, normalized, and a hash of its Name. The prefix separates procedure tools from built-ins; the hash prevents normalization collisions and tells same-named procedures of different namespaces apart.
func UsableBy ¶ added in v0.1.12
func UsableBy( ctx context.Context, authz rbac.Authorizer, role string, subject map[string]any, ) func(entity.Entity) bool
UsableBy reports the entities a caller can reach for some action, as entity discovery does (see rbac.Decision.Reachable); every entity when authz is nil. The answer for each entity is kept: naming one entity among many asks about its namesakes, and listing them asks about each.
func VisibleKeys ¶ added in v0.1.11
VisibleKeys lists the entity's identity keys whose every column is among visible, the fields a caller can use to address one row.
func WithDecisionID ¶ added in v0.1.5
WithDecisionID attaches a decision ID to ctx for hooks and telemetry.
func WrapDBError ¶
WrapDBError classifies a database driver error while preserving its cause: a refused privilege becomes ErrDatasourceForbidden, anything else ErrDatabase. Neither reports the driver message to clients.
Types ¶
type AggregateTool ¶
type AggregateTool struct{}
AggregateTool runs an aggregate query (cost-gated).
func (AggregateTool) CostGated ¶
func (AggregateTool) CostGated()
func (AggregateTool) Info ¶
func (AggregateTool) Info() Info
func (AggregateTool) Run ¶
func (AggregateTool) Run(ctx context.Context, input json.RawMessage, tc Context) (Result, error)
type AmbiguousEntityError ¶ added in v0.1.12
AmbiguousEntityError reports an entity reference naming more than one entity the caller can use. Candidates name each of them unambiguously; entities the caller cannot use are neither counted nor listed.
func (*AmbiguousEntityError) Error ¶ added in v0.1.12
func (e *AmbiguousEntityError) Error() string
func (*AmbiguousEntityError) Unwrap ¶ added in v0.1.12
func (e *AmbiguousEntityError) Unwrap() error
type AmbiguousFieldScopeError ¶ added in v0.1.11
AmbiguousFieldScopeError reports that no single grant covers every field a request uses. FieldScopes are field sets the caller can already read, so exposing them is not a side channel. It unwraps to ErrUnauthorized.
func (*AmbiguousFieldScopeError) Error ¶ added in v0.1.11
func (e *AmbiguousFieldScopeError) Error() string
func (*AmbiguousFieldScopeError) Unwrap ¶ added in v0.1.11
func (e *AmbiguousFieldScopeError) Unwrap() error
type BeginTransactionTool ¶
type BeginTransactionTool struct{}
func (BeginTransactionTool) Info ¶
func (BeginTransactionTool) Info() Info
func (BeginTransactionTool) Run ¶
func (BeginTransactionTool) Run(ctx context.Context, input json.RawMessage, tc Context) (Result, error)
type CacheTarget ¶ added in v0.1.11
CacheTarget names the cached reads a write invalidates: Relation of the physical database Physical, or all of it when Relation is empty (see cache.Cache).
type Caller ¶ added in v0.1.13
Caller is who holds a transaction: the transport session it was begun in and the authorization identity it was begun under. A statement of the transaction must come from the same caller.
type CommitTransactionTool ¶
type CommitTransactionTool struct{}
func (CommitTransactionTool) Enabled ¶
func (CommitTransactionTool) Enabled(f config.ToolFlags) bool
func (CommitTransactionTool) Info ¶
func (CommitTransactionTool) Info() Info
func (CommitTransactionTool) Run ¶
func (CommitTransactionTool) Run(ctx context.Context, input json.RawMessage, tc Context) (Result, error)
type ConstraintViolationError ¶ added in v0.1.11
ConstraintViolationError reports a write the database rejected for violating a constraint. Fields lists the violating columns the caller may see; the constraint name and the database message are never included.
func (*ConstraintViolationError) Error ¶ added in v0.1.11
func (e *ConstraintViolationError) Error() string
func (*ConstraintViolationError) Unwrap ¶ added in v0.1.11
func (e *ConstraintViolationError) Unwrap() error
type Context ¶
type Context struct {
*Services
Role string // principal key: a role name, or "user:<name>" for a user
User string // configured user name, empty on the role path
UserRoles []string // the user's roles, for audit
Subject map[string]any // caller attributes for row-level ${subject.x} policies
Session string // MCP session ID when the transport provides one
DecisionID string // per-call ID correlating response, audit, and trace
// DB, Dialect, Gate and Analyze are the entity's datasource's, once
// routed (see routeEntity); DataSource names it.
DB store.DB
Dialect dialect.Dialect
Gate cost.Gate
Analyze cost.AnalyzePolicy
DataSource string
BudgetLimits budget.Limits
Transaction string
// contains filtered or unexported fields
}
Context is one call: who makes it, where it is routed, and the Services it runs on. It is passed by value and routed by copy (see routeEntity).
type CostGated ¶
type CostGated interface {
Tool
CostGated()
}
CostGated marks a tool whose execution must pass the cost gate.
type CreateTool ¶
type CreateTool struct{}
CreateTool inserts one record.
func (CreateTool) Info ¶
func (CreateTool) Info() Info
func (CreateTool) Run ¶
func (CreateTool) Run(ctx context.Context, input json.RawMessage, tc Context) (Result, error)
type DataSource ¶
type DataSource struct {
DB store.DB
Write store.DB
Execute store.DB
ReadTx store.TxBeginner
ReadAfterWrite time.Duration
Dialect dialect.Dialect
Gate cost.Gate
Analyze cost.AnalyzePolicy
}
DataSource is the per-entity execution route: DB is the read connection; Write and Execute, when set, serve writes and procedure calls (see connectionFor), and ReadTx begins read-only transactions.
type DeleteTool ¶
type DeleteTool struct{}
DeleteTool deletes records (cost-gated, off by default).
func (DeleteTool) CostGated ¶
func (DeleteTool) CostGated()
func (DeleteTool) Info ¶
func (DeleteTool) Info() Info
func (DeleteTool) Run ¶
func (DeleteTool) Run(ctx context.Context, input json.RawMessage, tc Context) (Result, error)
type Denial ¶ added in v0.1.5
type Denial struct {
Code string `json:"code"`
Reason string `json:"reason"`
Retryable bool `json:"retryable"`
Constraints map[string]any `json:"constraints,omitempty"`
Hints []string `json:"hints,omitempty"`
DecisionID string `json:"decisionId"`
}
Denial is the stable, machine-readable rejection contract returned to MCP clients for business-level errors. Field names and code values are part of the public tool contract: adding a new optional field or a new code is a compatible change; renaming or removing either is breaking.
type DescribeTool ¶
type DescribeTool struct{}
DescribeTool lists entities or one entity's fields.
func (DescribeTool) Enabled ¶
func (DescribeTool) Enabled(f config.ToolFlags) bool
Enabled implements Tool.
func (DescribeTool) Run ¶
func (DescribeTool) Run(ctx context.Context, input json.RawMessage, tc Context) (Result, error)
Run implements Tool.
type ExecuteTool ¶
type ExecuteTool struct{}
ExecuteTool calls a stored procedure.
func (ExecuteTool) CostGated ¶
func (ExecuteTool) CostGated()
func (ExecuteTool) Info ¶
func (ExecuteTool) Info() Info
func (ExecuteTool) Run ¶
func (ExecuteTool) Run(ctx context.Context, input json.RawMessage, tc Context) (Result, error)
type Info ¶
type Info struct {
Name string
Description string
InputSchema json.RawMessage
ReadOnly bool
// Action is the audited action; read, aggregate and execute also size the
// budget reservation.
Action string
}
Info is a tool's static metadata, mapped to an MCP tool definition.
type Limits ¶ added in v0.1.11
type Limits struct {
Timeout time.Duration
MaxRows int64
MaxProcedureRows int64
MaxReturnedBytes int64
MaxINListSize int
MaxFilterConditions int
MaxGroupByFields int
MaxAggregates int
MaxExpand int
CacheMaxEntryRows int
CacheMaxEntryBytes int64
TransactionBeginTimeout time.Duration
TransactionCommitTimeout time.Duration
TransactionRollbackTimeout time.Duration
}
Limits are the configured per-call limits; zero means unlimited (or, for the transaction timeouts, the query Timeout).
type ProcedureTool ¶
ProcedureTool exposes one configured procedure as an independent MCP tool. It accepts the procedure parameters directly and delegates execution to the same implementation as execute_entity.
func FindProcedureTool ¶ added in v0.1.11
func FindProcedureTool(reg *entity.Registry, name string) (ProcedureTool, bool)
FindProcedureTool returns the exposed procedure tool named name.
func ProcedureTools ¶ added in v0.1.11
func ProcedureTools(reg *entity.Registry) []ProcedureTool
ProcedureTools returns the procedures exposed as their own tools: trusted procedures configured with a custom tool.
func (ProcedureTool) CostGated ¶
func (ProcedureTool) CostGated()
func (ProcedureTool) Enabled ¶
func (ProcedureTool) Enabled(config.ToolFlags) bool
Enabled implements Tool. Procedure tools are registered independently of the generic tool flags.
func (ProcedureTool) Run ¶
func (t ProcedureTool) Run(ctx context.Context, input json.RawMessage, tc Context) (Result, error)
Run implements Tool.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds an immutable set of tools.
func NewRegistry ¶
NewRegistry builds a Registry, rejecting duplicate tool names.
type Result ¶
type Result struct {
Content []map[string]any
ReturnedRows int64
ReturnedBytes int64
EstimatedScannedRows int64
// Grants lists covering grant IDs for audit; it is not sent to clients.
Grants []string
}
Result is a tool's outcome: the rows returned to the agent plus usage for budgets and audit.
func RunTool ¶
RunTool executes a tool with the cross-cutting wiring that must apply to every call regardless of transport: lifecycle hooks, bounded concurrency + backpressure + circuit breaking + (for read-only tools) singleflight via the engine, and best-effort audit. Transports (x/mcpserver) call this instead of Tool.Run directly, keeping the wiring in the core where it is unit-testable. A nil Engine/Auditor/Hooks each degrade to a no-op.
type ResultMemory ¶ added in v0.1.13
type ResultMemory struct {
// contains filtered or unexported fields
}
ResultMemory bounds the bytes of the results the process holds at once, across calls: each call's rows hold their share from when they are read until the call ends. It belongs to the process and outlives configurations. A nil ResultMemory bounds nothing.
func NewResultMemory ¶ added in v0.1.13
func NewResultMemory(capacity int64) *ResultMemory
NewResultMemory returns a bound of capacity bytes (0: none).
func (*ResultMemory) InUse ¶ added in v0.1.13
func (r *ResultMemory) InUse() int64
InUse returns the bytes held.
func (*ResultMemory) SetCapacity ¶ added in v0.1.13
func (r *ResultMemory) SetCapacity(capacity int64)
SetCapacity changes the bound for the reservations that follow.
type RollbackTransactionTool ¶
type RollbackTransactionTool struct{}
func (RollbackTransactionTool) Enabled ¶
func (RollbackTransactionTool) Enabled(f config.ToolFlags) bool
func (RollbackTransactionTool) Info ¶
func (RollbackTransactionTool) Info() Info
func (RollbackTransactionTool) Run ¶
func (RollbackTransactionTool) Run(ctx context.Context, input json.RawMessage, tc Context) (Result, error)
type Services ¶ added in v0.1.13
type Services struct {
Registry *entity.Registry
Authorizer rbac.Authorizer
Masker mask.Masker
Cache cache.Cache[[]map[string]any]
// Generation is the configuration generation serving the call; cached
// reads of different generations are kept apart.
Generation uint64
// WriteTargets, when set, holds what a write through each entity
// invalidates (see BuildWriteTargets).
WriteTargets *WriteTargets
Engine *engine.Engine
Auditor audit.Auditor
Hooks *hook.Hooks
Limits
Feedback cost.FeedbackStore
Sources map[string]DataSource
Budget budget.Manager
Transactions *TransactionManager
Writes *WriteTracker
TxBeginners map[string]store.TxBeginner
// ResultMemory bounds the results the process holds at once.
ResultMemory *ResultMemory
}
Services are what every call of one configuration shares: the entities and the policy, the cache, the engine, the audit sink and the hooks, the limits, the datasources, and the process state (transactions, budgets, write tracking, result memory) that outlives the configuration. A Context refers to them; it never copies them. A nil field degrades to a no-op where the call can go on without it.
type Tool ¶
type Tool interface {
Info() Info
Enabled(flags config.ToolFlags) bool
Run(ctx context.Context, input json.RawMessage, tc Context) (Result, error)
}
Tool is a single DML capability.
func DefaultTools ¶
func DefaultTools() []Tool
DefaultTools returns the entity and explicit transaction tool instances.
type TransactionEndedError ¶ added in v0.1.13
type TransactionEndedError struct{ Outcome string }
TransactionEndedError is ErrTransactionNotFound for a token whose transaction ended recently, saying how: a caller whose commit timed out learns whether it is worth retrying. Only the caller the transaction was bound to learns it.
func (*TransactionEndedError) Error ¶ added in v0.1.13
func (e *TransactionEndedError) Error() string
func (*TransactionEndedError) Unwrap ¶ added in v0.1.13
func (e *TransactionEndedError) Unwrap() error
type TransactionManager ¶
type TransactionManager struct {
// contains filtered or unexported fields
}
TransactionManager owns bounded, expiring explicit transaction handles. Tokens are unguessable and additionally bound to role, subject, and source.
func NewTransactionManager ¶
func NewTransactionManager(ttl time.Duration, maxOpen int) *TransactionManager
func (*TransactionManager) Close ¶
func (m *TransactionManager) Close()
func (*TransactionManager) Commit ¶
func (m *TransactionManager) Commit(ctx context.Context, token string, caller Caller) ([]CacheTarget, error)
Commit commits and returns the cache targets the transaction wrote. The list is returned only after a commit that succeeded, or may have (with an ErrOutcomeUnknown), so callers can invalidate global read caches without rollback pollution.
func (*TransactionManager) Configuration ¶
func (m *TransactionManager) Configuration() (time.Duration, int)
Configuration returns the limits in force.
func (*TransactionManager) MarkDirty ¶
func (m *TransactionManager) MarkDirty( ctx context.Context, token string, caller Caller, datasource string, connections []store.TxBeginner, targets ...CacheTarget, ) error
MarkDirty records the cache targets a write inside a transaction changed; connections are as for DB.
func (*TransactionManager) RollbackSession ¶
func (m *TransactionManager) RollbackSession(session string)
RollbackSession rolls back all handles owned by a disconnected non-empty transport session. Token-only transports use TTL and App.Close instead.
func (*TransactionManager) SetConnectionLimit ¶ added in v0.1.13
func (m *TransactionManager) SetConnectionLimit(n int)
SetConnectionLimit bounds the transactions open on one connection at once (0: no bound), for every later begin.
func (*TransactionManager) UpdateLimits ¶ added in v0.1.12
func (m *TransactionManager) UpdateLimits(ttl time.Duration, maxOpen int)
UpdateLimits applies new limits: ttl to the transactions begun from now on, maxOpen to every later begin.
func (*TransactionManager) UseBus ¶ added in v0.1.13
func (m *TransactionManager) UseBus(b *bus.Bus)
UseBus runs the expiry timers as background tasks of b.
type UpdateTool ¶
type UpdateTool struct{}
UpdateTool updates records (cost-gated).
func (UpdateTool) CostGated ¶
func (UpdateTool) CostGated()
func (UpdateTool) Info ¶
func (UpdateTool) Info() Info
func (UpdateTool) Run ¶
func (UpdateTool) Run(ctx context.Context, input json.RawMessage, tc Context) (Result, error)
type WriteTargets ¶ added in v0.1.12
type WriteTargets struct {
// contains filtered or unexported fields
}
WriteTargets is what a write through each entity invalidates, worked out once per configuration (see BuildWriteTargets), and which datasources reach each physical database, whose session reads then follow the write.
func BuildWriteTargets ¶ added in v0.1.12
func BuildWriteTargets(reg *entity.Registry) *WriteTargets
BuildWriteTargets works out what a write through each entity of reg invalidates, so writes do not walk the registry.
type WriteTracker ¶ added in v0.1.11
type WriteTracker struct {
// contains filtered or unexported fields
}
WriteTracker remembers when each session last wrote to each datasource, so its reads can follow its writes for a datasource's readAfterWrite window. A nil tracker records nothing.
func NewWriteTracker ¶ added in v0.1.11
func NewWriteTracker() *WriteTracker
NewWriteTracker returns an empty tracker.
func (*WriteTracker) Forget ¶ added in v0.1.11
func (w *WriteTracker) Forget(session string)
Forget drops a closed session.
func (*WriteTracker) Recent ¶ added in v0.1.11
func (w *WriteTracker) Recent(session, datasource string, window time.Duration) bool
Recent reports whether session wrote to datasource within window.
func (*WriteTracker) Record ¶ added in v0.1.11
func (w *WriteTracker) Record(session, datasource string)
Record notes a write by session to datasource.
func (*WriteTracker) Wrote ¶ added in v0.1.11
func (w *WriteTracker) Wrote(session string) bool
Wrote reports whether session wrote anything recently enough to be remembered.
Source Files
¶
- cascade.go
- denial.go
- doc.go
- entity_ref.go
- invalidation.go
- pipeline.go
- results.go
- routing.go
- schema.go
- tool.go
- tool_audit.go
- tool_input.go
- tool_registry.go
- tools.go
- tools_aggregate.go
- tools_aggregate_impl.go
- tools_describe.go
- tools_describe_impl.go
- tools_execute.go
- tools_execute_impl.go
- tools_helpers.go
- tools_read.go
- tools_read_impl.go
- tools_write.go
- tools_write_impl.go
- transaction.go
- transaction_ended.go
- transaction_tools.go