Skip to main content

ToolSpec

Struct ToolSpec 

Source
pub struct ToolSpec {
    pub name: String,
    pub description: String,
    pub schema_json: Value,
    pub title: Option<String>,
    pub needs_approval: bool,
    pub read_only: bool,
    pub destructive: bool,
    pub open_world: bool,
    pub cacheable_approval: bool,
}
Expand description

Declaration of a tool the model may invoke.

Mirrors the MCP Tool shape so built-in and connector tools are described uniformly: title is the MCP title annotation, and read_only / destructive / open_world are the readOnlyHint / destructiveHint / openWorldHint annotations. Build with ToolSpec::new + the chainable setters rather than a struct literal.

Fields§

§name: String

Unique tool name; the model references this when emitting a ToolCall.

§description: String

Human-readable description of what the tool does.

§schema_json: Value

JSON Schema object describing the tool’s argument shape.

§title: Option<String>

MCP-style human display name for this tool (the title annotation): a friendly label shown to people (e.g. in an approval prompt) while the machine-facing name stays the audit identifier.

None means no curated label was provided; callers derive a display name from name via polyc_proto::humanize_tool_name.

§needs_approval: bool

Intrinsic “this tool is side-effecting / requires human approval” flag.

When true the tool must be routed through the harness’s human-in-the-loop (HITL) approval gate before it executes, even when no operator-side allow-list names it. Pure, read-only tools leave this false.

This is the per-tool generalization of the old hard-coded approval-by-name list: it maps from the MCP destructiveHint tool annotation, so an upstream connector that advertises a destructive tool is gated per-tool rather than per-connector.

Defaults to false and is skipped when serializing the safe default, so older payloads that omit the field still deserialize as ungated.

§read_only: bool

MCP readOnlyHint: the tool does not modify its environment. Advisory — surfaced to the model and usable by callers (e.g. sandbox-mode gating never gates a read-only tool). Defaults to false.

§destructive: bool

MCP destructiveHint: the tool may perform irreversible / side-effecting changes. Drives sandbox-mode gating (destructive tools gate in read-only mode) and maps from a connector’s destructiveHint. Defaults to false.

§open_world: bool

MCP openWorldHint: the tool may interact with an open world of external entities, so its RESULT can carry content of uncontrolled provenance. This is the INBOUND (“untrusted content in context”) leg of the lethal trifecta — a tool with open_world = true seeds the leg when its result is in context (see polyc_agent’s untrusted_content_in_context). The built-in web fetchers set it; the sandbox coding tools do not. For a dialed connector it is read from openWorldHint at connect. Defaults to false.

§cacheable_approval: bool

Whether a single human approval for this tool may be remembered for the rest of a conversation session (per-caller) and reused for later calls, instead of re-prompting every time. Defaults to false.

The session grant is per-tool, not per-argument: approving one call authorizes the tool for ANY arguments for the rest of the session. So set this ONLY when the tool’s ENTIRE argument space is safe to auto-run within the sandbox boundary — i.e. it is both idempotent AND can’t reach anything the human wouldn’t have blanket-approved. file_read qualifies because it is workspace-confined (coding::workspace::resolve rejects absolute/.. paths), so “approve one read” only ever grants reads inside the sandbox. NEVER set it on a tool that spends money, has side effects, or whose risk varies by argument (e.g. it could read/write outside a confined root): those must get a fresh decision per call.

Implementations§

Source§

impl ToolSpec

Source

pub fn new( name: impl Into<String>, description: impl Into<String>, schema_json: Value, ) -> Self

A tool spec with the given name, description, and JSON-Schema schema_json; all annotations default off. Chain the setters below to add a title or mark it read-only / destructive / approval-gated.

Source

pub fn titled(self, title: impl Into<String>) -> Self

Set the MCP title display annotation.

Source

pub const fn read_only(self) -> Self

Mark the tool read-only (MCP readOnlyHint).

Source

pub const fn destructive(self) -> Self

Mark the tool destructive (MCP destructiveHint).

Source

pub const fn open_world(self) -> Self

Mark the tool open-world (MCP openWorldHint): its result can carry content of uncontrolled provenance, seeding the untrusted-content leg.

Source

pub const fn cacheable_approval(self) -> Self

Mark a single approval for this tool as rememberable for the rest of a conversation session (per-caller). Only set this on idempotent tools (see Self::cacheable_approval field docs).

Source

pub const fn approval_required(self) -> Self

Mark the tool as intrinsically requiring HITL approval (independent of sandbox mode — e.g. paid_fetch).

Trait Implementations§

Source§

impl Clone for ToolSpec

Source§

fn clone(&self) -> ToolSpec

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ToolSpec

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ToolSpec

Source§

fn default() -> ToolSpec

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for ToolSpec

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Serialize for ToolSpec

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more