Skip to main content
Glama
lincalinca

Crescender MCP Server

by lincalinca
README.md
# Crescender MCP Server

Model Context Protocol server for Crescender's public read API. Connects AI clients (Claude Desktop, Cursor, custom agents) to a school's assets, loans, members, and asset-comms threads — all read-only, scope-token-authenticated, and bound to a single school per token.

> **Status: 0.0.x — pre-publish.** This package has not yet been published to npm or pushed to GHCR. The publish pipelines exist but are gated to manual triggers; the first release ships when the source has been pressure-tested locally. For now, run from a clone or a `pnpm link` (see [Local development](#local-development) below).

## What it gives an AI

Six read-only tools, each backed by an `/api/v1/*` endpoint:

| MCP tool | Returns |
|---|---|
| `list_schools` | The single school the token is bound to (single-item array) |
| `get_asset` | Full asset detail by id — model, serial, location, role, current loan |
| `search_assets` | Up to 50 assets matching `q` / `item_class` / `category` / `status` |
| `get_loans_for_asset` | Loan history for one asset (current + past) |
| `list_members` | Member directory — id, role, status, external_id (no PII) |
| `list_asset_threads` | Asset-comms threads with status filter (read-only — vendor-token issuance is NOT exposed via MCP) |

Reads only. No `update_*` / `create_*` tools in v1.

## Configuration

Two environment variables, one of them required:

| Var | Default | Notes |
|---|---|---|
| `CRESCENDER_API_TOKEN` | — | **Required.** Issue from `https://app.crescender.com.au/school/<id>/settings/integrations` (the "Integrations" tile under your school's Settings). Format `crsc_<random>`. |
| `CRESCENDER_API_URL` | `https://app.crescender.com.au` | Override only if you're pointing at a non-production deployment. |
| `CRESCENDER_MCP_LOG_LEVEL` | `info` | One of `debug` / `info` / `warn` / `error`. Logs go to stderr (stdout is reserved for the MCP protocol). |
| `CRESCENDER_MCP_HEALTH` | `0` | Set to `1` in container deployments to enable the `GET /health` HTTP endpoint on `PORT` (default `3030`). |
| `CRESCENDER_API_TIMEOUT_MS` | `15000` | Per-request upstream timeout. |

## Connecting Claude Desktop (once published)

Once the first version is on npm:

```jsonc
// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
// %APPDATA%\Claude\claude_desktop_config.json (Windows)
{
  "mcpServers": {
    "crescender": {
      "command": "npx",
      "args": ["-y", "@crescender/mcp-server"],
      "env": {
        "CRESCENDER_API_TOKEN": "crsc_..."
      }
    }
  }
}
```

Then restart Claude Desktop. The six tools should appear in the MCP picker.

## Connecting Cursor (once published)

Cursor reads MCP config from `.cursor/mcp.json` in your workspace, or globally from settings. The shape is the same as Claude Desktop's `mcpServers` block.

## Local development

```bash
# Install
git clone https://github.com/lincalinca/crescender-mcp-server.git
cd crescender-mcp-server
pnpm install

# Build
pnpm build

# Smoke test (talks to stdin/stdout — Claude Desktop / Cursor will spawn it
# the same way)
CRESCENDER_API_TOKEN=crsc_... node dist/index.js
```

To wire your local clone into Claude Desktop without publishing:

```jsonc
{
  "mcpServers": {
    "crescender-local": {
      "command": "node",
      "args": ["/absolute/path/to/crescender-mcp-server/dist/index.js"],
      "env": {
        "CRESCENDER_API_TOKEN": "crsc_..."
      }
    }
  }
}
```

## Container

```bash
# Build (the published image is built by .github/workflows/publish-image.yml)
docker build -t crescender-mcp-server:dev .

# Run with the in-container /health endpoint enabled
docker run --rm -i \
  -e CRESCENDER_API_TOKEN=crsc_... \
  -e CRESCENDER_MCP_HEALTH=1 \
  -p 3030:3030 \
  crescender-mcp-server:dev
```

Once published, the image will be available at `ghcr.io/lincalinca/crescender-mcp-server`.

## Statelessness contract

- No in-memory token cache: validation hits the upstream API on every call (revocation is immediate).
- No filesystem state: stdout/stderr only.
- Config is env-driven, parsed once at boot, frozen.
- `/health` (when enabled) returns `{ ok, api_reachable, version }` and probes the upstream `/api/v1/health` to verify reachability — not just "this process is alive."
- Graceful shutdown: SIGTERM / SIGINT trigger a 30-second drain, then force-exit.

This is what makes the container k8s-ready without any k8s-specific code. When the orchestration decision is eventually made (ASK / AKS / GKE / Fly / Railway / etc.), nothing changes here.

## License

MIT — see [LICENSE](./LICENSE).

## Related

- [`@crescender/crescender-core`](https://github.com/lincalinca/crescender-core) — the platform itself, which serves the `/api/v1` endpoints this MCP wraps.
- OpenAPI spec: `https://app.crescender.com.au/api/v1/openapi.json`

TDQS

A3.9/5.0

Scored across 6 tools

Disambiguation5/5

Each tool targets a distinct resource or action: get_asset and search_assets are clearly different (detail vs. search), and loan history, threads, members, and schools are all separate concerns. No overlapping purposes.

Naming Consistency5/5

All tools consistently follow the 'verb_noun' pattern in lowercase snake_case (e.g., get_asset, list_asset_threads, search_assets). The naming is uniform and predictable.

Tool Count5/5

6 tools is well-scoped for an asset management server. Each tool covers a core area without being overwhelming or insufficient.

Completeness2/5

The tool surface is heavily read-only: assets have get and search but no create/update/delete; loans only show history; threads only list; members and schools only list. Missing basic write operations for the domain.

Maintenance

ActivityInactive
ResponsivenessNo issues