Skip to main content
Glama
tutorcruncher

tc-help-mcp

Official
README.md
# tc-help-mcp

A remote [MCP](https://modelcontextprotocol.io) server that gives the team's Claude.ai
workspace live, always-current access to product documentation across the portfolio —
**TutorCruncher** and **Bobbin** — with room to add more products by config alone.

It is a **general product-docs assistant**: the team adds it once as a remote connector and
asks questions; Claude calls the tools, the server fetches from source on demand, and Claude
reasons over the returned content. There is no local index and no persisted copy of the docs —
the docs are the only source of truth, so answers never drift. A short in-memory TTL cache
(default 300s) exists purely for latency/rate-limit protection.

Sources at launch:

1. **TutorCruncher help docs** — Intercom help centre, via the Intercom Articles API.
2. **Bobbin help docs** — Intercom help centre (separate Intercom workspace → separate token).
3. **TutorCruncher API docs** — the public [`tutorcruncher/tc-api-docs`](https://github.com/tutorcruncher/tc-api-docs) repo.

## Tools

Help tools take an optional `product` filter (`"tutorcruncher"` | `"bobbin"`); unset spans all
configured products. Every result carries its `product` so answers stay correctly attributed.

| Tool | Purpose |
|---|---|
| `list_help_articles(product=None)` | Lightweight catalogue (product, id, title, description, url, collection). No bodies. |
| `search_help(query, product=None)` | Server-side Intercom search across product(s); deduped, ranked, top N `{product, id, title, summary, url}`. |
| `get_help_article(product, id)` | Full cleaned article body (markdown, boilerplate stripped) + title and url. |
| `list_api_sections()` | All API sections from `api.yml` (`{id, title, kind}`). |
| `search_api_docs(query)` | Keyword filter over section and subsection titles → matching whole sections. |
| `get_api_section(id)` | Whole section assembled to markdown: endpoints, params, request/response examples, version notes. |

## tc-api-docs structure (Outcome B)

`pages/api.yml` is an index (`info_sections` + `endpoint_sections`), each entry pointing to a
`layout` file (e.g. `pages/clients/clients.yml`). A layout's `sections:` list references separate
content files per subsection: `description` (`.md`), `attributes`/`filters` (`.yml`), `response`
(`.json`), and `code` (`.py`) plus `code_type`/`code_url`. Paths are repo-absolute under
`pages/`. `get_api_section` resolves a section, fetches its layout, fans out over the referenced
files (cached, concurrency-capped) and assembles one clean markdown document.

## Configuration

All configuration is via environment variables (see [`.env.example`](.env.example)). Never
commit values. Each Intercom workspace has its own token — one token does not span both products.

| Variable | Default | Purpose |
|---|---|---|
| `GITHUB_OAUTH_CLIENT_ID` / `GITHUB_OAUTH_CLIENT_SECRET` | — | GitHub OAuth App credentials. |
| `BASE_URL` | — | Public HTTPS base URL of this server (callback `<BASE_URL>/auth/callback`). |
| `GITHUB_SCOPES` | `read:org read:user` | Scopes needed to verify org membership. |
| `ALLOWED_GITHUB_ORG` | — | Only active members of this org may use the tools. |
| `ALLOW_UNGATED` | `0` | Set `1` to run without an org gate (local dev only). |
| `ALLOWED_REDIRECT_URIS` | `https://claude.ai/api/mcp/auth_callback` | Permitted OAuth client redirect URIs. |
| `JWT_SIGNING_KEY` | — | Signing key for FastMCP-issued JWTs. |
| `REDIS_URL` | — | Redis URL for persisting OAuth state across restarts. Required on ephemeral-filesystem hosts (e.g. Heroku, where daily dyno cycling otherwise wipes the on-disk store and expires every connection). Unset = default on-disk store. |
| `INTERCOM_API_BASE` | `https://api.intercom.io` | Intercom API host (same for both workspaces). |
| `INTERCOM_TOKEN_TUTORCRUNCHER` | — | TutorCruncher Intercom workspace token. |
| `INTERCOM_TOKEN_BOBBIN` | — | Bobbin Intercom workspace token. |
| `TC_API_DOCS_REPO` | `tutorcruncher/tc-api-docs` | API-docs repo. |
| `TC_API_DOCS_REF` | `master` | API-docs git ref. |
| `GITHUB_TOKEN` | — | Optional, raises raw-content fetch rate limits (distinct from the OAuth creds). |
| `CACHE_TTL_SECONDS` | `300` | In-memory cache TTL (latency/rate-limit protection only). |
| `SEARCH_RESULT_LIMIT` | `8` | Max `search_help` results. |
| `PORT` | `8000` | Bind port (Heroku sets this automatically). |
| `LOGFIRE_TOKEN` | — | Optional. When set, traces httpx upstream calls + tool spans to [Pydantic Logfire](https://logfire.pydantic.dev). Unset = fully disabled. |

### Observability

Observability is opt-in via `LOGFIRE_TOKEN`. With it set, the server instruments httpx
(every Intercom and GitHub request traced with status and latency) and wraps each tool
call in a span. With no token, Logfire runs in local-only mode and exports nothing — tests
and local runs are unaffected. httpx instrumentation uses conservative defaults (no header
or body capture), so workspace tokens and credentials are never sent to Logfire.

### Adding another product

A product is one line in `KNOWN_HELP_PRODUCTS` in `app/config.py` (`(product, help_centre_base)`)
plus an `INTERCOM_TOKEN_<PRODUCT>` env var. Products whose token is unset are skipped. No tool
changes are needed.

## Local development

```bash
make install-dev          # deps + pre-commit hooks
cp .env.example .env       # fill in tokens; set ALLOW_UNGATED=1 for local use
make lint                  # ruff + ty
make test                  # pytest -n auto
make run-dev               # serve on 0.0.0.0:$PORT
```

## Deployment

Built as a single-stage Docker image and released to Heroku (container stack) by
`.github/workflows/deploy.yml` on push to `main`. Set secrets via Heroku config, never the repo:

Deploys run via `.github/workflows/deploy.yml` (GitHub Actions builds the image and
releases it to the container-stack Heroku app `help-mcp` on push to `main`). The repo
needs a `HEROKU_API_KEY` secret; the Heroku app needs its config vars set:

```bash
heroku stack:set container -a help-mcp
heroku config:set -a help-mcp \
  GITHUB_OAUTH_CLIENT_ID=... GITHUB_OAUTH_CLIENT_SECRET=... \
  BASE_URL=https://help-mcp-5710e51f90a5.herokuapp.com \
  JWT_SIGNING_KEY="$(openssl rand -hex 32)" \
  ALLOWED_GITHUB_ORG=tutorcruncher \
  INTERCOM_TOKEN_TUTORCRUNCHER=... INTERCOM_TOKEN_BOBBIN=...
```

Provision Redis so OAuth state survives Heroku's daily dyno cycling — without it every
connected client must re-authenticate each day. The add-on sets `REDIS_URL` automatically:

```bash
heroku addons:create heroku-redis:mini -a help-mcp
```

Then register `https://help-mcp-5710e51f90a5.herokuapp.com/mcp` as a remote connector in
the team's Claude.ai workspace.