Back to home@yangzhe1991

dsh-futu-mcp

DSH plugin: connect to Futu (富途) MCP via OAuth 2.1 with deferred authorization; tokens stored securely outside the workspace (~/.dsh, 0600)

Stars
0
Language
TypeScript
Created
Aug 31, 2026
Updated
Aug 31, 2026
GitHub repo

Introduction

@yangzhe1991/dsh-futu-mcp

English | 中文

npm version npm downloads license dsh-plugin

A DSH (DeepSeek Harness) plugin that connects the agent to the official Futu (富途) MCP server and registers its tools on ctx.tools under mcp__futu__<toolName>.

What it does

  • Connects to https://mcp.futunn.com/mcp using the pagination-aware MCP Streamable HTTP transport and publishes server tools to the agent as callable tools (mcp__futu__*).
  • Handles Futu's OAuth 2.1 authorization completely in-process:
    • RFC 9728 / RFC 8414 discovery, dynamic client registration (RFC 7591), PKCE S256, refresh-token rotation.
    • On first use your browser opens the Futu authorization page; after you authorize, tokens are saved and reused silently afterwards (automatic refresh; a new browser prompt appears only when refresh fails).
  • Scopes are strictly capped to what you configure (default: read-only quote:read + trade:read). The plugin intercepts the server's protected-resource metadata (which advertises every scope including trade:write order placement) and narrows it to your configured set, so neither client registration nor the authorization request can ever request more than you approved.
  • Deferred authorization — zero Futu traffic until you actually use it: plugin load only registers the previously synced tool list from the credentials cache (so the agent can see mcp__futu__* tools) with no network, no token refresh, no browser popup. A conversation that never touches Futu costs nothing. The moment a Futu tool is actually called, the plugin connects; if the token is stale it refreshes silently (only when the refresh token itself is expired/revoked — a 14-day cycle — does the browser login page open, and the same call then retries and succeeds). The one exception: on very first use there is no cached tool list yet, so the first sent message triggers the connect + login, after which the cache exists and everything is call-time-only.

Behavior

WhenWhat happens
dsh web starts / plugin loadsOnly reads the credentials file: cached tool list is registered (agent sees mcp__futu__*). No network, no token refresh, no browser.
You chat about anything unrelatedNothing (zero Futu activity). Tool calls never reach Futu.
A Futu tool is actually calledConnect + sync happens now; stale token → silent refresh (no UI).
Refresh token expired/revoked (≤14 days) and a Futu tool is calledLogin page opens at that moment; after you authorize, the same call retries and succeeds.
Very first use (no cached tool list)Your first sent message triggers connect + login; after authorizing, the tool list is cached for all future starts.

Token storage (secure, project-independent)

OAuth tokens, the registered client_id and the (non-sensitive) discovery cache are persisted to a credentials file outside any workspace or project directory:

  • Default: ~/.dsh/credentials/futu-mcp.json (i.e. $DSH_HOME/credentials/), file mode 0600, directory mode 0700.
  • Override with credentialFile in the plugin config (the path is resolved with ~ expansion; keep it inside your home directory, never inside a project).

The access token is short-lived (~2h) and refreshed automatically; the refresh token lives up to 14 days, after which the browser prompt appears again. You can revoke tokens at any time from the Futu OpenAPI Dashboard.

Install

dsh plugin --profile web add @yangzhe1991/dsh-futu-mcp

Then restart dsh web. On the first quote query the browser will open the Futu authorization page — log in and approve the requested scopes. Tip from the official docs: try asking the agent "查一下腾讯的实时股价" to verify.

Config

Override configuration in your profile's cordis.patch.yml:

- id: futu-mcp
  config:
    # 授权范围:只读 = quote:read + trade:read(默认)。
    # 需要自选管理/下单撤单时追加 quote:write / trade:write 并重新授权。
    scopes: [quote:read, trade:read]
    # 凭证文件(默认 ~/.dsh/credentials/futu-mcp.json,0600)。
    credentialFile: ~/.dsh/credentials/futu-mcp.json
    # 单次工具调用超时(ms)
    toolCallTimeoutMs: 60000
    # 授权页打开后的提醒间隔(ms)
    authTimeoutMs: 300000
    # 本地回调服务器端口(0 = 自动)
    callbackServerPort: 0
    reconnect:
      enabled: true
      initialDelayMs: 500
      maxDelayMs: 30000
      maxAttempts: 10

Local development

Dev mode (link) — edit your profile's package.json:

"dependencies": {
  "@yangzhe1991/dsh-futu-mcp": "link:/path/to/dsh-futu-mcp"
}
pnpm install   # in the profile directory
npm run build  # in the plugin directory, then hard-refresh / restart dsh web

dsh.profile.bundles must include @yangzhe1991/dsh-futu-mcp (run dsh plugin add to reconcile, or add it manually the same way as other plugins).

License

MIT