Skip to main content
Glama
README.md
# MCP Stark Brain (Payments)

Local MCP server that helps the **Payments** team in day-to-day work:

- Query the architectural pattern used by the Python microservices.
- Look up microservice specs (objective and responsibility of each service).
- Understand payment processing flows.
- Triage and investigate Customer Success (CS) tickets by combining documentation
  search with **GCP** analysis (Datastore + Cloud Logging / Log Explorer).
- Call **Stark Bank APIs** in **development** (default) or **sandbox** (only when
  explicitly requested) using your ECDSA Project credentials.

It performs **RAG** over the documentation in `starkbank/alexandria`, signs Stark
Bank API requests with your private key, and runs GCP queries using **your own**
`gcloud` identity (ADC).

---

## 1. How it works

```
IDE / LLM  --stdio-->  MCP server
                         |-- Docs (RAG): fetch alexandria via GitHub PAT -> local vector index
                         |-- Stark Bank API: ECDSA-signed HTTP to development (default) / sandbox
                         |-- GCP: Datastore + Cloud Logging via your gcloud ADC (project per call)
```

- **Docs are remote-first (no git clone kept).** The server downloads the repo
  tarball via the GitHub API (one request for the whole content) to build a local
  embedding index. Only the vector index is cached locally.
- **Rate-limit aware.** When the GitHub budget runs low, the server suggests cloning
  the repo and switching to `local` mode (see [section 10](#10-remote-vs-local-mode)).
- **Stark Bank API defaults to development** (`https://development.api.starkbank.com`).
  Sandbox is used only when a tool is called with `environment="sandbox"` after an
  explicit user request. **Production is never allowed.**
- **GCP project is passed per call.** There is no fixed project env var: each query
  takes an explicit `project`, so you can jump between microservice projects in the
  same session without touching your global `gcloud config`.
- **No service account keys for GCP.** GCP access uses your personal ADC credentials,
  which preserves per-user permissions and audit trails.

---

## 2. Prerequisites

- **Python 3.12 (required for building/installing the bundle).** `chromadb` and
  `fastembed` (via `onnxruntime`) do not reliably ship pre-built wheels for newer
  interpreters yet, so the project pins `requires-python = ">=3.11,<3.13"` and every
  command below explicitly targets **3.12** — do not substitute your system's default
  `python3` without checking its version first.
- **[uv](https://docs.astral.sh/uv/)** (recommended) or **[pipx](https://pipx.pypa.io/)** to install the bundle.
- **[Google Cloud SDK (`gcloud`)](https://cloud.google.com/sdk/docs/install)**.

Check/install the pinned Python version with `uv` (does not affect your system Python):

```bash
uv python install 3.12
```

---

## 3. Generate your GitHub PAT

Each developer generates their **own** PAT (never shared, never committed). `alexandria`
is private and owned by the `starkbank` org, so which token type works depends on the
org's token policy — read both options below before picking one.

### Option A: fine-grained PAT (try this first)

1. GitHub -> Settings -> Developer settings -> **Fine-grained tokens** -> *Generate new token*.
2. **Resource owner:** `starkbank`.
3. **Repository access:** *Only select repositories* -> `starkbank/alexandria`.
4. **Permissions:** Repository permissions -> **Contents: Read-only**.
5. Generate and copy the token (you will set it as an env var in your `mcp.json`).
6. **Check its status at <https://github.com/settings/personal-access-tokens>.** If
   the org requires approval, it will show as **Pending** and will 404 on every
   request until approved. Ask a `starkbank` org owner to approve it under the
   org's *Settings -> Personal access tokens -> Pending requests*, or skip to Option B.

### Option B: classic PAT (fallback if the org doesn't approve fine-grained tokens)

Classic PATs are **not** subject to the org-approval step above, so they are the
faster path if your org restricts fine-grained tokens:

1. GitHub -> Settings -> Developer settings -> **Tokens (classic)** -> *Generate new token*.
2. Scope: **`repo`** (classic tokens don't have a contents-only scope for private repos).
3. If the `starkbank` org enforces SSO, click **Configure SSO** next to the newly
   created token and **Authorize** it for `starkbank` — an unauthorized token will
   404 on `starkbank` resources exactly like an unapproved fine-grained one.

Either way, once installed, run the `diagnose_github_access` tool (see
[section 8](#8-first-run-and-usage)) to confirm the token actually works before
relying on it.

---

## 4. Authenticate with GCP (ADC)

```bash
gcloud auth login
gcloud auth application-default login
```

You do **not** need to set a project here — the MCP receives the `project` on each
GCP tool call. Use `analyze_ticket` / `resolve_project` to get project suggestions.

---

## 5. Stark Bank API credentials (ECDSA)

API calls are authenticated with ECDSA (secp256k1), not static API keys. See the
official docs: [Authentication](https://docs.starkbank.com/api#authentication).

1. Generate a key pair (if you have not already) and **register only the public key**
   in Web Banking (Integrations → Project) for the development environment.
2. Keep the **private key PEM** on your machine — never commit it and never put the
   public key inside this repo (the MCP does not need the public key to sign requests).
3. Note the **Project ID** shown in Web Banking after you create/register the Project.
4. Point the MCP at the PEM and Project ID via env vars (see step 6 / section 11).

Suggested location for the private key (outside the repo):

```bash
mkdir -p ~/.config/mcp-stark-brain
chmod 700 ~/.config/mcp-stark-brain
# copy your privateKey.pem there, then:
chmod 600 ~/.config/mcp-stark-brain/privateKey.pem
```

Default base URLs:

| Environment | Base URL | When used |
| --- | --- | --- |
| development | `https://development.api.starkbank.com` | Default for all API tools |
| sandbox | `https://sandbox.api.starkbank.com` | Only when `environment="sandbox"` and the user asked for sandbox |

---

## 6. Build the bundle (wheel)

From the repo root, always pin the interpreter explicitly to **Python 3.12** — do not
run a bare `uv build` and rely on whatever Python happens to be first on your `PATH`:

```bash
rm -rf dist  # avoid mixing wheels from a previous version/build
uv build --python 3.12 -o dist
```

This produces the installable artifacts in `dist/` (the exact version in the
filename comes from `version` in [pyproject.toml](pyproject.toml), currently `0.2.0`):

```
dist/
  mcp_stark_brain-0.2.0-py3-none-any.whl
  mcp_stark_brain-0.2.0.tar.gz
```

Distribute the `.whl` to the developers (or a shared location).

> Without `uv`: create a venv with `python3.12 -m venv .venv312`, activate it, then
> `pip install build && python -m build -o dist`. Verify first with
> `python3.12 --version` — if that command is not found, install Python 3.12 before
> continuing; do not build with a different major/minor version.

---

## 7. Install the MCP in the IDE

Install the wheel as an isolated tool, again pinning **Python 3.12** explicitly so the
tool's environment matches the one it was built/tested against. Use a glob so you
never have to hand-edit a version number (and risk installing a stale wheel left
over from a previous build):

```bash
# with uv (recommended)
uv tool install --python 3.12 ./dist/mcp_stark_brain-*-py3-none-any.whl

# or with pipx
pipx install --python python3.12 ./dist/mcp_stark_brain-*-py3-none-any.whl
```

This exposes the `mcp-stark-brain` command on your PATH.

Then add the server to your IDE's MCP config (e.g. Cursor `~/.cursor/mcp.json` or
the project `.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "stark-brain": {
      "command": "mcp-stark-brain",
      "env": {
        "ALEXANDRIA_GITHUB_PAT": "<your-personal-fine-grained-PAT>",
        "STARKBANK_PRIVATE_KEY_PATH": "/Users/you/.config/mcp-stark-brain/privateKey.pem",
        "STARKBANK_PROJECT_ID": "<your-project-id>",
        "STARKBANK_DEV_BASE_URL": "https://development.api.starkbank.com",
        "STARKBANK_SANDBOX_BASE_URL": "https://sandbox.api.starkbank.com"
      }
    }
  }
}
```

Restart/reload the IDE so it picks up the new MCP server.

> Want a custom icon next to `stark-brain` in the Tools & MCP list (like the
> official `github` MCP shows its logo)? See [cursor-plugin/README.md](cursor-plugin/README.md)
> for an optional wrapper that packages this same config as a local Cursor
> plugin with a `logo`. Purely cosmetic — skip it if you don't care.

---

## 8. Updating an already-installed MCP

Whenever this repo changes (new tools, bug fixes, config default fixes, etc.), you
need a new bundle. The command depends on how you originally installed it — using
the wrong one is the most common source of "why isn't my fix showing up?" confusion,
so pick the one that matches step 6:

```bash
# 1. Pull the latest source and rebuild the bundle (repo maintainer, or you if you
#    build it yourself). Always clean dist/ first to avoid mixing old/new wheels.
git pull
rm -rf dist
uv build --python 3.12 -o dist
```

```bash
# 2a. If you installed with `uv tool install`, use --reinstall (uv tool upgrade
#     does NOT work for local wheel paths, only for PyPI-published packages):
uv tool install --python 3.12 --reinstall ./dist/mcp_stark_brain-*-py3-none-any.whl

# 2b. If you installed with pipx, uninstall + reinstall (pipx has no local-wheel
#     upgrade command either):
pipx uninstall mcp-stark-brain
pipx install --python python3.12 ./dist/mcp_stark_brain-*-py3-none-any.whl
```

Then, get Cursor to actually respawn the server process — the tool list you see is
whatever that specific `stdio` subprocess announced when it started, so an on-disk
reinstall alone does not update it:

1. **First, verify the reinstall actually landed** (outside Cursor, in a plain
   terminal):
   ```bash
   uv tool list | grep -A2 mcp-stark-brain   # confirm the version bumped
   which mcp-stark-brain
   ```
2. **Toggle the server off/on in Cursor** — this is the officially supported way
   to respawn a single MCP server without quitting the whole app: `Cmd+Shift+J`
   -> **Tools & MCP** -> find `stark-brain` -> toggle it **off**, wait a couple
   seconds, toggle it **on**.
3. **Open a brand-new chat.** A chat that was already open before the toggle can
   keep showing the old tool list even after the server restarted.
4. If tools still look stale, it means Cursor's **Shared Process** — a single
   background process per app instance that hosts all MCP subprocesses (not
   per-window, so `Developer: Reload Window` does **not** restart it) — still
   has the old subprocess alive in memory. **Fully quit the app** (`Cmd+Q`, not
   just closing the window) and reopen it; that kills the Shared Process and
   every MCP subprocess with it.
5. To confirm at the protocol level rather than guessing: `Cmd+Shift+U` -> **MCP
   Logs** dropdown -> `stark-brain` -> check the `tools/list` response actually
   includes the new tool name. If it's missing there too, the problem is the
   installed bundle, not Cursor's cache — go back to step 1.
6. Once the new tools are visible, run the `status` tool to confirm the update
   picked up (check `docs_mode`, `repo`, `ref`, `embed_model` reflect what you
   expect).
7. If only docs content changed (not the code), you don't need to reinstall
   anything — just call `refresh_docs()` from the IDE.

You do **not** need to regenerate your PAT or redo `gcloud auth` when updating; those
credentials are independent of the installed version.

---

## 9. First run and usage

- On the first docs tool call, the server fetches the alexandria content and builds
  the local index (this can take a bit while the embedding model is downloaded once).
- Use `refresh_docs` to re-sync after documentation changes (incremental: only
  changed files are re-embedded).
- `status` reports docs mode, indexed file count, rate limit and whether Stark Bank
  API credentials are configured (`starkbank_api_configured`).
- Stark Bank API tools default to **development**. Pass `environment="sandbox"` only
  when the user explicitly asks for sandbox.

Available tools:

| Tool | Purpose |
| --- | --- |
| `search_docs(query, limit)` | Semantic search over alexandria. |
| `list_microservices()` | Microservices inferred from the docs structure. |
| `get_microservice_spec(name)` | Objective/responsibility/spec of a service. |
| `get_architecture_pattern()` | Python microservices architectural pattern. |
| `get_payment_flow(flow_name)` | A payment processing flow. |
| `analyze_ticket(description)` | CS ticket triage: docs context + suggested project + candidate GCP queries. |
| `resolve_project(microservice)` | Suggest GCP project(s) for a microservice (mined from docs). |
| `datastore_query(project, kind, filters, limit)` | Query Datastore in a project. |
| `logs_query(project, filter_, order, limit)` | Query Cloud Logging (Log Explorer). |
| `api_request(method, path, query?, body?, environment?)` | Generic signed Stark Bank API call (`/v2/...`). Default env: `dev`. |
| `get_balance(environment?)` | GET `/v2/balance`. |
| `get_transfer` / `query_transfers` | Read transfers. |
| `get_invoice` / `query_invoices` | Read invoices. |
| `get_transaction` / `query_transactions` | Read transactions. |
| `get_deposit` / `query_deposits` | Read deposits. |
| `set_docs_source(mode, path)` | Switch between `remote` and `local` docs source. |
| `refresh_docs()` | Re-fetch + reindex; reports rate limit. |
| `status()` | Current mode, indexed files, rate limit, Stark Bank API config flags. |
| `diagnose_github_access()` | Live check that your PAT can actually see alexandria; explains 404s. |

---

## 10. Remote vs local mode

- **remote** (default): docs are fetched from GitHub via your PAT. Efficient (tarball
  = 1 request per refresh), but consumes your GitHub API budget.
- **local**: docs are read from a directory you cloned yourself; **zero** API usage.

When the GitHub rate limit is close to exhaustion, the server warns you and suggests
switching. To switch:

```bash
# clone the repo once (your own credentials)
git clone git@github.com:starkbank/alexandria.git ~/repos/alexandria
```

Then either set it in `mcp.json`:

```json
"env": {
  "ALEXANDRIA_GITHUB_PAT": "<pat>",
  "STARK_BRAIN_DOCS_MODE": "local",
  "STARK_BRAIN_DOCS_PATH": "/Users/you/repos/alexandria"
}
```

or switch at runtime via the tool:

```
set_docs_source(mode="local", path="/Users/you/repos/alexandria")
refresh_docs()
```

---

## 11. Configuration reference (env vars)

| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `ALEXANDRIA_GITHUB_PAT` | remote mode | — | Your fine-grained PAT (Contents: Read-only). |
| `ALEXANDRIA_REPO` | no | `starkbank/alexandria` | `owner/name` of the docs repo. |
| `ALEXANDRIA_REF` | no | `master` | Branch/tag/sha to index (alexandria's default branch is `master`, not `main`). |
| `STARK_BRAIN_DOCS_MODE` | no | `remote` | `remote` or `local`. |
| `STARK_BRAIN_DOCS_PATH` | local mode | — | Path to your local alexandria clone. |
| `STARK_BRAIN_CACHE_DIR` | no | `~/.cache/mcp-stark-brain` | Vector index + model cache. |
| `STARK_BRAIN_EMBED_MODEL` | no | `BAAI/bge-small-en-v1.5` | fastembed model. |
| `STARK_BRAIN_RATE_LIMIT_THRESHOLD` | no | `200` | Warn to switch to local below this. |
| `STARKBANK_PRIVATE_KEY_PATH` | API tools | — | Absolute path to your ECDSA private-key PEM. |
| `STARKBANK_PROJECT_ID` | API tools | — | Project ID → `Access-Id: project/<id>`. |
| `STARKBANK_DEV_BASE_URL` | no | `https://development.api.starkbank.com` | Development API base URL. |
| `STARKBANK_SANDBOX_BASE_URL` | no | `https://sandbox.api.starkbank.com` | Sandbox API base URL. |

See [.env.example](.env.example).

---

## 12. Troubleshooting

- **`configuration error: ALEXANDRIA_GITHUB_PAT is required`** — set the PAT in your
  `mcp.json` env, or switch to `local` mode.
- **GitHub 401** — PAT invalid/expired. Regenerate it.
- **GitHub 404 ("Repo or ref not found") even though the repo exists** — for private
  repos GitHub returns 404 both when a resource truly doesn't exist and when your
  token cannot see it, so this is almost always a token/access issue, not a wrong
  `ALEXANDRIA_REPO`/`ALEXANDRIA_REF`. Most common cause: a **fine-grained PAT still
  pending org-admin approval** (check <https://github.com/settings/personal-access-tokens>
  — if it shows "Pending", see [section 3](#3-generate-your-github-pat) for the
  approval step or the classic-PAT fallback). Run `diagnose_github_access()` for a
  live check that pinpoints this.
- **GitHub 403 / rate limited** — check PAT permissions, or clone + use `local` mode.
- **`GCP credentials not found`** — run `gcloud auth application-default login`.
- **Datastore/Logging permission errors** — you queried a project you don't have
  access to; pick another `project` or request access.
- **Model download slow on first run** — the embedding model is cached after the
  first use under `STARK_BRAIN_CACHE_DIR`.
- **A newly added tool doesn't show up after reinstalling** — this is a Cursor-side
  stale process, not a bad install (see [section 8](#8-updating-an-already-installed-mcp)
  step-by-step): the running MCP subprocess doesn't pick up an on-disk reinstall by
  itself. Toggle the server off/on in Tools & MCP, open a new chat, and if that's
  still not enough, fully quit (`Cmd+Q`) and reopen Cursor.
- **`STARKBANK_PRIVATE_KEY_PATH is not set` / API tools fail** — set the absolute path
  to your PEM and `STARKBANK_PROJECT_ID` in `mcp.json` (see [section 5](#5-stark-bank-api-credentials-ecdsa)).
  Confirm `status().starkbank_api_configured` is `true`.
- **Stark Bank API 401 / invalid signature** — wrong Project ID, PEM not registered
  for that environment, or clock skew. Confirm the public key is registered in the
  matching Web Banking environment (development vs sandbox).

---

## 13. Security notes

- Your PAT is only sent in the `Authorization` header and is **never logged**.
- The Stark Bank private key is read from disk at request time and is **never logged**.
- No service account keys are distributed; GCP access is your personal ADC identity.
- The GCP `project` is passed per call — no shared/hardcoded project.
- Production Stark Bank API hosts are refused by the client.
- `.env`, `*.pem`, `keys/` and the local cache are git-ignored.

---

## 14. Development

Source files live flat under [src/](src/) (no extra `src/mcp_stark_brain/` nesting).
The build config in [pyproject.toml](pyproject.toml) ships them as the `mcp_stark_brain`
import package in the wheel (`packages = ["src"]` + `sources = {"src" = "mcp_stark_brain"}`),
so entry points and internal imports stay unchanged regardless of the on-disk layout.

That rename is not compatible with editable/dev-mode installs (a hatchling/`pip`
limitation), so `uv sync` is configured with `tool.uv.package = false`: it installs
dependencies only, not the project itself. [conftest.py](conftest.py) and
[scripts/smoke_test.py](scripts/smoke_test.py) use
[devtools/bootstrap.py](devtools/bootstrap.py) to make `import mcp_stark_brain` work
directly against `src/` for tests and local scripts, with no install step required.

```bash
uv python install 3.12
uv sync --extra dev --python 3.12
uv run ruff check .
uv run pytest
uv run python scripts/smoke_test.py
```

To actually try the server locally (no wheel build needed):

```bash
uv run --python 3.12 python -c "from devtools.bootstrap import ensure_importable; ensure_importable(); from mcp_stark_brain.server import main; main()"
```