Skip to main content
Glama

lark-mcp

A FastMCP (Python) server that wraps lark-cli to expose Lark/Feishu (IM, calendar, contacts, docs, drive, base, task) as MCP tools, gated by OAuth 2.1.

  • PUBLIC_BASE_URL/authorize federates to Lark's real login (OAuthProxy) purely to identify the caller by open_id — it does not grant any Lark API scope itself.

  • Acting as a specific person (rather than the shared bot) is handled separately, via lark-cli's own device-flow login (lark_login_start / lark_login_confirm tools), since lark-cli only trusts tokens it obtained itself.

  • Each linked user gets an isolated $HOME on the shared PVC (/data/homes/<open_id>), so lark-cli's per-user token store doesn't collide across identities under one app-id.

Deploying

  1. Build the image: docker build -t <your-registry>/lark-mcp:latest .

  2. Fill in lark-mcp-deployment.yaml's Secret (app-id/app-secret from the Lark Developer Console, and a signing key via openssl rand -hex 32) and your own hostname/TLS secret name.

  3. Register https://<your-hostname>/auth/callback as a redirect URI for your Lark app.

  4. kubectl apply -f lark-mcp-deployment.yaml -n <namespace>

Related MCP server: Feishu/Lark OpenAPI MCP

Using with Obot

Obot can act as an MCP gateway/proxy sitting between your actual client (e.g. Hermes) and this server:

Hermes  →  Obot (Remote MCP connector)  →  this server (https://<your-hostname>/mcp)  →  Lark

Add this server in Obot as a Remote connector pointing at https://<your-hostname>/mcp — not Containerized. Containerized means Obot runs the image itself, which doesn't fit this server: Obot's containerized MCP servers get only a cluster-internal ClusterIP (no public URL for the browser-based Lark login redirect to reach) and no persistent volume (every restart would wipe every linked user's lark-cli identity and the OAuth client registrations in /data). Remote avoids both problems entirely, since it just points at the already-running, already-provisioned deployment.

Why two separate "authenticate with Lark" steps happen

Going through Obot doesn't add extra authentication — it just makes both of this server's existing auth layers visible as separate steps, which can look redundant but aren't:

  1. Outer layer — proves who's calling, nothing else. When Obot (or any MCP client) connects, it's redirected through PUBLIC_BASE_URL/authorize to a real Lark login page. This step exists purely so the server can learn the caller's open_id — it deliberately requests zero Lark API scopes (extra_authorize_params={"scope": ""} in server.py). The resulting token lets Obot call this MCP server at all, but by itself grants no ability to read or write anything in Lark.

  2. Inner layer — grants actual Lark API permissions. Once identified, that person still needs to run lark_login_start → approve in a browser → lark_login_confirm. This is a second, independent OAuth device-flow login that lark-cli performs directly against Lark, because lark-cli refuses to accept a token it didn't obtain itself. This is the step that actually carries scopes like base:record:create or drive:file:upload, and it's stored per person under /data/homes/<open_id> on the PVC.

So "I already have a valid token" after step 1 is true, but that token was never carrying any Lark permissions to begin with — there's nothing in it for step 2 to reuse. Both are one-time per person, not per Obot session: step 1's token is valid for 90 days (fastmcp_access_token_expiry_seconds in server.py), and step 2's link persists on disk until you explicitly redo it.

One case that does require redoing step 2: adding a new scope to the Lark app in the Developer Console (and publishing a new app version, which some scopes require) does not retroactively apply to a token issued before that change. Refreshing an existing token, or Obot silently reconnecting with its existing session, does not pick up the new scope — only a fresh lark_login_start/lark_login_confirm does, since that's the step that actually requests scopes from Lark.

Using with Amazon Quick Desktop

Quick Desktop's Remote connection type (native HTTP + OAuth) has a track record of failing against otherwise spec-compliant MCP servers with a bare 401 — several confirmed cases in AWS's own Quick community forum, independent of this server. The reliable path is Quick's Local connection type via mcp-remote as a stdio bridge: mcp-remote performs the full browser-based OAuth handshake itself, and Quick just talks to it over stdio, never touching OAuth directly.

In Quick Desktop: Settings → Capabilities → MCP Servers → Add → Local

Field

Value

Name

lark-mcp

Command

npx

Args

-y mcp-remote@latest https://<your-hostname>/mcp --auth-timeout 300

--auth-timeout 300 matters: the default is 30 seconds, which isn't enough time to click through the consent screen and log into Lark interactively — the flow will otherwise fail right at the final redirect with "site can't be reached," even though everything up to that point worked.

The first tool call opens a real browser window to approve access once; mcp-remote caches the resulting token in ~/.mcp-auth/ afterward.

Known gotcha: stale local port on re-auth

mcp-remote reuses the same local callback port (e.g. 9210) across runs. If Quick Desktop doesn't cleanly kill a previous mcp-remote process before spawning a new one for re-auth, the new one crashes with EADDRINUSE — the browser still shows "Authorization successful" (caught by the old, orphaned process), but Quick never receives a working credential from its own new attempt and just waits indefinitely.

Fix: find and kill the stale listener, then retry.

lsof -i :9210        # note the PID in LISTEN state
kill -9 <PID>

Amazon Quick / Amazon Q Developer has a known bug in its OAuth state machine when handling re-authentication on expired tokens:

It spawns multiple overlapping authorization states (visible in the logs as duplicate /authorize requests with different state IDs). When the browser sends the callback to localhost:9210, the local HTTP handler responds with success HTML to the browser, but fails to dispatch the event to the waiting MCP client thread.

What the logs show

Looking directly at the Kubernetes pod logs during your re-auth attempt:

  1. MCP Server initiated the flow correctly:

  • Server returned 401 Unauthorized when the token expired.

  • Amazon Quick requested GET /authorize?...&redirect_uri=http://localhost:9210/oauth/callback...

  • Lark authorization completed and returned to the server at /auth/callback.

  1. MCP Server completed its handoff:

  • The MCP server issued an HTTP 302 Found redirecting your browser back to Amazon Quick's local listener (http://localhost:9210/oauth/callback?code=...&state=...).

  • Amazon Quick received the browser callback:

  • The webpage displaying "Authorized, you can close this tab" was served by Amazon Quick's local server running on localhost:9210.

  1. Amazon Quick failed to complete the exchange:

  • After receiving the authorization code, Amazon Quick was supposed to send a POST /token request to https://larkmcp.uzzikie.com/token to exchange the code for the session token.

  • No POST /token request was ever made to the MCP server.

  1. Amazon Quick's internal connection worker hung/lost the event from its own localhost:9210 listener and remained stuck waiting in the UI.

How to resolve it

  1. Restart Amazon Quick / Reload IDE Window:

  • Fully restart Amazon Quick (or in VS Code / JetBrains: Developer: Reload Window or restart the IDE).

  • This kills the orphaned localhost:9210 listener and clears the stuck internal state.

  1. Re-connect with a clean initial auth:

  • Reconnect or click authorize after the restart. Fresh/initial authentication succeeds cleanly, unlike the in-place re-auth flow.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    An MCP-based service that enables AI models to seamlessly interact with Feishu (Lark) platform, supporting document reading and chatbot messaging capabilities.
    16 npm
    54
    MIT
  • -
    license
    Not graded
    quality
    Not graded
    maintenance
    A tool designed to help users connect AI Agents with the Feishu/Lark platform, encapsulating Feishu/Lark Open Platform API interfaces as MCP tools for document processing, conversation management, calendar scheduling and more.
    6,645 npm
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to interact with Feishu/Lark platform through comprehensive OpenAPI integration, supporting message management, document operations, calendar scheduling, group chats, Bitable operations, and more automation scenarios with dual authentication support.
    6,645 npm
    MIT