Skip to content

About

A cala.ai mcp proxy in typescript to connect Claude Web on Cala - useful to deploy and share artefacts.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

8 Commits

Folders and files

Repository files navigation

cala-mcp-proxy

A tiny, transparent, stateless reverse proxy that lets Claude.ai (web) connect to the Cala MCP server at https://api.cala.ai/mcp/.

Deploy to Koyeb

One click pre-fills the repo, Dockerfile build, and port. You only need to paste your CALA_API_KEY (set it as a secret) before clicking Deploy.

It does exactly one job: inject the X-API-KEY header that Cala requires for authentication onto every request, while forwarding the Model Context Protocol traffic (JSON-RPC over HTTP + Server-Sent Events) faithfully in both directions.

⚠️ This is a prototype / reference implementation. It demonstrates a working pattern for bridging Claude.ai (web) to Cala's X-API-KEY-authenticated MCP server, and it is intentionally minimal — there is no authentication on the proxy itself, no rate limiting, no per-user identity, no caching, and no retries. Do not treat it as a hardened, multi-tenant production gateway. A robust, officially-supported proxy is more appropriately provided by Cala.ai. See Status & limitations before deploying beyond a small, trusted setting.


What is Cala?

Cala is a knowledge layer for AI agents. It turns the chaos of the open internet into structured, verified knowledge that an agent can call as a tool — a graph of typed entities (companies, products, people…) with structured fields, deterministic queries, and full traceability back to the source. Instead of building and maintaining brittle scraping/ingestion pipelines, you call a small set of MCP tools and get clean, typed data your agent can reason over immediately:

Tool What it returns
knowledge_search A succinct, sourced natural-language answer (markdown)
knowledge_query Verified knowledge as structured, typed JSON rows
entity_search Entities matched by name (fuzzy)
entity_introspection The field schema for an entity
retrieve_entity Full information for a known entity

Why connect it to Claude.ai in an enterprise setting

  • Verified, traceable answers reduce hallucination. Every fact comes back with sources, so analysts and decision-makers can audit where an answer came from — essential when the output feeds real business decisions (due diligence, market mapping, competitive research).
  • One governed key for the whole org, not one per person. The API key lives in a single hosted secret (e.g. Koyeb), injected by this proxy. End users never see or handle it, and it can be rotated centrally — far better for security and compliance than distributing keys into each person's local config.
  • Zero per-user setup. Cala's normal Claude integration uses a local npx mcp-remote bridge that every user must install and configure. With this proxy, an admin adds one URL as a custom connector and every team member on Claude.ai (web) gets Cala instantly — no local tooling, no machine-specific state.
  • Centralized control point. Because all Cala traffic flows through one hosted service, the org gets a single place for logging, rate-visibility, key rotation, and (if ever needed) revocation — without touching end users.

Why this proxy exists

Claude.ai supports custom connectors — you give it the URL of a remote MCP server and it can call that server's tools. However:

  1. Cala authenticates with an X-API-KEY request header. A request to https://api.cala.ai/mcp/ without a valid key is rejected:

    POST https://api.cala.ai/mcp/      ->  401  {"detail":"Invalid API key"}
    
  2. Claude.ai's custom-connector UI cannot attach arbitrary request headers. There is no field to set X-API-KEY, so a direct connection can never authenticate.

  3. Claude.ai connects from Anthropic's servers, not from your machine. The MCP server is reached from Anthropic's infrastructure, so a localhost proxy is not reachable — the proxy has to be publicly hosted.

This proxy closes that gap. You host it (e.g. on Koyeb), configure it with your Cala API key, and point Claude.ai at the proxy URL. The proxy adds the X-API-KEY header to every upstream request transparently, so Claude.ai never has to know the secret and never sees an auth challenge.

A note on OAuth

api.cala.ai also advertises an OAuth (Auth0) flow via a 401 + WWW-Authenticate: Bearer challenge and a /.well-known/oauth-protected-resource document. If you pointed Claude.ai directly at Cala, it would detect that challenge and try to run an interactive OAuth login — which is not what you want for a service-to-service API key.

The proxy sidesteps this entirely: because it injects a valid X-API-KEY it returns 200 before any auth challenge is produced, and it deliberately returns 404 for /.well-known/* probes. Claude.ai therefore treats the proxy as a no-auth MCP server and connects straight through.

                 X-API-KEY cannot be set here
                              │
   ┌──────────┐   MCP/JSON-RPC + SSE    ┌──────────────┐   + X-API-KEY    ┌────────────────────┐
   │ Claude.ai │ ──────────────────────▶│ cala-mcp-proxy │ ───────────────▶│ api.cala.ai/mcp/   │
   │  (web)    │ ◀──────────────────────│  (this repo)   │ ◀───────────────│  (Cala MCP server) │
   └──────────┘     transparent          └──────────────┘    authenticated  └────────────────────┘

Design

  • Transparent passthrough. Built on Node's native http/https with stream forwarding, so request and response bytes are relayed verbatim. SSE chunks flow through unbuffered — essential for MCP streaming responses. There is no gzip re-encoding and no client-side body timeout that could cut a long-lived stream.
  • Slow-call resilience. Cala's knowledge tools can take 15–20 s+ before emitting their result event. During that idle gap the proxy injects inert : keep-alive SSE comments (at message boundaries, so frames are never corrupted) and enables TCP keep-alive / TCP_NODELAY on both sockets, so no intermediary drops the connection mid-call. Tunable via SSE_HEARTBEAT_MS.
  • Stateless. The proxy stores nothing. The MCP mcp-session-id (and mcp-protocol-version, last-event-id, etc.) headers are forwarded transparently in both directions. (Cala itself runs stateless and does not issue a session id.)
  • Observable. One structured JSON log line per request to stdout (method, path, upstream status, duration) — never the API key or message bodies.
  • Secure by construction. Any client-supplied Authorization or X-API-KEY header is stripped and replaced with the configured key — the proxy is the sole authority on auth. Only the single MCP endpoint and /health are routed; everything else (including OAuth discovery probes) is a 404.
  • Zero runtime dependencies. The entire proxy is one TypeScript file run natively by Node 24 (no transpile step, no node_modules at runtime) — a small surface to audit.

Endpoints

Method(s) Path Behaviour
GET / HEAD /health Health check for Koyeb / load balancers — 200 {"status":"ok"}
POST/GET/DELETE /mcp Proxied to https://api.cala.ai/mcp/ with X-API-KEY injected
OPTIONS /mcp CORS preflight — 204
any other 404 (no OAuth challenge is advertised)

Configuration

Variable Required Default Description
CALA_API_KEY yes — Cala API key injected as X-API-KEY on every request
PORT no 3000 Port the proxy listens on
CALA_MCP_URL no https://api.cala.ai/mcp/ Upstream MCP endpoint
MCP_PATH no /mcp Local path that maps to the upstream endpoint
SSE_HEARTBEAT_MS no 15000 SSE keep-alive interval during idle gaps; 0 disables

Copy .env.example to .env and fill in your key for local runs.


Run locally

Requires Node ≥ 24 (for native TypeScript execution).

cp .env.example .env          # then edit CALA_API_KEY
npm install                   # dev type-defs only; no runtime deps
export CALA_API_KEY=your-key
npm start                     # -> listening on :3000

Test it with curl

# Health
curl -s http://localhost:3000/health
# -> {"status":"ok","upstream":"https://api.cala.ai/mcp/"}

# MCP initialize handshake (note: the proxy adds X-API-KEY for you)
curl -i -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'

A valid key returns the server's initialize result (as JSON or an SSE stream); an invalid key returns the upstream's 401 {"detail":"Invalid API key"}, forwarded faithfully.


Run with Docker

docker build -t cala-mcp-proxy .
docker run --rm -p 3000:3000 -e CALA_API_KEY=your-key cala-mcp-proxy

Deploy to Koyeb

One-click

Deploy to Koyeb

The button pre-fills the repository, branch, Dockerfile builder, region, and the 3000;http;/ port mapping. Before clicking Deploy, fill in CALA_API_KEY (mark it as a secret). After deploy, optionally switch the health check to HTTP GET /health in the service settings.

Manual

  1. In the Koyeb dashboard: Create Service → GitHub → this repo, and choose Dockerfile as the build method.
  2. Set the environment variable CALA_API_KEY (mark it as a secret).
  3. Expose port 3000 (HTTP). Koyeb injects PORT, which the proxy honors.
  4. Set the health check path to /health (HTTP, port 3000).
  5. Deploy. Koyeb gives you a public URL such as https://cala-mcp-proxy-<org>.koyeb.app.

Connect it to Claude.ai

In Claude.ai → Settings → Connectors → Add custom connector, enter:

https://cala-mcp-proxy-<org>.koyeb.app/mcp

(Note the /mcp path.) No authentication is configured on the Claude.ai side — the proxy supplies the Cala key for you.


Tests

npm run typecheck   # strict TypeScript type-check
npm test            # offline suite against a mock upstream (no key needed)

The offline suite (test/proxy.test.ts, test/config.test.ts) verifies header injection, stripping of client-supplied auth, faithful status/JSON forwarding, unbuffered SSE streaming, the SSE keep-alive heartbeat (bridges an idle gap without corrupting frames), mcp-session-id passthrough, routing/404s, CORS, and 502 handling on upstream failure — all without a real API key.

A live integration test (test/live.test.ts) runs a real initialize and tools/list against api.cala.ai through the proxy. It is skipped unless a key is present:

CALA_API_KEY=your-key npm run test:live

Status & limitations

This is a prototype — a small, auditable reference for the header-injection pattern, not a production-grade gateway. It is well suited to a single trusted team running their own instance with their own key. The limitations below are by design (the proxy has no policy layer); closing them is the natural job of an official, Cala-provided proxy.

Security

  • The proxy is unauthenticated. Anyone who knows the public URL can use your Cala key — the URL is effectively a bearer secret. There is no allowlist, shared secret, or basic-auth gate. Keep the URL private, or add one.
  • One shared identity. Every user of the connector acts as the same Cala principal — no per-user attribution, scoping, or revocation.
  • No rate limiting and permissive CORS — nothing throttles or restricts who can call it.

Reliability / operations

  • Cold starts on a free/scale-to-zero instance: the first request after idle can be slow or time out. Use a warm (paid) instance or a keep-alive ping for steady use.
  • Key validity is load-bearing. If the key expires/rotates without a redeploy, the connector returns 401 and Claude.ai may fall back to the OAuth flow this proxy is designed to avoid.
  • /health is liveness-only — it does not probe Cala, so the proxy reports healthy even if the upstream is down.
  • No retries, single instance, single region.

Protocol / streaming

  • The SSE keep-alive protects only the proxy→client leg; it can't prevent an idle timeout between the proxy and Cala.
  • No backpressure handling in the SSE forward path (fine for Cala's small payloads; not designed for very large streamed bodies).
  • Stateless only, and no server-initiated stream (Cala answers 405 to GET, faithfully passed through).

For anything beyond a small, trusted deployment — multi-tenant auth, per-user keys, rate limiting, HA, SLAs — a hardened proxy is more appropriately provided by Cala.ai as part of their service.


Troubleshooting

Parse impossible (stop_reason=max_tokens — réponse tronquée) / truncated tool result

If Claude.ai shows a parse error mentioning stop_reason=max_tokens after a Cala tool call, this is not the proxy truncating data — the proxy delivers Cala's response complete and byte-accurate (verified). stop_reason and the "via":"cala" wrapper are Claude.ai / Anthropic-side markers.

The cause is the size of Cala's tool result. knowledge_search inlines the full text of every source document in its context array — a single answer can be ~7–10k tokens (measured: ~40 KB by default, ~28 KB even with explainability:false and return_entities:false). When the model generates a response over a result that large, it can hit its output-token limit and truncate mid-JSON.

Workarounds (client-side — the model chooses the tool and arguments, so steer it by how you phrase the request):

  • Prefer knowledge_query for facts — it returns structured rows (~1.5 KB here) instead of inlined documents, and won't truncate.
  • Ask narrow, specific questions with knowledge_search (fewer matched sources → smaller context). Broad "tell me everything about X" prompts are the ones that blow the budget.
  • Tell the model to be concise / not echo sources.

Recommended upstream fix (Cala): add a parameter to knowledge_search to omit or cap the inlined source text — e.g. include_context: false, max_sources: N, or a result token budget — so the tool result fits a conversational client's generation budget. This is the appropriate place to solve it; the proxy stays a transparent relay and does not mutate MCP payloads.


Credits

This proxy exists to connect Claude.ai to Cala — Cala provides the MCP server (https://api.cala.ai/mcp/) and the knowledge/entity tools it exposes. All trademarks and the upstream service belong to Cala.ai. This repository is an independent, unaffiliated integration utility maintained by DealExMachina.

Built on the Model Context Protocol.

License

MIT © 2026 DealExMachina

About

A cala.ai mcp proxy in typescript to connect Claude Web on Cala - useful to deploy and share artefacts.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages