Skip to main content
Glama

lofty-mcp

An MCP server that gives Claude tools to work with a Lofty CRM account — leads, tasks/appointments, calendar, communications, and notes — by calling Lofty's REST API directly.

For the reasoning behind how this project is built (why REST instead of lofty-cli, the auth-scheme quirk, the HTTP-mode security tradeoffs), see DESIGN.md. This document is about what the project does and how to run it.

Concepts

  • MCP (Model Context Protocol) is the protocol Claude uses to discover and call tools exposed by an external server. This project is one such server: it doesn't talk to Claude directly, it registers tools (e.g. lofty_leads_list, lofty_notes_create) that a client (Claude Code, Cowork, etc.) can call on your behalf.

  • One tool module per Lofty resource. src/lofty_mcp/tools/ has one file per resource — leads.py, tasks.py, calendar.py, communication.py, notes.py, whoami.py — each registering the MCP tools for that resource's REST endpoints. 35 tools total today; see specs/rest/index.json for what's implemented vs. pending.

  • Tool annotations. Every tool carries MCP ToolAnnotations (readOnlyHint/destructiveHint/idempotentHint) — reads are marked read-only, creates/sends are non-idempotent, updates/deletes are destructive — so a well-behaved client can decide what to auto-approve vs. confirm with you. These are hints, not enforcement: nothing in the server itself blocks a destructive call.

  • Two transports, two use cases:

    • stdio (default) — Claude Code spawns the server as a local subprocess per session. This is how you'd normally use it day to day.

    • Streamable HTTP — the server runs as a long-lived process bound to a port, for remote clients (e.g. Cowork's "Add custom connector") that can't spawn a process on your machine. Needs a real HTTPS URL in front of it; see "Connecting to a remote client" below.

  • Auth is a single API key, not OAuth. LOFTY_API_KEY is a personal-access token from the Lofty CRM, sent as Authorization: token <key> on every request. There's no refresh flow — if it stops working, you regenerate it in the CRM.

Related MCP server: Rentalot MCP Server

Initial setup

You need:

  1. Docker. Everything — build, run, test — happens inside containers; nothing from this project's Python dependency stack is installed on your host. Install Docker Desktop (macOS/Windows) or Docker Engine (Linux), and confirm it's running with docker info.

  2. A Lofty API key. In the Lofty CRM, go to Settings → Integrations → API and generate a personal-access key.

  3. jq, used by several Makefile targets to edit JSON config in place: brew install jq (macOS) or your distro's package manager.

  4. (Only if you'll use HTTP mode / remote connectors) cloudflared: brew install cloudflare/cloudflare/cloudflared.

Then create a .env file at the repo root (already in .gitignore — never commit it):

LOFTY_API_KEY=your-key-here

If the key stops working, tool calls fail with a clear LoftyApiError pointing back at Settings → Integrations → API — there's no refresh path, so regenerating the key is always the fix.

Project layout

src/lofty_mcp/
  server.py         entrypoint: MCPServer, lifespan-injected httpx client, transport selection
  rest_client.py     HTTP client: base URL, auth header, error mapping
  tools/             one module per resource (leads, tasks, calendar, communication, notes, whoami)
specs/
  openapi.json        Lofty's full OpenAPI spec (source of truth)
  rest/*.json          generated per-resource summaries (see specs/README.md)
scripts/
  generate_rest_specs.py   regenerates specs/rest/*.json from specs/openapi.json
  mcp_test_harness.py       reliable stdio request/response harness for smoke-testing
Dockerfile             plain python:3.12-slim, no CLI binary needed
Makefile               build/run/register/teardown commands (see below)
DESIGN.md              why the project is built this way (not how to run it)

Building and testing

make help          # list all commands
make build          # docker build the image
make test           # run scripts/mcp_test_harness.py against it (real API calls)

Connecting to Claude Code

make up             # builds the image, registers it in .mcp.json

This writes a lofty entry into the project's .mcp.json (command: docker, args: [run, -i, --rm, --env-file, <path>, lofty-mcp:py]). Claude Code auto-detects project-scoped .mcp.json files — restart the session (or run /mcp) and approve the new server when prompted. make down removes the entry.

Each session spawns a fresh docker run -i --rm process over stdio; Docker tears it down automatically when the session ends, so there's no separate container lifecycle to manage here.

Connecting to a remote client (e.g. Cowork)

Remote connectors need a real HTTPS URL, since they run in the cloud and can't spawn processes on your machine. There are two ways to get one, depending on how long-lived and trusted the deployment needs to be.

Quick tunnel — short-lived local testing

make http-up         # runs the server in HTTP mode + a cloudflared quick tunnel
make http-down        # tears both down

make http-up binds the server to 0.0.0.0:8000 and fronts it with cloudflared tunnel --url — no Cloudflare account needed, but the URL is random and changes every run. By default this has no authentication beyond LOFTY_API_KEY baked into the container: anyone with the tunnel URL can call every tool, including sends and deletes.

To close that gap for a quick tunnel session, set a shared secret before starting it:

MCP_HTTP_AUTH_TOKEN=some-long-random-string make http-up

With MCP_HTTP_AUTH_TOKEN set, every request must include Authorization: Bearer <that token> or the server returns 401 before the request reaches any tool. Without it, the server prints a loud warning on startup and runs unauthenticated — fine for a few minutes of local testing torn down right after, not for anything left running.

Persistent tunnel — fixed hostname, Cloudflare Access OAuth

For anything longer-lived, use a named tunnel at a hostname you control, and put Cloudflare Access in front of it for real OAuth login (Google, GitHub, Okta, etc.) — unauthenticated requests never reach your machine at all.

One-time setup, requires a domain added to your Cloudflare account:

make tunnel-login     # opens a browser, authorizes cloudflared against your account (once per machine)

Add to .env:

TUNNEL_HOSTNAME=lofty-mcp.yourdomain.com
TUNNEL_NAME=lofty-mcp        # optional, defaults to lofty-mcp

Then:

make tunnel-create    # creates (or reuses) the named tunnel, routes TUNNEL_HOSTNAME's DNS to it

In the Cloudflare Zero Trust dashboard → Access → Applications, add a Self-hosted application for TUNNEL_HOSTNAME, pick an identity provider under Settings → Authentication, and add a policy allowing the people who should have access. Then:

make http-up-persistent     # runs the server + the named tunnel at TUNNEL_HOSTNAME
make http-down-persistent   # tears both down
make http-status-persistent # check what's running

Once Access is configured, MCP_HTTP_AUTH_TOKEN (above) is still worth setting as defense-in-depth at the application layer, but Access is what actually stops unauthenticated traffic from reaching the container.

Adding a new tool

Every resource in specs/rest/*.json not yet wired up (see specs/rest/index.json's resources list for what's implemented vs. pending) follows the same pattern as the existing ones:

  1. Read the resource's spec file for exact paths/params (or specs/openapi.json directly for full request/response schemas).

  2. Add src/lofty_mcp/tools/<resource>.py with a register(mcp) function, one @mcp.tool(annotations=ToolAnnotations(...))-decorated function per operation, calling rest_client.request().

  3. Register it in src/lofty_mcp/tools/__init__.py.

  4. make test to confirm it works against the real API.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server for the Rentalot API. Manage rental properties, contacts, showings, conversations, and more from any AI assistant.
    14
    1
    MIT
  • A
    license
    B
    quality
    D
    maintenance
    MCP server for GoHighLevel API v2 that provides 50+ tools for CRM, billing, marketing, and operations workflows, enabling natural language interaction with contacts, opportunities, conversations, and more.
    50
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    A production-ready MCP server for Method CRM API integration. It enables LLMs to interact with Method CRM data through tools for tables, files, users, events, and API key management.
    3
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nbhav/lofty-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server