From 5ccd7b2ba90a2c5128168a664c734b8c87e355fc Mon Sep 17 00:00:00 2001 From: Dark-Alex-17 Date: Mon, 24 Aug 2026 20:07:52 -0600 Subject: [PATCH 1/2] Document MCP resources and prompts support - MCP-Servers: new 'Interacting with MCP Servers' section covering the per-server meta-functions, the unified catalog (kind-aware search and describe, audience annotations), capability gating, mcp_read paging, pattern filtering and template expansion, blob sniff/spill behavior with size ceilings and eviction, bounded tool results, and prompts - REPL: .prompt command (usage, interactive args, result-as-chat-input, staged live tab-completion with silent-empty semantics, macro shadowing), .temp-role rename note, .list prompts row, completion bullet - Roles: temporary roles now use .temp-role (REPL) / --prompt (CLI) - Tools: MCP tool result bounding note and sanitized terminal error text - Home: MCP quick-link mentions resources and prompts - Sidebar: anchors for the new MCP sections --- Home.md | 2 +- MCP-Servers.md | 140 +++++++++++++++++++++++++++++++++++++++++++++++++ REPL.md | 59 +++++++++++++++++++-- Roles.md | 5 +- Tools.md | 9 ++++ _Sidebar.md | 3 ++ 6 files changed, 212 insertions(+), 6 deletions(-) diff --git a/Home.md b/Home.md index 84598d0..838be3b 100644 --- a/Home.md +++ b/Home.md @@ -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. diff --git a/MCP-Servers.md b/MCP-Servers.md index 8ee265f..845d7bf 100644 --- a/MCP-Servers.md +++ b/MCP-Servers.md @@ -544,6 +544,143 @@ 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_` | Keyword search over the server's catalog of tools, resources, resource templates, and prompts | +| `mcp_describe_` | Fetch the full schema or metadata for exactly one catalog item | +| `mcp_invoke_` | Call a tool on the server | +| `mcp_read_` | Read a resource (or expand a resource template) from the server | +| `mcp_prompt_` | 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_` 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_` 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_` 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 + `/mcp-resources//.` with `0600` permissions, and 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_` 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 +690,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. diff --git a/REPL.md b/REPL.md index b0f1c60..728df32 100644 --- a/REPL.md +++ b/REPL.md @@ -18,6 +18,8 @@ things like * `.set key ` to complete configuration values * `.mcp auth ` to complete remote MCP server names * `.macro ` to complete macro names and the `enable`/`disable` subcommands + * `.prompt ` 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 +73,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 `, update them to `.temp-role `. ![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 [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 ` 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 ` | Enabled, running servers that advertise the prompts capability | +| `.prompt ` | That server's prompt names, with descriptions | +| `.prompt ` | `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 +452,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] ` (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] ` (configured servers plus mapping aliases) | diff --git a/Roles.md b/Roles.md index 3f0de90..a86f5f2 100644 --- a/Roles.md +++ b/Roles.md @@ -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) diff --git a/Tools.md b/Tools.md index ad9e9c7..c41b071 100644 --- a/Tools.md +++ b/Tools.md @@ -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: diff --git a/_Sidebar.md b/_Sidebar.md index 2f2ff69..1577de2 100644 --- a/_Sidebar.md +++ b/_Sidebar.md @@ -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 From f8614276e271b1e6f18feb717a6e47f5b0b242ea Mon Sep 17 00:00:00 2001 From: Dark-Alex-17 Date: Tue, 25 Aug 2026 11:38:53 -0600 Subject: [PATCH 2/2] Refine spill and completion docs after cross-platform hardening - Spill permissions are POSIX-only; note the temp-file + rename write so readers know a visible spill file is always complete - Add the .list kind completion bullet, which now includes prompts --- MCP-Servers.md | 5 +++-- REPL.md | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/MCP-Servers.md b/MCP-Servers.md index 845d7bf..dff4922 100644 --- a/MCP-Servers.md +++ b/MCP-Servers.md @@ -635,8 +635,9 @@ 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 - `/mcp-resources//.` with `0600` permissions, and the read returns a - self-describing metadata object instead of the bytes: + `/mcp-resources//.`, 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 { diff --git a/REPL.md b/REPL.md index 728df32..081cf18 100644 --- a/REPL.md +++ b/REPL.md @@ -18,6 +18,8 @@ things like * `.set key ` to complete configuration values * `.mcp auth ` to complete remote MCP server names * `.macro ` to complete macro names and the `enable`/`disable` subcommands + * `.list ` to complete the listable kinds (`roles`, `sessions`, `agents`, `rags`, `macros`, `skills`, + `prompts`, `tools`, `mcp-servers`, `bundles`) * `.prompt ` 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