跳转到内容
Crystal CodeDocs
简简体中文

External tools

此内容尚不支持你的语言。

Operators add model-callable tools as tool sets. A set is one directory, one tools.json, and one runner (one executable prefix, or one assembly and one load context). Each set contributes one or more Crystal tool values. Dotnet sets may contribute native ITool, IMultimodalTool, or combined implementations.

The host owns catalog registration, workspace fencing of model-supplied paths, approval classification, output truncation, and timeouts. Operator code never bypasses ToolInvocationPolicy.

Invocation channels:

  • exec + stdin JSON: the fenced argument object for that call is written to the child stdin, then stdin is closed.
  • exec + argv: listed scalar properties become extra process arguments. Operator-authored command arrays are never interpolated.
  • exec content: when a tool sets "output": "content", stdout is one JSON object (text, optional images) instead of plain text. Logs stay on stderr.
  • dotnet: a class library whose public Crystal.Tools.ITool and Crystal.Multimodal.Tools.IMultimodalTool types are loaded in one isolated AssemblyLoadContext. Optional CrystalCode.Tools.IHostTool and IHostMultimodalTool implementations receive host facts on each call.

Exec may use stdin, argv, or both. Dotnet does not use stdin or argv.

Mechanism Role
IPlugin / PluginRegistry First-party in-process contributions (built-in tools and the six wire protocols).
~/.crystal/plugins/ Operator plugins. They use CrystalCode.Plugins, not this manifest. See plugins.md.
Skills Markdown instructions loaded through the skill tool. They do not become extra ITool entries.
MCP A separate, deferred integration; it is not an external-tool runner.

First-party code contributions stay on PluginRegistry. Operator tools are discovered as tool sets. Dotnet runners load class libraries from the set directory only, with the dependency rules below. That is not a general plugin loader.

The tool set has no JSON identity field. There is no root name and no id. The set is its directory under tools/.

Overlay key: the directory name. <workspace>/.crystal/tools/Acme.Tools/ replaces ~/.crystal/tools/Acme.Tools/ as a whole set. Lists are not merged. Directory names may include uppercase. Pattern: 1–64 characters, start with a letter, then A–Z a–z 0–9 _ . -. No space, no /. On Windows the filesystem is case-insensitive; two folders that only differ by case are the same set.

Root name is not a field of this format. Like any other unrecognized root key, it is ignored.

Each tool has a model-facing name: exec tools[].name, or dotnet ITool.Definition.Name. That is what Crystal and the provider send. It is not <directory>/<tool>. A slash is not a legal OpenAI-compatible function name. The host does not compose a qualified id. Tool names match ^[A-Za-z][A-Za-z0-9_-]*$ (1–64 characters) and must not be a built-in name. --disable-tool can omit a built-in for one process. The name stays reserved, so a plugin or external tool still cannot take it.

One-tool exec shorthand (no tools array): the tool name defaults to the directory name. A different model-facing name requires a one-element tools array. Shorthand therefore requires a directory name that is also a legal tool name (no . in the directory).

Tool names must be unique in the process catalog. Built-ins (read, list, tree, glob, grep, search, todowrite, todoread, question, edit, write, bash, skill) win. An exec set whose tool name is reserved is skipped. A dotnet type whose Definition.Name is reserved is omitted. Two sets that both contribute deploy collide the same way: the later name is omitted with an English note. If authors want a prefix, they put it in the tool name (Acme_deploy).

Crystal-owned paths only. OpenCode and Claude trees are not scanned for tools.

~/.crystal/tools/<directory>/tools.json
<workspace>/.crystal/tools/<directory>/tools.json

Every immediate subdirectory that contains tools.json is a candidate set. The session enters the terminal frame first, shows Loading Tools on the progress row, then scans and loads. /cd reloads the same way from the new workspace.

Runtime data is separate from the install tree:

~/.crystal/data/tools/<directory>/ global data
<workspace>/.crystal/data/tools/<directory>/ project data

The host resolves both and creates them lazily on the first call.

config.json field externalTools enables the feature (default true), matching skills. When true, the field is omitted from the written file. When false, manifests are not scanned.

tools.json field enabled turns one set on or off (default true). "enabled": false omits that set after overlay, with no operator note. A project set can disable a home set of the same directory name this way. The manifest is still parsed; an invalid disabled file is skipped with a note.

/tools enable|disable|show <directory> changes or shows one set. Prefix home or project to choose that tree. Without a prefix, a project directory wins over Home. A change reloads the catalog. It does not create a directory, and a broken manifest is left unchanged. /tools on|off|reload remains the discovery switch for every set.

crystal tools list, show, enable, and disable do the same outside a session. --source home|project chooses the tree. The default output is a Spectre.Console table. --format text prints the same fields as aligned plain text. These commands write enabled in tools.json and do not write config.json. Turning one set on does not turn discovery on.

A missing, unreadable, or invalid manifest skips that set and records an English operator note. The session still starts. A set that loads with several tools and fails one name omits only that name when the failure is per-tool (reserved or duplicate name). Failures that are per-set (missing DLL, invalid runner, duplicate names inside one exec set) omit the whole set. When a project directory has a tools.json, it hides the same-named Home set even if its manifest is unreadable or invalid.

Tool set (directory + tools.json root) Each contributed tool
Identity Directory name Model-facing name / Definition.Name
Enabled enabled on the set (default true) Same runner; no per-tool switch
Runner One exec or dotnet Same runner
Exec command Shared argv prefix Optional extra argv suffix (subcommand)
Dotnet One assembly, one ALC One public ITool and/or IMultimodalTool type
catalogs Default for every tool Override
pathArguments / argv / schema Defaults when there is a single exec tool Required per exec tool when several exist

The product has two tool catalogs, Plan and Work. Approval modes (Default, Edit, Review, Audit, Full) are not catalogs. Do not put review, audit, default, or full in this field.

Field name is catalogs, not modes.

"catalogs": ["plan", "work"]

The array is a set. ["plan", "work"] and ["work", "plan"] both mean the tool is registered in Plan and in Work. ["plan"] is Plan only. ["work"] is Work only.

Closed members: plan, work. Duplicates are ignored. Unknown values refuse the set (or that tool override). An empty array refuses: a tool that is in neither catalog must not be listed.

Default when catalogs is omitted: ["plan", "work"]. Installing a set opts it into the whole product unless the author narrows it.

A set-level catalogs is the default for every tool in the set. A tool-level catalogs replaces that default; it does not merge.

Built-in Plan is still without edit, write, and bash. External tools listed for Plan are extra entries. They keep the Write + Workspace floor, so Plan is no longer built-in reads only once such a tool is installed. Approval still runs. Plan + Full can auto-pass a workspace-bounded write from an external tool. Authors who want Work-only set "catalogs": ["work"].

First-party tools declare HostToolCatalogs on IToolContribution. Disk plugins declare PluginToolCatalogs. Each is Plan, Work, or both. External sets do not go through PluginRegistry.

tools.json is JSON. Unknown fields are ignored, including a root name. Required at the root: runner.

Single exec tool (shorthand — one contributed tool, model-facing name defaults to the directory name):

{
"runner": "exec",
"description": "Ship the current workspace to a named environment.",
"schema": {
"type": "object",
"properties": {
"environment": { "type": "string" },
"path": { "type": "string" }
},
"required": ["environment"]
},
"command": ["deploy", "release"],
"stdin": true,
"argv": {
"environment": "--env",
"path": "--path"
},
"pathArguments": ["path"],
"timeoutSeconds": 120
}

Exec set with several tools (shared binary, subcommand dispatch):

{
"runner": "exec",
"command": ["acme"],
"stdin": true,
"timeoutSeconds": 120,
"tools": [
{
"name": "acme_deploy",
"description": "Ship the current workspace to a named environment.",
"schema": { "type": "object", "properties": { "environment": { "type": "string" } }, "required": ["environment"] },
"command": ["deploy"],
"argv": { "environment": "--env" }
},
{
"name": "acme_inventory",
"description": "List inventory for this workspace.",
"schema": { "type": "object", "properties": {} },
"command": ["inventory"]
}
]
}

Dotnet set (all public ITool types; optional per-tool-name overlay). Tool names come from Definition.Name:

{
"runner": "dotnet",
"assembly": "Acme.Tools.dll",
"tools": {
"acme_inventory": {
"catalogs": ["plan"],
"pathArguments": ["path"]
}
}
}
Field Layer Meaning
name Tool only Model-facing tool name. Exec tools[] required. Shorthand defaults to the directory name. Dotnet from Definition.Name. A root name is unrecognized and ignored.
runner Set exec or dotnet.
enabled Set Boolean. Default true. false omits the whole set after overlay.
approval Set default, tool override inherit or always. Default inherit.
command Set and/or tool Exec argv. Set prefix then tool suffix. No substitution.
stdin Set Exec only. Boolean, default true. Applies to every exec tool in the set.
argv Tool (or shorthand root) Map of schema property name to one flag string.
pathArguments Tool (or shorthand root) Property names treated as filesystem paths.
output Exec tool (or shorthand root) text (default) or content. content reads a JSON object from stdout. Dotnet rejects this field.
timeoutSeconds Set Positive integer up to 4,294,967, or "unlimited"; default 120. Applies to exec and dotnet.
catalogs Set default, tool override plan and/or work. Default ["plan", "work"]. Both members means both catalogs.
description / schema Each exec tool Required for exec. Dotnet takes these from the native tool Definition. Overlay does not replace them.
assembly Set Dotnet only. File name relative to the set directory.
types Set Dotnet only. Optional allowlist of type names. Default: every public ITool or IMultimodalTool.
tools Set Exec: array of tool objects. Dotnet: optional map keyed by Definition.Name for overlays (catalogs, pathArguments).

Stdin is always the arguments object for the one call. There is no JSON envelope ({ "tool": "...", "arguments": { } }). Which exec tool ran is expressed by argv (shared prefix + per-tool suffix).

Shorthand vs tools: an exec set may use the shorthand root fields or a tools array, not both. Mixing refuses the set. A tools array with one element is valid and is not shorthand.

An author may declare that ordinary calls should not ask or enter Review:

{
"runner": "exec",
"approval": "always",
"command": ["acme"],
"tools": [
{
"name": "acme_inventory",
"description": "List inventory.",
"schema": { "type": "object", "properties": {} }
},
{
"name": "acme_deploy",
"description": "Deploy.",
"schema": { "type": "object", "properties": {} },
"approval": "inherit"
}
]
}

inherit uses the host approval mode and remembered call grants. always skips the operator prompt and Review when the source policy follows the author. The tool-level value replaces the set default. Updating a tool does not revoke or re-confirm the declaration.

The operator configures each source in ~/.crystal/config.json:

{
"externalToolApproval": {
"home": "author",
"project": "host"
}
}

author follows tools.json; host treats every declaration as inherit. Defaults are Home author and Project host. /tools home author|host and /tools project author|host change these values. /tools lists the declared and effective value for every loaded external tool; /tools reload rescans, and /tools on|off controls discovery.

A process or loaded assembly is at least Write + Workspace. Path properties in pathArguments are resolved with the same fence as read / write. Credential paths become Forbidden. Paths outside the workspace become OutsideWorkspace. The rewritten absolute path is what the child or assembly sees.

Host classification looks up the external tool by its model-facing name. It does not fall through to “Unknown tool” after a successful load.

Full auto-passes workspace-bounded Write for any such tool, not only built-in write / edit. Edit and Review still auto-pass only those built-in names. Default asks the operator for external Write. Review and Audit send it to the reviewing model. A tool-provided pre-classifier can only raise risk or authority for its own calls and fails closed; it never removes the floor or the pathArguments checks. Session and Persistent grants apply only to ordinary Workspace Read/Write calls without RequirePrompt. A raised classification goes through approval again, even when a grant for that tool already exists. Restricted calls allow Once only. RequirePrompt prevents policy, grant, and reviewer Allow from bypassing the operator; reviewer Deny still rejects. Headless runs deny operator prompts.

Secrets never appear in tools.json. The host does not inject credentials into the child environment or into a loaded assembly. Exec children inherit the host process environment. Dotnet assemblies must not receive secrets via Environment.SetEnvironmentVariable (process-wide). Do not log argument JSON that may contain secrets. Stdout and stderr are drained concurrently while only a bounded prefix is retained. The combined result is truncated to MaximumToolOutputCharacters, the same limit as bash.

Each set’s global and project runtime-data directories are ordinary writable directories. They are not a secret store: do not write credentials there. The project directory is inside the workspace, so anything written there is workspace content.

The text WorkspaceCatalog is: built-in plugin tools, then disk plugin tools, then every contributed external ITool whose catalogs contains Plan or Work respectively, then skill when enabled. For an image-capable turn, the multimodal executor adds built-in, disk, and external IMultimodalTool definitions in that order. External tools are not registered through PluginRegistry.Add. A disk plugin tool name is reserved before external registration. The external classifier is appended after disk classifiers.

  • MCP, HTTP/OpenAPI runners, argv string templates, stdin dispatch envelopes.
  • Qualified model-facing names (Set/tool or automatic Set_tool prefixes).
  • Collectible unload, hot reload, signing, marketplace.
  • Host-injected secrets into child processes or in-process assemblies.