Less Annoying CRM (remote, OAuth)

com.northkeellabs由 northkeellabs.com 發布官方發布者v0.1.0更新於 Nov 11, 2026

Unofficial remote MCP server for Less Annoying CRM: OAuth 2.1, read-only grants, 10 tools.

已驗證Streamable HTTP可網頁執行效率與工作流商業與電商

概覽

AI 產生的概覽

Less Annoying CRM 的非官方遠端 MCP 伺服器,透過 OAuth 登入讓助理搜尋聯絡人、讀取備註與任務並建立記錄。

可以做什麼
  • 搜尋聯絡人與公司,並取得單一聯絡人的完整記錄
  • 讀取聯絡人備註,依日期範圍列出任務
  • 列出銷售流程及其中的項目,可依狀態篩選
  • 建立聯絡人或公司、新增純文字備註、建立任務
  • 將流程項目移到其他狀態(不會觸發自動化)
適用情境
適合讓助理查詢 CRM 中的聯絡人、備註、任務或流程狀態,或記錄備註與任務,而不必在本機安裝伺服器。唯讀授權適合查詢與摘要,讀寫授權用於新增記錄。
執行需求
遠端 Streamable HTTP 端點,使用 OAuth 2.1 登入,不需本機執行環境。需要 Less Annoying CRM 帳號及其 API 金鑰,在授權頁面貼上並選擇存取層級(唯讀或讀寫)。每個連線每分鐘最多 60 次工具呼叫。
安裝前請注意
授權頁面會要求填寫 LACRM API 金鑰,金鑰保存在第三方託管的 Worker 上的加密 OAuth 授權中,等於把 CRM 存取權託付給該營運方。寫入工具會建立聯絡人、備註與任務並變更流程狀態;CRM 內容被標示為不可信資料以降低提示注入風險,但無法完全消除。沒有刪除工具。免費、盡力而為、無 SLA;可在 LACRM 中輪換金鑰來撤銷。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Less Annoying CRM (remote, OAuth),將其新增到工作區。
  2. 連接 所需的 憑證。它們會被私密儲存,絕不會在公開頁面上展示。
  3. 為需要使用其工具的對話啟用該服務。

Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。

交給代理程式安裝

把這段提示詞貼上到 Claude Code、Codex、Cursor 等代理程式,或 SourceWeft 對話中。代理程式會讀取該伺服器的安裝指南,先向你展示發布者、端點和登入方式,經你同意後再把它新增到你的客戶端。金鑰不會寫進任何命令。

閱讀 https://sourceweft.com/mcp/com.northkeellabs%2Flacrm-mcp-remote/install.md,並按其中說明把這個 MCP 伺服器接入我的客戶端。修改任何設定之前,先告訴我它的發布者、端點和登入方式,等我確認。不要把 API 金鑰寫進命令或我會提交的檔案;問我金鑰應從哪裡取得。

檢視代理程式所依據的安裝指南

其他 MCP 客戶端

把它新增到你客戶端的 mcpServers 設定中。 客戶端會在首次連接時完成 OAuth 登入。

{
  "mcpServers": {
    "less-annoying-crm-remote-oauth": {
      "type": "http",
      "url": "https://lacrm-mcp.northkeellabs.com/mcp"
    }
  }
}

README

lacrm-mcp-remote

A remote MCP server for Less Annoying CRM (LACRM), hosted on Cloudflare Workers, with OAuth 2.1 sign-in. It exposes the same 10 tools as the local lacrm-mcp server (stdio, Python, also by Northkeel Labs), but over Streamable HTTP, so it works as a claude.ai custom connector and in any MCP client that supports remote servers with OAuth.

Independent and unofficial. Not affiliated with, endorsed by or supported by Less Annoying CRM.

Hosted endpoint

https://lacrm-mcp.northkeellabs.com/mcp

Northkeel Labs runs this code at that URL. Paste it into any MCP client that supports remote servers with OAuth (see Connect a client), sign in, and paste your LACRM API key on the consent page. The Worker stores only the encrypted OAuth grant; it keeps no other copy of your key or CRM data. Free, best effort, no SLA, 60 tool calls per minute per connection. To run it on your own Cloudflare account instead, see Deploy your own.

Why remote + OAuth

A local stdio server needs every user to install Python, store an API key in a config file and keep the process running on their machine. A remote server is one URL. The user pastes it into claude.ai, Claude Desktop or Cursor, signs in once in the browser, and the client gets a short-lived, revocable token. The API key stays out of the client, out of config files and out of the model's context.

LACRM itself has no OAuth, only API keys. This server bridges the two with a "bring your key, get OAuth" pattern: the Worker is a standards-compliant OAuth 2.1 authorization server (discovery metadata, dynamic client registration, Client ID Metadata Documents, PKCE S256, refresh-token rotation), and its consent page is where the user pastes their LACRM key once. The key is checked with a single read-only LACRM call and then lives only inside the encrypted OAuth grant.

Architecture

mermaid
flowchart LR  subgraph Client["MCP client (claude.ai, Claude Desktop, Cursor, Inspector)"]    C[OAuth + Streamable HTTP]  end  subgraph Worker["Cloudflare Worker: lacrm-mcp-remote"]    OP["OAuthProvider<br/>discovery, register, token"]    AU["Consent page at authorize<br/>key + access level"]    MCP["MCP endpoint<br/>rate limit, scope gate, 10 tools"]    RL[("RateLimiter<br/>SQLite Durable Object<br/>one per grant")]  end  KV[("KV: OAUTH_KV<br/>clients, grants, token hashes<br/>props encrypted")]  LACRM["LACRM API v2<br/>api.lessannoyingcrm.com"]  C -- discovery, DCR/CIMD, token --> OP  C -- browser sign-in --> AU  C -- Bearer token --> MCP  OP <--> KV  AU -- GetUser (validate key) --> LACRM  AU -- completeAuthorization --> OP  MCP --> RL  MCP -- key from decrypted grant --> LACRM

Code map: src/index.ts (OAuthProvider wiring), src/authorize.ts (consent page), src/mcp.ts (protected endpoint), src/tools.ts (the 10 tools), src/lacrm.ts (API client, retries, error mapping), src/security.ts (truncation, untrusted-data wrapper, redaction), src/ratelimit.ts (Durable Object).

Built on @cloudflare/workers-oauth-provider 1.2.4, agents 0.33.0 (createMcpHandler) and the MCP TypeScript SDK v2 (@modelcontextprotocol/server 2.4.0). The handler is stateless: every request gets a fresh MCP server built for that grant. It serves both 2025-era Streamable HTTP clients and the stateless 2026 protocol from the same /mcp endpoint. There is no /sse endpoint. McpAgent is deprecated as of agents 0.33.0, so this uses createMcpHandler instead; the only Durable Object is the per-grant rate limiter.

The auth flow, step by step

  1. The client calls POST /mcp without a token and gets 401 with WWW-Authenticate: Bearer resource_metadata=".../.well-known/oauth-protected-resource/mcp", scope="lacrm.read".
  2. It reads the protected resource metadata (RFC 9728), then the authorization server metadata at /.well-known/oauth-authorization-server (RFC 8414).
  3. It identifies itself, either with a Client ID Metadata Document URL or by registering at /oauth/register (RFC 7591 dynamic client registration).
  4. It opens /authorize in the browser with PKCE (S256 only), state and resource. The server rejects unknown clients and any redirect URI that was not registered, and renders those errors locally instead of redirecting.
  5. The consent page shows which client is asking (name, verified domain for CIMD clients, redirect host, a warning for local apps), asks for the LACRM API key and an access level: read-only (lacrm.read) or read and write (lacrm.read lacrm.write).
  6. On submit (POST only, CSRF handle bound to a __Host- cookie, single use, 10 minutes), the server calls LACRM GetUser with the key. A rejected key shows an error and nothing is stored.
  7. The server completes the authorization. The key, LACRM user id, granted scopes and a grant id are stored as grant props, encrypted with key material wrapped by the token. The browser is redirected to the client with a code.
  8. The client exchanges the code plus PKCE verifier at /oauth/token for an access token (1 hour) and a rotating refresh token.
  9. Every /mcp call is rate limited per grant (60 per minute by default) and scope-checked: a read-only grant sees 6 tools in tools/list and any call to a write tool is refused, whether or not it was listed.

Tools

ToolAccessLACRM functionWhat it does
search_contactsreadGetContactsSearch contacts and companies by text; summary per record, paged
get_contactreadGetContactFull record for one contact or company
list_contact_notesreadGetNotesAttachedToContactNotes on a contact, paged
list_tasksreadGetTasksTasks in a date range (max 366 days), optional contact filter
list_pipelinesreadGetPipelinesPipelines and their status ids
list_pipeline_itemsreadGetPipelineItemsItems in one pipeline, optional status filter
create_contactwriteCreateContactNew contact or company; owner defaults to the key's user
add_notewriteCreateNotePlain-text note on a contact
create_taskwriteCreateTaskNew task, optionally on a contact
update_pipeline_item_statuswriteEditPipelineItemMove an item to another status; automations are not run

There are no delete tools. Limits match the Python version: at most 50 results per page with has_more_results, text fields truncated at 2,000 characters, lists capped at 50 items, and every response that contains CRM records carries a data_notice marking the content as untrusted data, not instructions. Inputs are validated with zod (ids are digit strings, enums, ISO dates, email format, length limits) before any request reaches LACRM.

Deploy your own

You need a Cloudflare account (the free plan is enough: one Worker, one KV namespace, SQLite-backed Durable Objects) and Node 22+.

sh
git clone <this repo> lacrm-mcp-remote && cd lacrm-mcp-remotenpm installnpx wrangler loginnpx wrangler kv namespace create OAUTH_KV

Paste the printed namespace id into kv_namespaces[0].id in wrangler.jsonc (the id in the repo belongs to the hosted instance and will not work on your account). The repo config serves the Worker on our custom domain, so change the address too:

  • Your own domain (the zone must be on the same Cloudflare account): set routes[0].pattern to your hostname, for example mcp.example.com, and vars.MCP_RESOURCE to https://mcp.example.com/mcp.
  • Or workers.dev: delete the routes entry, set "workers_dev": true, and set vars.MCP_RESOURCE to https://lacrm-mcp-remote.<your-workers-subdomain>.workers.dev/mcp (your subdomain is shown in the Cloudflare dashboard under Workers and Pages).

Tokens are bound to this exact URL. Then:

sh
npm test                 # unit and integration tests in the Workers runtimenpx wrangler deploy

No server-side secret is needed: each user's LACRM key arrives through the consent page and is stored only in their encrypted grant. Optional settings are plain vars in wrangler.jsonc (RATE_LIMIT_PER_MINUTE, default 60). If you later add a secret of your own, use npx wrangler secret put NAME in production and .dev.vars locally (see .dev.vars.example; .dev.vars is gitignored).

Local development: copy .dev.vars.example to .dev.vars (it sets MCP_RESOURCE to http://localhost:8787/mcp, overriding the production value), then npx wrangler dev serves everything at http://localhost:8787.

Connect a client

Use the hosted endpoint https://lacrm-mcp.northkeellabs.com/mcp or your own deployment's URL.

  • claude.ai: Settings > Connectors > Add custom connector. Paste the URL, then Connect. A browser tab opens the consent page; paste your LACRM key (LACRM: Settings > Programmer API) and choose the access level.

  • Claude Desktop: Settings > Connectors > Add custom connector, same URL and flow as claude.ai.

  • Cursor: add to ~/.cursor/mcp.json and enable the server; Cursor opens the browser for sign-in.

    json
    { "mcpServers": { "lacrm": { "url": "https://lacrm-mcp.northkeellabs.com/mcp" } } }
  • MCP Inspector: npx @modelcontextprotocol/inspector, choose transport "Streamable HTTP", enter the URL, and complete its OAuth sign-in flow. Against local dev use http://localhost:8787/mcp.

To revoke access, delete or rotate the API key in LACRM. Every grant made with that key then stops working, whatever client holds the token.

Security

Summary (details and threat model in SECURITY.md):

  • The LACRM key is never accepted as a tool argument, never returned to the client, never logged. Error text from LACRM is redacted before it reaches a tool result.
  • Grant props (which hold the key) are encrypted by the OAuth provider; access tokens, refresh tokens and codes are stored only as hashes.
  • Consent page: POST only, single-use CSRF handle bound to the browser, all client-supplied text HTML-escaped, Content-Security-Policy: default-src 'none'; script-src 'none'; frame-ancestors 'none' with one nonce'd inline style, no JavaScript and no third-party resources, X-Frame-Options: DENY, no-store.
  • Exact redirect URI matching; unknown clients and unregistered redirect URIs are rendered as local errors, never redirected.
  • Scope enforcement on every call; per-grant rate limit; writes are never auto-retried on 5xx or timeouts.
  • CRM content is bounded and labelled as untrusted to reduce prompt-injection risk. It cannot be made harmless; keep a human in the loop for writes and prefer read-only grants for summarising.

Tests

Test results, 2026-11-10: npm test 50 tests passing (Workers runtime via @cloudflare/vitest-plugin, LACRM mocked at fetch); tsc --noEmit clean; wrangler deploy --dry-run succeeds; live end-to-end (scripts/e2e-live.mjs against wrangler dev and a real LACRM account holding demo data) 20/20 steps passing, and the same 20/20 against the production deployment at https://lacrm-mcp.northkeellabs.com. Redacted output: local, production. Also tested 2026-11-10 as a claude.ai custom connector (read-only grant).

The tests cover each tool's request mapping and output shape, truncation, pagination, the untrusted-data wrapper, error mapping and redaction, retry policy, scope gating (a read grant can neither see nor call write tools, including in a batch), the consent page (bad key, missing or forged CSRF token, unknown redirect URI, unknown client, HTML escaping, deny, single-use handle), rate limiting, and that the key never appears in any response or log line.

To run the live test yourself: start npx wrangler dev, then LACRM_API_KEY=... node scripts/e2e-live.mjs. It makes one write: a note on the contact "Maya Demo".

Disclaimer

This is an independent, unofficial project. It is not affiliated with, endorsed by or supported by Less Annoying CRM. "Less Annoying CRM" is a trademark of its owner. Use at your own risk; review the code before connecting a production CRM account. MIT licensed, see LICENSE.

來源:README.md,提交 62c7082

工具

0
該伺服器需要登入,因此 SourceWeft 無法在沒有帳號的情況下讀取其工具,也未收錄。已連接帳號所能看到的內容可能因帳號及其權限而異。

版本歷史

1
  1. v0.1.0最新Nov 11, 2026

評分

已滿足適用檢查項的 85%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通必需25 分
  • 已宣告認證方式必需10 分
  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分

其他 Northkeellabs 整合