Skip to content

Connection Modes

This guide shows how to wire the LenserFight MCP server into your product for each supported connection mode. Pick the mode that matches your deployment environment.

ModeWhen to useSetup timeRequires local repo?
LF Cloud (HTTP)Production — your product connects to the hosted endpoint~5 minNo
stdioLocal development inside the LenserFight repo~5 minYes
HTTP + tunnelTesting local MCP changes before deploying~10 minYes

LF Cloud (HTTP)

Use this for all production integrations.

The LenserFight MCP server is proxied through Cloudflare at a stable public domain. There is no separate server to deploy or maintain. Your client sends standard MCP JSON-RPC over HTTPS.

Endpoint

https://mcp.lenserfight.com/mcp

OAuth discovery (automatic for compliant clients)

A fully MCP-compliant client reads the discovery document and handles OAuth automatically:

bash
curl https://mcp.lenserfight.com/.well-known/oauth-authorization-server
json
{
  "issuer": "https://mcp.lenserfight.com",
  "authorization_endpoint": "https://mcp.lenserfight.com/oauth/authorize",
  "token_endpoint": "https://mcp.lenserfight.com/oauth/token",
  "registration_endpoint": "https://mcp.lenserfight.com/oauth/register",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"]
}

Claude.ai custom connector

  1. Open claude.ai → Settings → Connectors → Add custom connector.
  2. Fill in:
    • Name: LenserFight
    • Remote MCP server URL: https://mcp.lenserfight.com/mcp
    • OAuth Client ID: leave blank (dynamic registration)
    • OAuth Client Secret: leave blank (PKCE only — no secret)
  3. Click Add.
  4. Authorize with your LenserFight credentials in the popup.

Cursor

In ~/.cursor/mcp.json (or the per-project .mcp.json in your workspace root):

json
{
  "mcpServers": {
    "lenserfight": {
      "url": "https://mcp.lenserfight.com/mcp"
    }
  }
}

Restart Cursor. On first use of a LenserFight tool, Cursor opens a browser window for the OAuth flow.

Any HTTP MCP client

Add a remote MCP server entry pointing to the endpoint above. The client must support:

  • OAuth 2.1 authorization code flow
  • PKCE (S256 code challenge)
  • RFC 7591 dynamic client registration (or manually supply a client_id from a prior registration)

If your client does not support dynamic registration, call POST /oauth/register once manually (see OAuth & Authentication) and hard-code the returned client_id.

Custom backend client example (Node.js)

typescript
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'

const transport = new StreamableHTTPClientTransport(
  new URL('https://mcp.lenserfight.com/mcp'),
  {
    // After completing the OAuth flow, supply the lf_mcp_* access token here
    requestInit: {
      headers: { Authorization: `Bearer ${process.env.LF_MCP_TOKEN}` },
    },
  }
)

const client = new Client({ name: 'my-product', version: '1.0.0' }, { capabilities: {} })
await client.connect(transport)

const result = await client.callTool({ name: 'list_lenses', arguments: { limit: 10 } })
console.log(result)

stdio (local dev)

Use this only when developing inside the LenserFight repository.

In stdio mode the MCP server is spawned as a child process. Communication happens over stdin/stdout. The service role key bypasses all RLS — this mode is not safe for production use.

Prerequisites

  1. Local Supabase instance running: supabase start
  2. MCP server built: pnpm nx build mcp-server
  3. Node.js 22+, pnpm 9+

Environment variables

VariableValue
SUPABASE_URLhttp://127.0.0.1:54321
SUPABASE_SERVICE_ROLE_KEYFrom supabase status -o env
SUPABASE_ANON_KEYFrom supabase status -o env
SUPABASE_JWT_SECRETsuper-secret-jwt-token-with-at-least-32-characters-long
MCP_TRANSPORTstdio
LENSERFIGHT_LENSER_ID(optional) UUID of the lenser to scope calls to

.mcp.json (already in the repo root)

json
{
  "mcpServers": {
    "lenserfight": {
      "command": "node",
      "args": ["dist/apps/mcp-server/main.js"],
      "env": {
        "SUPABASE_URL": "${SUPABASE_URL}",
        "SUPABASE_SERVICE_ROLE_KEY": "${SUPABASE_SERVICE_ROLE_KEY}",
        "SUPABASE_ANON_KEY": "${SUPABASE_ANON_KEY}",
        "SUPABASE_JWT_SECRET": "${SUPABASE_JWT_SECRET}",
        "MCP_TRANSPORT": "stdio"
      }
    }
  }
}

Source your env vars, then open Claude Code or Cursor from the repo root. The client reads .mcp.json automatically.


HTTP + tunnel (local dev)

Use this when you want Claude.ai or a remote client to hit your local MCP server changes.

Claude.ai's token exchange happens from Anthropic's cloud. It cannot reach localhost. You need a public tunnel.

Step 1 — Start ngrok

bash
ngrok http 3001
# Forwarding: https://<id>.ngrok-free.app -> http://localhost:3001

The server auto-detects the ngrok public URL from ngrok's local API. No manual copy-paste needed.

With cloudflared instead:

bash
cloudflared tunnel --url http://localhost:3001
export MCP_OAUTH_BASE_URL=https://<your-id>.trycloudflare.com

Step 2 — Build and start the server in HTTP mode

Make sure Supabase is running and env vars are sourced, then:

bash
export MCP_TRANSPORT=http
export MCP_HTTP_PORT=3001
node dist/apps/mcp-server/main.js

Startup banner:

[lenserfight-mcp] auto-detected public tunnel: https://<tunnel>.ngrok-free.app
[lenserfight-mcp] HTTP transport ready on http://localhost:3001/mcp

  ┌─ Local dev OAuth credentials ──────────────────────────────────┐
  │  OAuth Client ID : lf_mcp_client_localdev                      │
  │  Auth method     : PKCE (no client secret required)            │
  │  Server base URL : https://<tunnel>.ngrok-free.app             │
  │                                                                 │
  │  Claude.ai → Settings → Connectors → Add connector:            │
  │    URL       : https://<tunnel>.ngrok-free.app/mcp             │
  │    Client ID : lf_mcp_client_localdev                          │
  └─────────────────────────────────────────────────────────────────┘

Step 3 — Add the connector in Claude.ai

Use the tunnel URL from the banner, not localhost:

  • URL: https://<tunnel>.ngrok-free.app/mcp
  • OAuth Client ID: lf_mcp_client_localdev

After restarting ngrok

The free ngrok tier assigns a new URL on every restart. After each restart:

  1. Restart the MCP server — it auto-detects the new URL.
  2. In Claude.ai, delete and re-add the connector with the updated URL.

Troubleshooting

mcp_token_exchange_failed

The discovery document advertised a localhost URL that the remote client cannot reach.

  • Confirm the startup banner shows the tunnel URL, not http://localhost.
  • Confirm the connector URL in Claude.ai ends with /mcp and matches the tunnel exactly.
  • If you restarted ngrok, the URL changed — delete and re-add the connector.

401 Unauthorized on POST /mcp

  • The bearer token is missing, expired, or malformed.
  • Re-authorize via the connector settings.
  • Check that the token format is lf_mcp_<64 hex chars>.

Sign-in failed

  • Wrong email or password in the OAuth login form.
  • Confirm credentials at lenserfight.com.

No Lenser profile found

  • The user completed account creation but never finished onboarding (picking a handle).
  • Send them to lenserfight.com to complete the handle selection step, then retry.

PUBLIC URL REQUIRED warning at startup (tunnel mode)

  • ngrok is not running or was not started before the MCP server.
  • Start ngrok first, then start the MCP server.
  • Or set MCP_OAUTH_BASE_URL=https://your-url manually before starting.

WARN: environment variable is unset

  • Forgot to source the env file. Run source ./.env.mcp.local.