AnyEvent-MCP
view release on metacpan or search on metacpan
lib/AnyEvent/MCP/Intro.pod view on Meta::CPAN
which includes network access, the agent socket and C<known_hosts>, but not
general home-directory access. Read permission for ordinary file tools does
not automatically authorize a VCS operation.
For arbitrary C<exec> commands, the same output options are available:
C<grep> filters stdout using PCRE, C<max_lines> limits matching stdout lines,
and C<max_bytes> bounds returned bytes per stream. Prefer these options to a
shell wrapper that mixes execution and output processing. Stderr is unfiltered,
and exit status still describes the original command.
Defaults return at most 2000 stdout lines and 64 KiB per stream. Raw capture
has a separate 1 MiB limit per stream. A true C<capture_truncated> means
output was omitted before filtering or collection ended on timeout, so absent
matches do not prove the omitted output had none. See L<mcp-fs/Command output>
for all result flags and limits.
=head1 CONFIGURING YOUR SETUP
There are three different things to configure: how the client connects,
how the server runs, and which operations the user allows. Changing one
does not necessarily change the other two.
=head2 Where each setting belongs
=over
=item Directories to expose
Pass workdirs on the mcp-fs startup command. For a stdio server, that
command belongs in the client's server configuration. For an HTTP server,
it belongs in the command used to run the service.
=item Connection transport
The client launches a command for stdio or connects to a URL for HTTP.
C<--stdio> selects the former for mcp-fs and the multibroker.
=item Listener address, port and tokens
Use C<[instance NAME]> in the global configuration or command-line
C<--bind> and C<--token> overrides.
=item Trusted mounts and utility paths
For mcp-fs, use C<[program NAME]> defaults and C<[instance NAME]>
overrides in the global configuration.
=item Allowed operations and sandbox grants
Use the permission rules file, normally created through approval dialogs.
This file is separate from the global configuration.
=item Location of permission prompts
Use C<--ask helper>, C<--ask tty>, C<--ask yad>, C<--ask auto> or C<--ask mcp>, or set
C<PERL_ANYEVENT_MCP_PERMISSION_ASK> in the server's environment.
=item Combined server connections
Put prefix/server pairs on the multibroker startup command. HTTP child
credentials can also use C<[client PREFIX]> in the global configuration.
=back
=head2 One complete named configuration
The global file is C<$XDG_CONFIG_HOME/anyevent-mcp/global.ini>, falling
back to C<$HOME/.config/anyevent-mcp/global.ini>. Settings are UTF-8
C<key = value> lines; values are unquoted. Whole-line C<#> and C<;>
comments are allowed. See L<AnyEvent::MCP/GLOBAL CONFIGURATION> for the
complete format.
Start with the existing file, preserving its C<[global]> secret.
If you have no file yet, C<anyevent-mcp init> creates it privately.
Add this section:
[instance code]
bind = 127.0.0.1:31741
Now start:
mcp-fs --name code /home/alice/src/project
and retrieve its URL with:
mcp-fs --name code --print-url
Both use C<[instance code]>. No explicit token is needed: the server
derives one from its name and the existing master secret.
For this setup:
instance settings: [instance code]
selected workdir: /home/alice/src/project
permission file: ~/.config/anyevent-mcp/rules/fs-code.ini
rule-manager profile: fs-code
The permission pathname above assumes the default configuration directory.
C<anyevent-mcp manage-rules fs-code> opens the graphical rule manager;
C<anyevent-mcp edit-rules fs-code> opens the file in your editor.
Settings shared by all mcp-fs instances go in C<[program mcp-fs]>.
Instance settings override program settings.
File ownership and permissions are under the user's control. The global
configuration contains a master authentication secret, so take care when
sharing it. C<--config FILE> on C<anyevent-mcp init>
selects that command's output file; it does not make the servers use an
arbitrary alternate file. Use the same C<HOME>/C<XDG_CONFIG_HOME>
environment when running the server and its management commands.
=head2 Names, prefixes and profiles
C<--name code> selects instance settings, derived HTTP credentials and
mcp-fs's C<rules/fs-code.ini>. Without C<--name>, mcp-fs uses C<default>,
giving profile C<fs-default>. Two processes with the same program, name and
configuration share credentials and rules, even if their workdirs differ.
A multibroker prefix, such as C<files>, only routes tools: C<grep> becomes
C<files.grep>. It does not rename the child server or select its rules.
For example:
mcp-multibroker files \
'stdio:mcp-fs --name code /home/alice/src/project'
The tools are C<files.*>, but the child still uses instance C<code>
and rule-manager profile C<fs-code>. The multibroker's own C<--name>
selects its upstream listener settings.
=head2 Configuration changes and restarts
Command-line listener options override the named global settings.
For mcp-fs trusted settings, an instance entry overrides the corresponding
program entry; an empty instance mount value suppresses the inherited mount.
Workdirs, mounts, utility paths, listener settings and tokens are read at
startup. Change the file or startup command, then restart the affected
server. The multibroker also needs restarting to change its prefix/server
pairs; refreshing discovery does not change those pairs.
Permission rules are reread on permission checks, so editing them does
not require a restart. A client's displayed tool list can still be cached.
After restarting a child, refresh tool discovery or reconnect the client
as described in L</COMBINING SERVERS>.
=head1 PERMISSIONS IN PRACTICE
HTTP authentication establishes that the client may connect.
Permission checks decide whether a tool may perform an operation.
A correct token therefore does not prevent permission prompts.
=head2 Your first permission dialog
Try the read-only request in L</Make a useful request>. When C<mcp-fs>
asks to list the project directory, the YAD window shows the server,
tool, action, paths and C<Required grants>. Read these before choosing
anything. For a C<list> request, expect action C<inspect> and normally
the C<workdir-read> grant. C<inspect> permits inspection operations;
C<workdir-read> gives the worker read access to the selected workdir,
not just the displayed path. The action and the sandbox access are
separate decisions.
The displayed paths or command subject describe what is requested I<now>. The
C<Reason> field is an explanation supplied with the request, not a
security guarantee. Check the requested grants as well: C<network>,
C<world-write>, C<ssh-agent> or other grants may give the worker access
unrelated to the displayed file. An C<UNSANDBOXED> warning means the
worker has the full privileges of your host user, regardless of the
apparently narrow request.
For this first listing, choose C<Allow once> if the request matches
what you asked the assistant to do. That authorizes this tool call,
not the whole conversation. If the assistant subsequently reads the
README, that is a different action (C<read>) and may prompt again.
Choose C<Deny once>, or close the window, if anything is unexpected.
Denial is safe: the operation does not run, and you can reconsider
after seeing or changing the request.
lib/AnyEvent/MCP/Intro.pod view on Meta::CPAN
regardless of its own asking mode. Children delegate permission prompts to
their calling MCP client; the multibroker handles or forwards those prompts
according to its own C<--ask> mode. An explicit child C<--ask> still overrides
this default. Independently running HTTP servers keep their own configuration.
The multibroker can handle form elicitation locally for both stdio and HTTP
children. C<--ask helper> selects local dialogs even if the upstream client
advertises native forms; C<--ask mcp> forwards forms upstream, and C<auto>
uses native forms when advertised. If a client claims support but silently
declines questions, use C<helper>. A decline never causes a fallback prompt.
Standalone C<mcp-ask-user> also supports these modes and defaults to the
local helper. A question is different from an approval: answering it does
not grant a filesystem worker any authority.
=head1 COMBINING SERVERS
Once the first connection works, C<mcp-multibroker> lets you give the
client one connection for several MCP servers. It works with the servers
in this distribution and with third-party MCP servers, including ones
written in other languages. Each downstream server can be a local
subprocess communicating over stdio or an existing HTTP endpoint; you
can mix both in one broker. The broker discovers their tools and
forwards calls to the appropriate server without changing how that
server implements its tools.
For example, combine two filesystem instances:
mcp-multibroker --name tools --bind :31740 \
code 'stdio:mcp-fs --name code /home/alice/src/project' \
notes 'stdio:mcp-fs --name notes /home/alice/notes'
Quote each C<stdio:> shell command as one argument.
The broker supplies the child's stdio environment and starts ordinary
children lazily, on first use. The explicit child names give them separate
permission profiles C<fs-code> and C<fs-notes>.
Obtain the upstream HTTP URL with:
mcp-multibroker --name tools --bind :31740 --print-url
Configure this URL in place of the client's old direct connection.
The client can now see C<code.grep>, C<code.read-lines>, C<notes.find>
and other prefixed tools. It does not need a separate connection to each
child.
For a client launching the multibroker locally, configure:
mcp-multibroker --stdio --name tools \
code 'stdio:mcp-fs --name code /home/alice/src/project' \
notes 'stdio:mcp-fs --name notes /home/alice/notes'
C<--stdio> selects the upstream connection. The child commands still use
C<stdio:>; that selects each downstream connection independently.
No upstream HTTP URL is needed in this case.
The same setup can include Vim, browser pages, a third-party stdio
server or a remote HTTP service. See L</Add an HTTP child> for an
example mixing local and remote servers, and L<mcp-multibroker/DOWNSTREAM
SERVERS> for the complete syntax, including direct argv execution,
credentials and per-child options.
=head2 Discover and refresh tools
Ask the LLM:
Call discover-brokers and list-tools, then show me which servers
are available and what each is for.
C<discover-brokers> returns advertised instructions (which may be short),
no instructions, or an error for each configured server.
C<list-tools> requests current tool definitions, including any prefixed
C<meta-instructions> tools and their full instruction descriptions;
unreachable servers are omitted. A reachable server can still fail later.
If the client still has an old direct tool list, C<call-tool> can invoke
an exact prefixed name found by C<list-tools>. Refresh the client's MCP
connection if it continues to hide newly available tools.
Discovery queries configured servers and currently registered reverse
connections. Changing the static downstream command still requires a restart.
=head2 Let a server connect to the multibroker
To add independently started servers without editing the multibroker command,
keep its HTTP/extension listener active alongside stdio:
mcp-multibroker --stdio --listen --bind 127.0.0.1:31740
mcp-multibroker --bind 127.0.0.1:31740 --print-url
Append an alias to the returned credential-bearing URL:
mcp-fs --connect http://127.0.0.1:31740/TOKEN/files /home/alice/project
The filesystem server initiates the TCP connection but remains the MCP server.
The multibroker authenticates the same token used for HTTP, registers the
alias and discovers the tools. A configured or connected alias cannot be
replaced. An unexpected disconnect starts a new connection period; refused TCP
connections retry once per second, at most sixty times. Other TCP errors and
TLS verification failures log an error and stop. Pending calls fail and are
never replayed. Remote registration does not grant spawned-child shutdown
authority to the multibroker.
The hello is a raw JSON line, followed by ordinary MCP JSONL on the same
socket; the HTTP-shaped URL is an address and credential, not an HTTP request.
C<https://> reverse URLs enable verified TLS; C<http://> uses plaintext.
A certificate mismatch is an error, not a reason to fall back to HTTP.
With C<--tls-cert> and C<--tls-key> on the listener, C<--print-url> gives
HTTPS and HTTP URL prefixes. For HTTPS, use a trusted certificate whose
hostname matches the URL. An HTTP listener is already active by default
when upstream is HTTP.
=head2 Add an HTTP child
A broker can mix spawned stdio servers and existing HTTP endpoints,
including independently implemented third-party servers:
mcp-multibroker --stdio \
code 'stdio:mcp-fs --name code /home/alice/src/project' \
remote https://example.org/mcp/ --bearer TOKEN
C<--bearer> immediately after the HTTP pair belongs to that child.
C<--token> before all pairs configures the multibroker's own HTTP listener.
They authenticate different connections.
To avoid putting a child's bearer token in the startup command, add:
[client remote]
bearer = TOKEN
timeout = 300
to the private global configuration, and omit C<--bearer> from the command.
The prefix C<remote> selects these client defaults. The command-line URL
still chooses the endpoint. Use the actual credential accepted by that
server, not the literal placeholder C<TOKEN>.
A token already present in a child's URL needs no separate bearer option.
The bundled servers use static URL-safe tokens; they do not validate
OAuth access tokens or implement HTTP Basic authentication.
Use TLS or an appropriate tunnel when credentials cross untrusted networks.
The multibroker does not put every child inside the mcp-fs sandbox.
Each process or remote service has its own authority and enforcement.
=head2 Add a live Vim editor
Install the Vim plugin using L<mcp-vim/VIM PLUGIN INSTALLATION>. It is
included in the source distribution but is not installed by Perl's
C<make install>.
Without a multibroker, configure the MCP client to launch:
mcp-vim --stdio --name editor
Even in stdio mode, mcp-vim listens separately for editor connections.
If using a multibroker, start its child eagerly so Vim can connect
before the first MCP tool call:
mcp-multibroker --stdio \
code 'stdio:mcp-fs --name code /home/alice/src/project' \
vim 'stdio:mcp-vim --name editor' --eager
In another terminal, obtain the editor broker's URL prefix:
mcp-vim --name editor --print-url
Append the desired target name in Vim:
:Mcp http://HOST:PORT/TOKEN/my-editor
Use the actual printed prefix in place of C<http://HOST:PORT/TOKEN/>.
Ask the LLM to call C<targets>, then C<state> before reading or editing
buffers (C<vim.targets> and C<vim.state> through the multibroker).
These tools see live, possibly unsaved buffers. Edits require a current
changedtick and do not save files to disk.
To pass arguments directly, use C<PREFIX --- PROGRAM ARG... --->:
mcp-multibroker --stdio \
vim --- mcp-vim --name editor --bind :: --- --eager \
code --- mcp-fs --name code '/home/alice/project with spaces' ---
Only the invoking shell processes quoting. The broker passes all arguments
between the delimiters unchanged, including empty arguments and child options.
Use C<stdio:COMMAND> for shell syntax or a literal C<---> child argument.
An empty command or missing closing delimiter is an error.
C<--eager> belongs immediately after its stdio URL or closing C<--->.
An eager connection failure is logged; the broker does not continuously
restart the child. Later tool use can try connecting again.
=head2 Ask the client's human a question
To let the assistant ask you questions, configure its MCP client to launch:
mcp-ask-user --stdio
Or add the question server to a multibroker:
mcp-multibroker --stdio \
lib/AnyEvent/MCP/Intro.pod view on Meta::CPAN
=head1 CONNECTED BROWSER PAGES
C<mcp-webbrowser> is the browser-page counterpart to C<mcp-vim>. It exposes
only tabs explicitly allowed by the browser user. Configure your MCP client
to launch:
mcp-webbrowser --stdio
The broker keeps its own HTTP/WebSocket listener active even in stdio mode;
the userscript connects to that listener, not to MCP's stdio stream.
In a separate terminal, obtain its URL:
mcp-webbrowser --print-url
Use the same instance name and configuration as the broker. Keep this
credential-bearing URL private.
Install the companion userscript from
C<webbrowser/mcp-webbrowser.user.js> in the source distribution or from
L<https://dist.schmorp.de/mcp-webbrowser/1/mcp-webbrowser.user.js>.
Chromium with ScriptCat has been tested. On a tab you want to expose,
choose B<Allow MCP access to this tab> from the userscript menu and enter
the printed URL when prompted. Other tabs remain unexposed.
The broker supplies C<tabs>, C<execute> and C<add-element> tools, permission
prompts, cancellation and execution timeouts. See
L<mcp-webbrowser/USERSCRIPT INSTALLATION AND USE> for setup, compatibility
and the current ScriptCat discarded-tab limitation.
If the multibroker launches the browser broker, keep that child running
from startup so browser tabs can connect:
mcp-multibroker --stdio browser --- mcp-webbrowser --- --eager
=head1 HTTP TOKENS AND SERVER IDENTITY
For the ordinary bundled HTTP endpoints, a client can present an accepted
token in either of these ways:
POST /TOKEN/
POST / Authorization: Bearer TOKEN
URL tokens are path components, not browser cookies.
For another configured endpoint, C</TOKEN> prefixes that endpoint path.
The HTTP server also accepts C</TOKEN> and the corresponding bare path
without the final slash when the endpoint is configured with one. This
works around clients (observed with Claude) that drop the final slash
from a configured URL.
Managed tokens authenticate distinct users for saved permissions. Existing
rules without match.user still apply to every user.
To add a user and obtain their multibroker URL:
anyevent-mcp create-user chatgpt
mcp-multibroker --print-url=chatgpt
An implicit global C<default> user starts at generation 1. The program,
instance, user and generation derive its token; the program, instance and
master secret determine a stable port in 16384..32767. Revoke a user to
disable its token; create it again to rotate the generation. Losing the master secret changes
all derived credentials. Keep an existing C<[global]> secret when editing
other configuration.
To configure explicit tokens:
[instance code]
tokens = first-secret,second-secret
Replace those placeholders with actual unguessable URL-safe tokens.
Repeated C<--token> options replace the whole configured list and the
derived managed users. In this mode C<--print-url> lists all enabled
users; explicit tokens still print the first accepted token.
Vim editors, browser tabs, and reverse servers need the effective
C<connect_user> token (default C<default>), not an arbitrary named user's token.
If C<--print-url> disagrees with the client's URL, compare the name,
command-line overrides, user and configuration environment with the running
process. The command resolves configuration; it does not ask the running
server which URL it uses. A changed file needs a server restart.
An Origin allowlist restricts browser origins; it does not authenticate
clients. A CORS preflight can succeed without a token, while the MCP POST
still requires one. See L<AnyEvent::MCP::Server::HTTP/AUTHENTICATION> for
the low-level server interface.
=head1 CLIENT EXAMPLES
Client settings and command syntax evolve independently of this distribution.
These examples show the connection choices; consult the linked client
documentation if its interface differs. Replace illustrative paths and
credentials with your own.
=head2 OpenAI Codex CLI
For the first direct filesystem connection:
codex mcp add code -- \
mcp-fs --stdio --name code /home/alice/src/project
codex mcp list
For a multibroker connection:
codex mcp add mytools -- \
mcp-multibroker --stdio \
code 'stdio:mcp-fs --name code /home/alice/src/project'
Use C</mcp> within Codex to inspect connections.
See L<Codex MCP documentation|https://learn.chatgpt.com/docs/extend/mcp>.
=head2 Anthropic Claude Code
For the direct connection:
claude mcp add --transport stdio --scope user code -- \
mcp-fs --stdio --name code /home/alice/src/project
claude mcp list
Replace the launched command with the multibroker command above when you
want several servers. Use C</mcp> for connection status. See
L<Claude Code MCP documentation|https://code.claude.com/docs/en/mcp>.
=head2 Google Gemini CLI
Merge a direct server entry into C<~/.gemini/settings.json>:
{
"mcpServers": {
"code": {
"command": "mcp-fs",
"args": [
"--stdio", "--name", "code", "/home/alice/src/project"
]
}
}
}
For a combined connection, use C<mcp-multibroker> as the command and its
C<--stdio> and prefix/server pairs as the argument array.
Use C</mcp> to inspect the connection. See
L<Gemini CLI MCP documentation|https://geminicli.com/docs/tools/mcp-server/>.
=head2 Hosted clients, including ChatGPT web
A hosted client cannot spawn your local stdio command or reach your
machine's loopback address directly. A Secure MCP Tunnel runs locally,
connects out to OpenAI, and forwards MCP requests to a local stdio command
or HTTP URL. No public listener or inbound firewall port is needed.
First create a tunnel in OpenAI Platform tunnel settings and obtain its
C<tunnel_...> ID and a runtime API key. Arrange for
C<CONTROL_PLANE_API_KEY> to be set securely in the tunnel client's
environment; do not put a real key in a script, command line or example.
Associate the tunnel with the ChatGPT workspace where you want to use it.
For B<stdio>, put the server startup command in a small executable wrapper,
for example C</home/alice/bin/my-mcp>:
#!/bin/sh
exec mcp-multibroker --stdio \
code --- mcp-fs --name code /home/alice/src/project --- \
questions --- mcp-ask-user ---
With a tunnel installation providing C<tunnel-client-runtime>, run:
tunnel-client-runtime run \
--log.format=struct-text \
--log.level=warn \
--control-plane.tunnel-id=tunnel_... \
--mcp.connection-max-ttl=120s \
--mcp.command=/home/alice/bin/my-mcp
The tunnel starts the wrapper as its stdio MCP server. Do not start a
separate copy yourself. Make the wrapper executable and ensure its
programs and Perl modules are on the tunnel process's C<PATH>.
For B<HTTP>, instead start the server or multibroker separately:
mcp-multibroker --name tools --bind 127.0.0.1:16586 \
code 'stdio:mcp-fs --name code /home/alice/src/project'
Obtain the matching credential-bearing URL:
mcp-multibroker --name tools --bind 127.0.0.1:16586 --print-url
Then point the tunnel runtime at that URL, rather than a command:
tunnel-client-runtime run \
--log.format=struct-text \
--log.level=warn \
--control-plane.tunnel-id=tunnel_... \
--mcp.connection-max-ttl=120s \
--mcp.server-url=http://127.0.0.1:16586/TOKEN/
Replace C<TOKEN> with the actual URL path printed above, and keep it
private. In this mode, the multibroker must remain running separately;
the tunnel only connects to it. Both modes require the tunnel runtime
to remain active while the hosted client uses the server. Use B<one>
of C<--mcp.command> and C<--mcp.server-url>, not both.
In ChatGPT, add a custom MCP server using a Tunnel connection and select
the tunnel ID. Creation and visibility depend on the Platform and ChatGPT
workspace permissions. The tunnel client's CLI and supported configuration
forms can vary between releases; the examples above use the direct runtime
CLI rather than its optional profile setup. See
L<Secure MCP Tunnel|https://developers.openai.com/api/docs/guides/secure-mcp-tunnels>
for obtaining a tunnel, runtime, credentials and connecting it to ChatGPT.
=head1 TROUBLESHOOTING
=head2 The server does not start
Read stderr or the client's server log. Missing Perl modules, a missing
workdir, unsuitable utilities or an unavailable bubblewrap executable can
fail startup. Confirm that the client launches the same executable and
arguments you intended, with the expected C<PATH> and Perl installation.
For a local sandbox check, run from the project directory:
mcp-fs --exec-cmd workdir-read -- ls
This runs C<ls> with the named grants and trusted baseline mounts, without
MCP or saved permission rules. A failure here can identify a bubblewrap
or runtime problem. It does not test the client connection or approval UI.
C<--sandbox none> disables isolation and changes worker authority; it is
not a fix that preserves the same permission boundary.
=head2 The client cannot connect
For stdio, configure a command the client launches, rather than a URL.
A separately running stdio process is not the client's connection.
Do not put banners or diagnostics on stdout.
For HTTP, leave the server running and check its address and port.
C<--print-url> exits without starting it. Loopback binds accept local
connections only; hosted or remote clients need the appropriate route or
tunnel. An occupied port causes a listener error, so check the server log.
For reverse C<--connect> servers, check the connecting process's log as
well as the multibroker. C<https://> requires a certificate trusted by
the connecting process and a hostname matching the URL. A TLS error
stops that reverse server immediately; it does not retry with plaintext.
TCP connection refusal is retried for up to sixty attempts.
=head2 Authentication fails
Obtain the URL with the same name, bind/token overrides and configuration
environment as the running process. Use the complete path; the final
slash is optional for endpoints whose configured path ends in one.
A clean URL needs an accepted bearer token.
A multibroker child credential is not necessarily the upstream listener's
credential. C<--bearer> after a child pair and C<--token> before the pairs
configure different sides of the connection.
For IMAP OAuth2, obtaining an access token does not by itself prove that
IMAP authentication will work. Check the authorized browser account,
requested IMAP scope and saved C<user> name. Copy the complete redirected
address-bar URL after authorization, even when the browser displays an
unreachable loopback page.
If the server warns that it cannot save the master secret, another process
may derive a different token. Check write access to the global configuration.
Do not replace an existing secret simply to add a setting.
=head2 Connected, but there are no tools
lib/AnyEvent/MCP/Intro.pod view on Meta::CPAN
tool reports configuration even when a utility-dependent tool is absent.
For a multibroker, call C<discover-brokers> to see individual connection
errors, then C<list-tools> to refresh definitions.
A third-party child might require initialization or features this client
implementation does not support. See L<AnyEvent::MCP::Client> and its
transport documentation for limitations.
=head2 There is no approval dialog
First check whether the tool was denied by a saved rule or is waiting for
the client's own approval. An allowed operation needs no server prompt.
The default helper runs on the server machine. A background service may
have neither a usable display nor C</dev/tty>, even though your interactive
shell has both. Supply the appropriate display environment, a usable helper,
or C<--ask auto> with a modern client supporting form elicitation.
C<--ask mcp> reports an error if that support is absent.
Setting an asking mode cannot make an unsupported client display a form.
Check stderr and the tool error instead of assuming a missing dialog is
an approval.
=head2 It asks again after I chose allow
Allow-once covers one tool invocation, including its permission retries.
The next invocation asks again. Use allow-always to save a rule.
Inspect the saved rule's action, workdir and grants. Listing names and
reading contents are different actions. A rule for one exact workdir
spelling will not match a differently spelled startup argument.
A request for extra authority, such as networking, does not match a
workdir-read-only allow rule. Earlier ask or deny rules take precedence.
Confirm that the prompt's permanent rule was successfully saved and that
you are editing the rules for the actual child name, not merely its
multibroker prefix.
=head2 The worker cannot see a directory or executable
The worker sees granted mounts and trusted baseline mounts, not everything
the server process can see. Check C<workdirs> for the selected path, mounts
and runtime paths. A symlink target outside those mounts is unavailable.
The ordinary sandbox runtime includes C</usr>, C</bin> and system libraries,
but not C</opt>. For tools or libraries under C</opt>, add this to
C<[program mcp-fs]> in the private global configuration:
mount.opt = ro:/opt
This assumes C</opt> exists. Restart mcp-fs after changing trusted mounts.
If your worker Perl is C</opt/bin/perl>, you can also set
C<executable.perl = /opt/bin/perl> in that program section. Supply the
actual executable path: selecting it does not itself mount the
executable or its dependencies inside the worker.
A trusted mount supplies baseline access to every worker using it; a
writable mount likewise supplies baseline write authority.
An arbitrary command might also need its user's configuration or repository
credentials under HOME, or network access. Request the corresponding grants
when needed; do not assume C<workdir-read> includes them.
For example, CVS over SSH can use C<workdir-read> and C<ssh-agent>.
The latter includes network access, the agent socket and C<~/.ssh/known_hosts>,
without exposing private key files or the rest of HOME. The broker must
inherit C<SSH_AUTH_SOCK> from your environment. User-configured helpers
outside the runtime mounts need an explicit trusted mount.
An explicit exec grant array replaces the default rather than adding to it.
=head2 My configuration change seems to be ignored
Check which file and section the process actually selects. Global settings
use the server user's C<HOME>/C<XDG_CONFIG_HOME>. Instance names, program
basenames and multibroker prefixes serve different purposes.
Restart after changing startup settings, mounts or utilities. Permission
rules need no restart, but their order still matters. Refresh discovery
after a child restart; reconnect a client that retains an old tool list.
C<--print-url> reads the current configuration, which can differ from an
older running process.
=head2 The command failed, but the MCP request succeeded
C<exec> reports exit status, signal or timeout as a normal tool result.
A successful MCP exchange is not a successful Unix command.
Conversely, a transport failure does not prove a command did nothing:
it may have completed before the connection was lost. Calls are not
automatically replayed after a transport failure.
=head1 TESTING
The default test level is 0. It checks protocol behaviour, permissions,
validation and ordinary process operation, including worker cancellation
and reaping. Watchdogs allow slow machines time to finish; they do not
make successful tests wait for the whole timeout.
prove -Ilib t
Set C<PERL_ANYEVENT_MCP_TESTS> to 1 to include the extended tests:
PERL_ANYEVENT_MCP_TESTS=1 prove -Ilib t
Level 1 adds GNU find/grep/sed/patch operations, real Vim scripting, browser
transfer integration, short idle-lifetime tests, a helper descendant holding
a pipe open, and tests which deliberately wait for a command timeout or
cancel a form helper across several processes. Run this level before releases.
Values greater than 1 currently select the same tests as level 1.
Missing GNU utilities or required Vim functions cause explicit skips even
at level 1. The permission and form UI tests use a YAD stand-in: neither
level requires YAD, X11 or an interactive display. The filesystem tests
use a bubblewrap stand-in and check arguments and worker behaviour; they
do not test kernel isolation. The browser tests use simulated peers.
=head1 WHERE TO GO NEXT
For available tools and server-specific options:
perldoc mcp-fs
perldoc mcp-multibroker
perldoc mcp-vim
( run in 1.325 second using v1.01-cache-2.11-cpan-3f962061493 )