Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
edc7b77
feat(PSGO-261): exchange programmatic tokens for Storage tokens via a…
Matovidlo Jun 24, 2026
57f6bd4
feat(PSGO-261): local browser PKCE login (stack-only) with token refresh
Matovidlo Jun 24, 2026
3c242ce
docs(PSGO-261): extend RFC with multi-project scope
Matovidlo Jul 1, 2026
8c9f13c
feat(PSGO-261): multi-project scope via introspect + scoped exchange
Matovidlo Jul 1, 2026
9dc5550
feat(PSGO-261): per-call project filter for read tools
Matovidlo Jul 1, 2026
546cac0
feat(PSGO-261): surface current scope in get_accessible_projects
Matovidlo Jul 1, 2026
5200b63
feat(PSGO-261): fan out query_data across scoped projects
Matovidlo Jul 1, 2026
5d54a89
feat(PSGO-261): enrich get_accessible_projects with per-project SQL d…
Matovidlo Jul 1, 2026
21b6897
refactor(PSGO-261): rename get_accessible_projects llm_instructions -…
Matovidlo Jul 1, 2026
ac95137
docs(PSGO-261): RFC increment 3 — query fan-out, dialect bootstrap, s…
Matovidlo Jul 1, 2026
5bc49e4
test(PSGO-261): add get_accessible_projects + set_project_scope to in…
Matovidlo Jul 1, 2026
e54bc49
test(PSGO-261): CI integration coverage for the multi-project PAT flow
Matovidlo Jul 1, 2026
a55e015
test(PSGO-261): run integtests across sapi/pat_single/pat_mpa auth ov…
Matovidlo Jul 1, 2026
475624a
feat(PSGO-261): auto-login on local server start when no/dead session
Matovidlo Jul 2, 2026
207004b
feat(PSGO-261): login --pat leases a PAT over all projects (sudo MFA …
Matovidlo Jul 2, 2026
ee7677b
feat(PSGO-261): scope-first tool visibility for programmatic sessions
Matovidlo Jul 3, 2026
b561c17
feat(PSGO-261): count-first fan-out with a safety cap for big projects
Matovidlo Jul 3, 2026
196b6a6
feat(PSGO-261): refresh-first login + partial-result fan-out on per-p…
Matovidlo Jul 3, 2026
9fc4aa6
feat(PSGO-261): login --show-token to print the session access token
Matovidlo Jul 3, 2026
bb85cc4
feat(PSGO-261): local HTTP server self-refreshes from the stored PKCE…
Matovidlo Jul 3, 2026
09a9236
fix(PSGO-261): call-time tool gating + drop surfaced active_project_id
Matovidlo Jul 3, 2026
d95e700
fix(PSGO-261): don't introspect on /list requests (tools/list 30s tim…
Matovidlo Jul 3, 2026
8d3ed0e
fix(PSGO-261): add --no-stateless-http so multi-project scope persist…
Matovidlo Jul 3, 2026
a50671d
fix(PSGO-261): skip list-time feature filtering for all programmatic …
Matovidlo Jul 3, 2026
3de5d0d
feat(PSGO-261): add logout command and login --force to switch tokens
Matovidlo Jul 3, 2026
0d12f27
fix(PSGO-261): stop fanning out argument-validation errors across pro…
Matovidlo Jul 3, 2026
4029bd3
fix(PSGO-261): stop polling workspace-creation job after terminal fai…
Matovidlo Jul 3, 2026
abbd61c
fix(PSGO-261): send PAT as Bearer token on jobs-queue, ai-service and…
Matovidlo Jul 21, 2026
3dfad98
docs(PSGO-261): reconcile as-built RFC with shipped per-service token…
Matovidlo Jul 21, 2026
353c0a2
chore(PSGO-261): regenerate TOOLS.md with locked pydantic 2.13.4
Matovidlo Jul 21, 2026
c36fb8f
refactor(PSGO-261): apply post-rebase review cleanups (simplify/reuse…
Matovidlo Jul 21, 2026
44a3a8f
docs(PSGO-261): document browser login for local setup; drop legacy t…
Matovidlo Jul 21, 2026
d88d949
fix(PSGO-261): normalize inbound Bearer scheme to avoid Bearer Bearer…
Matovidlo Jul 21, 2026
8f5903e
docs(PSGO-261): use conventional Note: marker in _swap_project docstring
Matovidlo Jul 21, 2026
68095e7
fix(PSGO-261): safer dialect fallback, login timeout, traceback logging
Matovidlo Jul 21, 2026
03b54fa
refactor(PSGO-261): public client_for_project, legacy-token instructi…
Matovidlo Jul 21, 2026
b05b4d3
fix(PSGO-261): propagate cancellation in dialect fan-out; correct /li…
Matovidlo Jul 21, 2026
5b923b8
fix(PSGO-261): keep the workspace when creation ends with a 'warning'…
Matovidlo Jul 21, 2026
c1916fd
fix(PSGO-261): reject empty project_ids; chmod credentials before wri…
Matovidlo Jul 21, 2026
be5a848
fix(PSGO-261): refresh an expired stored token on /list (keep valid t…
Matovidlo Jul 21, 2026
8fdb8dd
fix(PSGO-261): log swallowed auth-refresh errors with traceback; don'…
Matovidlo Jul 21, 2026
200cd5c
docs(PSGO-261): RFC for OAuth login exchanging into a programmatic se…
Matovidlo Jul 22, 2026
f893055
feat(PSGO-261): implement OAuth login exchange into a programmatic se…
Matovidlo Jul 22, 2026
228d1e8
refactor(PSGO-261): dedupe StorageTokenResolver/OAuthSessionExchanger…
Matovidlo Jul 22, 2026
aaf6c97
fix(PSGO-261): map refresh network errors to HTTPException; dedupe to…
Matovidlo Jul 22, 2026
e8ff640
fix(PSGO-261): stop logging token material; don't require project_id …
Matovidlo Jul 22, 2026
8ecc889
fix(PSGO-261): correct with_llm_instruction wording; log dialect fail…
Matovidlo Jul 22, 2026
5f414c3
fix(PSGO-261): use per-request Storage API URL in fan-out; don't dupl…
Matovidlo Jul 23, 2026
f49c74a
fix(PSGO-261): re-run per-project authorization during fan-out; decou…
Matovidlo Jul 23, 2026
d64779e
fix(PSGO-261): validate _parent_subject_token's bearer is actually pr…
Matovidlo Jul 23, 2026
4d7a5d6
fix(PSGO-261): raise the right exception class for OAuth-visible errors
Matovidlo Jul 23, 2026
7fbeb25
fix(PSGO-261): stop sending X-KBC-ManageApiToken to exchange-oauth-token
Matovidlo Jul 23, 2026
660aab9
fix(PSGO-261): never consult the local PKCE store on the deployed server
Matovidlo Jul 28, 2026
eba47d9
fix(PSGO-261): exempt bootstrap tools from ToolsFilteringMiddleware's…
Matovidlo Jul 28, 2026
1c2bd36
fix(PSGO-261): apply confirmed scope's project id on deployed session…
Matovidlo Jul 28, 2026
b79e298
fix(PSGO-261): carry multi-project scope as a signed scope_token inst…
Matovidlo Jul 28, 2026
78a0bf6
fix(PSGO-261): drop the auth-bridge resolve-storage-token exchange fo…
Matovidlo Jul 28, 2026
7dff1af
docs(PSGO-261): add Postgres-backed OAuth session store RFC; reconcil…
Matovidlo Jul 29, 2026
9abc2a5
feat(PSGO-261): add Postgres-backed session_store package (Phase 1, n…
Matovidlo Jul 29, 2026
f214cad
fix(PSGO-261): alias postgres_dsn to MCP_DB_URL, matching this infra'…
Matovidlo Jul 29, 2026
7e3e6e9
feat(PSGO-261): wire SimpleOAuthProvider onto the Postgres session st…
Matovidlo Jul 30, 2026
a406aec
feat(PSGO-261): persist multi-project scope on the OAuth session row
Matovidlo Jul 31, 2026
0b28517
feat(PSGO-261): tag merged fan-out items with their source project
Matovidlo Jul 31, 2026
10e3b3d
feat(PSGO-261): partition oauth_sessions by month, 2-month retention
Matovidlo Jul 31, 2026
b6fc782
refactor(PSGO-261): split mcp.py under 1k lines; address code-quality…
Matovidlo Jul 31, 2026
11d7d45
fix(PSGO-261): keep exactly retention_months of oauth_sessions partit…
Matovidlo Jul 31, 2026
a3d1ba2
fix(PSGO-261): move overlapping default-partition rows before attachi…
Matovidlo Jul 31, 2026
8a7b5c1
fix(PSGO-261): log lazy Keboola-session token refresh in load_access_…
Matovidlo Jul 31, 2026
0e64b37
fix(PSGO-261): require KBC_SESSION_ENCRYPTION_KEY when OAuth is enabled
Matovidlo Jul 31, 2026
5958223
fix(PSGO-261): enforce real per-partition uniqueness on oauth_session…
Matovidlo Jul 31, 2026
18b9613
fix(PSGO-261): require explicit project_id on write tools instead of …
Matovidlo Aug 4, 2026
099d13e
feat(PSGO-261): surface organization_id/organization_name on get_acce…
Matovidlo Aug 5, 2026
9e1b967
fix(PSGO-261): give get_project_info an explicit project_id like writ…
Matovidlo Aug 6, 2026
12b6ff8
fix(PSGO-261): scope_token clutter + OAuth scoped-token never refreshing
Matovidlo Aug 6, 2026
1c4fefd
feat(PSGO-261): Kai (header-token) session-scope persistence + post-r…
Matovidlo Aug 7, 2026
56b035b
docs(PSGO-261): RFC increment 7 -- security hardening response to PR …
Matovidlo Aug 9, 2026
3471706
fix(PSGO-261): allowlist header-derived Config fields, close domain-c…
Matovidlo Aug 9, 2026
b0ed1ae
fix(PSGO-261): stop read_only from failing open for the active project
Matovidlo Aug 9, 2026
de372da
fix(PSGO-261): encrypt scope_token instead of just signing it, wire l…
Matovidlo Aug 9, 2026
7584d63
fix(PSGO-261): scope login-time, key credentials per interface, promp…
Matovidlo Aug 9, 2026
f214738
feat(PSGO-261): skip scoping when a session can only reach one project
Matovidlo Aug 11, 2026
28dbd70
fix(PSGO-261): extend local-login token fallback to streamable-http
Matovidlo Aug 11, 2026
6e1b70e
fix(PSGO-261): stop instructing the LLM to re-scope an already-scoped…
Matovidlo Aug 11, 2026
4c6264f
fix(PSGO-261): stop X-KBC-ProjectId from overriding a confirmed scope
Matovidlo Aug 12, 2026
6fa5844
test(PSGO-261): drop a redundant no-scope test added in c0d8fe02
Matovidlo Aug 12, 2026
f50a90b
fix(PSGO-261): stop the login fallback from crashing streamable-http …
Matovidlo Aug 12, 2026
13d2c8e
docs(PSGO-261): restructure RFC around architecture, not a fix changelog
Matovidlo Aug 12, 2026
bf10d41
docs(PSGO-261): fix RFC claim about login-time scoped-token minting
Matovidlo Aug 13, 2026
4a54436
fix(PSGO-261): post-rebase fixups — stale call sites, dead import, du…
Matovidlo Aug 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ jobs:
INTEGTEST_STORAGE_TOKENS: ${{ secrets.INTEGTEST_STORAGE_TOKENS }}
INTEGTEST_POOL_STORAGE_API_URL: ${{ vars.INTEGTEST_POOL_STORAGE_API_URL }}
INTEGTEST_STORAGE_TOKEN_STORAGE_BRANCHES: ${{ secrets.INTEGTEST_STORAGE_TOKEN_STORAGE_BRANCHES }}
# A single programmatic token (kbc_pat_/kbc_at_) whose user is a member of all pool projects.
# Drives the PAT/MPA auth modes against the SAME pool projects (uses the pool URL). PAT-auth
# tests in integtests/test_pat_multiproject.py skip when it is unset.
INTEGTEST_STORAGE_PAT: ${{ secrets.INTEGTEST_STORAGE_PAT }}
run: |
uv run tox -e integtests

Expand Down
66 changes: 31 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,40 +124,41 @@ For detailed documentation, see [developers.keboola.com/integrate/mcp/#tool-auth

## Local MCP Server Setup (Custom or Dev Way)

Run the MCP server on your own machine for full control and easy development. Choose this when you want to customize tools, debug locally, or iterate quickly. You’ll clone the repo, set Keboola credentials via environment variables or headers depending on the server transport, install dependencies, and start the server. This approach offers maximum flexibility (custom tools, local logging, offline iteration) but requires manual setup and you manage updates and secrets yourself.
Run the MCP server on your own machine for full control and easy development. Choose this when you want to customize tools, debug locally, or iterate quickly. You’ll install the server, authenticate (a one-time browser login — no token to paste), and start it. This approach offers maximum flexibility (custom tools, local logging, offline iteration) but requires manual setup and you manage updates and secrets yourself.

The server supports multiple **transport** options, which can be selected by providing the `--transport <transport>` argument when starting the server:
- `stdio` - Default when `--transport` is not specified. Standard input/output, typically used for local deployment with a single client.
- `streamable-http` - Runs the server remotely over HTTP with a bidirectional streaming channel, allowing the client and server to continuously exchange messages. Connect via <url>/mcp (e.g., http://localhost:8000/mcp).
- `http-compat` - An alias for `streamable-http`, kept for backwards compatibility.

For client–server communication, Keboola credentials must be provided to enable working with your project in your Keboola Region. The following are required: `KBC_STORAGE_TOKEN`, `KBC_STORAGE_API_URL`, `KBC_WORKSPACE_SCHEMA` and optionally `KBC_BRANCH_ID`. You can provide these in two ways:
- For personal use (mainly with stdio transport): set the environment variables before starting the server. All requests will reuse these predefined credentials.
- For multi-user use: include the variables in the request headers so that each request uses the credentials provided with it.
To work with your Keboola project the server needs two things: your **Keboola Region** (`KBC_STORAGE_API_URL`) and a way to **authenticate**. The recommended way is a one-time browser **login** — you never create, copy, or paste a token. Optionally set `KBC_BRANCH_ID` to work inside a development branch.

Two of the variables are not taken from the request headers:
- `KBC_STORAGE_API_URL`: a server that was started with its own Storage API URL (the `--api-url` parameter or the `KBC_STORAGE_API_URL` environment variable) only serves that one Keboola stack. An `X-Storage-Api-Url` header asking for a different host is ignored (a warning is logged) — the server keeps its own URL for the request. Start the server without a Storage API URL of its own if you want each request to choose its stack.
- `KBC_KUBERNETES_TOKEN_PATH` (deployed servers only, see [docs/kubernetes-sa-auth.md](docs/kubernetes-sa-auth.md)): read from the environment only, never from a header.

### Logging in

### KBC_STORAGE_TOKEN
Sign in once with your browser; the server stores the session and refreshes it automatically, so there are no tokens to manage:

This is your authentication token for Keboola:

For instructions on how to create and manage Storage API tokens, refer to the [official Keboola documentation](https://help.keboola.com/management/project/tokens/).

**Note**: If you want the MCP server to have limited access, use custom storage token, if you want the MCP to access everything in your project, use the master token.
```bash
uvx keboola_mcp_server login --api-url https://connection.YOUR_REGION.keboola.com
```

### KBC_WORKSPACE_SCHEMA
This opens your browser to sign in to Keboola, then saves the stack-wide session to `~/.keboola/mcp/credentials.json` (readable only by you, one entry per stack). Afterwards, start the server with only `KBC_STORAGE_API_URL` set — no token required. Which project(s) to work on is chosen afterwards, in the conversation (`get_accessible_projects` / `set_project_scope`), not during login.

This identifies your workspace in Keboola and is used for SQL queries. However, this is **only required if you're using a custom storage token** instead of the Master Token:
| Command | What it does |
|---------|--------------|
| `login --api-url <url>` | Sign in to a stack |
| `login --force` | Sign in again / switch account |
| `login --show-token` | Print the current session token (debugging) |
| `logout [--api-url <url>] [--all]` | Remove the stored session for a stack (or all stacks) |

- If using [Master Token](https://help.keboola.com/management/project/tokens/#master-tokens): The workspace is created automatically behind the scenes
- If using [custom storage token](https://help.keboola.com/management/project/tokens/#limited-tokens): Follow this [Keboola guide](https://help.keboola.com/tutorial/manipulate/workspace/) to get your KBC_WORKSPACE_SCHEMA
When you start the server over **stdio in an interactive terminal** with no stored session, it runs this browser login automatically on first start. MCP clients (Claude, Cursor, …) launch the server in the background where a browser can't open, so run `login` once yourself first.

**Note**: When creating a workspace manually, check Grant read-only access to all Project data option
#### Authenticating without a browser

**Note**: KBC_WORKSPACE_SCHEMA is called Dataset Name in BigQuery workspaces, you simply click connect and copy the Dataset Name
For containers or CI where a browser login isn't possible, provide a Keboola [access or personal access token](https://help.keboola.com/management/project/tokens/) directly — set `KBC_STORAGE_TOKEN` (env var) or send the `X-StorageAPI-Token` header — together with `KBC_PROJECT_ID` (or the `X-KBC-ProjectId` header) to select the project. On HTTP transports these can be supplied per request as headers, so each request carries its own credentials.

### KBC_STORAGE_API_URL (Keboola Region)

Expand Down Expand Up @@ -223,10 +224,14 @@ There are four ways to use the Keboola MCP Server, depending on your needs:

### Option A: Integrated Mode (Recommended)

In this mode, Claude or Cursor automatically starts the MCP server for you. **You do not need to run any commands in your terminal**.
In this mode, Claude or Cursor automatically starts the MCP server for you.

1. Configure your MCP client (Claude/Cursor) with the appropriate settings
2. The client will automatically launch the MCP server when needed
1. **Log in once** in a terminal so a session is stored (the client launches the server in the background, where a browser can't open):
```bash
uvx keboola_mcp_server login --api-url https://connection.YOUR_REGION.keboola.com
```
2. Configure your MCP client (Claude/Cursor) with the settings below — only `KBC_STORAGE_API_URL` is needed.
3. The client will automatically launch the MCP server when needed.

#### Claude Desktop Configuration

Expand All @@ -242,8 +247,6 @@ In this mode, Claude or Cursor automatically starts the MCP server for you. **Yo
"args": ["keboola_mcp_server --transport <transport>"],
"env": {
"KBC_STORAGE_API_URL": "https://connection.YOUR_REGION.keboola.com",
"KBC_STORAGE_TOKEN": "your_keboola_storage_token",
"KBC_WORKSPACE_SCHEMA": "your_workspace_schema",
"KBC_BRANCH_ID": "your_branch_id_optional"
}
}
Expand All @@ -270,8 +273,6 @@ Config file locations:
"args": ["keboola_mcp_server --transport <transport>"],
"env": {
"KBC_STORAGE_API_URL": "https://connection.YOUR_REGION.keboola.com",
"KBC_STORAGE_TOKEN": "your_keboola_storage_token",
"KBC_WORKSPACE_SCHEMA": "your_workspace_schema",
"KBC_BRANCH_ID": "your_branch_id_optional"
}
}
Expand All @@ -295,8 +296,6 @@ When running the MCP server from Windows Subsystem for Linux with Cursor AI, use
"bash",
"-c '",
"export KBC_STORAGE_API_URL=https://connection.YOUR_REGION.keboola.com &&",
"export KBC_STORAGE_TOKEN=your_keboola_storage_token &&",
"export KBC_WORKSPACE_SCHEMA=your_workspace_schema &&",
"export KBC_BRANCH_ID=your_branch_id_optional &&",
"/snap/bin/uvx keboola_mcp_server --transport <transport>",
"'"
Expand Down Expand Up @@ -324,8 +323,6 @@ For developers working on the MCP server code itself:
],
"env": {
"KBC_STORAGE_API_URL": "https://connection.YOUR_REGION.keboola.com",
"KBC_STORAGE_TOKEN": "your_keboola_storage_token",
"KBC_WORKSPACE_SCHEMA": "your_workspace_schema",
"KBC_BRANCH_ID": "your_branch_id_optional"
}
}
Expand All @@ -338,11 +335,9 @@ For developers working on the MCP server code itself:
You can run the server manually in a terminal for testing or debugging:

```bash
# Set environment variables
# Sign in once (stores a session under ~/.keboola/mcp), then start the server.
export KBC_STORAGE_API_URL=https://connection.YOUR_REGION.keboola.com
export KBC_STORAGE_TOKEN=your_keboola_storage_token
export KBC_WORKSPACE_SCHEMA=your_workspace_schema
export KBC_BRANCH_ID=your_branch_id_optional
uvx keboola_mcp_server login --api-url "$KBC_STORAGE_API_URL"

uvx keboola_mcp_server --transport streamable-http
```
Expand All @@ -355,6 +350,8 @@ uvx keboola_mcp_server --transport streamable-http

### Option D: Using Docker

A container can't open a browser, so authenticate with a token (see [Authenticating without a browser](#authenticating-without-a-browser)): set `KBC_STORAGE_TOKEN` to a Keboola access/personal access token and `KBC_PROJECT_ID` to the target project. (Over HTTP you can instead pass `X-StorageAPI-Token` / `X-KBC-ProjectId` headers per request and omit these.)

```shell
docker pull keboola/mcp-server:latest

Expand All @@ -364,8 +361,8 @@ docker run \
-it \
-p 127.0.0.1:8000:8000 \
-e KBC_STORAGE_API_URL="https://connection.YOUR_REGION.keboola.com" \
-e KBC_STORAGE_TOKEN="YOUR_KEBOOLA_STORAGE_TOKEN" \
-e KBC_WORKSPACE_SCHEMA="YOUR_WORKSPACE_SCHEMA" \
-e KBC_STORAGE_TOKEN="YOUR_KEBOOLA_TOKEN" \
-e KBC_PROJECT_ID="YOUR_PROJECT_ID" \
-e KBC_BRANCH_ID="YOUR_BRANCH_ID_OPTIONAL" \
keboola/mcp-server:latest \
--transport streamable-http \
Expand Down Expand Up @@ -437,8 +434,7 @@ For a complete list of available tools with detailed descriptions, parameters, a

| Issue | Solution |
|-------|----------|
| **Authentication Errors** | Verify `KBC_STORAGE_TOKEN` is valid |
| **Workspace Issues** | Confirm `KBC_WORKSPACE_SCHEMA` is correct |
| **Authentication Errors** | Re-run `keboola_mcp_server login` (or, if authenticating with a token, verify the token and `KBC_PROJECT_ID`) |
| **Connection Timeout** | Check network connectivity |

## Development
Expand Down
Loading
Loading