forked from admin/VolcanicAgenticCore
docs(mcp): MCP client and Playwright preset
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
```
|
||||
src/Volcanic.AgenticCore.Abstractions contracts (no runtime code except helpers)
|
||||
src/Volcanic.AgenticCore runtime, organised as vertical slices
|
||||
extensions/* provider extensions built on the public API only
|
||||
extensions/* provider extensions and the MCP client, built on the public API only
|
||||
samples/* reference host
|
||||
tests/* xunit
|
||||
```
|
||||
@@ -108,8 +108,9 @@ Two independent event systems exist:
|
||||
|
||||
## Processes and secrets
|
||||
|
||||
Every process started on the agent's behalf (`bash`, `IExtensionApi.ExecAsync`, and, with their specs, scheduler
|
||||
scripts and MCP servers) gets its command, cwd and environment from one `IProcessSpawnPolicy`, configured through
|
||||
Every process started on the agent's behalf (`bash`, `IExtensionApi.ExecAsync`, MCP server processes, which are
|
||||
prepared through `ctx.PrepareProcessAsync` per session and through `api.Processes` for catalogue discovery, and, with
|
||||
its spec, scheduler scripts) gets its command, cwd and environment from one `IProcessSpawnPolicy`, configured through
|
||||
`AgenticCoreOptions.ProcessEnvironment`:
|
||||
|
||||
```
|
||||
|
||||
129
docs/mcp.md
Normal file
129
docs/mcp.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# MCP servers and the Playwright browser
|
||||
|
||||
`extensions/Volcanic.AgenticCore.Extensions.Mcp` is an MCP **client**: it starts stdio MCP servers and exposes their
|
||||
tools to the agent as ordinary tools. It uses only the public extension API and the official `ModelContextProtocol.Core`
|
||||
SDK. (`ClaudeCode.Bridge` is the opposite, an MCP *server* that shows core tools to `claude -p`.)
|
||||
|
||||
## Configuration
|
||||
|
||||
Servers come from two places. The first one wins a server name:
|
||||
|
||||
1. the host's `Mcp` section (`appsettings.json`, or `Mcp__Servers__<name>__Command` and friends in the environment),
|
||||
in configuration order, then the Playwright preset;
|
||||
2. `"mcpServers"` in `~/.volcanic/agent/settings.json` (`VOLCANIC_AGENT_DIR` moves it), with the same entry shape in
|
||||
camelCase.
|
||||
|
||||
There is no per-workspace source: a Telegram workspace is writable by the agent itself.
|
||||
|
||||
```jsonc
|
||||
"Mcp": {
|
||||
"Servers": {
|
||||
"github": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-github@2025.9.1"],
|
||||
"env": { "GITHUB_TOKEN": "${env:GITHUB_TOKEN}" },
|
||||
"toolPrefix": "gh_",
|
||||
"enabled": true,
|
||||
"parallel": false,
|
||||
"startupTimeoutSeconds": 60,
|
||||
"callTimeoutSeconds": 120,
|
||||
"idleTimeoutSeconds": 1800,
|
||||
"stderrLevel": "Warning",
|
||||
"promptSection": null
|
||||
}
|
||||
},
|
||||
"Playwright": { "Enabled": false, "Headless": true, "Browser": null, "ExtraArgs": [], "Package": null, "Command": null }
|
||||
}
|
||||
```
|
||||
|
||||
- `${workspace}` in `command`, `args`, `cwd` and `env` values becomes the chat's workspace when the process starts.
|
||||
The process runs in `${workspace}` unless `cwd` says otherwise.
|
||||
- `${env:NAME}` in an `env` value copies a host variable (unset: empty). It's the only way a deny-listed variable such
|
||||
as `GITHUB_TOKEN` reaches a server, and it reaches only that server. A `null` value removes a variable (use the user
|
||||
settings file for that; configuration binding may turn `null` into an empty string).
|
||||
- Every server process gets the process policy's filtered environment (see [extensions.md](extensions.md#spawning-processes)).
|
||||
Host secrets (`TELEGRAM_BOT_TOKEN`, `OPENAI_API_KEY`, ...) are never inherited, and known secret values in tool output
|
||||
become `[REDACTED]`.
|
||||
- `toolPrefix` may contain letters, digits, `_` and `-`, and may not start with `mcp__`. Timeouts must be positive;
|
||||
`idleTimeoutSeconds: 0` never stops an idle process. The host refuses to start on invalid configuration.
|
||||
|
||||
## The Playwright preset
|
||||
|
||||
`PLAYWRIGHT_ENABLED=true` (or `Mcp:Playwright:Enabled`) adds a server named `playwright` running `@playwright/mcp`:
|
||||
|
||||
```
|
||||
npx -y @playwright/mcp@0.0.82 [--headless] --browser chromium
|
||||
--output-dir ${workspace}/browser --user-data-dir ${workspace}/.volcanic/browser-profile <extra args>
|
||||
```
|
||||
|
||||
`--user-data-dir` is left out when the extra arguments contain `--isolated`.
|
||||
|
||||
| Variable | Setting | Default | Notes |
|
||||
|---|---|---|---|
|
||||
| `PLAYWRIGHT_ENABLED` | `Mcp:Playwright:Enabled` | `false` | |
|
||||
| `PLAYWRIGHT_HEADLESS` | `Mcp:Playwright:Headless` | `true` | |
|
||||
| `PLAYWRIGHT_BROWSER` | `Mcp:Playwright:Browser` | `chromium` | Without it `@playwright/mcp` would look for Google Chrome |
|
||||
| `PLAYWRIGHT_EXTRA_ARGS` | `Mcp:Playwright:ExtraArgs` | — | Shell-style split: `--device "iPhone 15"` is two arguments. `--isolated` keeps no profile on disk |
|
||||
| `PLAYWRIGHT_COMMAND` | `Mcp:Playwright:Command` | `npx` | Run this binary (e.g. a global `playwright-mcp`) instead of `npx -y <package>` |
|
||||
| — | `Mcp:Playwright:Package` | `@playwright/mcp@0.0.82` | npm spec used with `npx`. The pinned version is `PlaywrightPreset.DefaultPackage` |
|
||||
|
||||
Configured values win over the variables; an empty variable counts as unset. The bot logs
|
||||
`MCP: playwright enabled (headless, chromium)` or `MCP: playwright disabled (set PLAYWRIGHT_ENABLED=true)` at startup.
|
||||
|
||||
- Screenshots saved with `filename: "browser/<name>.png"`, PDFs and downloads land in the workspace's `browser/`
|
||||
folder, where `send` can deliver them. A screenshot without `filename` comes back to the model as an image (the
|
||||
OpenAI chat-completions provider can't show tool-result images, so there it only gets `[image]`).
|
||||
- Each chat has its own browser profile under `.volcanic/browser-profile`, so site logins survive idle restarts,
|
||||
`/new` and bot restarts. The agent's `bash` can read that profile, like everything else in its workspace.
|
||||
- The system prompt gets a short "Browser" section while the browser tools are active. It mentions `send` only when
|
||||
a `send` tool exists.
|
||||
|
||||
### Deployment requirements
|
||||
|
||||
The server needs Node and the Chromium build that the pinned `@playwright/mcp` expects:
|
||||
`npx playwright install --with-deps chromium`, run from that package so the versions match
|
||||
(`PLAYWRIGHT_BROWSERS_PATH` is honoured). The first `npx` run downloads the package, which is why the preset allows
|
||||
120 s to start. The Docker image (`docker.md`, when the docker-deployment spec lands) preinstalls both and sets
|
||||
`PLAYWRIGHT_COMMAND=playwright-mcp` and `PLAYWRIGHT_EXTRA_ARGS=--no-sandbox`. Chromium's sandbox needs user namespaces,
|
||||
which Docker's default profile blocks, so there the container is the isolation boundary. A missing browser shows up as
|
||||
an error from the first `browser_*` call.
|
||||
|
||||
## Tool names
|
||||
|
||||
The agent sees `toolPrefix + name`, with characters outside `[A-Za-z0-9_-]` replaced by `_`, cut to 48 characters,
|
||||
and `_2`, `_3` added to duplicates. The server is always called with its own name. Through the Claude Code provider the
|
||||
tools appear as `mcp__volcanic__<name>`.
|
||||
|
||||
- A tool named like a built-in (`read`, `bash`, ...) is **not** registered, and a warning suggests a `toolPrefix`.
|
||||
- If two servers offer the same name, the first configured wins and a warning names both.
|
||||
- MCP tools run one at a time unless the server sets `"parallel": true`: most servers (Playwright included) are stateful.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
- The tool list is discovered once per bot process per server configuration, in a temp directory, while the first
|
||||
session activates. Concurrent chats share that one discovery. A server that fails to start is logged, shows up in
|
||||
`/mcp` as failed, contributes no tools, and is retried after 60 s. Other servers and the chat carry on.
|
||||
- Each chat gets its own server process, started by the first call in the chat's workspace. It stops after
|
||||
`idleTimeoutSeconds` without calls, and on `/new`, `/resume` or shutdown, so a new conversation gets a fresh browser.
|
||||
- A server that dies turns the call into an error ("… MCP server exited (code): <stderr>. It restarts on the next call;
|
||||
browser state was lost."), and the next call starts a new one. Failed calls are never retried automatically.
|
||||
- A call runs at most `callTimeoutSeconds`. `/stop` cancels it and the server is told (`notifications/cancelled`).
|
||||
|
||||
## `/mcp`
|
||||
|
||||
| Command | |
|
||||
|---|---|
|
||||
| `/mcp` | Each server: `not started`, `running`, `failed: <reason>` or `disabled`, and its tool count |
|
||||
| `/mcp <name>` | Command line (without env values), env names, server version, tools, last error, stderr tail |
|
||||
| `/mcp restart <name>` | Stop the chat's process; the next call starts a fresh one (clears a wedged browser) |
|
||||
|
||||
No model call is made.
|
||||
|
||||
## Manual check (not in CI)
|
||||
|
||||
With `PLAYWRIGHT_ENABLED=true` and Chromium installed:
|
||||
|
||||
1. "Open https://example.com and tell me the H1" → `Example Domain`.
|
||||
2. "Screenshot example.com and send it to me" → a photo arrives in Telegram (needs the `send` tool).
|
||||
3. `/mcp` shows `playwright: running`.
|
||||
4. Two chats browse at the same time without seeing each other's tabs.
|
||||
@@ -1,7 +1,7 @@
|
||||
# MCP client extension and Playwright browser tools — design
|
||||
|
||||
Date: 2026-09-26
|
||||
Status: draft
|
||||
Status: implemented (the Dockerfile version test arrives with 2026-09-26-docker-deployment-design.md)
|
||||
Depends on: `2026-09-26-telegram-attachments-design.md` (the `send` tool that delivers browser output);
|
||||
`2026-09-26-process-environment-hook-design.md` (the environment every server process gets, and the redaction of
|
||||
known secrets in tool output).
|
||||
@@ -382,7 +382,8 @@ No test touches the network, npm or a real browser.
|
||||
`settings.json` merge and precedence;
|
||||
- Playwright preset: argv for headless/browser/extra args, output dir and profile under the workspace, a pinned
|
||||
package; with `PLAYWRIGHT_COMMAND` the binary is used and `-y <package>` is dropped; the Dockerfile's
|
||||
`PLAYWRIGHT_MCP_VERSION` default equals `DefaultPackage`'s version; the prompt section appears only with `browser_*` tools active and mentions `send` only when it exists;
|
||||
`PLAYWRIGHT_MCP_VERSION` default equals `DefaultPackage`'s version (deferred: the Dockerfile and that test come with the
|
||||
docker-deployment spec); the prompt section appears only with `browser_*` tools active and mentions `send` only when it exists;
|
||||
- end to end through the core with `FauxProvider`: the scripted model calls the fake `image` tool, and the tool
|
||||
result message holds the image.
|
||||
- **Claude Code**: a `ToolCatalog` test that `browser_navigate`-style names alias to themselves (no digest).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Process environment hook, host hardening and secret redaction: keep host secrets away from the agent — design
|
||||
|
||||
Date: 2026-09-26
|
||||
Status: implemented (the scheduler `ScriptRunner` and the MCP spawners in §5 come with their own specs)
|
||||
Status: implemented (the MCP spawners in §5 came with 2026-09-26-mcp-client-playwright-design.md; the scheduler `ScriptRunner` comes with its own spec)
|
||||
Used by: `2026-09-26-scheduler-design.md` (watcher scripts), `2026-09-26-mcp-client-playwright-design.md` (MCP
|
||||
server processes), `2026-09-26-telegram-attachments-design.md` (`OPENAI_API_KEY`), `2026-09-26-google-calendar-design.md`
|
||||
(`GOOGLE_CLIENT_SECRET`), `2026-09-26-docker-deployment-design.md` (the secrets set in Coolify).
|
||||
@@ -230,6 +230,8 @@ Process-wide spawners without a session (the scheduler, MCP catalogue discovery)
|
||||
| MCP `McpServerConnection` | `McpServer`, `Origin` = server name, `Arguments` = expanded args | the server's `env` entries | Through `ctx.PrepareProcessAsync` from the first tool call. The result's env goes to `StdioClientTransportOptions.EnvironmentVariables`, with `InheritEnvironmentVariables = false`. |
|
||||
| MCP `McpCatalogCache` discovery | as above, `Cwd` = the temp dir | the server's `env` entries | Through `api.Processes.Prepare` (activation, no session). |
|
||||
|
||||
*The two MCP rows are implemented in `extensions/Volcanic.AgenticCore.Extensions.Mcp` (`McpServerConnection`, `McpCatalogCache`).*
|
||||
|
||||
A spawner whose `Prepare` throws reports it the way it reports a failed start: `bash` returns an error result
|
||||
`process environment hook failed: <message>`, a watcher is `Failed(null, "…")`, an MCP call or discovery fails with the
|
||||
message. The message comes from the exception, never from the environment.
|
||||
|
||||
@@ -35,6 +35,9 @@ Environment variables, or the `Telegram` section of `appsettings.json` (`Telegra
|
||||
| `PROCESS_ENV_ALLOW` | — | Comma-separated names or globs let through to `bash`, scheduled scripts and MCP servers despite the deny-list (e.g. `GH_TOKEN` for `gh`). Added to `ProcessEnvironment:Allow` |
|
||||
| `ProcessEnvironment:Deny` / `ProcessEnvironment:Allow` | built-in deny-list | Added to the defaults (`*_API_KEY`, `*_TOKEN`, `*_SECRET`, `*_PASSWORD`, their `Section__Key` forms and the named provider credentials), never replacing them. Allowing a name that is denied by name stops startup |
|
||||
| `ProcessEnvironment:HardenHostProcess` | `true` | Linux: the bot process is made non-dumpable. `ProcessEnvironment__HardenHostProcess=false` for a debugging session only |
|
||||
| `PLAYWRIGHT_ENABLED` / `Mcp:Playwright:Enabled` | `false` | Browser tools through `@playwright/mcp` ([mcp.md](mcp.md)) |
|
||||
| `PLAYWRIGHT_HEADLESS`, `PLAYWRIGHT_BROWSER`, `PLAYWRIGHT_EXTRA_ARGS`, `PLAYWRIGHT_COMMAND` | `true`, `chromium`, —, `npx` | See [mcp.md](mcp.md#the-playwright-preset) |
|
||||
| `Mcp:Servers:<name>` | — | Other stdio MCP servers ([mcp.md](mcp.md#configuration)) |
|
||||
|
||||
Sessions are stored as usual under `~/.volcanic/agent/sessions/telegram-<chatId>/`; after a restart each chat resumes
|
||||
its most recent conversation.
|
||||
@@ -66,7 +69,7 @@ A message sent while the agent works steers the current run. Photos (with or wit
|
||||
| `/help` | Short help |
|
||||
|
||||
Any other `/command` is passed to the session, so extension commands such as `/claude-code-doctor` and
|
||||
`/claude-code-login` work.
|
||||
`/claude-code-login` work. `/mcp` lists the MCP servers (see [mcp.md](mcp.md#mcp)).
|
||||
When an extension asks something, the question appears with buttons (or as a reply prompt for free text).
|
||||
|
||||
If the Claude subscription session behind the bot expires, the **first id in `AllowedUserIds`** (the bot owner)
|
||||
|
||||
Reference in New Issue
Block a user