
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.
概览
Less Annoying CRM 的非官方远程 MCP 服务器,通过 OAuth 登录让助手搜索联系人、读取备注与任务并创建记录。
- 可以做什么
- 搜索联系人和公司,并获取单个联系人的完整记录
- 读取联系人备注,按日期范围列出任务
- 列出销售管道及其中的项目,可按状态筛选
- 创建联系人或公司、添加纯文本备注、创建任务
- 将管道项目移动到其他状态(不会触发自动化)
- 适用场景
- 适合让助手查询 CRM 中的联系人、备注、任务或管道状态,或记录备注和任务,而无需在本机安装服务器。只读授权适合查询与总结,读写授权用于新增记录。
- 运行要求
- 远程 Streamable HTTP 端点,使用 OAuth 2.1 登录,无需本地运行时。需要 Less Annoying CRM 账号及其 API 密钥,在授权页面粘贴并选择访问级别(只读或读写)。每个连接每分钟最多 60 次工具调用。
安装
在 SourceWeft 中
- 打开 控制台中的 Less Annoying CRM (remote, OAuth),将其添加到工作区。
- 连接 所需的 凭据。它们会被私密存储,绝不会在公开页面上展示。
- 为需要使用其工具的对话启用该服务。
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
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
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
- The client calls
POST /mcpwithout a token and gets401withWWW-Authenticate: Bearer resource_metadata=".../.well-known/oauth-protected-resource/mcp", scope="lacrm.read". - It reads the protected resource metadata (RFC 9728), then the authorization server metadata at
/.well-known/oauth-authorization-server(RFC 8414). - It identifies itself, either with a Client ID Metadata Document URL or by registering at
/oauth/register(RFC 7591 dynamic client registration). - It opens
/authorizein the browser with PKCE (S256 only),stateandresource. The server rejects unknown clients and any redirect URI that was not registered, and renders those errors locally instead of redirecting. - 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). - On submit (POST only, CSRF handle bound to a
__Host-cookie, single use, 10 minutes), the server calls LACRMGetUserwith the key. A rejected key shows an error and nothing is stored. - 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.
- The client exchanges the code plus PKCE verifier at
/oauth/tokenfor an access token (1 hour) and a rotating refresh token. - Every
/mcpcall is rate limited per grant (60 per minute by default) and scope-checked: a read-only grant sees 6 tools intools/listand any call to a write tool is refused, whether or not it was listed.
Tools
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+.
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].patternto your hostname, for examplemcp.example.com, andvars.MCP_RESOURCEtohttps://mcp.example.com/mcp. - Or workers.dev: delete the
routesentry, set"workers_dev": true, and setvars.MCP_RESOURCEtohttps://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:
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.jsonand enable the server; Cursor opens the browser for sign-in. -
MCP Inspector:
npx @modelcontextprotocol/inspector, choose transport "Streamable HTTP", enter the URL, and complete its OAuth sign-in flow. Against local dev usehttp://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版本历史
1- v0.1.0最新Nov 11, 2026
评分
已满足适用检查项的 85%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
- 近 30 天内可连通必需25 分
- 已声明认证方式必需10 分
- 已列出工具15 分
请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。
- 描述必需15 分
- 图标10 分
- 文档15 分
- 许可证10 分