Skip to main content
Glama
a-kamari

Planning Center MCP Server

by a-kamari
README.md
# Planning Center MCP Server

A [FastMCP](https://github.com/jlowin/fastmcp) server that exposes **read-only
tools** for the [Planning Center People API](https://developer.planning.center/docs/#/apps/people)
(JSON API 1.0). Point an MCP client (Claude Desktop, etc.) at it to query and
filter people, households, background checks, lists, and more with natural
language.

All 18 tools are read-only (`GET` requests only) — the server never creates,
updates, or deletes data in your Planning Center account.

## Features

**People**
- `search_people` — search for people by name
- `list_people` — general-purpose query (first/last name, status, created/updated since, ordering)
- `list_people_by_gender` — filter by gender
- `list_people_by_age_range` — filter by age (translated to a `birthdate` range)
- `list_people_in_family` — filter by family/last name
- `list_people_with_membership` — filter by membership value
- `get_person_details` — one person with households, emails, phone numbers, and addresses
- `find_person_by_email` — look up a person by email address
- `get_person_contact_info` — a person's emails, phone numbers, and addresses

**Households**
- `list_people_with_household` — people who belong to at least one household
- `list_households` — list households (optionally filtered by name)
- `get_household_members` — the people in a specific household

**Background checks**
- `list_people_with_approved_background_checks` — people with an approved check
- `list_background_checks` — background checks filtered by status and/or completed-date range

**Lists & organization**
- `list_people_in_list` — people belonging to a named People List (segment)
- `list_people_lists` — discover People Lists (`id`, `name`, `total_people`)
- `list_campuses` — the organization's campuses
- `list_forms` — forms (optionally active-only)

## Requirements

- Python 3.10+
- [`uv`](https://docs.astral.sh/uv/) for dependency management
- A Planning Center [Personal Access Token](https://api.planningcenteronline.com/oauth/applications)
  (Application ID + Secret)

## Setup

### 1. Install dependencies

```bash
uv sync
```

### 2. Configure credentials

Copy the example env file and fill in your Planning Center credentials:

```bash
cp .env.example .env
```

```
PLANNING_CENTER_CLIENT_ID=your_application_id_here
PLANNING_CENTER_SECRET=your_secret_here
```

Get credentials by creating a Personal Access Token at
<https://api.planningcenteronline.com/oauth/applications>. The server
authenticates using HTTP Basic auth (Application ID as username, Secret as
password).

### 3. Run the server

```bash
# Console script (STDIO transport — for MCP clients)
uv run planning-center-mcp

# Equivalent module invocation
uv run python -m planning_center_mcp.server
```

## Claude Desktop integration

Add the server to your Claude Desktop config
(`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS,
`%APPDATA%\Claude\claude_desktop_config.json` on Windows):

```json
{
  "mcpServers": {
    "planning-center": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/absolute/path/to/planning-center-mcp",
        "planning-center-mcp"
      ]
    }
  }
}
```

Replace `/absolute/path/to/planning-center-mcp` with the path where you cloned
this repo. The server reads credentials from the `.env` file in that directory;
alternatively you can supply them via an `"env"` block in the config.

## Testing

A smoke-test script connects to the server in-process (no external MCP client needed):

```bash
# List the registered tools and show whether credentials are configured
uv run python tests/test_tools.py

# Also call a read-only tool against the live API (requires a valid .env)
uv run python tests/test_tools.py --live
uv run python tests/test_tools.py --live --gender Female
```

Without credentials the server still boots and lists its tools; live calls
return a clear `HTTP 401` error (not a silent empty list) telling you to set
your `.env`.

## Running over HTTP (streamable transport)

```bash
uv run python run_http_server.py --port 8000
```

This exposes a **streamable-HTTP MCP endpoint at `http://localhost:8000/mcp`**.
It is an MCP endpoint for MCP clients — it does **not** serve an OpenAPI/ReDoc
REST UI. Point an MCP client at that URL, or use `tests/test_tools.py` for a
quick check.

## Behavior notes

- **Errors are surfaced, not swallowed.** A bad filter, invalid include, auth
  failure, or rate limit raises an error with the HTTP status and API detail so
  you can see what went wrong (instead of an empty result).
- **Pagination.** List tools page through results up to a `max_records` cap
  (default 500) using the JSON:API `offset` cursor.
- **Rate limiting.** Planning Center allows 100 requests/minute. The client
  enforces this limit locally and also reports server-side `429` responses.
- **Age.** The People API has no `age` attribute, so `list_people_by_age_range`
  converts ages to a `birthdate` range; people without a birthdate on file are
  excluded when an age filter is applied.
- **Roles to Lists.** Planning Center People has no "roles" concept, so
  role-style segmentation is served via People Lists (`list_people_in_list` /
  `list_people_lists`).

## Project structure

```
planning-center-mcp/
├── planning_center_mcp/
│   ├── __init__.py
│   ├── server.py        # MCP server entrypoint (stdio / http transport)
│   ├── client.py        # API client (auth, rate limit, pagination, errors)
│   └── tools.py         # MCP tool definitions
├── tests/
│   └── test_tools.py    # In-process smoke test
├── run_http_server.py   # Convenience launcher for HTTP transport
├── pyproject.toml       # Project configuration and dependencies
├── .env.example         # Example environment variables
├── LICENSE
└── README.md
```

## Troubleshooting

1. **Authentication (401/403):** confirm `PLANNING_CENTER_CLIENT_ID` and
   `PLANNING_CENTER_SECRET` are set correctly in `.env`.
2. **Rate limiting (429):** wait a minute before making more requests.
3. **Empty results:** a `200` with no data means no records matched — the tools
   raise on real errors, so an empty list is a genuine "no matches."

## License

Released under the [MIT License](LICENSE).