mcp-stark-brain
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()"
```
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues