pump-fun-connector
# pump-fun-connector
An MCP server that lets Claude discover new pump.fun (Solana meme coin) tokens, risk-screen
them with RugCheck + GMGN, check your on-chain position, and **prepare or execute** buy/sell
transactions sized to your rules.
## What this is (and isn't)
The default is non-custodial: `prepare_buy` and `prepare_sell` return unsigned transactions.
Opt-in `execute_buy` and `execute_sell` can load a Solana CLI keypair file, sign locally, and
broadcast after the same risk and spend checks. Use a dedicated wallet funded only with money you
can afford to lose.
Automatic execution is disabled unless `AUTO_TRADING_ENABLED=true`.
## Layers
| Layer | Tool(s) | What it does |
|---|---|---|
| Discovery | `get_new_tokens`, `get_token_detail` | Live pump.fun feed via PumpPortal's WebSocket data API |
| Risk scoring | `get_risk_score` | Composite RugCheck + GMGN score (0–100, higher = riskier) |
| Position | `get_position` | Reads your wallet's actual on-chain SPL token balances |
| Trade prep | `prepare_buy`, `prepare_sell` | Builds an unsigned PumpPortal transaction, enforces guardrails |
| Trade execution | `execute_buy`, `execute_sell` | Builds, signs, submits, and confirms a transaction when explicitly enabled |
| Market activity | `get_market_activity` | Buy/sell pressure, volume acceleration and liquidity depth from DexScreener (keyless) |
| Logging | `create_investment_plan`, `log_transaction`, `log_creator_activity`, `log_creator_activity_batch`, `update_active_position`, `get_plans`, `close_plan` | Plan/trade/creator records in two Google Sheets, joined by Plan ID |
| Position review | `review_positions` | Rebuilds full position context from the sheets, joined with live chain data |
| Connectivity | `gdrive_status` | Verifies Sheets access and reports the identity being used |
## Guardrails (enforced in code, not left to the model)
- **Per-trade size cap** — `prepare_buy` refuses any `usdAmount` outside `[MIN_TRADE_USD, MAX_TRADE_USD]`.
- **Daily/weekly spend cap** — tracked in `data/ledger.json` against `DAILY_SPEND_CAP_USD` / `WEEKLY_SPEND_CAP_USD`.
Note: this tracks *prepared* trades, since the server has no way to know which prepared
transactions you actually went on to sign and broadcast. Treat the cap as a soft guard on
intent, and `get_position` (reads the chain directly) as the source of truth on what actually happened.
- **Risk gate** — `prepare_buy` refuses to run unless `get_risk_score` was called for that mint
within the last `RISK_SCORE_FRESHNESS_MIN` minutes and it cleared `RISK_SCORE_THRESHOLD`.
- **Wallet isolation** — use a dedicated wallet funded only with your trading budget. The server
loads the keypair only when an execution tool is called and verifies its public key matches
`WALLET_PUBLIC_KEY`.
None of these thresholds are hardcoded opinions about what's "safe" — they're config in `.env`.
Tune them, and periodically sanity-check `get_risk_score`'s flags against a token yourself before
trusting the threshold blindly.
## Setup
```bash
npm install
npm run build
cp .env.example .env
```
Edit `.env`:
- `WALLET_PUBLIC_KEY` — your Solana wallet's public address (not a private key/seed phrase).
- `WALLET_KEYPAIR_PATH` — path to a wallet secret file, default `keys`. The file must be mode `600`
or stricter and must match `WALLET_PUBLIC_KEY`. Accepted formats:
1. Solana CLI JSON array (64 integers)
2. Base58 secret key string
3. Simple label format containing `Private key: <base58-secret>`
- `AUTO_TRADING_ENABLED` — leave `false` for manual signing; set to `true` to enable automatic
execution. Never enable this with a wallet holding unrelated funds.
- `SOLANA_RPC_URL` — defaults to the public mainnet RPC; a dedicated RPC (Helius, QuickNode, etc.)
will be faster and more reliable for `get_position`.
- `PUMPPORTAL_API_KEY` — free key from [pumpportal.fun](https://pumpportal.fun/), used only for
the real-time data WebSocket (new-token/trade feeds). Not required for building trade
transactions — that endpoint is keyless.
- `GMGN_API_KEY` — optional second risk source. Leave blank to run on RugCheck-only scoring;
the composite reweights to 100% RugCheck automatically and says so in its output.
`GMGN_API_BASE_URL` defaults to `https://openapi.gmgn.ai` and rarely needs setting.
To obtain a key: `npm install -g gmgn-cli`, then `gmgn-cli config` to generate an Ed25519
keypair, and upload the **public** key when creating the key in GMGN's dashboard. Derive the
public half with `openssl pkey -in ~/.config/gmgn/keypair.pem -pubout`.
**Create the key with trading disabled.** GMGN splits auth by route: trading endpoints
require an Ed25519 signature over each request, while the market/token/portfolio reads this
connector uses need only an `X-APIKEY` header plus `timestamp` and `client_id` query params.
Nothing here routes trades through GMGN, so the private key is never loaded and trading
permission would be standing privilege for no benefit. Leaving it off also avoids GMGN's 2FA
requirement.
- Trade sizing / spend caps / risk threshold — see comments in `.env.example`.
### Register with Claude Desktop (or any MCP client)
```json
{
"mcpServers": {
"pump-fun-connector": {
"command": "node",
"args": ["/absolute/path/to/pump-fun-connector/dist/index.js"],
"env": {
"WALLET_PUBLIC_KEY": "your-public-key-here",
"SOLANA_RPC_URL": "https://api.mainnet-beta.solana.com",
"PUMPPORTAL_API_KEY": "your-pumpportal-key",
"MIN_TRADE_USD": "10",
"MAX_TRADE_USD": "20",
"DAILY_SPEND_CAP_USD": "100",
"WEEKLY_SPEND_CAP_USD": "400",
"RISK_SCORE_THRESHOLD": "50"
}
}
}
}
```
## Signing a prepared transaction
`prepare_buy`/`prepare_sell` return `unsignedTransactionBase64`. To sign and send it yourself:
```js
import { Connection, VersionedTransaction, Keypair } from "@solana/web3.js";
const connection = new Connection("https://api.mainnet-beta.solana.com");
const wallet = Keypair.fromSecretKey(/* load your key from wherever YOU keep it — never this server */);
const tx = VersionedTransaction.deserialize(Buffer.from(base64FromTool, "base64"));
tx.sign([wallet]);
const signature = await connection.sendTransaction(tx);
console.log(signature);
```
Wallets that support raw transaction signing (e.g. via a browser extension's signAndSendTransaction
API, given the raw bytes) can also be wired up to consume this output directly.
## Automatic execution
The execution tools use the same risk gate, trade-size limits, and daily/weekly buy caps as the
prepare tools. They verify the fee payer, sign with the configured keypair, submit with preflight
enabled, and wait for `confirmed` commitment.
To enable them locally, first restrict the key file:
```bash
chmod 600 keys
```
Then set `WALLET_KEYPAIR_PATH=keys`, `WALLET_PUBLIC_KEY` to the matching address, and
`AUTO_TRADING_ENABLED=true` in `.env`. Start with very small limits and a disposable wallet.
## Google Sheets logging layer
Two spreadsheets in Google Drive are the project's record of every decision and trade, joined by
a shared **Plan ID** (`PLAN-0001`, ...).
- **Trade log** — permanent history. `Investment Plans` (one row per decision, written *before*
buying), `Trade Log` (one row per transaction), `Summary` (rollup).
- **Active positions** — working file. `Active Positions` (one row per plan) and
`Creator Activity` (append-only creator mentions).
All stored data is in English, including the `Buy`/`Sell` transaction types that the aggregate
formulas filter on.
### Setting up the sheets
**Do not import the `.xlsx` templates through Drive.** Drive's converter flattens a multi-tab
workbook into a single sheet with headers only, dropping every formula — and it does so
silently. Nothing errors afterwards; the connector writes rows whose totals simply never
compute. The `templates/*.xlsx` files exist as a reference artifact, not as the setup path.
Instead:
1. Create **two empty Google Sheets** in Drive — any folder, any filename.
2. Share both with the service account email as **Editor** (untick "Notify people").
3. Put their IDs in `.env` as `GOOGLE_TRADE_LOG_SPREADSHEET_ID` and
`GOOGLE_ACTIVE_POSITIONS_SPREADSHEET_ID`.
4. Provision the structure:
```bash
npm run provision:sheets # writes tabs, headers, formulas, dropdowns
npm run provision:sheets -- --dry-run # preview without writing
```
The script is idempotent — safe to re-run after a schema change. It only writes headers and
formula columns, so re-running never erases plans or transactions already recorded, and it
refuses to delete a leftover `Sheet1` that still holds data.
Locally, the script authenticates as *you* unless told otherwise, and a plain `gcloud` token
lacks the Sheets scope. To run it as the service account without downloading a key file:
```bash
export GOOGLE_ACCESS_TOKEN=$(gcloud auth print-access-token \
--impersonate-service-account=<SERVICE_ACCOUNT_EMAIL> \
--scopes=https://www.googleapis.com/auth/spreadsheets)
npm run provision:sheets
```
(That needs `roles/iam.serviceAccountTokenCreator` on the service account.)
`scripts/sheet-spec.mjs` is the single source of truth for the structure, shared by the
provisioner and the template generator so the two can't drift apart.
### Where the files live in Drive, and what they're called, does not matter
The connector addresses spreadsheets by **ID**, never by path or filename. Put them in any
folder, nested however you like, and name them whatever you want. All the connector needs is
the ID from the URL:
```
https://docs.google.com/spreadsheets/d/1AbCdEfGhIjKlMnOpQrStUvWxYz/edit
└──────── this is the ID ────────┘
```
Set those two IDs in `.env` and you're done. There is no folder path to configure, which is why
there's no env variable for one.
**What *does* have to match is the tab names inside each spreadsheet** — `Investment Plans`,
`Trade Log`, `Active Positions`, `Creator Activity`. Those are referenced directly in the range
addresses the connector builds, so renaming a tab breaks it. (They're defined in
`src/sheets/schema.ts` if you ever need to change them.) Column layout matters for the same
reason — see the table below.
### Controlled vocabulary
Stored values are English and, for the columns that formulas depend on, exact:
| Field | Allowed values |
|---|---|
| Trade Log `Type` | `Buy`, `Sell` — **formulas filter on these literals** |
| Lifecycle Phase | `Launch`, `Trending`, `Plateau`, `Decline` |
| Social Momentum | `Growing`, `Stable`, `Declining` |
| Tone | `Positive`, `Neutral`, `Negative` |
| Status (tracking) | `ACTIVE`, `CLOSED` |
| Status (computed, col S) | `No Transactions`, `Open`, `Partially Closed`, `Closed` |
The `Type` column carries a dropdown constraint in the template, because a typo there doesn't
error — it silently makes every total for that plan read zero.
### Plans are marked CLOSED, never deleted
Closing a plan sets its status column to `CLOSED` — nothing is deleted or cleared. Two reasons:
the evidence trail stays auditable, and rows never shift out from under the sheets' pre-filled
formulas.
| Sheet | Status column | Connector writes | Never write (formulas) |
|---|---|---|---|
| Investment Plans | — (col S `Estado` is formula-computed) | A–I | J–S |
| Trade Log | — | A–I | — |
| Active Positions | **M** (`ACTIVE`/`CLOSED`) | A–G, K–N | H, I, J |
| Creator Activity | **L** (`ACTIVE`/`CLOSED`) | A–L | — |
Note the two different notions of "closed": column S in Investment Plans is *formula-computed*
from the transaction rows (`Open` / `Partially Closed` / `Closed`) and reflects whether
the position is financially closed. The `ACTIVE`/`CLOSED` columns are *written by the connector*
and reflect whether you're still actively tracking the plan. `close_plan` refuses to mark a plan
`CLOSED` unless column S already reads `Closed`, unless you pass `force: true` (e.g. after a rug
where the tokens can't be sold at all).
Because nothing is deleted, these sheets fill up over time — `Active Positions` has room for 50
plans, `Creator Activity` for 499 rows. When full, copy the last formula row down to extend
them; the connector will tell you when it runs out of space rather than overwriting anything.
### Authorizing access to Google Drive
The connector authenticates as a **service account** — no interactive login, no refresh tokens to
manage, and it works identically on Cloud Run and locally. You grant it access by simply sharing
the two spreadsheets with the service account's email address, exactly as you'd share with a
colleague.
**1. Create a service account and enable the API**
```bash
gcloud config set project YOUR_PROJECT_ID
gcloud services enable sheets.googleapis.com
gcloud iam service-accounts create pumpfun-connector \
--display-name="Pump.fun MCP Connector"
```
Its email will be `pumpfun-connector@YOUR_PROJECT_ID.iam.gserviceaccount.com`.
**2. Share both spreadsheets with that email**
Open each spreadsheet in Google Drive → **Share** → paste the service account email → set to
**Editor** → uncheck "Notify people" → **Share**.
This is the entire authorization step. No OAuth consent screen, no scopes to approve — the
service account can only reach the two specific files you shared with it, nothing else in
your Drive.
**3. Point the connector at the spreadsheets**
Copy each spreadsheet's ID from its URL:
```
https://docs.google.com/spreadsheets/d/1AbCdEfGhIjKlMnOpQrStUvWxYz/edit
^^^^^^^^^^^^^^^^^^^^^^^^^^^ this part
```
Set `GOOGLE_TRADE_LOG_SPREADSHEET_ID` and `GOOGLE_ACTIVE_POSITIONS_SPREADSHEET_ID` in `.env`.
**4. Provide credentials**
- **On Cloud Run** — attach the service account to the service (below). Credentials resolve
automatically from the metadata server; leave `GOOGLE_APPLICATION_CREDENTIALS` blank.
- **Locally** — either download a key file and set `GOOGLE_APPLICATION_CREDENTIALS` to its path:
```bash
gcloud iam service-accounts keys create ~/pumpfun-sa-key.json \
--iam-account=pumpfun-connector@YOUR_PROJECT_ID.iam.gserviceaccount.com
```
(treat that file like a password — it's covered by `.gitignore`), or use your own account with
`gcloud auth application-default login`, in which case share the sheets with *your* email instead.
**5. Verify**
Call the `gdrive_status` tool. It prints the identity it's authenticating as and both
spreadsheets' titles and tabs. If it reports 403, the sharing step didn't take — it will name the
exact email to share with.
### Logging tools
| Tool | When |
|---|---|
| `gdrive_status` | First — verifies access and prints the service account email |
| `create_investment_plan` | Before any buy — writes the thesis to both sheets, returns the Plan ID |
| `log_transaction` | After each buy/sell leg settles — one row per leg |
| `log_creator_activity` | Daily while a plan is open — the evolving evidence trail |
| `update_active_position` | When lifecycle phase / momentum / review cadence changes |
| `get_plans` | To review track record before a new trade |
| `close_plan` | When a plan is fully exited — marks rows CLOSED |
## Transports
The connector speaks two transports from one shared tool registry (`src/server.ts`), so the
tool surface can't drift between them:
| Transport | Entry point | Use |
|---|---|---|
| stdio | `dist/index.js` | Local — Claude Code / Claude Desktop launching the process itself |
| Streamable HTTP | `dist/http.js` | Hosted — Claude custom connector pointing at a URL |
### Running the HTTP server locally
```bash
npm run build
MCP_AUTH_TOKEN=$(openssl rand -hex 32) npm run start:http
```
Endpoints:
| Method | Path | Purpose |
|---|---|---|
| `POST` | `/mcp` | Initialize a session, then send requests with the `mcp-session-id` header |
| `GET` | `/mcp` | SSE stream for server→client messages |
| `DELETE` | `/mcp` | Terminate a session |
| `GET` | `/health` | Health check — the only unauthenticated route |
**The server refuses to start without `MCP_AUTH_TOKEN`.** This is deliberate: the endpoint
exposes tools that can move funds, so an unauthenticated public URL is a direct path to fund
loss for anyone who finds it. The URL is not a secret. Override with
`MCP_ALLOW_UNAUTHENTICATED=true` only on a genuinely private network.
## Deployment guide
Start-to-finish: obtaining every value, deploying to Cloud Run, and connecting it to Claude.
> Replace every `<PLACEHOLDER>` with your own values. Keep real project ids, service account
> emails, wallet addresses and URLs out of this repo — they belong in `.env` (gitignored) or
> Secret Manager.
### Environment variable reference
**Required — the server refuses to start without these:**
| Variable | Where it comes from |
|---|---|
| `WALLET_PUBLIC_KEY` | Your Solana wallet's public address. Step 1 below. |
| `MCP_AUTH_TOKEN` | You generate it. Step 5. Required only for the HTTP transport (`dist/http.js`). |
**Required for the logging layer** (tools error with a clear message without them):
| Variable | Where it comes from |
|---|---|
| `GOOGLE_TRADE_LOG_SPREADSHEET_ID` | The trade-log spreadsheet's URL. Step 3. |
| `GOOGLE_ACTIVE_POSITIONS_SPREADSHEET_ID` | The active-positions spreadsheet's URL. Step 3. |
**Required for the Claude connector UI:**
| Variable | Where it comes from |
|---|---|
| `PUBLIC_URL` | The deployed service URL. Advertised as the OAuth issuer, so it must match exactly what Claude connects to. Step 7. |
**Optional — sensible defaults, or features degrade cleanly:**
| Variable | Default | Notes |
|---|---|---|
| `SOLANA_RPC_URL` | public mainnet RPC | Rate-limits under load; a free Helius/QuickNode endpoint is more reliable |
| `PUMPPORTAL_API_KEY` | — | Only for the live token feed. Step 2. |
| `GMGN_API_KEY` | — | Second risk source. Without it, scoring is RugCheck-only and says so. Step 4. |
| `GMGN_API_BASE_URL` | `https://openapi.gmgn.ai` | Rarely needs changing |
| `MIN_TRADE_USD` / `MAX_TRADE_USD` | `10` / `20` | Per-trade size cap |
| `DAILY_SPEND_CAP_USD` / `WEEKLY_SPEND_CAP_USD` | `100` / `400` | Cumulative buy caps |
| `RISK_SCORE_THRESHOLD` | `50` | 0–100, higher = riskier. Buys above this are refused. |
| `RISK_SCORE_FRESHNESS_MIN` | `10` | How recently `get_risk_score` must have run to gate a buy |
| `RUGCHECK_WEIGHT` / `GMGN_WEIGHT` | `0.6` / `0.4` | Composite blend; reweights automatically if a source is down |
| `DEFAULT_SLIPPAGE_PCT` | `10` | |
| `DEFAULT_PRIORITY_FEE_SOL` | `0.0005` | |
| `DEFAULT_POOL` | `auto` | |
| `AUTO_TRADING_ENABLED` | `false` | `true` lets `execute_buy`/`execute_sell` sign and broadcast without review |
| `WALLET_KEYPAIR_SECRET` / `WALLET_KEYPAIR_PATH` | — / `keys` | Private key source; only read when auto-trading is enabled |
| `GOOGLE_APPLICATION_CREDENTIALS` | — | Local dev only. Leave blank on Cloud Run. |
| `MCP_ALLOW_UNAUTHENTICATED` | `false` | Disables the bearer requirement. Private networks only. |
| `PORT` | `8080` | Cloud Run sets this itself |
---
### Step 1 — Solana wallet
Use a **dedicated** wallet holding only your trading budget, never your main one.
Either take the public address from an existing wallet (Phantom, Solflare), or generate one:
```bash
node -e "
const { Keypair } = require('@solana/web3.js');
const fs = require('fs');
const kp = Keypair.generate();
fs.writeFileSync(process.env.HOME + '/.pump-fun-wallet.json', JSON.stringify(Array.from(kp.secretKey)), { mode: 0o600 });
console.log('PUBLIC KEY:', kp.publicKey.toBase58());
"
```
`WALLET_PUBLIC_KEY` is the printed address. The private key file is yours — never commit it, and
never put it in this repo. Fund the wallet with only what you can afford to lose.
### Step 2 — PumpPortal API key (optional)
Sign up at [pumpportal.fun](https://pumpportal.fun/) → "Create Wallet & API Key". Keep the
**API key**; the Lightning wallet it also creates is unrelated to this connector.
Used only for the real-time token feed. Building trade transactions needs no key.
### Step 3 — Google Sheets
1. Create **two empty Google Sheets** in Drive — any folder, any filename.
2. Copy each ID from its URL:
```
https://docs.google.com/spreadsheets/d/1AbCdEfGhIjKlMnOpQrStUvWxYz/edit
└────────── the ID ──────────┘
```
3. Set `GOOGLE_TRADE_LOG_SPREADSHEET_ID` and `GOOGLE_ACTIVE_POSITIONS_SPREADSHEET_ID`.
4. Share both with the service account from Step 6 (Editor, untick "Notify people").
5. Provision the structure: `npm run provision:sheets`
**Do not import the `.xlsx` templates through Drive** — its converter silently flattens
multi-tab workbooks into a single sheet with headers only, dropping every formula. See the
Google Sheets section above.
### Step 4 — GMGN API key (optional)
```bash
npm install -g gmgn-cli
gmgn-cli config # generates the keypair
openssl pkey -in ~/.config/gmgn/keypair.pem -pubout # the public half, for upload
```
In GMGN's dashboard, paste that public key, enable **Reading**, leave **Trading off**. The key
it returns is `GMGN_API_KEY`.
### Step 5 — Generate the connector auth token
```bash
openssl rand -hex 32
```
This is `MCP_AUTH_TOKEN` — what you'll type once in Claude's OAuth approval page.
### Step 6 — Google Cloud setup
```bash
gcloud services enable run.googleapis.com sheets.googleapis.com \
secretmanager.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com \
--project=<GCP_PROJECT_ID>
gcloud iam service-accounts create <SA_NAME> \
--display-name="Pump.fun MCP Connector" --project=<GCP_PROJECT_ID>
```
The service account email is `<SA_NAME>@<GCP_PROJECT_ID>.iam.gserviceaccount.com` — go back and
share both spreadsheets with it (Step 3.4).
Store the secrets:
```bash
printf '%s' '<YOUR_MCP_AUTH_TOKEN>' | gcloud secrets create mcp-auth-token --data-file=- --project=<GCP_PROJECT_ID>
printf '%s' '<YOUR_PUMPPORTAL_KEY>' | gcloud secrets create pumpportal-key --data-file=- --project=<GCP_PROJECT_ID>
printf '%s' '<YOUR_GMGN_KEY>' | gcloud secrets create gmgn-api-key --data-file=- --project=<GCP_PROJECT_ID>
for s in mcp-auth-token pumpportal-key gmgn-api-key; do
gcloud secrets add-iam-policy-binding "$s" \
--member=serviceAccount:<SERVICE_ACCOUNT_EMAIL> \
--role=roles/secretmanager.secretAccessor --project=<GCP_PROJECT_ID>
done
```
**Use `printf '%s'`, never `echo`.** `echo` appends a newline, Secret Manager stores it as part
of the value, and Cloud Run injects it into the env var — producing a token that compares
unequal to the identical string you type, with no useful error. The code trims whitespace
defensively, but clean secrets are better than relying on that.
### Step 7 — Deploy
```bash
cd /path/to/pump-fun-connector # --source . uploads the CURRENT directory
gcloud run deploy pumpfun-connector \
--source . \
--region <REGION> \
--project <GCP_PROJECT_ID> \
--service-account <SERVICE_ACCOUNT_EMAIL> \
--set-env-vars "WALLET_PUBLIC_KEY=<WALLET_PUBLIC_KEY>,GOOGLE_TRADE_LOG_SPREADSHEET_ID=<ID>,GOOGLE_ACTIVE_POSITIONS_SPREADSHEET_ID=<ID>,MIN_TRADE_USD=10,MAX_TRADE_USD=20,DAILY_SPEND_CAP_USD=100,WEEKLY_SPEND_CAP_USD=400,RISK_SCORE_THRESHOLD=50" \
--set-secrets "MCP_AUTH_TOKEN=mcp-auth-token:latest,PUMPPORTAL_API_KEY=pumpportal-key:latest,GMGN_API_KEY=gmgn-api-key:latest" \
--min-instances 0 --max-instances 1 \
--allow-unauthenticated
```
**Verify your working directory first.** `--source .` uploads whatever directory you're standing
in — running it from `~` tries to upload your entire home folder, SSH keys included.
Then set `PUBLIC_URL` to the URL it printed, and redeploy config:
```bash
gcloud run services update pumpfun-connector --region <REGION> --project <GCP_PROJECT_ID> \
--update-env-vars PUBLIC_URL=<SERVICE_URL>
```
Cloud Run gives each service **two URLs** — a legacy hash form
(`<service>-<hash>-<region>.a.run.app`) and a newer deterministic form
(`<service>-<project-number>.<region>.run.app`). Both work, but OAuth issuer matching is strict:
**pick one and use it for both `PUBLIC_URL` and the connector URL in Claude.**
### Step 8 — Connect to Claude
Claude → **Settings → Connectors → Add custom connector**:
- **Name:** anything
- **URL:** `<SERVICE_URL>/mcp`
Leave "Sign in now" and "Register automatically" selected — both are detected from the server's
OAuth metadata. Claude redirects to an approval page; enter your `MCP_AUTH_TOKEN`.
One approval lasts indefinitely as long as you use the connector at least monthly — access
tokens last an hour and refresh silently, and refresh tokens (30 days) rotate on each use.
### Step 9 — Verify
Ask Claude to run `gdrive_status`. Success returns both spreadsheet titles and their tabs, which
exercises the whole chain: Claude → OAuth → Cloud Run → service account → Sheets.
Then `get_risk_score` on any mint. A populated `gmgn` block confirms that integration too.
---
### Updating later
| Change | Command | Time |
|---|---|---|
| Config only (env var, secret, scaling) | `gcloud run services update ...` | seconds |
| Code | `gcloud run deploy --source . ...` | 3–5 min (rebuilds) |
`services update` reuses the existing image, so a code change needs the full deploy. Use
`--update-env-vars` (touches only what you name) rather than `--set-env-vars`, which **replaces
the entire set** and will silently drop your wallet key and spreadsheet ids.
### Troubleshooting
| Symptom | Cause |
|---|---|
| OAuth page rejects the correct secret | Trailing newline in the stored secret — see Step 6 |
| OAuth metadata shows `issuer: http://localhost:8080` | `PUBLIC_URL` not set on the deployed revision |
| OAuth fails after appearing to start | `PUBLIC_URL` and the connector URL are different Cloud Run URLs for the same service |
| `ZIP does not support timestamps before 1980` | You ran `--source .` from the wrong directory |
| Tools missing in Claude after a deploy | Claude caches the tool list per connection — reconnect the connector |
| `get_new_tokens` returns nothing | Expected with CPU throttling; see Known limitations |
| Sheets tools report 403 | Spreadsheets not shared with the service account — `gdrive_status` prints the address to use |
| Sheet totals stay at zero | Workbook imported from `.xlsx` instead of provisioned — run `npm run provision:sheets` |
## Known limitations / open items
- **PumpPortal, RugCheck, and GMGN are third-party, unofficial APIs** — pump.fun has no official
public API. Field shapes and endpoints can drift; every client in `src/clients/` degrades
gracefully (returns "unavailable" rather than crashing) if a response shape changes, but you
should periodically sanity-check `get_risk_score` output against the RugCheck/GMGN websites
directly.
- `get_token_detail` price data is only available for tokens this server has observed live
trades for (it auto-subscribes on first call — call again a few seconds later if price fields
are empty).
- `get_position`'s cost-basis/P&L is an estimate based on this server's own log of *prepared*
buys, not confirmed on-chain fills. The holdings and current value themselves are read directly
from the chain and are accurate; the P&L number is not.
- No stop-loss automation, by design — this mirrors the trade profile this project was built
around (small, fully-disposable position sizes; sizing is the risk control).
TDQS
Scored across 18 tools
Each tool has a clearly distinct purpose: trading actions are split into prepare (unsigned) and execute (signed), position management includes get, review, update, and close, and logging is separated by transaction, single creator activity, and batch creator activity. No two tools appear to do the same thing; descriptions provide clear boundaries.
Almost all tools follow a consistent verb_noun snake_case pattern (e.g., get_position, prepare_buy, log_transaction), with only gdrive_status deviating slightly as a noun phrase, but it remains understandable and unique. The overall convention is consistent and readable.
With 18 tools, the set is slightly heavy for a connector, falling into the borderline range where the number may be more than needed for some workflows. However, each tool serves a specific purpose and none appear redundant, though consolidation could be possible.
The surface covers the full lifecycle: discovery (get_new_tokens), risk assessment (get_risk_score), market analysis (get_market_activity), planning (create_investment_plan, get_plans), position management (get_position, review_positions, update_active_position, close_plan), trading (prepare/execute buy/sell), and logging (log_transaction, log_creator_activity, log_creator_activity_batch). Minor gaps might include a direct tool to retrieve a specific plan by ID or to read trade history, but the sheets serve as the source of truth.