Documentation
¶
Index ¶
- Constants
- func CoerceBodyDates(payload any) error
- func ErrorResult(err error) *mcp.CallToolResult
- func ExtractBodyBaseline(in map[string]any) (any, error)
- func IsReadOnlyEndpoint(resource, verb string, e catalog.HelpEntry) bool
- func RegisterHelp(server *mcp.Server)
- func RegisterSpecDriven(server *mcp.Server, doer Doer, opts Options)
- func SanitizeName(s string) string
- func SortedKeys[V any](m map[string]V) []string
- func StringValue(v any) string
- func ToolName(resource, verb string) string
- func WrapUntrustedFields(v any) any
- func WrapUntrustedFieldsWithKeys(v any, keys map[string]bool) any
- type APIError
- type Doer
- type EmbedAugmentor
- type Options
- type SpecTool
Constants ¶
const WorkspaceParamName = "workspaceId"
WorkspaceParamName is the required workspace selector every desktop tool carries. It is not a spec path arg — it routes the call to the (client, workspace) grant that signs the request, and is substituted into any `{workspaceId}` path placeholder when present.
Variables ¶
This section is empty.
Functions ¶
func CoerceBodyDates ¶
CoerceBodyDates is a thin alias over qparam.CoerceBodyDates so the MCP path reads symmetrically alongside coerceBodyDates calls in the CLI's generated write handlers — same coercion, single source of truth.
func ErrorResult ¶
func ErrorResult(err error) *mcp.CallToolResult
ErrorResult wraps a Bron API error (or any error) into an MCP tool-error payload — the structured envelope (status, code, message, requestId) survives for the agent to branch on without parsing strings. All string fields go through output.SanitizeForTerminal because backend error messages can echo user-controlled input (e.g. "external id 'foo<script>' already taken") which a naive renderer might interpret.
func ExtractBodyBaseline ¶
ExtractBodyBaseline pulls the optional `body` field out of the input map and returns it as the JSON baseline. Maps pass through (interface{} == any in Go 1.18+, no copy needed); anything else gets re-marshalled through a json.Decoder with UseNumber so big-int amount fields don't lose precision (`15000000000` would otherwise round-trip as `1.5e+10` and fail the backend's decimal parser).
func IsReadOnlyEndpoint ¶
IsReadOnlyEndpoint flags endpoints that are safe to expose under `bron mcp --read-only`. The source of truth is the OpenAPI spec's per-endpoint API-key permissions list (mined into `e.Permissions` by cligen): a tool is read-only iff its permissions include "View only". This avoids the "GET that mutates" footgun where a future endpoint like `GET /transactions/{id}/retry-broadcast` would slip through a method-only heuristic, and inverts the safety polarity to fail-closed (no permissions metadata → not read-only).
One explicit allow-list entry: `tx.dry-run` is a POST per spec but pure validation (no DB writes, no audit-log entries, no rate-counter advance — confirmed against backend). It's the only POST surfaced in read-only mode.
func RegisterHelp ¶
RegisterHelp adds the read-only `bron_help` tool: static data-model help with no network access. It is not a spec endpoint, so it registers directly rather than through RegisterSpecDriven.
func RegisterSpecDriven ¶
RegisterSpecDriven auto-registers one MCP tool per CLI endpoint that passes opts, all driven by the generated metadata. Untouched when new endpoints land — regen is the only step.
func SanitizeName ¶
func SortedKeys ¶
func StringValue ¶
StringValue stringifies one input value the way the CLI does — strings pass through, numbers/booleans become their JSON repr (so body.Compose's json.Unmarshal recovers the typed scalar), nested objects/arrays go through json.Marshal. Empty / nil → empty string so callers can `if s == ""` skip.
func ToolName ¶
ToolName converts (resource, verb) into a stable MCP tool name.
bron_<resource>_<verb> with dashes turned into underscores.
The MCP spec restricts tool names to [a-zA-Z0-9_-]; our resources and verbs already comply, but address-book/create-signing-request style verbs need the dash → underscore swap so the JSON-Schema name pattern ($_a-z0-9) is satisfied uniformly.
func WrapUntrustedFields ¶
WrapUntrustedFields walks a JSON-shaped value tree and wraps known user-controlled string fields (`description`, `memo`, `note`, `comment`, `reason`) in `<untrusted source="<key>">…</untrusted>` markers. Pairs with the server-instructions directive that tells the agent to treat envelope content as inert data.
Field-name match is intentionally narrow — wrapping every `name` field would also catch server-controlled labels (asset names, network labels, account names that are technically operator-set but high-trust within the workspace). Better to under-wrap than to flood the agent with markers.
In-place mutation; safe to call on `any` (returns input on non-map roots).
func WrapUntrustedFieldsWithKeys ¶
WrapUntrustedFieldsWithKeys is the spec-driven variant: it wraps the global always-wrap set plus every string under a key in `keys` (the endpoint's `format:"external-text"` set from the datamodel), anywhere in the tree. The structural DTO-shape heuristics are dropped — the spec set supersedes them.
Types ¶
type APIError ¶
APIError is the toolkit's own structured Bron API error. Consumers adapt their transport error into it at the boundary (bron-cli maps sdk/http's APIError; desktop builds it from its parsed response) so the lib never has to import sdk/http — which would pull sdk/auth + JWT into every consumer.
type Doer ¶
type Doer interface {
Do(ctx context.Context, method, path string, pathParams map[string]string, body, query, result any) error
}
Doer executes one Bron API request. Both the CLI's HTTP client (process-wide workspace + API key) and Desktop's per-grant signer satisfy it, so the spec-driven registration below is shared: the caller supplies how a request is authenticated and dispatched, the registrar owns which tools exist and how their arguments map onto a request.
type EmbedAugmentor ¶
type EmbedAugmentor struct {
Description string
Apply func(ctx context.Context, doer Doer, result any, tokens []string) error
}
EmbedAugmentor encapsulates a client-side join: when the agent passes an `embed` token on a registered (resource, verb), Apply mutates the result in place to attach the resolved/calculated extras under `_embedded`.
type Options ¶
type Options struct {
ReadOnly bool
Allow func(toolName string) bool
WorkspaceParam bool
EmbedAugmentors map[string]*EmbedAugmentor
PreCallValidators map[string]func(in map[string]any) error
}
Options tunes spec-driven registration for one consumer.
- ReadOnly skips state-changing endpoints (keeps GET + `tx.dry-run`).
- Allow gates each candidate tool by name; nil means "register everything".
- WorkspaceParam adds a required `workspaceId` selector to every tool and routes it into the request path — Desktop resolves the signing grant from it. The CLI leaves it off (its client injects a process-wide workspace).
- EmbedAugmentors / PreCallValidators attach per-tool client-side behaviour.