Sharing Local MCP Tools Between Machines with MCPWire

A teammate’s agent needs to use a tool that already works on your machine, but running it on theirs means installing the server, configuring credentials, and reproducing access to the systems behind it. If the tool depends on a local database, an internal network, or a browser session, copying its configuration may not be enough.
I built MCPWire to share that existing execution environment through MCP, while keeping the server process and its configured credentials on the host. The guest runs a small bridge that looks like a local stdio MCP server to their client, and requests travel across a Noise-encrypted peer connection to your machine.
The interesting part is where execution and authority end up, because keeping a credential on your laptop still lets someone else exercise some of its permissions through an allowed tool. The architecture has to make that delegation explicit.
Two bridges around an existing server
MCPWire has two commands, share on the host and join on the guest, with the existing MCP server running as a child process on the host side. The client and server continue exchanging newline-delimited JSON-RPC through standard input and output.
On the guest, join connects the client’s stdin and stdout to the peer socket, while diagnostic messages go to stderr so they do not corrupt the protocol stream. It accepts one host connection per join process.
On the host, share inspects the traffic before forwarding it to the child process, rejecting disallowed tool calls and filtering the tool descriptors returned to the guest. The implementation is small enough to follow in join.ts and share.ts, without introducing a separate application protocol around MCP.
Each connected peer gets its own MCP child process, so two guests do not send their initialization messages into the same server process or share its in-memory session state. They can still reach the same files, database, and external services, and starting a separate process does not create an operating-system sandbox.
Discovery and the session ticket
When the host starts sharing, it generates 32 random bytes and encodes them as a wire:// ticket, which both sides use as the discovery topic in Hyperswarm. The host announces on that topic, and the guest looks for a host using the same value.
The default discovery network is the public Hyperswarm DHT, with HyperDHT handling connection establishment and attempting UDP hole punching across NATs. MCPWire does not operate its own relay service, but the connection still depends on networking infrastructure and the behavior of the networks at both ends.
That means a restrictive firewall or NAT configuration can prevent a connection, even when both developers have internet access and the ticket is correct. A peer connection reduces the infrastructure you need to deploy, while giving up the predictable reachability you might get from a publicly accessible hosted endpoint.
The ticket identifies the discovery topic, and the Noise connection supplies encryption; the ticket itself is neither an encryption key nor an identity certificate for your teammate. MCPWire does not bind it to a named user, so it must be handled as an access credential and delivered through a trusted channel.
A ticket remains reusable while its host session is running, and starting a new host session generates a new one, rather than maintaining a permanent address for that server. Custom DHT bootstrap nodes are configurable through MCPWIRE_BOOTSTRAP, as shown in the network setup code.
Following a tool call through the host
Suppose you expose a filesystem server with read_file and list_directory allowed, and the guest asks their agent to inspect a file in the shared directory. The agent first discovers the server’s tools, then sends a tools/call request through its local MCP client.
When the server returns its tool descriptors, MCPWire filters the list using the host’s policy, so the guest sees the tools selected for that session. When an actual call arrives, MCPWire checks the requested tool name again before writing the request to the server’s stdin.
Both checks matter, because hiding write_file from the advertised list does not stop a client from sending a request with that name anyway. The execution check is what prevents a disallowed call from reaching the child process.
The policy implementation gives an explicit deny precedence over an allow, and an omitted allowlist permits every tool that has not been denied. An empty allowlist permits no tools.
With --confirm, an allowed call also requires approval in the host terminal, where the operator can reject it, approve it once, or approve that tool for the rest of the session. That last choice is shared session policy, so it should not be interpreted as approval limited to one guest connection.
The boundary of a tool permission
Allowing read_file controls which operation can be requested, but the filesystem server still decides which paths are valid and which files its process can read. Similarly, allowing a SQL tool says nothing about whether the submitted query can modify data, unless the server or database enforces that restriction.
The current policy checks tool names, without validating their arguments, filtering MCP resources or prompts, or restricting the child process’s access to the host. Credentials with limited permissions and a narrowly configured server remain necessary, even when the session exposes only one tool.
This also explains the difference between keeping credentials local and keeping data local, because a tool can use a local credential to retrieve information that it then returns to the guest. MCPWire avoids requiring the guest to configure that credential, but the permitted responses still cross the connection and enter the guest’s MCP client.
Optional --redact-secrets processing scans outbound messages for recognized patterns before sending them, replacing matches with [REDACTED_BY_MCPWIRE]. Pattern matching can miss a private value or change legitimate output, so it adds a check on responses without establishing that everything leaving the machine is safe.
Session limits and failure semantics
The host can set a time limit with --ttl and a forwarded tool-call budget with --max-calls, with the call counter shared across the session’s peers. Neither limit is enabled by default, and redaction and manual approval also require explicit flags.
On a peer disconnect, the host requests termination of that peer’s child process, but terminating a process cannot reverse an operation already committed to a database or external service. A tool that completed a write just before the connection failed may leave the guest without its response.
MCPWire does not automatically replay calls after a disconnect, because resending a request could execute that write a second time while making the failure look recovered. The application needs an operation identifier, a way to inspect the result, or another recovery mechanism before a retry can be considered safe.
The optional session recorder stores local events and response metadata, including timing and success or error status, rather than full response bodies. Its records also include the ticket, server command, and unredacted argument previews, so a recording can itself contain sensitive information even when response redaction is enabled.
A small filesystem example
For a first test, use a dedicated directory with disposable data, then expose only the read and directory-listing tools for a session lasting 30 minutes. The CLI requires Node.js 18 or newer, while the pinned filesystem server below requires Node.js 24 or newer on the host.
mkdir -p "$HOME/mcpwire-demo" printf 'Hello from the host.\n' > "$HOME/mcpwire-demo/hello.txt" npx -y @mcpwire/cli share \ --name shared-files \ --allow "read_file,list_directory" \ --redact-secrets \ --ttl 30m \ -- npx -y @modelcontextprotocol/server-filesystem@2.0.1 \ "$HOME/mcpwire-demo" The host prints a client configuration containing its session ticket, which the guest adds to a client that supports stdio MCP servers. A configuration using the mcpServers convention has this shape, with the placeholder replaced by the full ticket from the host:
{ "mcpServers": { "shared-files": { "command": "npx", "args": [ "-y", "@mcpwire/cli", "join", "wire://PASTE_THE_FULL_TICKET_FROM_THE_HOST" ] } } } Give the guest the absolute path to hello.txt on the host, then ask their agent to read it and check that the returned text matches the file. A useful second check is a direct write_file request, which should be rejected by MCPWire even though the underlying filesystem server implements it.
Where this architecture fits
For temporary collaboration, this arrangement lets a teammate use an existing local environment without reproducing its dependencies or moving the server to a hosted service. For a persistent team endpoint, requirements such as named-user authentication, centralized policy, and reliable reachability may justify a different deployment model.
The design keeps execution on the host and puts selected controls in front of it, while leaving argument validation, data permissions, and recovery from ambiguous execution to the systems that own those decisions. A remote agent can only be constrained as precisely as those underlying boundaries allow.
原文:https://www.a8q9.cn/en/article/sharing-local-mcp-tools-between-machines-with-mcpwire-9976ff.html
