dshlink
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@dshlinksend a message to bob-pc saying I'll be online tonight"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
dsh-link
Peer-to-peer messaging and file exchange between DeepSeek Harness (DSH) instances — across machines, and across the open internet if you want.
Run one dshlink node on each machine. They can then send each other messages, pull and push
files, queue messages while the other side is offline, and appear inside DSH as native tools.
Zero runtime dependencies. Only Node built-ins (
node:http,node:crypto,node:fs). There is nothing tonpm install— clone and run.Three ways to connect. Direct LAN/WAN, FRP STCP through a public relay (no inbound ports, recommended), or relay forwarding (A → hub → B).
Two ways into DSH. MCP (17
mcp__dshlink__link_*tools viadsh-mcp-client) and the CLI (dshlink …, which an agent can drive from a shell).An agent that notices messages.
integrations/dsh-link-bridge/is a DSH Host plugin that wakes a conversation when unread messages arrive — one conversation per topic, retired sessions archived.Opt-in remote visibility (0.3). With explicit switches, a peer can browse your DSH workspaces and sessions, read a transcript, or ask your bridge to create/prompt/archive a conversation. All of it is default-off.
Files stay inside declared roots. Paths go through realpath validation,
.env/*.key/.sshare denied by default, writes are off by default, and transfers are sha256-checked on both ends.
Contents
Related MCP server: dsh-chatgpt-bridge
Quick start (two machines)
Both machines need Node.js >= 20. Nothing else.
On machine A (this repository, e.g. D:\dsh-link):
# 1) create the node: name, port, shared folder (repeat --root for more roots)
node bin\dshlink.mjs init --name alice-pc --port 8787 --root ws=D:\ws
# the output prints a one-time inbound token and an STCP secretKey for the peer
# 2) start it (HTTP API + MCP endpoint)
node bin\dshlink.mjs serveDo the same on machine B, then register B from A:
node bin\dshlink.mjs peers add --name bob-pc --url http://<B-ip>:8787 --token <B's token>
node bin\dshlink.mjs peers ping --name bob-pc
node bin\dshlink.mjs send --to bob-pc --subject hi --body "hello from A"
node bin\dshlink.mjs pull --peer bob-pc --root ws --path report.pdf --out .\report.pdfOn B, read and answer:
node bin\dshlink.mjs inbox --unread
node bin\dshlink.mjs show --id msg_xxx
node bin\dshlink.mjs send --to alice-pc --body "got it" --thread msg_xxxOffline is fine. If the peer is unreachable the message goes to the local outbox (
sendreturnspending). Laterdshlink flushretries it, or the peer picks it up withdshlink sync --from <you>.
Wire it into DSH
Add one MCP entry pointing at the local node — one entry per machine, not per peer (remote
nodes are reached by name through the peer argument):
- id: mcp-dshlink
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: dshlink
transport: streamable-http
url: http://127.0.0.1:8787/mcpRestart DSH and the native tools appear: link_status, link_peers, link_send_message,
link_inbox, link_read_message, link_reply, link_sync, link_flush,
link_list_files, link_stat_file, link_pull_file, link_push_file, link_help, plus
the opt-in link_dsh_workspaces, link_dsh_sessions, link_dsh_transcript and link_call.
Loopback requests are trusted by default, so the local MCP client needs no token. To require one
(and to turn loopback trust off), see docs/DSH-INTEGRATION.md.
You can also copy integrations/dsh-skill/ to $DSH_HOME/skills/dshlink/ so an agent knows how
to drive the CLI.
One command does the wiring
node bin\dshlink.mjs install-dsh --profile web --write # MCP entry + skill, preview first
node bin\dshlink.mjs install-dsh --profile web --bridge --bridge-workspace "D:\dshlink-bridge" --writeinstall-dsh edits the profile's cordis.patch.yml (timestamped backup first) — a restart of the
DSH host is needed once for the bridge entry to load.
Wake a conversation automatically (bridge)
MCP alone answers "can the agent use the tools"; it does not answer "does the agent know a message
arrived". integrations/dsh-link-bridge/ is a DSH Host plugin: it polls the local node and,
when unread messages appear, reuses or creates a conversation in a dedicated workspace and injects
a prompt containing the message and the peer name. The agent then reads the full text, pulls
attachments, replies with link_reply and marks the message read.
Since 0.3 the bridge is topic-aware: one conversation per topic, new conversation when a topic hits
its wake budget, age limit or idle timeout, and retired conversations are archived through DSH's
own registry (hidden from the sidebar, logs kept). Configuration, state file layout and the
self-check are documented in
integrations/dsh-link-bridge/README.md.
Point
workspacePathat a dedicated empty directory (e.g.D:\dshlink-bridge) and add it — together with the node's data directory — todsh.excludeWorkspaces. Otherwise bridge sessions pollute a normal workspace, and the node's own data directory (which holds peer tokens) can end up visible through the DSH view.
See and drive the other machine's DSH (0.3)
Two capabilities, both off by default:
# read-only view: list the peer's workspaces and sessions, read a transcript
dshlink dsh enable --write
dshlink dsh workspaces --peer bob-pc
dshlink dsh sessions --peer bob-pc --archived
dshlink dsh transcript session-xxxx --peer bob-pc --tail --limit 40
# files: one virtual root over "all workspaces" (first segment = workspace id or title)
# config: "files": { "roots": [ { "name": "allws", "kind": "workspaces", "read": true } ] }
dshlink ls --peer bob-pc --root allws
dshlink pull --peer bob-pc --root allws --path "<workspace-id>/src/index.mjs" --out x.mjs
# actions: let the peer read/create/prompt/rename/archive conversations (explicit allow-list)
dshlink dsh enable --actions workspaces.list,sessions.list,sessions.read,sessions.prompt --write
dshlink dsh call sessions.prompt --peer bob-pc --wait 30 --params '{"sessionId":"session-xxxx","text":"status?"}'The read-only view is disk-level: the node reads storages/workspace.json and the session logs
itself, so any peer running node 0.3 can do it. Actions go through the capability channel:
peer → your node → your bridge (inside DSH) → the DSH service, so they need bridge 0.3.
Opening the DSH view lets a token-holding peer read every workspace path and every conversation on the machine. Only enable it for peers you trust, and read
docs/DSH-ACCESS.mdfirst.
NAT traversal with FRP STCP
When the machines share no LAN and you have no public port, put an frps on a host with a public
address. dsh-link does not ship frp binaries — download frp v0.71.0 yourself from
https://github.com/fatedier/frp/releases/tag/v0.71.0 and place frpc / frpc.exe where the
node looks for it (vendor/frp/windows-amd64/, vendor/frp/linux-amd64/, DSHLINK_FRPC, or
--frpc). node scripts/verify-vendor.mjs checks whatever is there against the recorded
upstream sha256 values.
Generate the credentials (and, optionally, mutual-TLS certificates) with the bundled scripts:
node scripts/gen-secrets.mjs # frp token + STCP secretKey + dsh-link inbound token
powershell -ExecutionPolicy Bypass -File .\scripts\gen-certs.ps1 -ServerName <frps-host> # Windows: CA + server + client certs
./scripts/gen-certs.sh <frps-host> # Linux / WSL / macOSRead docs/FRP.md §2.3 before exposing an STCP tunnel: the secretKey — not the
dsh-link token — is the real boundary on that path.
# public server (once): frps only needs 7000/tcp open; template in docs/FRP.md
# on each DSH machine:
node bin\dshlink.mjs tunnel setup --server <frps-host> --port 7000 --token <frp token>
node bin\dshlink.mjs tunnel sync # writes frpc.toml, starts frpc (background)
node bin\dshlink.mjs tunnel share # prints the peer's half: proxyName + secretKeyThe other side joins with one command:
node bin\dshlink.mjs tunnel import --peer alice-pc --frp-server-name dshlink-alice-pc \
--frp-secret <secretKey> --token <alice-pc's dshlink token>
node bin\dshlink.mjs peers ping --name alice-pcEvery node exposes its 127.0.0.1:8787 as an STCP provider and opens a local visitor port
(19100+) to reach a peer. Both directions are outbound; the only public port is the relay's 7000,
and the dsh-link API itself is never exposed. See docs/FRP.md.
Command reference
Command | What it does |
| Write the config, generate the inbound token and STCP identity (`--root name=path[:ro |
| Start the HTTP API + |
| Local status, peer registry, liveness probes |
| Manage inbound tokens (only the sha256 is stored; the plaintext is shown once) |
| Messages: send, list, read one, pull the peer's queue, retry the outbox |
| List a directory, pull a file, push a file (needs |
| Local audit log (who pulled which file, when) |
| FRP tunnel configuration and process management |
| Register both directions from one portable pairing code |
| Self-check: config, roots, port, MCP, peers, tunnel, DSH view — non-zero exit on failure |
| Write the MCP entry into the profile patch and install the skill (preview by default) |
Every command accepts --json (for agents and scripts) and --data-dir / --config.
Exit codes: 0 success, 2 usage/config error, 3 message still queued, 4 hash mismatch.
Config hot reload (since 0.2.1)
serve re-reads the config file before every request, so adding a peer, a token or a file root
takes effect immediately — no restart. /api/v1/status reports runtime.configReloads, and
dshlink doctor compares the running process with the file on disk. Only bind, port,
limits.requestTimeoutMs and the tunnel process itself still need a restart.
Keep it running (Windows)
scripts\start-dshlink.cmd # idempotent: starts the node and frpc hidden if they are not upstart-dshlink.cmd → start-dshlink.vbs → start-dshlink.ps1 checks port 8787 and the frpc process
and starts whatever is missing, always with -WindowStyle Hidden. Register the .vbs as a
scheduled task (at logon, plus a periodic top-up) to survive reboots.
Security model
Item | Default | Notes |
Bind address |
| Only the local machine; |
Auth | outbound token + optional loopback trust | Inbound tokens stored as sha256; loopback is trusted by default |
File roots | explicit | Each root is read-only unless |
Deny list |
| Overridable per node |
Uploads | off |
|
Limits | 32 MiB request, 1 MiB message, 60 s timeout | See |
Audit | on |
|
DSH view (0.3) | off |
|
Capability channel (0.3) | off |
|
Loopback trust + a tunnel that terminates on loopback means inbound tokens are not checked on
that path. If your machines are joined over an untrusted network, set
auth.trustLocalhost: false on both ends and give local clients explicit tokens. See
SECURITY.md for the full hardening checklist.
Repository layout
dsh-link/
├── bin/dshlink.mjs # CLI entry point (init/serve/send/pull/tunnel/...)
├── src/
│ ├── config.mjs # config, identity, token hashes, peer registry
│ ├── store.mjs # inbox/outbox JSONL, read marks, delivery state, audit
│ ├── fsroot.mjs # shared-root sandbox (realpath, deny list, sha256)
│ ├── server.mjs # inbound HTTP API (auth, limits, Range download, audit)
│ ├── mcp.mjs # MCP streamable-HTTP endpoint (17 link_* tools)
│ ├── ops.mjs # delivery, queues, sync, relay, file operations
│ ├── client.mjs # outbound HTTP client (streaming download + hash check)
│ ├── tunnel.mjs # FRP STCP: frpc.toml generation, process management, share/import
│ ├── reload.mjs # config hot reload
│ ├── dshview.mjs # DSH workspaces/sessions/transcripts read straight from disk (0.3)
│ ├── capabilities.mjs # capability channel: command queue, policy, audit (0.3)
│ └── pairing.mjs # pairing codes + DSH install (MCP entry / skill / bridge)
├── integrations/
│ ├── dsh-link-bridge/ # DSH Host plugin: wake a conversation on unread messages (0.3: topics + archive)
│ ├── dsh-skill/ # agent-facing skill (SKILL.md)
│ ├── dsh-mcp-settings.example.yaml
│ └── wan-lab/ # two-machine WAN test lab (WSL helper scripts)
├── scripts/ # autostart, bundle build/verify, gen-secrets, gen-certs, vendor check
├── vendor/frp/ # sha256 manifest only — download the frp binaries yourself
├── test/ # 15 test files / 116 checks + CLI smoke script
├── docs/ # architecture, design, protocol, DSH access, FRP, operations, troubleshooting
└── examples/ # sanitised config samplesTesting
node test/run.mjs # 116/116, ~10 s
node --test --test-concurrency=1 test/ # the same suites via node:test
powershell -NoProfile -ExecutionPolicy Bypass -File test/cli-smoke.ps1 # 13/13, two real processestest/frp.test.mjs really starts frps plus two frpc processes and moves messages and files
through the STCP tunnel only; it skips itself when you have not downloaded the frp binaries.
The MCP test drives dsh-link through the @modelcontextprotocol/sdk that DSH itself uses, and
scripts/verify-bundle.ps1 -Bundle <zip> -PeerDataDir <throwaway-data-dir> unpacks a built
portable bundle and runs an end-to-end pairing against a node on the same machine.
Cross-internet verification (Windows node ↔ WSL node through a public frps) is written up in
docs/WAN-VERIFICATION.md.
Known limitations
Pairing is manual: both sides exchange tokens (
invite/peers accept); there is no automatic key exchange.FRP support generates STCP configuration only — deploying the public
frpsand its certificate is up to you.Large files have no chunking or resume (HTTP Range is supported server-side, the client does not use it yet).
Inline attachments are capped at 512 KiB; bigger payloads go through the file API.
The DSH view reads session logs from disk, so a DSH format change needs a dsh-link update; Node without zstd (older than 22.15) reports
zstd-unsupported— use the bridge's capability channel instead.Message counts in the session list are a lower bound (the index reads the head of each log).
No end-to-end encryption. Traffic relies on the transport: loopback, a LAN you control, frp STCP with TLS, or your own HTTPS reverse proxy.
Documentation
Document | Contents |
Component map: node, CLI, MCP, bridge, capability channel; files and versions | |
Deployment, autostart, upgrades, token rotation, wake-up troubleshooting | |
HTTP API and message/file wire protocol | |
MCP entry, skill install, bridge install, profile patch mechanics | |
The 0.3 switches: read-only view, transcripts, capability actions, threat model | |
Public | |
What was actually verified across the internet, and how | |
Symptom → cause → fix, plus a one-minute health check | |
Why the pieces look the way they do | |
Trust model and hardening checklist | |
Release history |
License
MIT. frp is a separate project by fatedier, licensed under Apache-2.0 — it is not redistributed here.
AIGC notice — this repository is generated and maintained entirely by AI. What that means for you (and what to verify yourself) is written down in
AIGC-Notice.md.
This server cannot be deployed
Maintenance
Related MCP Connectors
Secure P2P File Transfer, Encrypted Chat & Communication | Decentralized P2P & AES-256-GCM encryption | Zero cloud logs. Zero registration. For humans and autonomous AI agents / MCP servers.
Shared long-term memory vault for AI agents with 20 MCP tools.
AI Reasoning Cache & Consensus Layer with 11 MCP tools via Streamable HTTP.
End-to-end encrypted messaging and work coordination for autonomous AI agents.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenancePeer-to-peer WebRTC communication for AI agents, connecting autonomous coding agents over low-latency WebRTC DataChannels with room-based signaling, video stream bridging, and multi-agent coordination — all exposed as MCP tools.12 npmMIT
- AlicenseNot gradedqualityAmaintenanceMCP bridge enabling ChatGPT to create, view, and control DeepSeek Harness agent sessions, with session management, messaging, and approval tools.174 npm15MIT
- AlicenseNot gradedqualityBmaintenanceMCP server bridging ChatGPT to DeepSeek Harness, exposing 13 tools for task submission, status tracking, result retrieval, project/session management, and human-in-the-loop approvals.1MIT
- FlicenseAqualityCmaintenanceMCP server wrapping DeepSeek's agent harness tools into minimal/standard modes, enabling persistent shell, file editing, search, job management, and more via natural language.2-