MCP: BizManager as a connector for Claude and ChatGPT
Owner's question (2026-10-11): can BizManager run an MCP server so it integrates with Claude and ChatGPT, since it already has an API? Answer: yes. The tools are thin wrappers over the API that already exists; the work is the sign-in (OAuth) that both assistants require, plus a rule for PHI.
Research (sources read 2026-10-11)
| Finding | Source |
|---|---|
MCP's current revision is 2026-07-28: one HTTP endpoint that takes POST; each request carries its protocol version (header MCP-Protocol-Version and _meta); no sessions and no GET stream. Servers answer with plain JSON or a per-request SSE stream. Servers MUST check Origin and answer 403 to a bad one | https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http |
Earlier clients use the initialize handshake (2025-03-26 to 2025-11-25). A "dual-era" server answers initialize for them and per-request metadata for modern clients, on the same endpoint | https://modelcontextprotocol.io/specification/2026-07-28/basic/versioning |
Authorization: the MCP server is an OAuth 2.1 resource server; it MUST publish Protected Resource Metadata (RFC 9728), answer 401 with WWW-Authenticate: Bearer resource_metadata=..., accept only tokens issued for itself (audience = its URL, RFC 8707), never take tokens in the query string, and answer 403 insufficient_scope with the scopes needed. Client ID Metadata Documents are SHOULD; Dynamic Client Registration is MAY and deprecated | https://modelcontextprotocol.io/specification/latest/basic/authorization |
Claude (claude.ai, Desktop, mobile, Cowork and Claude Code share one OAuth client stack) supports OAuth with DCR or CIMD, authless servers, and fixed API-key headers only in a limited beta. It needs a 401 to start sign-in, uses only the first authorization_servers entry, uses CIMD only when the authorization server advertises client_id_metadata_document_supported: true and none in token_endpoint_auth_methods_supported, sends PKCE S256 always, appends offline_access when listed, refreshes on 401 and up to 5 minutes early, expects refresh-token rotation for public clients and invalid_grant for a dead refresh token, gives discovery/registration/token endpoints 10 s (refresh 30 s), and takes form-encoded token requests. Redirects: https://claude.ai/api/mcp/auth_callback for the hosted apps; Claude Code uses a loopback http://localhost:<port>/callback or 127.0.0.1, matched without the port. No client_credentials grant. Traffic comes from 160.79.104.0/21 | https://claude.com/docs/connectors/building/authentication |
| A protected tool call must fail at the HTTP level (401 with the header), not as a 200 tool error, or Claude never shows the sign-in card. Claude caches discovery documents about 5 minutes | https://claude.com/docs/connectors/building/lazy-authentication |
| Custom connectors by URL work on Claude Free (one connector), Pro, Max, Team and Enterprise; on Team and Enterprise an Owner adds it and members connect with their own accounts | https://claude.com/docs/connectors/custom/add-unlisted |
ChatGPT: protected resource metadata and a 401 pointer; authorization server metadata with S256; CIMD with client_id https://chatgpt.com/oauth/client.json (auth method none or private_key_jwt), else DCR (once per connection) or a predefined client; resource echoed into the token's audience; iss returned in every authorization response (RFC 9207) and rejected when missing or different; redirect https://chatgpt.com/connector_platform_oauth_redirect (or a per-connector https://chatgpt.com/connector/oauth/{callback_id}). ChatGPT cannot present API keys and has no machine-to-machine grants | https://developers.openai.com/plugins/build/auth |
ChatGPT custom MCP apps with write actions run in developer mode on ChatGPT Business, Enterprise and Edu (web); admins publish them to the workspace; refresh tokens need offline_access advertised or users must sign in again | https://help.openai.com/en/articles/12584461-developer-mode-and-mcp-apps-in-chatgpt |
| Anthropic's BAA does not cover connectors: "Your BAA doesn't cover any of them, even after an Owner turns them on", and data sent to third parties through MCP is not covered | https://privacy.claude.com/en/articles/8114513-business-associate-agreements-baa-for-commercial-customers |
| OpenAI signs BAAs for the API and ChatGPT Enterprise/Edu; whether custom MCP apps are covered is not stated on OpenAI's help page; secondary sources say third-party plugins are excluded | https://help.openai.com/en/articles/12584461-developer-mode-and-mcp-apps-in-chatgpt, https://baagenerator.com/blog/does-openai-sign-a-baa |
What it means for BizManager
- **It fits.** The API already has scoped permissions (docs/API.md), a PHI rule, rate limits, audit rows and tenancy tests. Each MCP tool calls an existing route in-process through the same guard, so a tool can never do more than the route allows.
- **OAuth is the real work.** Neither claude.ai nor ChatGPT can send a BizManager API key (Claude's header option is a limited beta; ChatGPT has none). BizManager becomes its own small OAuth 2.1 authorization server: discovery documents, a consent screen where a signed-in person picks the business and the permissions, codes with PKCE, tokens bound to the MCP URL, refresh tokens that rotate, CIMD for Claude and ChatGPT and DCR as the fallback,
isson every redirect. - **A token acts as the person who approved it**, limited to the permissions ticked on the consent screen and to what their role allows (an office member never gets payroll). Audit rows name the person and the assistant. API keys also work on the MCP endpoint for Claude Code and scripts.
- **PHI businesses are excluded.** Anthropic's BAA does not cover connectors and OpenAI's coverage of custom apps is not stated, so a business in PHI mode cannot connect an assistant at all.
- **Who can use it:** any Claude plan (Free allows one custom connector); on ChatGPT, Business, Enterprise or Edu workspaces in developer mode.
- **LurkerLock:** the five reviewed files stay untouched; the session guard for BizManager watches the console cookie only, so the consent screen is covered and bearer tokens pass as API keys do now.
How to connect
Copy the connector address from Business > Integrations or My account: your BizManager address followed by /mcp, for example https://bizmanager.autosecurelogin.com/mcp.
- **Claude** (any plan; Free allows one custom connector): Customize > Connectors > Add custom connector, paste the address, then Connect. On Team and Enterprise an Owner first adds it in Organization settings > Connectors; then each member connects with their own BizManager account.
- **ChatGPT** (Business, Enterprise or Edu, on the web): turn on developer mode, then Settings > Apps > Create, paste the address, choose OAuth, and sign in.
- **Claude Code**:
claude mcp add --transport http bizmanager https://<your address>/mcp, then/mcpto sign in. Or skip OAuth with an API key from Business > Integrations: add--header "Authorization: Bearer bmk_...".
Signing in opens BizManager's consent screen: pick the business (when you belong to several) and tick what the assistant may do. It never gets more than your role allows. End a connection in My account; owners and admins can end anyone's in Business > Integrations.
Claude Code sign-in and the ASL web firewall (resolved 2026-10-11)
Found on the live server after the 0.7.0 release: Claude Code signs in with a loopback callback (http://localhost:<port>/callback), and the LurkerLock-managed web firewall (OWASP Core Rule Set 4.25.0, paranoia level 1, GET and HEAD only) refused any GET argument holding a localhost or IP-address URL: rule 934110 (server-side request forgery list) and rule 931100 (URL with an IP address). claude.ai, Claude Desktop and ChatGPT were not affected (https callbacks).
With the owner's approval, BizManager's nginx site file (deploy/nginx/bizmanager.autosecurelogin.com.conf) gained a location = /oauth/authorize whose ModSecurity rule removes only ARGS:redirect_uri from those two rules on that path; everything else is still inspected. It was applied on 2026-10-11 at 23:38 UTC as a signed LurkerLock platform-edge change (LurkerLock repo, docs/BIZMANAGER-OAUTH-WAF-20261011.md, PR sasysolutions1/lurkerlock#43). Afterwards Claude Code's request reaches the consent screen, and the same localhost values in any other argument or path are still refused.
What it can do (tools)
| Area | Tools | Permission |
|---|---|---|
| Business | get_business (name, time zone, tax rates) | any |
| Clients | search_clients, get_client, create_client, add_note | read:clients, write:clients (notes also write:requests) |
| Price list | list_services | read or write of clients, quotes or jobs |
| Requests | list_requests, get_request, add_request, update_request | read:requests, write:requests |
| Schedule | get_schedule, get_visit, reschedule_visit | read:schedule, write:schedule |
| Jobs | list_jobs, get_job, create_job (one-off, price as a job line) | read:jobs, write:jobs |
| Quotes | list_quotes, get_quote, create_quote (draft), send_quote (contacts the client) | read:quotes, write:quotes |
| Invoices | list_invoices, get_invoice | read:invoices |
| Payments | record_payment | write:payments |
| Reports | business_summary (the dashboard) | read:reports |
Each tool is a call to a documented API route (docs/API.md), so validation, tenancy and audit are the route's own. Read tools carry readOnlyHint; send_quote carries openWorldHint.
Design
| Piece | File | What |
|---|---|---|
| Authorization server | src/modules/mcp/oauth.mjs | discovery, clients, consent, codes, tokens, connections |
| MCP endpoint | src/modules/mcp/server.mjs | POST /mcp, JSON-RPC, tool dispatch, audit tagging |
| Tools | src/modules/mcp/tools.mjs | the 24 tools, their schemas, hints and routes |
| Module | src/modules/mcp/index.mjs | tables, public routes, console routes, the sweep job |
| Guard hook | src/api.mjs | ctx.internal: an in-process call with a verified caller |
| Console | public/assets/app/views/mcp/ | consent screen #/connect/:token, the "AI assistants" card |
| Tests | test/mcp.test.mjs | the full flow, every tool, step-up, refresh, PHI, tenancy, keys |
**Public routes.** /.well-known/oauth-protected-resource (and /mcp suffixed), /.well-known/oauth-authorization-server, GET /oauth/authorize, POST /oauth/token, POST /oauth/register, POST /oauth/revoke, POST /mcp (GET and DELETE answer 405), /docs/mcp.
**Authorization.**
- Issuer: the BizManager address. Resource: the address plus
/mcp; tokens are only accepted there. - Clients: a Client ID Metadata Document is fetched (https only, public addresses only, no redirects, 5 seconds, 16 KB, cached 10 minutes); it must name itself, list usable redirect URIs and allow token auth method
none. Otherwise Dynamic Client Registration (public clients, https or loopback redirects, 30 an hour per address; unused clients removed after 30 days). - Authorize checks the client, the redirect URI (exact, or loopback on any port), PKCE S256 and the resource; an unknown client or redirect gets an error page and is never redirected. Other errors go back to the client with
iss. A valid request is kept 15 minutes and the browser goes to/app#/connect/<token>. - Consent (signed-in console, CSRF-protected): the person's businesses, their role's permissions, the requested ones ticked. PHI businesses are listed but cannot be chosen. A loopback redirect shows a warning that a program on this computer will receive the answer.
- Codes: 2 minutes, once; a replay ends the connection. Access tokens:
bmo_+ 32 characters, 1 hour. Refresh tokens:bmr_, 30 days, rotated on use; a replay within 30 seconds (a client retry) gets a fresh pair, later it ends the connection. Only SHA-256 hashes are stored. - A connection (
mcp_grants) ends when revoked, when the person leaves the business or changes membership, when the business closes or turns on PHI mode, or when its refresh token expires.
**Endpoint.**
- Dual-era: a request with
_meta["io.modelcontextprotocol/protocolVersion"]is served as 2026-07-28 (headersMCP-Protocol-Version,Mcp-Method,Mcp-Namemust match the body,-32020; unknown versions-32022with the supported list;server/discover); otherwiseinitializenegotiates 2025-11-25, 2025-06-18 or 2025-03-26. Answers are JSON; no sessions and no event stream. - Callers: an access token acts as the person (
user_idon every audit row, withvia: "assistant", the assistant's host and the connection id), with capabilities = the ticked scopes intersected with their role. An API key acts as the business with its scopes. tools/listshows the tools the role (or key) could use. A tool whose permission was not ticked answers HTTP 403insufficient_scopewith the scopes to add, so Claude and ChatGPT ask the person to approve more. 300 requests a minute per connection.Origin: absent, the BizManager origin, claude.ai, claude.com or chatgpt.com; anything else 403.
**Console routes.** GET|POST /api/mcp/consent/:token, GET /api/mcp/connections, DELETE /api/mcp/connections/:id (yours), GET /api/mcp/business, DELETE /api/mcp/business/:id (settings.manage). /api/mcp is closed to API keys and assistants.
**LurkerLock.** No reviewed file changed. The local session guard covers the console cookie (so the consent screen); bearer tokens on /mcp are checked by BizManager, as API keys are.