alidocs-web-mcp

alidocs-web-mcp

Bridges the editing capabilities of an already-open DingTalk document in the browser to any MCP host via standard stdio, forwarding tools to the page's internal MCP server over a local WebSocket. Supports read/write operations through suggested edits, pairing-based security, and zero runtime dependencies.

Category
访问服务器

README

alidocs-web-mcp

Let your AI agent read and edit the DingTalk Doc you already have open — in your own browser, under your own login, with every change landing as a suggestion you approve or discard.

npm CI License: MIT Node

中文文档


Why this exists

You are editing a document in your browser. Your AI agent lives somewhere else — an IDE, a terminal, a desktop app. To let the agent help, you normally have two bad options:

Option Why it falls short
Server-side document API Cannot express block-level suggestions awaiting human review, and needs its own credentials and permission plumbing
Give the agent its own browser Splits your session in two: the document you are looking at is not the document the agent drives

The capability you actually want — structured, block-level editing that renders as a reviewable suggestion — only exists inside the page runtime. So instead of recreating it elsewhere, this tool connects your agent to the page you already have open.

The direction is inverted on purpose. The page dials out to a local process; the local process never reaches into your browser. That is what makes it work without debug ports, browser extensions, or any change to your agent's host application.

What you get

  • Standard MCP over stdio — works with any MCP host (IDE, terminal, desktop agent). No custom protocol to adopt.
  • Zero runtime dependencies — plain Node ≥ 18, hand-rolled WebSocket framing. npx and go.
  • Zero code injection — the pairing credential is data, never a script. Nothing is ever eval'd.
  • Read-only by default — writes require an explicit flag, and land as suggestions rather than saved edits.
  • Loopback only — binds 127.0.0.1, enforces an Origin allowlist, and authenticates with an HMAC challenge-response.

Requirements

[!IMPORTANT] This bridge is half of a pair. The document page must ship a matching connector that discovers the bridge and initiates pairing. Without it, the bridge starts fine but no document tools will ever appear.

As of now that connector is not yet generally available in production DingTalk Docs. If get_bridge_status keeps reporting connected: false while the bridge is clearly running, this is almost certainly why — not a misconfiguration on your side.

  • Node.js ≥ 18
  • A DingTalk Doc page open in a browser, with the page-side connector present

Install & run

Register it with your MCP host — no global install needed:

{
  "mcpServers": {
    "alidocs-web-mcp": {
      "command": "npx",
      "args": ["-y", "alidocs-web-mcp", "--allow-write"]
    }
  }
}

Or run it directly:

npx -y alidocs-web-mcp              # read-only
npx -y alidocs-web-mcp --allow-write # allow the page to register write tools

The bridge tries ports 19837 → 19838 → 19839 and takes the first free one. The page discovers it by probing those same ports, which is why they are fixed rather than random.

How pairing works

Three steps, and the agent can drive all of them:

  1. Call get_pairing_code → you get a pairing code (a string of data) and the port.
  2. Put that code into the page's "local agent" pairing field — the agent can type and click it, or you can paste it yourself.
  3. The page completes an HMAC handshake. From then on tools/list includes the document tools.

After a refresh or same-tab navigation, the page reconnects automatically using the code it kept in sessionStorage. No re-pairing.

Architecture

flowchart LR
    subgraph outside["Outside the browser"]
        host["MCP host<br/>(IDE / terminal / desktop agent)"]
        bridge["alidocs-web-mcp<br/>pairing + dumb pipe"]
    end
    subgraph browser["Your browser, your login"]
        page["Document page<br/>MCP server + tools"]
        doc["Document<br/>suggestion state"]
    end

    host <-->|"stdio · standard MCP"| bridge
    page -->|"1 · discover: GET /health"| bridge
    page <-->|"2 · ws://127.0.0.1 · HMAC handshake<br/>3 · JSON-RPC passthrough"| bridge
    page --> doc

    classDef trust fill:#eef7ff,stroke:#4b86c9
    classDef local fill:#f6f6f6,stroke:#999
    class browser trust
    class outside local

Two properties worth noting:

  • The page always initiates. The bridge only listens on loopback; it never dials into the browser.
  • The bridge is a dumb pipe. Beyond its own three tools, it merges tools/list and forwards tools/call verbatim. It does not understand document semantics — so the page can add tools without changing the bridge.

Data flow

sequenceDiagram
    autonumber
    participant H as MCP host
    participant B as alidocs-web-mcp
    participant P as Document page
    participant D as Document

    Note over B: bind 127.0.0.1, generate a per-session pairing code (CSPRNG)

    H->>B: tools/call get_pairing_code
    B-->>H: pairingCode + port (data, never a script)

    P->>B: GET /health on 19837/38/39
    B-->>P: { service, originAllowed, ... }

    Note over H,P: the code reaches the page via UI (typed or pasted)

    P->>B: WS upgrade (Origin checked here → 403 if not allowed)
    B-->>P: challenge { nonce }
    P->>B: auth { mac = HMAC-SHA256(pairingCode, nonce) }
    B-->>P: ready { sessionId }
    B->>H: notifications/tools/list_changed

    H->>B: tools/call read_document
    B->>P: forwarded verbatim (id remapped)
    P->>D: read
    D-->>P: content
    P-->>B: result
    B-->>H: result

    H->>B: tools/call update_block
    B->>P: forwarded verbatim
    P->>D: write as a suggestion (not saved)
    Note over D: you approve or discard it

The pairing code itself is never transmitted — only HMAC(code, nonce) is. Someone who squats the port and captures the mac still cannot recover the code.

Bridge tools

Everything else you see in tools/list comes from the page; the bridge only forwards it.

Tool What it does
get_pairing_code Returns the pairing code (data) plus the port and write-permission state. Never returns a script.
get_bridge_status Port, whether a page is paired, whether its MCP session is ready, in-flight requests, Origin allowlist, audit log path. Start here when a call fails.
revoke_session Rotates the pairing code and drops the session. Anything the page stored becomes invalid immediately.

CLI options

Flag Meaning
--port <n> Use only this port instead of the candidate set
--allow-origin <pattern> Append an allowlist entry (repeatable); * matches a single label or port, never across . : /
--only-origin <pattern> Replace the default allowlist entirely
--allow-write Allow the page to register write tools (read-only otherwise)
--audit-log <path> / --no-audit Audit log location, default ~/.alidocs-web-mcp/audit.log
--handshake-timeout-ms <n> Handshake deadline, default 10000
--request-timeout-ms <n> Timeout for requests forwarded to the page, default 60000

The default allowlist contains only the official document origins plus local dev hosts, enumerated one by one. There is deliberately no wildcard like https://*.dingtalk.com — that would let any subdomain reach your local bridge.

Security posture

This tool opens a listening port on your machine, so it is worth being explicit. Four attack directions, each with its own defence:

Direction Defence
A malicious web page → your local bridge Loopback-only bind plus an Origin allowlist enforced during the WS upgrade (403 before any state changes)
A malicious local process → the bridge A per-session CSPRNG pairing code. Origin headers can be forged by non-browser clients; the code cannot be guessed
A local impostor squatting the port → your page HMAC challenge-response, so the code never goes over the wire; plus port-contention detection
A poisoned distribution or prompt injection → your page Credentials travel as data, never as code; read-only by default; writes only ever become suggestions

Also: /health responses are tiered by Origin (outsiders cannot read connected or allowWrite), one session at a time, and the audit log records tool names and argument keys — never argument values or the pairing code.

Full threat model and the S1–S13 control list: docs/security.md. Reporting a vulnerability: SECURITY.md.

Troubleshooting

Symptom Likely cause
tools/list only shows the three bridge tools No page is paired yet. Run get_pairing_code and complete pairing.
get_bridge_status shows connected: false forever The page has no connector (see Requirements), or the page is on an origin outside the allowlist.
ORIGIN_REJECTED Your document origin is not allowlisted. Add it with --allow-origin.
PORT_CONTENDED All three candidate ports are taken. Free one, or pass --port.
AUTH_FAILED right after a bridge restart Expected: restarting rotates the code. Pair again with the fresh one.
PAGE_DISCONNECTED mid-call The page navigated or refreshed. It reconnects on its own; retry the call.
PAGE_TIMEOUT The page did not answer within --request-timeout-ms.

Development

npm install       # dev deps only (typescript, @types/node)
npm run build     # tsc → dist/ (CommonJS + .d.ts)
npm test          # runs against dist/ — i.e. against what actually ships
npm run verify    # build + test
npm run typecheck # tsc --noEmit

Source is TypeScript under src/; the published artifact is compiled CommonJS in dist/. Tests stay in plain JavaScript on purpose — they exercise the built output rather than the sources, so the thing being verified is the thing being published. Because the bridge uses a fixed port set, tests run serially.

Downstream projects can build contract tests against a real bridge process:

const { startTestBridge, connectFakePage } = require('alidocs-web-mcp/test-helpers');

See CONTRIBUTING.md and AGENT.md (the latter lists constraints that must not be violated, e.g. "never return executable code").

Project status

Early (0.x). Verified today:

  • 49 automated tests over the compiled artifact
  • 12 Origin bypass attempts (subdomain suffixing, full-URL-in-Origin, trailing dot, case variants, scheme downgrade, null, missing, port injection, backslash confusion) all rejected at the real upgrade path
  • Business messages sent before the handshake are rejected and the socket closed
  • Cross-implementation HMAC agreement with the page side, pinned by shared test vectors

Not yet verified: a real MCP host consuming this server end to end. Development so far used a purpose-built minimal MCP client, which also means the notifications/tools/list_changed path — how a host learns that document tools appeared after pairing — has only been covered by automated tests with a fake host, not observed against a real one.

Documentation

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选