Tools and MCP
Tools and MCP
Tools are how the agent acts on the world. Holaryn ships a small set of native tools, consumes any number of external tools over the Model Context Protocol (MCP), and can generate images, run tool pipelines programmatically, drive a real browser, and supervise selected Windows applications. Every tool carries a declared consequence, so everything here is governed by the autonomy and approval model.
With dynamic tool discovery enabled (an opt-in Nightly
rollout setting), these
tools remain registered but the model initially sees only the bounded
tool_search, tool_list, and tool_inspect schemas. Exact action schemas are
loaded after selection; discovery never grants permission.
Native tools
Four native tools are registered in a non-coding session:
| Tool | What it does | Consequence |
|---|---|---|
read_file |
Read a UTF-8 text file | reversible, fs.read |
list_dir |
List files and directories under a path | reversible, fs.read |
write_file |
Write text content to a file | irreversible, fs.write |
run_shell |
Run an argv-based command (never a shell string) | irreversible, shell |
The filesystem tools are confined by a workspace sandbox: a path allowlist (HOLARYN_WORKSPACE_ROOTS, defaulting to the working directory) that paths are fully resolved against before any access — symlinks and Windows junctions pointing outside a root are rejected, and a path is compared in one spelling (8.3 short names expanded, the \\?\ extended-length spelling of a workspace path accepted, Windows paths without regard to case). A tool always opens the path it checked: an extended-length path is opened without its prefix only when that ordinary spelling names the same file, because Windows reads some ordinary spellings differently (alias. as alias). In a coding session the search tools follow the same rule for every result: glob_files and grep_files (with ripgrep and without) never enter a linked directory or junction, and they list or read a file only when its resolved target lies inside a root, so a link to a file elsewhere is left out. grep_files runs ripgrep without its configuration file (RIPGREP_CONFIG_PATH), so a configured preprocessor or link-following option never applies. A glob_files pattern must be relative and may not contain .. (on Windows, nor a name made only of dots and spaces); choose the directory to search with its path argument. A pattern ending in / matches folders only, so it lists no files. Arming Unrestricted lifts this local workspace restriction for the attended session; switching back restores it. HOLARYN_FS_UNRESTRICTED remains an explicit configuration option. Inherited workspace limits and selected execution profiles still apply. shell is part of the allow-all residual, so shell commands ask first under every posture short of unrestricted.
The file tools never write git metadata: a .git directory or file at any depth (in any spelling the file system treats as .git), a git directory wherever it is (a separate git directory, a bare repository, a worktree's or submodule's git directory), or the directory a workspace's .git file points to; nor can writes assemble a new git directory. Git runs programs that its configuration and hooks name, so a write there is refused with an explanation even where other writes need no approval. write_file replaces a file rather than writing into it, so a hard link in the workspace is replaced, not written through; the new file keeps the old one's permissions (on Windows, its access list and hidden or system attributes). A read-only file, a file whose permissions deny writing, a file another program holds open, a folder that does not allow creating files, or permissions or attributes that cannot be kept end the write with an explanation and leave the file unchanged. Reading those files still works; change git settings with a git command you run or approve. Holaryn's own git calls (checkpoints, status, worktree sessions) run with the repository's hooks, filters, fsmonitor and other configured programs switched off; see Programs a repository configures for git.
MCP: connecting external tools
Holaryn is an MCP client aligned with the MCP 2025-11-25 lifecycle. Point it at MCP servers and approved tools, prompts, resources, and resource templates join the session. Local stdio and secure Streamable HTTP are the primary transports; legacy SSE remains available for older servers.
Stdio trust boundary: adding or probing a stdio server starts that executable as your local
user. It can access the same files and network destinations as your account; MCP approval gates
constrain tool calls but do not sandbox the server process itself. Install and run only reviewed
commands. Prefer a container or remote HTTPS MCP service when process isolation is required.Program lookup: the MCP SDK finds a stdio server's command the way your shell would, on the
PATHof the server's environment. That lookup does not follow the rules Holaryn applies to the
helper programs it starts itself: aPATHdirectory inside a project folder is not skipped, and
a Windows.cmdor.batlauncher's arguments are not checked for characterscmd.exewould
re-parse. Configure an absolute path to the server's executable (for example
--command C:\Tools\uv\uvx.exeor--command /usr/local/bin/uvx), or keep project folders
off thePATHthe server sees; with a.cmdlauncher such asnpx.cmd, keep its arguments
free of& | < > ^ % " ( ) !. On Windows the server's environment sets
NoDefaultCurrentDirectoryInExePath=1, which turns off only the implicit search of the
current directory: a program the server itself starts by name (thenodeannpx.cmdshim
runs) is no longer looked up in its working directory beforePATH. APATHentry that names
that directory (its full path,.or another relative entry) is still searched. See
Helper programs Holaryn starts
for the rules and this exception.
Configure servers in Settings → MCP, or with the CLI:
holaryn mcp list
holaryn mcp inspect <server>
holaryn mcp add <server> --transport stdio --command npx --arg -y --arg some-mcp-server
holaryn mcp add <server> --transport http --url https://host/mcp --api-key-env MY_KEY_VAR
holaryn mcp add <server> --transport stdio --command server \
--approve-capability tools --approve-capability prompts --approve-capability resources
holaryn mcp remove <server>
Servers live in a hand-editable TOML manifest at ~/.holaryn/mcp.toml:
[[server]]
name = "playwright"
transport = "stdio"
trust = "untrusted"
command = "npx"
args = ["@playwright/mcp@latest"]
approved_capabilities = ["tools", "prompts"]
sampling_policy = "deny"
elicitation_policy = "deny"
Secrets never enter the manifest: bearer/OAuth access tokens and custom headers are named as environment variables (api_key_env, headers_env) and resolved at connect time. Remote URLs require HTTPS except for loopback development, credentials in URLs are rejected, and connection-control headers cannot be overridden. A URL may keep a query the server needs; Settings shows it without its query and fragment (https://host/mcp?<query not shown>), saving the server with that URL unchanged keeps the saved one, and a connection error never repeats the URL. The same holds for holaryn mcp add with an existing server's name. A URL that still holds <query not shown> or <fragment not shown> after any other change is refused, so the placeholder never replaces the real query: leave the field exactly as it was shown, or enter the whole URL. The OAuth sign-in steps, and holaryn mcp inspect-auth, show the resource and every other URL the same way; the authorization server still receives it in full. A destination on another origin is reviewed in that form too, and approved by an opaque reference rather than by its URL (see MCP authorization setup). Stdio servers receive the SDK's minimal safe environment plus only explicitly inherited variables (on Windows, also NoDefaultCurrentDirectoryInExePath=1, which turns off the implicit current-directory search but not a PATH entry naming that directory). The values of credential-named variables a server receives (names ending in KEY, TOKEN, SECRET, PASSWORD, PAT, CREDENTIALS or AUTH), and header credentials including the token inside Bearer <token> and the password inside a Basic header, are registered with the secret redactor, so a server that echoes one does not put it into chats, logs or exported traces. Every tool, prompt, and resource call has a configurable bounded deadline (call_timeout_seconds, default 60 seconds; CLI --timeout-seconds). Manifest changes apply when a new chat or scheduled runtime starts; open sessions keep their initial MCP sessions.
Capability authority
The initialize handshake records the server name/version, protocol revision, and advertised capabilities. approved_capabilities is the authority ceiling. Existing manifests approve only tools and prompts; if a server update later advertises resources or logging, Settings marks it pending approval and the runtime does not list or use it. Edit the server to approve the expansion.
Each inventory also has glob-style allow and deny lists:
approved_capabilities = ["tools", "prompts", "resources", "logging"]
allow_tools = ["search_*", "get_*"]
deny_tools = ["delete_*"]
allow_resources = ["docs://public/*"]
deny_resources = ["docs://private/*"]
subscribe_resources = true
Deny always wins. Lists are paginated with cursor-loop, page-count, and item-count limits, so a malformed server cannot grow discovery without bound.
Resources, roots, and subscriptions
Approved MCP resources appear with their original URI and a Holaryn attachment reference. Reads are provenance-labelled and tainted as external, untrusted context. Text is bounded before entering a model; binary data remains a typed attachment instead of being copied into text as base64. A session gets a reversible <server>.__read_resource tool containing only the approved resource URI inventory.
Configure absolute roots to answer a server's roots/list request. No other local paths are shared. Resource subscriptions are off by default; when enabled, update/list-change notifications appear in the event stream and diagnostics.
Sampling and elicitation
Both client capabilities default to deny.
sampling_policy = "ask"requests approval every time;allowpermits requests withinsampling_allowed_models,sampling_max_tokens, andsampling_total_tokens. Sampling receives no Holaryn conversation context and no tools. It runs through the active provider behind a serialized adapter lock and gets its own trace ID. Any registered secret the model repeats is replaced with[REDACTED SECRET], both in the text you see streamed and in the result returned to the server.elicitation_policy = "ask"asks before showing a request;allowshows it directly. Form schemas render as labelled typed controls in the web UI and as an explicit JSON form in the CLI. Answers are schema-validated, and the operator can decline or cancel. Fields that appear to request passwords, tokens, private keys, payment data, or credentials are rejected; those flows must use an explicit HTTPS URL that Holaryn opens only after consent. Open secure page sends nothing until Holaryn knows the page opened. The desktop app connected to a host on this computer confirms the open and sends your agreement in the same press. In a web browser, and in a desktop app connected to a host on another computer or over https, nothing can confirm it, so it takes a second press: the question says the page was opened (or asked for) and that nothing has been sent yet, and focus moves to Confirm the page opened, which sends your agreement once the page has opened. If the page did not open or could not be confirmed in time (a browser blocked the new tab, or the desktop app could not open it), nothing is sent, the question stays open with the reason, Copy link and Confirm the page opened, and focus stays on Open secure page. Confirm the page opened sends your agreement on your word that the page opened (or that you opened it yourself from Copy link); it opens nothing. An address Holaryn does not open, such as one with a user name and password in it, is refused, with nothing to copy: choose Decline or Cancel request.
Connection diagnostics and conformance
Settings → MCP → Test connection shows the negotiated protocol/server identity, health, advertised/approved/pending capability ledger, tools, prompts, resources/templates, redacted logs, notifications, failures, and the conformance check list. holaryn mcp inspect <server> prints the same operator-focused evidence. Probes are explicit because a stdio probe starts its process.
The conformance suite checks initialization, transport security, capability authority, paginated inventories, resource subscription round trips when approved, and sampling/elicitation policy bounds. Server failures discard the pooled connection so a later independent call can reconnect. A failed tool call is never replayed because the action might already have happened.
Interactive MCP Apps
Servers that implement the stable MCP Apps extension may attach a ui:// resource to a tool. Apps
remain undiscovered and unloaded until apps is present in approved_capabilities, the tool
finishes, and you choose Load app. The model sees only tools whose visibility includes
model; an app may call only same-server tools whose visibility includes app. All app-originated
tool calls and host actions re-enter the ordinary approval and event pipeline.
approved_capabilities = ["tools", "prompts", "resources", "apps"]
The app runs on a separate local origin around an opaque-origin inner frame. It receives no ambient
credentials, direct filesystem access, top-level navigation, or undeclared network authority.
Inspect shows identity, resource URI, requested permissions/network, pinned digest, size, and
the data exchanged. Open as structure keeps the result usable without scripts; Revoke
removes the persisted app resource. See MCP Apps for the full operator and developer
contract.
Trust and safety:
- Every server is untrusted by default. Results from untrusted servers are visibly framed as untrusted before the model sees them, and their tools resolve through the approval policy like any consequential action. Mark a server
--trusted(or trust it in Settings) once you vouch for it. - Reversibility comes from each tool's own MCP annotations, never from server trust — a trusted server can still expose genuinely irreversible tools, and an undeclared tool is treated as irreversible.
--autonomous-tool <name>(repeatable) allowlists specific tools from a server to run without asking.- Server instructions, sampling prompts, resource contents, logs, and elicitation descriptions remain data; none can silently widen tool, context, filesystem, model, or secret authority.
Browser automation
The first-party browser agent is built into the host. It needs no Node, npx,
MCP preset, or restart. Seven compact tools cover session start, navigation, semantic observation,
approval-gated interaction, bounded research, status/trace export, and pause/stop control. Page
content remains untrusted and uploads/final submissions enter the ordinary approval policy.
Native computer use
Native computer use provides five compact host-owned tools for confirmed
application launch, bounded semantic observation, one action, status/trace, and
pause/takeover/resume/stop. Windows UI Automation is the first adapter. Accessibility roles,
names, states, values, and supported actions are preferred over coordinates; the last-resort
selected-window visual fallback is disabled by default and always approval-gated. Application
content remains untrusted.
Image generation
The generate_image tool creates images from text prompts using an image-modality model from your Providers & Models registry — official OpenAI or any compatible endpoint; nothing is vendor-hardcoded. Generated images are saved to disk, and in the web UI they also appear in the chat's Artifacts panel.
Programmatic tool calling
The run_tool_script tool lets the model write a short Python script that calls the session's other tools — collapsing a multi-step pipeline (read → transform → act on each item) into a single turn:
# One turn: read a file, act on each line.
lines = call_tool("read_file", path="hosts.txt").splitlines()
for line in lines:
if line.strip():
print(call_tool("run_shell", cmd=["ping", "-n", "1", line.strip()]))
Running model-written code is consequential: run_tool_script is classified exactly like shell (ask-first under the default posture, part of the allow-all residual). Crucially, approving the script does not bypass per-call gates — every call_tool inside the script passes the same approval gate as a native call, appears in the event stream, and can prompt or be denied individually. Dry-run composes too: a script's irreversible calls stage into the same reviewable plan. Scripts run as sandboxed subprocesses with a hard timeout (default 120 s, max 600 s), talking to the agent over an authenticated loopback bridge. Not currently supported under the Docker execution backend (the container cannot reach the loopback bridge; the tool returns an actionable error).
Execution backends
Where run_shell commands actually execute. Select in Settings → Capability Center or via HOLARYN_EXECUTION_BACKEND; applies to new sessions.
| Backend | What happens | When to use |
|---|---|---|
local (default) |
Ordinary subprocesses on this machine, argv-only, hard timeout, whole-process-tree kill | Trusted personal use; fastest |
docker |
Every command runs in a fresh --rm container of the configured image (HOLARYN_DOCKER_IMAGE, default python:3-slim) with the working directory mounted at /workspace |
Sandboxing shell away from the host — scheduled runs, untrusted tasks |
The Docker backend needs a running Docker daemon. Only run_shell is containerized; the native file tools stay in the host process, confined by the filesystem sandbox. Sandboxing reduces blast radius — it does not make a command non-consequential, so the approval model is unchanged.
Related pages
- autonomy-and-approvals.md — how consequences gate every tool here
- skills.md — skills bundle instructions and scripts on top of tools
- slash-commands.md — MCP prompts surface as
/<server>:<prompt>commands - mcp-apps.md — sandboxed interactive MCP tool results
- settings.md — the MCP and Capability Center pages
- capability-center.md — effective catalog, authority reasons, and source actions