tool

package
v0.1.12 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 31 Imported by: 0

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

View Source
const (
	CodeUnauthorized        = "UNAUTHORIZED"
	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"
)

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.

Variables

View Source
var (
	// ErrEntityNotFound is returned when the requested entity is unknown.
	ErrEntityNotFound = errors.New("tool: entity not found")
	// ErrUnauthorized is returned when the role lacks the action.
	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.

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

Functions

func DecisionIDFromContext added in v0.1.5

func DecisionIDFromContext(ctx context.Context) string

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

func ProcedureToolName(e entity.Entity) string

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.

func VisibleKeys added in v0.1.11

func VisibleKeys(e entity.Entity, visible []string) [][]string

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

func WithDecisionID(ctx context.Context, id string) context.Context

WithDecisionID attaches a decision ID to ctx for hooks and telemetry.

func WrapDBError

func WrapDBError(err error) error

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

func (AggregateTool) Enabled(f config.ToolFlags) bool

func (AggregateTool) Info

func (AggregateTool) Info() Info

func (AggregateTool) Run

type AmbiguousEntityError added in v0.1.12

type AmbiguousEntityError struct {
	Ref        string
	Candidates []string
}

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

type AmbiguousFieldScopeError struct {
	Reason      string
	FieldScopes [][]string
}

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

func (BeginTransactionTool) Info

func (BeginTransactionTool) Info() Info

func (BeginTransactionTool) Run

type CacheTarget added in v0.1.11

type CacheTarget struct {
	Physical string
	Relation string
}

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 CommitTransactionTool

type CommitTransactionTool struct{}

func (CommitTransactionTool) Enabled

func (CommitTransactionTool) Info

func (CommitTransactionTool) Info() Info

func (CommitTransactionTool) Run

type ConstraintViolationError added in v0.1.11

type ConstraintViolationError struct {
	Kind   string
	Fields []string
}

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 {
	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         store.DB
	Dialect    dialect.Dialect
	Registry   *entity.Registry
	Authorizer rbac.Authorizer
	Masker     mask.Masker
	Gate       cost.Gate
	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
	Analyze      cost.AnalyzePolicy
	DataSource   string
	Sources      map[string]DataSource
	Budget       budget.Manager
	BudgetLimits budget.Limits
	Transactions *TransactionManager
	Writes       *WriteTracker
	TxBeginners  map[string]store.TxBeginner
	Transaction  string
	// contains filtered or unexported fields
}

Context carries per-request dependencies (injected, no mutable global state).

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

func (CreateTool) Enabled(f config.ToolFlags) bool

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

func (DeleteTool) Enabled(f config.ToolFlags) bool

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.

func DenialFor added in v0.1.5

func DenialFor(err error, decisionID string) (Denial, bool)

DenialFor maps a business-level error to the rejection contract. ok is false for internal errors, which must not be reflected to clients.

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

func (DescribeTool) Info() Info

Info 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) Enabled

func (ExecuteTool) Enabled(f config.ToolFlags) bool

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

type ProcedureTool struct {
	Entity entity.Entity
}

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

Enabled implements Tool. Procedure tools are registered independently of the generic tool flags.

func (ProcedureTool) Info

func (t ProcedureTool) Info() Info

Info implements Tool.

func (ProcedureTool) Run

func (t ProcedureTool) Run(ctx context.Context, input json.RawMessage, tc Context) (Result, error)

Run implements Tool.

type ReadTool

type ReadTool struct{}

ReadTool reads records (cost-gated).

func (ReadTool) CostGated

func (ReadTool) CostGated()

func (ReadTool) Enabled

func (ReadTool) Enabled(f config.ToolFlags) bool

func (ReadTool) Info

func (ReadTool) Info() Info

func (ReadTool) Run

func (ReadTool) Run(ctx context.Context, input json.RawMessage, tc Context) (Result, error)

type Registry

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

Registry holds an immutable set of tools.

func NewRegistry

func NewRegistry(tools []Tool) (*Registry, error)

NewRegistry builds a Registry, rejecting duplicate tool names.

func (*Registry) Enabled

func (r *Registry) Enabled(flags config.ToolFlags) []Tool

Enabled returns the tools whose Enabled(flags) is true.

func (*Registry) Get

func (r *Registry) Get(name string) (Tool, bool)

Get returns a tool by name.

func (*Registry) Tools

func (r *Registry) Tools() []Tool

Tools returns all registered tools.

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

func RunTool(ctx context.Context, t Tool, input json.RawMessage, tc Context) (Result, error)

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 RollbackTransactionTool

type RollbackTransactionTool struct{}

func (RollbackTransactionTool) Enabled

func (RollbackTransactionTool) Info

func (RollbackTransactionTool) Run

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

func (m *TransactionManager) Begin(
	ctx context.Context,
	beginner store.TxBeginner,
	session, role string,
	subject map[string]any,
	datasource string,
	opts *store.TxOptions,
) (string, error)

func (*TransactionManager) Close

func (m *TransactionManager) Close()

func (*TransactionManager) Commit

func (m *TransactionManager) Commit(
	ctx context.Context,
	token, session, role string,
	subject map[string]any,
) ([]CacheTarget, error)

Commit commits and returns the cache targets the transaction wrote. The list is returned only after a successful commit 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) DB

func (m *TransactionManager) DB(
	token, session, role string,
	subject map[string]any,
	datasource string,
	connections []store.TxBeginner,
) (store.DB, error)

func (*TransactionManager) MarkDirty

func (m *TransactionManager) MarkDirty(
	token, session, role string,
	subject map[string]any,
	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) Rollback

func (m *TransactionManager) Rollback(ctx context.Context, token, session, role string, subject map[string]any) error

Rollback rolls back the transaction.

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

func (m *TransactionManager) Validate(token, session, role string, subject map[string]any) error

Validate verifies a token's transport and authorization identity without exposing its transaction or datasource.

type UpdateTool

type UpdateTool struct{}

UpdateTool updates records (cost-gated).

func (UpdateTool) CostGated

func (UpdateTool) CostGated()

func (UpdateTool) Enabled

func (UpdateTool) Enabled(f config.ToolFlags) bool

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.

Jump to

Keyboard shortcuts

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