Metadata-Version: 2.4
Name: accela-mcp
Version: 0.1.0
Summary: Model Context Protocol server for the Accela Construct API
Project-URL: Homepage, https://github.com/Donatoni/accela-mcp
Project-URL: Documentation, https://github.com/Donatoni/accela-mcp#readme
Project-URL: Issues, https://github.com/Donatoni/accela-mcp/issues
Author: Accela MCP Maintainers
License: Apache-2.0
License-File: LICENSE
Keywords: accela,civic-platform,government,mcp,permits
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.11
Requires-Dist: click>=8.1
Requires-Dist: cryptography>=42.0
Requires-Dist: httpx[http2]>=0.27
Requires-Dist: mcp>=1.0
Requires-Dist: platformdirs>=4.0
Requires-Dist: pydantic-settings>=2.1
Requires-Dist: pydantic>=2.5
Requires-Dist: python-dateutil>=2.9
Requires-Dist: pyyaml>=6.0
Requires-Dist: structlog>=24.1
Requires-Dist: tenacity>=8.2
Provides-Extra: dev
Requires-Dist: freezegun>=1.5; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# Accela MCP

A [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server
that wraps the Accela Construct API as a curated, capability-grouped tool
set. Designed for production deployment by government IT staff and
implementation partners running [Accela Civic Platform](https://www.accela.com/).

The server is **safe by default** — every install ships read-only.
Destructive and financial capability groups must be explicitly enabled in
configuration. Tokens are stored encrypted at rest, refreshed
automatically, and never logged.

> Status: **v0.1.0** — implements the default read-only v1 tool catalog.
> Write tools are deferred to v0.2.

---

## Prerequisites

- **Python 3.11+**.
- An **Accela Developer Portal** account at <https://developer.accela.com>.
- An app registered there (My Apps → Add New App):
    - Targeted Users: **Agency**
    - Stage: **Under Development** (move to **Published** when ready)
    - Authorized Redirect URIs must include the value you'll set in
      `ACCELA_REDIRECT_URI` (e.g., `http://localhost:8765/oauth/callback`).
- An agency to authenticate against. For sandbox use **NULLISLAND** (newer)
  or **ISLANDTON** (legacy, more variety).

## Install

```bash
# From PyPI (after publish)
pip install accela-mcp

# From source
git clone https://github.com/Donatoni/accela-mcp.git
cd accela-mcp
pip install -e ".[dev]"

# Or with uv (recommended)
uv tool install accela-mcp
```

## Easy Setup

For most users, run the guided setup wizard:

```bash
accela-mcp setup
```

It asks for:

- Accela App ID
- Accela App Secret
- Agency, such as `NULLISLAND`
- Environment, usually `TEST`
- Redirect URI, usually `http://localhost:8765/oauth/callback`
- Where to install the MCP entry: Claude Desktop, Codex, both, or neither

Then it:

- Generates the local encryption key automatically.
- Creates a private user-level env file.
- Creates a safe read-only `capabilities.yaml`.
- Opens the browser so you can sign in to Accela.
- Saves encrypted OAuth tokens.
- Adds the MCP server to the selected app config without putting Accela
  secrets in Claude or Codex config files.

After setup finishes, restart the selected app(s).

To check the installation later:

```bash
accela-mcp doctor
```

`doctor` checks the private setup file, capabilities config, encrypted
token file, refresh-token expiry, and selected app config. Add `--online`
if you also want it to call Accela's token-info endpoint. Use
`--apps claude`, `--apps codex`, or `--apps both` to choose which app
configs to check.

## Manual Setup

Use this only if you are deploying for an agency, scripting setup, or need
custom paths. The wizard above writes these values for you.

### Required Environment Variables

| Var                   | Purpose                                                                                                                                                                                                               |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ACCELA_APP_ID`       | Your Developer Portal app ID.                                                                                                                                                                                         |
| `ACCELA_APP_SECRET`   | App secret used for token exchange / refresh.                                                                                                                                                                         |
| `ACCELA_REDIRECT_URI` | Must match a registered redirect URI on the Developer Portal. The CLI binds a one-shot loopback listener on its host:port during the auth flow.                                                                       |
| `ACCELA_MCP_KEY`      | Local Fernet key for token storage. Generate with: `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` and treat like a password. If lost, you must re-run `accela-mcp auth`. |

### Optional Environment Variables

| Var                      | Default                                    | Purpose                                                      |
| ------------------------ | ------------------------------------------ | ------------------------------------------------------------ |
| `ACCELA_MCP_ENV_PATH`    | user config `.env`, then repo-local `.env` | Path to the private setup env file generated by `setup`.     |
| `ACCELA_MCP_CONFIG_PATH` | user config `capabilities.yaml`            | Path to the YAML config.                                     |
| `ACCELA_MCP_TOKEN_PATH`  | user config `tokens.json`                  | Path to the encrypted token bundle.                          |
| `ACCELA_MCP_LOG_LEVEL`   | `INFO`                                     | `DEBUG` / `INFO` / `WARNING` / `ERROR`.                      |
| `ACCELA_MCP_LOG_FORMAT`  | `json`                                     | `json` (production) or `console` (human-readable, dev only). |
| `ACCELA_AUTH_BASE_URL`   | `https://auth.accela.com`                  | Override for regional / on-prem deployments.                 |
| `ACCELA_API_BASE_URL`    | `https://apis.accela.com`                  | Same.                                                        |

The default user config directory is platform-specific:

- macOS: `~/Library/Application Support/accela-mcp/`
- Linux: `~/.config/accela-mcp/`
- Windows: `%APPDATA%\accela-mcp\`

### `capabilities.yaml`

Drop a copy of `capabilities.yaml.example` at the path
`ACCELA_MCP_CONFIG_PATH` points to and edit:

```yaml
version: 1
agency: NULLISLAND
environment: TEST

# Optional — replaces the spec defaults if present.
enabled_groups:
    - discovery
    - records_read
    - inspections_read
    - documents_read
    - property_read
    - people_read
    - workflow_read
    - fees_read
    - reference_data
    - search
```

`discovery` is always enabled. The full validation rules and every
optional knob are documented in `capabilities.yaml.example`.

### Manual Quickstart

```bash
export ACCELA_APP_ID="your_app_id"
export ACCELA_APP_SECRET="your_app_secret"
export ACCELA_REDIRECT_URI="http://localhost:8765/oauth/callback"
export ACCELA_MCP_KEY="$(python3 -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')"

accela-mcp auth --agency NULLISLAND --environment TEST
accela-mcp status
accela-mcp serve
```

`accela-mcp auth` creates a default `capabilities.yaml` if one does not
exist. Edit that file to opt into additional groups, the escape hatch, or
different rate-limit settings.

## Connecting to MCP Clients

### Claude Desktop

Choose `claude` or `both` during `accela-mcp setup` to update Claude
Desktop automatically. The generated entry looks like this:

```json
{
    "mcpServers": {
        "accela": {
            "command": "accela-mcp",
            "args": ["serve"],
            "env": {
                "ACCELA_MCP_ENV_PATH": "/path/to/private/accela-mcp/.env"
            }
        }
    }
}
```

The `ACCELA_MCP_ENV_PATH` value is not secret; it points to the private
file where the real Accela credentials live. After config changes,
restart Claude Desktop.

### Codex

Choose `codex` or `both` during `accela-mcp setup` to update Codex
automatically. The setup wizard writes or updates this block in
`~/.codex/config.toml` (or `%USERPROFILE%\.codex\config.toml` on Windows):

```toml
[mcp_servers.accela]
command = "accela-mcp"
args = ["serve"]

[mcp_servers.accela.env]
ACCELA_MCP_ENV_PATH = "/path/to/private/accela-mcp/.env"
```

The setup wizard creates a timestamped backup before changing an existing
Codex config. Restart Codex after setup.

### Claude Code

For Claude Code CLI, point it at the generated env file:

```bash
claude mcp add accela --command accela-mcp --args serve \
  -e ACCELA_MCP_ENV_PATH=/path/to/private/accela-mcp/.env
```

Or use the manual environment-variable form:

```bash
claude mcp add accela --command accela-mcp --args serve \
  -e ACCELA_APP_ID=... \
  -e ACCELA_APP_SECRET=... \
  -e ACCELA_REDIRECT_URI=http://localhost:8765/oauth/callback \
  -e ACCELA_MCP_KEY=...
```

## Capability groups

| Group                                                                        | Default   | Purpose                                                                                                                             |
| ---------------------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `discovery`                                                                  | always on | List capabilities, agency info, record-type and custom-form metadata.                                                               |
| `records_read`                                                               | on        | Search records, get record details, get my records, read custom data.                                                               |
| `inspections_read`                                                           | on        | List inspections, get details, history, checklists.                                                                                 |
| `documents_read`                                                             | on        | List record documents, download (≤25 MB inline).                                                                                    |
| `property_read`                                                              | on        | Address, parcel, owner lookups.                                                                                                     |
| `people_read`                                                                | on        | Contacts and licensed professionals.                                                                                                |
| `workflow_read`                                                              | on        | Workflow tasks and history for a record.                                                                                            |
| `fees_read`                                                                  | on        | List fees, estimate fees, list invoices.                                                                                            |
| `reference_data`                                                             | on        | Record types, statuses, departments, fee schedules (TTL-cached).                                                                    |
| `search`                                                                     | on        | Cross-entity global search.                                                                                                         |
| `records_write` / `inspections_write` / `documents_write` / `workflow_write` | off (v2)  | Write tools, deferred.                                                                                                              |
| `payments_read` / `payments_write`                                           | off (v2)  | Financial tools.                                                                                                                    |
| `gis` / `reports`                                                            | off (v2)  | Geocoding and report generation.                                                                                                    |
| `admin_escape_hatch`                                                         | off       | `accela_raw_request` for endpoints not wrapped — gated by a regex path allowlist and an HTTP-method allowlist (default `GET` only). |

## Operational behavior

- **Auth.** OAuth 2.0 Authorization Code flow with PKCE (always on). The
  refresh token is rotated on every refresh; the 7-day refresh window is
  honored, and `accela-mcp status` warns when within 24 hours of expiry.
- **Retries.** 429 / 5xx / transient network errors are retried with
  jittered exponential backoff (configurable via
  `rate_limit.max_retries` etc. in YAML). 401 forces a refresh and
  retries exactly once.
- **Logging.** One JSON line per API call to **stderr**, including method,
  path, status, duration, attempt number, and Accela `traceId` on errors.
  Rate-limit headers are surfaced when present.
- **Caching.** Reference-data endpoints (record types, departments, etc.)
  are cached with a 1-hour TTL by default. Every reference-data tool
  exposes `cache_bypass: bool = False` for forced refresh.
- **Document downloads.** Inline base64; refused for files >25 MB.

## Troubleshooting

Start with:

```bash
accela-mcp doctor
```

| Symptom                                                                      | Cause                                               | Fix                                                                                                   |
| ---------------------------------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `accela-mcp setup` or `auth` errors with "Failed to bind ..."                | Port in use                                         | Pick a different port in the redirect URI and update the registered redirect on the Developer Portal. |
| `OAuth state mismatch`                                                       | Stale browser session, possible CSRF                | Close the auth tab, retry.                                                                            |
| `Refresh token expired`                                                      | More than 7 days since last successful auth/refresh | Re-run `accela-mcp setup` or `accela-mcp auth`.                                                       |
| `Failed to decrypt token file`                                               | `ACCELA_MCP_KEY` changed since tokens were saved    | Re-run `accela-mcp setup` (or restore the original key).                                              |
| `capabilities.yaml agency 'X' does not match the persisted token agency 'Y'` | YAML and tokens disagree                            | Re-run setup/auth for the right agency, or update `capabilities.yaml`.                                |
| Claude Desktop or Codex does not show Accela tools                           | App config not updated or app not restarted         | Run `accela-mcp doctor --apps both`, then restart the affected app.                                   |
| Tool returns `{ "error": "accela_api_error", ... }`                          | API returned 4xx                                    | Check `trace_id` and Accela's docs; surface to your agency admin if persistent.                       |

## Development

```bash
# Install dev deps.
uv pip install -e ".[dev]"

# Lint + format.
ruff check
ruff format --check

# Unit tests (mocked HTTP — no real API).
pytest tests/unit -v

# Coverage.
pytest tests/unit --cov=accela_mcp --cov-report=term-missing

# Integration tests (real sandbox; gated).
ACCELA_INTEGRATION_TEST=1 pytest tests/integration -v
```

## Limitations (v0.1.0)

- Single-agency, single-environment per running server.
- Read-only tools shipped by default; write capabilities are v0.2 work.
- Document upload via legacy `/v4/documents` only; ACDS chunked upload
  is deferred.
- No webhook support — Accela does not expose a native webhook API
  (EMSE is agency-side and not in scope).

## License

Apache 2.0 — see `LICENSE`.
