MCP tool servers
Declare a server as
[tool_servers.<name>] in config.toml. Give it exactly one transport. command spawns a process and speaks stdio. url speaks streamable HTTP over https, or over http on loopback. A declaration with both, or with neither, refuses to load.
dir, headers, oauth, timeout_ms and enabled. Every declared server connects at startup unless you set enabled = false.
Narya keeps one long-lived session per server and reconnects with backoff. When a server disconnects, Narya withdraws its tools from the registry, so the model never sees a departed server’s tools.
Set oauth = true for a remote server that signs in. Narya holds the connection open, prints the authorization URL, and listens on 127.0.0.1:7666 for the callback while the sign-in is open. A sign-in nobody answers costs that one server and nothing else. A one-shot run cannot complete a sign-in, and it says so.
OpenAPI tool trees
Declare
[tool_trees.<name>] to turn an OpenAPI document into a callable tool tree. The keys are spec, base_url, headers, timeout_ms and enabled. A relative spec path resolves against your Narya home. Set base_url when the document declares no server of its own. Narya derives the tools once at startup. No server runs, and no session reconnects.
Confined scripts
Narya runs ECMAScript in a confined engine. The engine reaches no filesystem, no network, no environment and no module loader. It has no dynamic evaluation. Call depth stops at 4,096, and source stops at 256 KiB. Three callers use it: the model’s
run_program tool, workflows, and tool discovery.
The host API
The host serves one HTTP API over a Unix socket, and every client drives it. The host API describes the transport, the event stream and the envelopes.
Packages
narya ext add <source> installs a package from a local path or a Go module reference. narya ext list, remove, enable and disable manage what you installed.
A package that declares only resources adds agents, skills, commands and hooks, and installs by copy. A package with Go code compiles into your Narya binary, so it needs a local Go toolchain. Narya asks you to trust a package that carries code or hooks before it installs it. Add --yes to skip that question in a script.
Registered extensions
To add an extension that runs as its own HTTPS service, register it with
POST /v1/extensions. That needs the extensions:manage permission. The body needs two fields: name, in the form publisher/extension, and endpoint. The other fields are optional: description, points, slots, position, grants, models and enabled.
The endpoint must be an https:// URL, and this applies to loopback addresses too. The host resolves the address before it stores the registration. It refuses a loopback or private address unless allow_private_endpoints in the [extensions] table of config.toml admits that range. No setting admits any other address outside the public internet, such as a link-local or multicast address.
An exclusive slot holds one extension. A registration that claims an exclusive slot that another extension holds gets 409 with NRY-0006. To admit both, name the winner of that slot under prefer in the [extensions] table.
The response carries the extension’s secret, once. No read returns it again. To replace the secret, call POST /v1/extensions/{publisher}/{name}/secret. The response carries the new secret and the time when the old one stops working. The host signs each call to the extension under the secret. The extension signs its own calls the same way, and it can call only the event stream.
The extension serves its own operations at its endpoint. The host calls /transform at the decision points that the extension sits on. It calls /models and /supply when the extension supplies models, /tools when it publishes tools, and /invoke to run a named operation. The host does not follow a redirect from the extension.
A client reaches an extension through the host with POST /v1/extensions/{name}/invoke. The token of an ordinary member is enough. The host signs the forwarded call, so the client does not need the secret.
Agents, skills, commands and hooks
Narya reads Claude Code’s own formats without any change. Agents are markdown files with YAML frontmatter, and the body is the prompt. The keys are
name, description, tools and model, and a file with no name takes its file name. An agent in your Narya home or in .narya/agents also reads mode, hidden and permission-policy. tools is an allowlist and never a grant: Narya intersects a child’s list with its parent’s, so a child agent can lose tools, never gain them.
Narya reads agents from your Narya home, then ~/.claude/agents, then the .claude/agents and .narya/agents of a repository you trust. Four agents ship with the binary: explore, general, plan and review.
Skills follow the Agent Skills standard: a directory that holds a SKILL.md. Narya reads the same ladder, ~/.claude/skills included, and re-reads it on each request. A skill you add mid-session reaches the next turn.
Commands come from your Narya home, from ~/.claude/commands, and from the .claude/commands and .narya/commands of a repository you trust.
Hooks come from the hooks table of ~/.claude/settings.json, and from the same file in a repository you trust. Narya reads them with the stdin keys and exit codes those scripts already use. Four events map: PreToolUse, PostToolUse, UserPromptSubmit and SessionStart. Other events, such as Stop, SubagentStop, Notification, PreCompact and SessionEnd, have no equivalent. Narya skips them and says so in its log.
Narya’s own hooks
Declare
[[hooks]] in config.toml with point, command, timeout_ms, fail and match. fail carries no default, so state it.
match filters tool names at before-tool-call and after-tool-call. Use a tool name or a Go regular expression that matches the whole name, such as read|write. An empty value or * matches every tool. Other hook points do not accept a nonempty match.
Four points decide, and a hook there can stop what it fronts:
before-tool-call, before-model-request, after-tool-call, at-permission-ask.
Eight points observe, and a hook there learns what happened without changing it:
session-started, lane-started, lane-finished, delegation-created, child-asked, skill-invoked, permission-denied, session-compacted.
A repository’s own file cannot declare at-permission-ask.
Delegation
Delegation is a tool call. Set its bounds under
[delegation] in config.toml. The max_depth key counts the levels below your conversation, and 0 turns delegation off. Set max_concurrent to bound how many lanes one conversation runs at any instant.
