Merge branch 'master' of github.com:Dark-Alex-17/coyote.wiki

2026-08-25 11:40:40 -06:00
6 changed files with 215 additions and 6 deletions
+1 -1
@@ -35,7 +35,7 @@ Coming from [AIChat](https://github.com/sigoden/aichat)? Follow the [migration g
* [Create Custom TypeScript Tools](Custom-Tools#custom-typescript-based-tools)
* [Create Custom Bash Tools](Custom-Bash-Tools)
* [Bash Prompt Utilities](Bash-Prompt-Helpers)
* [First-Class MCP Server Support](MCP-Servers): Easily connect and interact with MCP servers for advanced functionality.
* [First-Class MCP Server Support](MCP-Servers): Easily connect and interact with MCP servers for advanced functionality. Coyote supports all three MCP capabilities: tools, resources, and prompts, with capability-gated meta-tools for the model and a `.prompt` REPL command for invoking server prompts yourself.
* [Macros](Macros): Automate repetitive tasks and workflows with Coyote "scripts" (macros), and invoke them as your own custom REPL commands.
* [RAG](RAG): Retrieval-Augmented Generation for enhanced information retrieval and generation.
* [Sessions](Sessions): Manage and persist conversational contexts and settings across multiple interactions.
+141
@@ -544,6 +544,144 @@ To pick up new default servers added in a Coyote update, run `coyote --install m
in the REPL). This **merges** the bundled template into your existing configuration: only servers not already present
in your file are added; your existing servers and any custom secret references are left untouched.
# Interacting with MCP Servers
Coyote does not flood the model's context with a separate function declaration for every tool an MCP server exposes.
Instead, each enabled server contributes a small set of **meta-functions** the model uses to discover and invoke the
server's capabilities on demand:
| Meta-function | Purpose |
|-------------------------|------------------------------------------------------------------------------------------------|
| `mcp_search_<server>` | Keyword search over the server's catalog of tools, resources, resource templates, and prompts |
| `mcp_describe_<server>` | Fetch the full schema or metadata for exactly one catalog item |
| `mcp_invoke_<server>` | Call a tool on the server |
| `mcp_read_<server>` | Read a resource (or expand a resource template) from the server |
| `mcp_prompt_<server>` | Fetch a server-defined prompt, rendered with the given arguments |
This keeps the per-server context cost small and constant no matter how large the server is: the model searches for
what it needs, describes the matching item to get its exact schema, and then invokes, reads, or fetches it.
## The Unified Catalog
`mcp_search_<server>` searches a single catalog spanning everything the server advertises: tools, resources, resource
templates, and prompts. Every search result carries a `kind` field (`tool`, `resource`, `resource_template`, or
`prompt`) so the model knows whether to follow up with `mcp_invoke`, `mcp_read`, or `mcp_prompt`. Listings follow
server-side pagination, so large servers are cataloged completely.
`mcp_describe_<server>` accepts the same `kind` as an optional parameter (default: `tool`). The `tool` parameter
carries the identifier for every kind: a tool name, a resource URI, a template's URI template, or a prompt name.
* `kind: "tool"` returns the tool's full invocation schema
* `kind: "resource"` returns the resource's catalog metadata (URI, MIME type, size)
* `kind: "resource_template"` returns the template and its variables
* `kind: "prompt"` returns the prompt's name, description, and arguments
Servers can annotate resources with an intended `audience` (`user` and/or `assistant`). Per the MCP spec this is
advisory metadata, not access control, and Coyote surfaces it on both search results and read results so the model
(and you) can see who the content was meant for.
## Capability Gating
Servers advertise which capabilities they support (tools, resources, prompts) during the connection handshake, and
Coyote only emits the meta-functions that make sense for each server:
| Meta-function | Emitted when... |
|----------------|------------------------------------------------------------------------------------------------------------------------------------|
| `mcp_search` | Always (the catalog degrades per kind, so search works with whatever the server supports) |
| `mcp_describe` | Always (same reason) |
| `mcp_invoke` | The server advertises tools, **or** the handshake info is unavailable (fail-open: a handshake hiccup never strips a working server's tools) |
| `mcp_read` | The server advertises resources |
| `mcp_prompt` | The server advertises prompts |
So a resources-only server still gets `mcp_search`, `mcp_describe`, and `mcp_read`, and never wastes context on an
`mcp_invoke` or `mcp_prompt` that would always error.
## Reading Resources
`mcp_read_<server>` reads a resource by URI, or expands a resource template with variable values:
| Parameter | Required | Description |
|-------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `uri` | yes | The resource URI, or a resource template with `{var}` placeholders |
| `arguments` | no | Template variable values. Only [RFC 6570](https://www.rfc-editor.org/rfc/rfc6570) Level 1 simple substitution is supported; values are percent-encoded |
| `pattern` | no | A regex applied to text content: only matching lines are returned, grep-style, with 2 lines of context and line-number prefixes |
| `offset` | no | Byte offset for paging text (default: `0`). When `pattern` is set, offsets refer to the **filtered** stream, not the raw resource |
| `max_bytes` | no | Maximum text bytes to return (default: `51200`, clamped to `204800`) |
Text content comes back as a structured result with paging metadata:
```json
{
"uri": "file:///var/log/app.log",
"mime_type": "text/plain",
"text": "...",
"truncated": true,
"total_bytes": 1048576,
"next_offset": 51200,
"note": "Content truncated; re-call with offset=51200 to continue (max_bytes is clamped to 204800)"
}
```
The `pattern` filter is applied before slicing, so paging walks the filtered stream: `total_bytes` and `next_offset`
describe the filtered content, and for a multi-megabyte log resource, "lines matching ERROR" is one call instead of
dozens of pages. When a read returns multiple content items, the paging parameters apply per text item and the whole
response is additionally capped at 204800 bytes; items beyond the cap are replaced with a marker naming how many were
omitted.
### Binary Content
Binary content is **never** inlined into model context. Instead:
1. Coyote first attempts a UTF-8 decode of the blob. If it decodes cleanly, it is treated as text and paged inline
regardless of the server's claimed MIME type (servers mislabel text constantly).
2. Genuinely binary content is decoded (up to a 50 MiB ceiling) and written to
`<cache_dir>/mcp-resources/<server>/<sha256>.<ext>`, with `0600` permissions on POSIX systems. Writes land in a
temp file and are renamed into place, so a visible spill file is always complete, even with concurrent readers.
The read returns a self-describing metadata object instead of the bytes:
```json
{
"spilled": true,
"path": "/home/you/.cache/coyote/mcp-resources/github/3b0c44...98fb.png",
"uri": "resource://github/chart",
"mime_type": "image/png",
"sniffed": false,
"size_bytes": 204812,
"sha256": "3b0c44...98fb"
}
```
The path is actionable by you, by `execute_command`, by the `fs_*` tools, and by sub-agents, which is more useful
than base64 the model cannot act on anyway. The spill directory is bounded: when the `mcp-resources/` tree exceeds
512 MiB, the oldest files are evicted best-effort. Files are content-addressed by SHA-256, so re-reading the same
content regenerates the identical path. Spilled files are untrusted input: Coyote never executes or auto-opens them.
## Bounded Tool Results
The same content policy bounds `mcp_invoke` tool results before they reach the model:
* Text content larger than 204800 bytes is sliced at a UTF-8 boundary with a truncation note asking the model to
re-call with narrower arguments.
* Image, audio, and embedded binary content is routed through the same spill pipeline as resource reads: written to
disk, with the metadata object returned in its place.
* Oversized structured content (beyond the same 204800-byte ceiling once serialized) is replaced with a truncation
marker.
* Server-supplied metadata strings (URIs, MIME types, and similar) are clamped to 4096 bytes.
In-bounds results pass through byte-identical, so well-behaved servers are unaffected.
## Prompts
Servers that advertise the prompts capability expose named, parameterized message templates rendered server-side.
Coyote surfaces them in two places:
* **For you:** the [`.prompt` REPL command](REPL#prompt---invoke-an-mcp-server-prompt) invokes a prompt and submits
the result as your chat input, and `.list prompts` lists every prompt across enabled servers.
* **For the model:** the `mcp_prompt_<server>` meta-function takes a `prompt` name and an optional `arguments` object
(string values only; prompt arguments have no schemas per the MCP spec) and returns the rendered prompt text as the
tool result. Multi-message prompts are flattened into a single block with `[user]` / `[assistant]` labels, and
missing required arguments produce an error listing them.
# Coyote Configuration
MCP servers, like tools, can be used in a handful of contexts:
* Inside a session
@@ -553,6 +691,9 @@ MCP servers, like tools, can be used in a handful of contexts:
Each of these has a different configuration and interaction with the global configuration.
Enabling a server in any of these contexts exposes its capability-gated meta-functions to the model; see
[Interacting with MCP Servers](#interacting-with-mcp-servers) above.
***Note:** The names of each MCP server referenced in the below configuration properties directly corresponds
to the names given in the `functions/mcp.json` configuration file. So if you change the name of an MCP server
from `slack` to `lucem-slack`, then you need to also update your Coyote configuration accordingly.
+58 -3
@@ -18,6 +18,10 @@ things like
* `.set key <tab>` to complete configuration values
* `.mcp auth <tab>` to complete remote MCP server names
* `.macro <tab>` to complete macro names and the `enable`/`disable` subcommands
* `.list <tab>` to complete the listable kinds (`roles`, `sessions`, `agents`, `rags`, `macros`, `skills`,
`prompts`, `tools`, `mcp-servers`, `bundles`)
* `.prompt <tab>` to complete MCP servers, then prompt names, then `key=` arguments, queried live from the
running servers (see [`.prompt`](#prompt---invoke-an-mcp-server-prompt) below)
* **Multi-Line Prompts:** You can also type prompts that span more than one line to help organize your thoughts. This
can be done in the following ways:
* `Ctrl-o` to open the current input buffer in your preferred editor (either the value of `editor` or `$EDITOR`)
@@ -71,12 +75,62 @@ Coyote offers the following commands to manage your roles:
For more information about roles in Coyote and how to build them, refer to the [roles documentation](Roles).
## `.prompt` - Set a temporary role using a prompt
If you need to create a temporary role that you want to discard after use, you use `.prompt`. `.prompt`-based roles
cannot be persisted to a file and saved.
## `.temp-role` - Set a temporary role using a prompt
If you need to create a temporary role that you want to discard after use, you use `.temp-role`. `.temp-role`-based
roles cannot be persisted to a file and saved.
> **Renamed from `.prompt`:** this command used to be called `.prompt`. That name now invokes MCP server prompts
> (see [`.prompt`](#prompt---invoke-an-mcp-server-prompt) below). If you have muscle memory or macros built around
> `.prompt <text>`, update them to `.temp-role <text>`.
![prompt-role](./images/roles/prompt-role.gif)
## `.prompt` - Invoke an MCP server prompt
MCP servers can expose [prompts](MCP-Servers#prompts): named, parameterized message templates that are rendered
server-side, so the server can embed live data the client never sees. `.prompt` invokes one and submits the rendered
result as your chat input:
```
.prompt <server> <name> [key=value ...]
```
For example:
```
.prompt github summarize_pr repo=coyote number=42
```
* **Arguments are named**, matching the prompt's declared argument names (MCP prompt arguments have no positional
order). Values containing spaces can be quoted: `key="some value"`.
* **Missing required arguments are prompted for interactively**, one at a time, before the prompt is fetched.
* **The result is submitted as your chat input**, exactly as if you had typed it. Multi-message prompts are flattened
into a single user message with `[user]` / `[assistant]` labels marking each original message's role. The flattened
text is never parsed as a REPL command or shell line, so prompt content beginning with `.` or `!` is chatted
verbatim, not executed.
* **Authentication errors surface here.** If the server's token has expired (or it was never authenticated),
`.prompt` reports the auth-required error the same way `mcp_invoke` does; run `.mcp auth <server>` to recover.
Discover available prompts with `.list prompts`, which prints a `server / name / description / args` table across
all enabled servers.
**Tab completion** is staged and queries the running MCP servers live on every TAB press (bounded by a 2-second
timeout per query):
| Input | Suggestions |
|----------------------------------|-----------------------------------------------------------------------------------------------|
| `.prompt <TAB>` | Enabled, running servers that advertise the prompts capability |
| `.prompt <server> <TAB>` | That server's prompt names, with descriptions |
| `.prompt <server> <name> <TAB>` | `key=` suggestions for the prompt's arguments, with required ones marked; keys you've already typed are excluded |
An empty completion list is silent by design: it means the server is not running or authenticated, or has no
prompts, and is not a bug. Completion never starts a server and never triggers an authentication flow; an
enabled-but-unauthenticated (or stopped) server simply shows nothing at TAB. Auth recovery happens at invocation,
where `.prompt` surfaces the actual error.
> **Macro shadowing:** built-in commands win name collisions, so a user macro named `prompt` is shadowed by this
> command. `.list macros` marks it `shadowed (built-in)`, and you can still run it explicitly with
> `.macro prompt [args...]`.
## `.skill` - Skill management
Skills are modular knowledge or capability packs the LLM can load and unload mid-conversation. Multiple skills can be
loaded at once; their instructions stack and their tools/MCP servers union with the active role/agent/session.
@@ -400,6 +454,7 @@ The `.list` command lists the assets of a given kind, making them discoverable w
| `.list macros` | List all macros |
| `.list bundles` | List installed bundles with their source, version, ref pin, and drift status (see [Sharing Configurations](Sharing-Configurations)) |
| `.list skills` | List skills available in this context, with descriptions and a `(loaded)` marker for active skills |
| `.list prompts` | List MCP prompts across all enabled servers (live listing), with each prompt's server, description, and arguments |
| `.list tools` | List the tools that can be enabled/disabled via `.tool [enable\|disable] <name>` (excludes internal tools; in an agent context, lists the agent's tool pool) |
| `.list mcp-servers` | List the MCP servers that can be enabled/disabled via `.mcp [enable\|disable] <name>` (configured servers plus mapping aliases) |
+3 -2
@@ -258,7 +258,8 @@ for more examples.
* `slack`: Interact with Slack using natural language
# Temporary Roles
Coyote also enables you to create temporary roles that will be discarded once you're finished with them. This is done via
the `.prompt/--prompt` command:
Coyote also enables you to create temporary roles that will be discarded once you're finished with them. This is done via
the `.temp-role` REPL command (previously named `.prompt`; that name now invokes [MCP server prompts](REPL#prompt---invoke-an-mcp-server-prompt))
or the `--prompt` CLI flag:
![prompt role](./images/roles/prompt-role.gif)
+9
@@ -204,6 +204,15 @@ the model receives:
This allows the model to understand that an external service failed and take appropriate action (retry, use an
alternative approach, or inform the user).
MCP error text shown in your terminal is sanitized (escape sequences are stripped so a misbehaving server cannot
drive your terminal); the JSON payload the model receives keeps the raw message.
## MCP Tool Result Bounding
MCP tool results are bounded before they reach the model: text larger than 200 KiB is sliced with a truncation
marker, images and binary blobs are written to disk (with a metadata object returned in their place) instead of
being inlined as base64, and oversized structured content is replaced with a truncation marker. In-bounds results
pass through unchanged. See [MCP Servers: Bounded Tool Results](MCP-Servers#bounded-tool-results) for details.
## Why This Matters
Without proper error propagation, models would only know that "something went wrong" without understanding *what*
went wrong. By including stderr output and detailed error messages, models can:
+3
@@ -36,6 +36,9 @@
- [Managing from CLI](MCP-Servers#managing-mcp-servers-from-the-cli)
- [Workspace-Local Servers](MCP-Servers#workspace-local-mcp-servers)
- [OAuth Authentication](MCP-Servers#oauth-authentication)
- [Interacting with MCP Servers](MCP-Servers#interacting-with-mcp-servers)
- [Reading Resources](MCP-Servers#reading-resources)
- [Prompts](MCP-Servers#prompts)
- [Sandbox Compatibility](MCP-Servers#sandbox-compatibility)
## Agents