lofty-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@lofty-mcpshow me the leads I added this week"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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; seespecs/rest/index.jsonfor 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_KEYis a personal-access token from the Lofty CRM, sent asAuthorization: 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:
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.A Lofty API key. In the Lofty CRM, go to Settings → Integrations → API and generate a personal-access key.
jq, used by severalMakefiletargets to edit JSON config in place:brew install jq(macOS) or your distro's package manager.(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-hereIf 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.jsonThis 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 downmake 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-upWith 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-mcpThen:
make tunnel-create # creates (or reuses) the named tunnel, routes TUNNEL_HOSTNAME's DNS to itIn 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 runningOnce 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:
Read the resource's spec file for exact paths/params (or
specs/openapi.jsondirectly for full request/response schemas).Add
src/lofty_mcp/tools/<resource>.pywith aregister(mcp)function, one@mcp.tool(annotations=ToolAnnotations(...))-decorated function per operation, callingrest_client.request().Register it in
src/lofty_mcp/tools/__init__.py.make testto confirm it works against the real API.
This server cannot be installed
Maintenance
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
API-first CRM for LLMs - contacts, companies, deals and activities over a native MCP server.
Cross-product MCP server for CRM, LeadKit, ProjectKit, Bookio. 10 action types, MIT open spec.
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
The official Planning Center MCP server for interacting with your ministry's data.
Related MCP Servers
- MIT

Rentalot MCP Serverofficial
AlicenseNot gradedqualityCmaintenanceMCP server for the Rentalot API. Manage rental properties, contacts, showings, conversations, and more from any AI assistant.141MIT- AlicenseBqualityDmaintenanceMCP 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.501MIT
- AlicenseNot gradedqualityCmaintenanceA 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.3MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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