Skip to content

Configuration reference (knosh.json)

Knosh reads its global configuration from ~/.config/knosh/knosh.json and, optionally, a project-local configuration from .knosh.json in the current directory. When both files exist, their contents are merged: local values take precedence over global values. If a key is missing from both files, the default is used.

Run knosh init to create a file pre-populated with all defaults.

Options

agentsDir

Type: string
Default: ~/.config/knosh/agents

Path to the directory containing agent Markdown files. Knosh looks for <agentsDir>/<agentId>.md when an agent is referenced by ID.

~ and $HOME are expanded to the user's home directory.


commandsDir

Type: string
Default: ~/.config/knosh/commands

Path to the directory containing file-defined command Markdown files. Knosh loads every .md file in this directory at startup and registers each one as a subcommand.

~ and $HOME are expanded to the user's home directory.


defaultAgentId

Type: string or null
Default: none

Agent ID to use when a command does not specify one explicitly. When set, --agentId becomes optional for prompt, kdocs, and any file-defined command that does not declare agent: in its frontmatter. The explicit --agentId option always takes precedence when supplied.

The value must match an agent filename (without the .md extension) in the agents directory. Knosh validates that the agent exists at startup and exits with an error if the file is missing.


anthropicApiKey

Type: string or null
Default: none

API key for the Anthropic provider. Required to use models with model: "anthropic/..." in an agent configuration. If this is unset, Knosh falls back to the ANTHROPIC_API_KEY environment variable.


mistralApiKey

Type: string or null
Default: none

API key for the Mistral provider. Required to use models with model: "mistral/..." in an agent configuration. If this is unset, Knosh falls back to the MISTRAL_API_KEY environment variable.

Note

Mistral support is currently in beta.


ollamaURL

Type: string
Default: http://localhost:11434

Base URL of the Ollama server. Required to use models with model: "ollama/..." in an agent configuration. The default points to a locally running Ollama instance.


openAiApiKey

Type: string or null
Default: none

API key for the OpenAI provider. Required to use models with model: "openai/..." in an agent configuration. If this is unset, Knosh falls back to the OPENAI_API_KEY environment variable.


toolLimits

Type: object
Default: all sub-fields at their defaults (shown below)

Upper bounds on the size of tool responses. Each sub-field is an integer that must be greater than zero; a non-positive value makes Knosh exit at startup with a configuration error. Each field name carries its unit of measure.

Field Unit Default Controls
globMaxFiles files 100 Maximum files the glob tool returns before truncating
grepMaxMatches matches 100 Maximum matches the grep tool returns before truncating
grepMaxLineLength characters 100000 Maximum characters of a line the grep tool feeds to the regex matcher; longer lines are matched only within this prefix
grepTimeoutMillis milliseconds 5000 Wall-clock time budget for a single grep search; the search stops early and returns partial results with a note once exceeded
textReadMaxBytes bytes 51200 Maximum output bytes the text-read tool emits (50 KB)
webFetchMaxBytes bytes 131072 Maximum bytes of content the web-fetch tool returns (128 KB, ≈32K tokens)

A project-local toolLimits object merges with the global one field by field: a field the local file names overrides the global value, and a field it omits keeps the global value. Within a single toolLimits object, a field omitted from both files falls back to its default.

"toolLimits": {
  "globMaxFiles": 100,
  "grepMaxMatches": 100,
  "grepMaxLineLength": 100000,
  "grepTimeoutMillis": 5000,
  "textReadMaxBytes": 51200,
  "webFetchMaxBytes": 131072
}

webFetch

Type: object
Default: {"allowedHosts": [], "allowInternalHosts": false}

SSRF egress policy for the web-fetch tool: an optional host allowlist and a toggle for blocking requests that resolve to internal network addresses.

Field Type Default Controls
allowedHosts array of strings [] The hosts web-fetch may reach, matched case-insensitively against the request URL's host. An empty list (the default) allows any host, subject to allowInternalHosts. A non-empty list refuses any host not present in it
allowInternalHosts boolean false When false, a request that resolves to a loopback, link-local (including the 169.254.169.254 cloud-metadata address), site-local, any-local, multicast, or IPv6 unique-local (fc00::/7) address is refused. Set to true to allow an agent to deliberately reach an internal resource — a local dev server, an intranet wiki; allowedHosts, if non-empty, still applies

Every requested URL is checked against this policy, and so is every redirect hop the tool follows (up to an internal cap of 5), so a clean allowlisted URL cannot 302 into an internal address.

A project-local webFetch object merges with the global one field by field: a field the local file names overrides the global value, and a field it omits keeps the global value. Within a single webFetch object, a field omitted from both files falls back to its default. allowedHosts is a list, so a local file that names it replaces the list whole rather than concatenating it with the global list — a security allowlist should never silently accumulate across files.

"webFetch": {
  "allowedHosts": ["example.com", "api.example.com"],
  "allowInternalHosts": false
}

Note

The allowlist is host-based, not domain-name based — subdomains are not implicitly included. The internal-address block is IP-range based (name-based blocklists are trivially bypassed by IP literals). See web-fetch's Security / SSRF section for the full list of blocked ranges and a known limitation around DNS rebinding.

mcp

Type: object
Default: {} (no servers configured)

Declares MCP (Model Context Protocol) servers, modeled on OpenCode's local and remote server schemas. Each key is an in-Knosh server identifier; each value is that server's configuration, distinguished by type. Use add-mcp to add a server without hand-editing this file, list-mcp to inspect the servers a config resolves to, and mcp-info to actually connect to one server and confirm it works.

Note

Enabled servers are connected for the duration of a prompt, kdocs, or file-defined command run, then torn down. A "local" server is spawned as a child process and connected over stdio; a "remote" server is connected over MCP Streamable HTTP — the only remote transport Knosh supports (the deprecated HTTP+SSE transport is not implemented, and there is no transport: field to select it). Each server's tools are discovered during that startup window and offered to the agent alongside Knosh's built-in tools.

Each MCP tool is offered to the agent under a namespaced name, <serverHandle>_<toolName>, where <serverHandle> is the key under mcp in the table above. For example, a server configured as "ksrc-mcp": {"command": ["ksrc", "mcp"]} whose own tool list includes search is offered to the agent as ksrc-mcp_search. This prefix keeps MCP tools from ever colliding with a Knosh built-in — if a name collision does happen (e.g. two servers exposing the same unprefixed name), the later tool is dropped and a warning is printed to stderr; a Knosh built-in always wins over an MCP tool of the same name.

Fields shared by both types:

Field Type Default Controls
type string "local" The server type: "local" or "remote". Any other value is a configuration error
enabled boolean true Whether the server is active. Disabled servers still appear in list-mcp, annotated (disabled)
timeout integer (seconds) Bounds the server's startup — spawn/connect plus MCP handshake plus tool discovery, for both types. Defaults to 5 seconds when unset. A server that exceeds it is torn down and then follows the required policy
required boolean false When false, a server that fails to spawn/connect, fails the handshake, or exceeds timeout is skipped with a stderr warning and the run continues. When true, that failure aborts the run

Fields for type: "local":

Field Type Default Controls
command array of strings The server's argv, e.g. ["bun", "x", "my-mcp"]. Set either command or commandLine, not both
commandLine string A single-string alternative to command, split into argv using POSIX-style quote-aware tokenization (quoted substrings and backslash-escaped characters are honored, e.g. "bun x my-mcp \"/a b/c\""). Set either command or commandLine, not both
environment object of strings {} Environment variables to pass to the server process

A local server entry must set exactly one of command/commandLine, and the resulting argv must be non-empty; violating either is a configuration error at startup. A local entry must not set url or headers.

Fields for type: "remote":

Field Type Default Controls
url string The remote server's MCP Streamable HTTP endpoint. Required and must be non-blank
headers object of strings {} HTTP headers sent with every request to the server, e.g. for bearer-token authentication

A remote server entry must set url and must not set command, commandLine, or environment.

A header value exactly equal to {env:NAME} (the whole value, no surrounding text) is resolved from the NAME environment variable at connect time rather than sent literally — the way to put a secret like a bearer token into a header without writing it into knosh.json. Any other value, including one that merely contains {env:...} as a substring, is sent verbatim. NAME must be non-empty ({env:} is not a match). If NAME is set but empty, that resolves to an empty string; if NAME is unset entirely, resolution fails and the server is handled by the required policy above, same as any other startup failure. This interpolation applies to headers values only — not to url, and not to a local server's command, commandLine, or environment.

There is no OAuth support: a remote server that requires an OAuth flow simply fails to connect and is handled by the required policy.

Header names and values are never printed by list-mcp — only a count, the same treatment environment already gets for local servers.

A project-local mcp object merges with the global one per server handle: a handle declared in only one file survives, and a handle declared in both is resolved per-server, not field-by-field. If the local entry for that handle is a full server definition, it wholly supersedes the global entry — merging a local-type definition with a remote-type one field-by-field could produce an invalid server that sets both command and url. If the local entry names only enabled (e.g. {"enabled": false}), it instead overlays the global entry, flipping just that flag and keeping the rest of the global definition — the supported way to disable or re-enable a globally-declared server from a project file without restating its command or url. An enabled-only entry that has no counterpart to overlay in the other file (most often a typo'd handle) is a configuration error at startup.

"mcp": {
  "my-server": {
    "command": ["bun", "x", "my-mcp"],
    "environment": {"API_KEY": "..."},
    "timeout": 30,
    "required": true
  },
  "another-server": {
    "commandLine": "node ./server.js",
    "enabled": false
  },
  "remote-server": {
    "type": "remote",
    "url": "https://mcp.example.com/mcp",
    "headers": {"Authorization": "{env:REMOTE_MCP_TOKEN}"},
    "timeout": 10
  }
}

Config file merging

When both a global and a local config exist, their JSON keys are merged before parsing. Most keys merge at the top level: the local file's value wins outright on any key that appears in both. This lets you set API keys globally and override individual settings (such as agentsDir) per project.

mcp, toolLimits, and webFetch merge one level deeper instead of being replaced wholesale — see each key's own section above for its merge rule. A non-object value for one of these three keys (e.g. "mcp": 5) falls back to the top-level rule: the local value simply wins outright, and decoding fails exactly as it would today.

File permission advisory

A knosh.json that anyone on the system can modify undermines the trust that other settings — such as the webFetch SSRF allowlist — are meant to provide: a local attacker who can write the file can simply rewrite it. At startup, Knosh checks whether the global config file (~/.config/knosh/knosh.json), its parent directory (~/.config/knosh/), the local config file (.knosh.json), and its parent directory (the current working directory) are writable by other users on the system, and prints a Warning: … line to stderr for each one that is.

This check is advisory only: Knosh does not refuse to start, and it does not change or repair the file's permissions. It only flags the others-writable (world-writable) bit — group-writable files are not flagged, since per-user private groups make that bit common and not necessarily a security issue. The check is POSIX-only; on a filesystem with no POSIX permission view (such as Windows), it is a silent no-op.

Note

The permission check and the actual config read happen as separate filesystem operations, so a race is possible between the two (check-then-use, or TOCTOU). The warning is a best-effort signal of an existing exposure, not a guarantee that the file read afterward is untampered — the same caveat noted for DNS rebinding in the web-fetch SSRF guard.

Example

{
  "agentsDir": "~/.config/knosh/agents",
  "anthropicApiKey": "sk-ant-...",
  "defaultAgentId": "my-default-agent",
  "ollamaURL": "http://localhost:11434",
  "toolLimits": {
    "webFetchMaxBytes": 131072
  }
}