Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
80 changes: 52 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,27 @@ This repo installs Baserow's MCP server and a short skill that teaches the agent
## Server URL

- **baserow.io**: `https://api.baserow.io/mcp`
- **Self-hosted**: your backend URL followed by `/mcp`, for example `https://baserow.example.com/mcp`. Copy it from Settings → MCP server in Baserow. Sign-in needs `BASEROW_MCP_OAUTH_ENABLED=true` on the backend; it's off by default. Your reverse proxy must send `/mcp`, `/oauth/`, `/.well-known/oauth-authorization-server` and `/.well-known/oauth-protected-resource` to the backend. See [MCP OAuth sign-in for self-hosted Baserow](https://github.com/baserow/baserow/blob/develop/docs/installation/mcp-oauth.md).
- **Self-hosted**: your backend URL followed by `/mcp`, for example `https://baserow.example.com/mcp`. Copy it from My settings → MCP server in Baserow.

The examples below use the baserow.io URL. On self-hosted Baserow, use your own URL instead.

## Quick start

This works for most agents:

```
npx add-mcp https://api.baserow.io/mcp -n baserow
npx add-mcp https://api.baserow.io/mcp -n baserow -g
npx skills add baserow/baserow-mcp
```

`add-mcp` finds the agents you have installed and adds the server to each. Add `-a <agent>` to pick one, or `-g` to install globally. `skills add` copies the `baserow-basics` skill into your agents' skill folders.
`add-mcp` finds the agents you have installed and asks which ones to add Baserow to. Untick Claude Desktop if it's listed and add Baserow there as a [custom connector](#claudeai-and-claude-desktop) instead. `skills add` copies the `baserow-basics` skill into your agents' skill folders. On self-hosted Baserow, run `skills add` too, since only the plugins below include the skill.

## Self-hosted requirements

- A Baserow version newer than 2.4.1. Older versions don't have `/mcp` or sign-in; use an [endpoint key](#endpoint-keys) there.
- `BASEROW_MCP_OAUTH_ENABLED=true` on the backend. It's off by default.
- A public HTTPS backend URL, so hosted apps like Claude.ai and ChatGPT can reach it, and outbound HTTPS from the backend, which fetches the apps' client metadata.
- Your reverse proxy sends `/mcp` and paths under `/mcp/`, `/oauth/`, `/.well-known/oauth-authorization-server` and `/.well-known/oauth-protected-resource` to the backend. Match `/mcp` exactly, not as a prefix: the frontend serves the sign-in page at `/mcp-authorize/`.

See [MCP OAuth sign-in for self-hosted Baserow](https://github.com/baserow/baserow/blob/develop/docs/installation/mcp-oauth.md).

## Install per agent

Expand All @@ -29,14 +36,16 @@ npx skills add baserow/baserow-mcp
| Claude Code | [Plugin](#claude-code) | Same plugin, enter your URL when asked |
| Claude.ai, Claude Desktop | [Custom connector](#claudeai-and-claude-desktop) | Same, with your URL |
| ChatGPT | [Custom MCP server](#chatgpt) | Same, with your URL |
| Codex, Cursor, VS Code | `npx plugins add baserow/baserow-mcp` | [Codex](#codex) or [config file](#config-files) |
| GitHub Copilot CLI | `npx plugins add baserow/baserow-mcp` | `copilot mcp add --transport http baserow <your url>` |
| Gemini CLI | `gemini extensions install https://github.com/baserow/baserow-mcp` | `gemini mcp add -t http baserow <your url>` |
| Goose | [Add to Goose](goose://extension?url=https%3A%2F%2Fapi.baserow.io%2Fmcp&type=streamable_http&id=baserow&name=Baserow) | [Quick start](#quick-start) with your URL |
| Codex | [Plugin](#codex) | `codex mcp add baserow --url <your url>` |
| GitHub Copilot CLI | [Plugin](#github-copilot-cli) | [Server only](#github-copilot-cli) |
| Gemini CLI | `gemini extensions install https://github.com/baserow/baserow-mcp` | `gemini mcp add -s user -t http baserow <your url>` |
| Cursor | [Add to Cursor](https://cursor.com/install-mcp?name=baserow&config=eyJ1cmwiOiJodHRwczovL2FwaS5iYXNlcm93LmlvL21jcCJ9) | [Config file](#config-files) |
| VS Code | [Add to VS Code](https://vscode.dev/redirect/mcp/install?name=baserow&config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fapi.baserow.io%2Fmcp%22%7D) | [Config file](#config-files) |
| Goose | [Add to Goose](https://goose-docs.ai/extension?url=https%3A%2F%2Fapi.baserow.io%2Fmcp&type=streamable_http&id=baserow&name=Baserow) | [Quick start](#quick-start) with your URL |
| OpenCode, Windsurf, Zed | [Config file](#config-files) | Same, with your URL |
| Any other agent | [Quick start](#quick-start) | Same, with your URL |

The Codex, Cursor, VS Code, Copilot and Gemini plugins always point at baserow.io, so on self-hosted Baserow add only the server for those agents.
The Codex, Copilot and Gemini plugins and the Add to buttons always point at baserow.io, so on self-hosted Baserow add only the server for those agents.

### Claude Code

Expand All @@ -45,7 +54,7 @@ The Codex, Cursor, VS Code, Copilot and Gemini plugins always point at baserow.i
/plugin install baserow@baserow
```

Keep the default URL when asked, or enter your self-hosted URL. To change it later, run `/plugin configure baserow@baserow` in Claude Code. Then run `/mcp`, pick `plugin:baserow:baserow` and sign in.
When it asks for the URL, press Esc to keep baserow.io, or enter your self-hosted URL. To change it later, run `/plugin configure baserow@baserow`.

### Claude.ai and Claude Desktop

Expand All @@ -58,19 +67,29 @@ Open Plugins, select the plus button, then Add custom MCP server, and enter your
### Codex

```
codex mcp add baserow --url https://api.baserow.io/mcp
codex plugin marketplace add baserow/baserow-mcp
codex plugin add baserow@baserow
codex mcp login baserow
```

### GitHub Copilot CLI

```
copilot plugin marketplace add baserow/baserow-mcp
copilot plugin install baserow@baserow
```

On self-hosted Baserow, add only the server: `copilot mcp add --transport http baserow <your url>`. If the plugin is installed, uninstall it first with `copilot plugin uninstall baserow`; its server replaces yours without a warning.

### Config files

To add only the server, without the plugin:

- Cursor: [Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=baserow&config=eyJ1cmwiOiJodHRwczovL2FwaS5iYXNlcm93LmlvL21jcCJ9), or `~/.cursor/mcp.json`:
- Cursor, `~/.cursor/mcp.json`:
```json
{ "mcpServers": { "baserow": { "url": "https://api.baserow.io/mcp" } } }
```
- VS Code: [Add to VS Code](vscode:mcp/install?%7B%22name%22%3A%22baserow%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fapi.baserow.io%2Fmcp%22%7D), or `.vscode/mcp.json`:
- VS Code, `.vscode/mcp.json`:
```json
{ "servers": { "baserow": { "type": "http", "url": "https://api.baserow.io/mcp" } } }
```
Expand All @@ -87,36 +106,41 @@ To add only the server, without the plugin:
{ "context_servers": { "baserow": { "url": "https://api.baserow.io/mcp" } } }
```

The Add to Cursor and Add to VS Code links use the baserow.io URL.

## Signing in

The first time your agent uses Baserow, it opens a Baserow page in your browser. Sign in, pick the workspace and tick the tools the agent may use. Read tools let the agent browse databases, tables and rows; write tools let it create, update and delete rows. Only the read tools are ticked on a first connection. To change the workspace or tools, reconnect the server from your agent's MCP settings.
Your agent opens a Baserow page in your browser. Sign in, pick the workspace and tick the tools the agent may use. Read tools let the agent browse databases, tables and rows; write tools let it create, update and delete rows. Only the read tools are ticked on a first connection.

Where the sign-in starts:

- Claude Code: run `/mcp`, pick `plugin:baserow:baserow` and authenticate.
- Codex: `codex mcp login baserow`.
- Gemini CLI: `/mcp auth baserow`.
- Claude.ai, ChatGPT, Cursor and VS Code: the Connect or Sign in button next to the server.

Your connected apps are listed under Settings → MCP server in Baserow. Disconnect one there to revoke its access.
To change the workspace or tools, sign in again: re-authenticate the server in your agent, or disconnect the app under My settings → MCP server in Baserow and connect again. A plain reconnect keeps the old sign-in.

## Troubleshooting

- **A tool is missing, or no databases are listed.** The connection only has the workspace and tools you picked at sign-in. Reconnect and pick again.
- **The sign-in never starts, or the client says the server doesn't support OAuth or client registration.** On self-hosted Baserow, check that `BASEROW_MCP_OAUTH_ENABLED=true` is set and that the proxy routes the OAuth paths in [Server URL](#server-url). Older Baserow versions don't support OAuth for MCP; update Baserow or use an [endpoint key](#endpoint-keys).
- **The connection fails with a URL you typed.** Copy the URL exactly as Settings → MCP server shows it. It ends in `/mcp`, with no trailing slash.
- **Two Baserow servers in Claude Code.** If you also added a `baserow` server by hand with the same URL, Claude Code shows only that one and hides the plugin's. Keep one: `claude mcp remove baserow` removes the hand-added server.
- **A tool is missing, or no databases are listed.** The connection only has the workspace and tools you picked at sign-in. Sign in again as described above. Tools that change tables or fields aren't available over MCP.
- **The sign-in never starts, or the client says the server doesn't support OAuth or client registration.** On self-hosted Baserow, check the [self-hosted requirements](#self-hosted-requirements).
- **The connection fails with a URL you typed.** Copy the URL exactly as My settings → MCP server shows it. It ends in `/mcp`, with no trailing slash.
- **Two Baserow servers in Claude Code.** If `claude mcp list` shows the plugin's `plugin:baserow:baserow` and a `baserow` server you added by hand, keep the one that connects. `claude plugin disable baserow@baserow` turns off the plugin's; `claude mcp remove baserow` removes yours.

### Endpoint keys

Older Baserow versions only accept an endpoint key, and the plugin doesn't set this up. Copy the endpoint URL from Settings → MCP server. It ends in `/sse` and contains your key. Add it by hand:
Baserow 2.4.1 and older only accept an endpoint key, and the plugin doesn't set this up. Copy the endpoint URL from My settings → MCP server. It ends in `/sse` and contains your key. Add it by hand to an agent that supports SSE servers, such as Claude Code, Cursor or VS Code; Codex and Goose don't:

```
npx add-mcp "<endpoint url>" -t sse -n baserow -g
npx add-mcp "<endpoint url>" -t sse -n baserow -g -a claude-code
```

Baserow versions with OAuth for MCP also accept the key as a header on `/mcp`, which is useful for scripts:
Newer versions also accept the key as a header on `/mcp`, which is useful for scripts:

```
npx add-mcp https://baserow.example.com/mcp -n baserow -g -h "Authorization: Bearer <key>"
npx add-mcp https://baserow.example.com/mcp -n baserow -g -a claude-code -h "Authorization: Bearer <key>"
```

The key gives full access to its workspace. `-g` writes it to your global agent config instead of the current folder, so it can't end up in a commit.
The key gives full access to its workspace. `-g` writes it to your user config instead of the current folder, so it can't end up in a commit.

## Privacy and support

Expand All @@ -128,7 +152,7 @@ The key gives full access to its workspace. `-g` writes it to your global agent

Run `npm install`, then `npm test` and `npm run check`. CI runs the same checks.

The Claude Code plugin declares its server in `.claude-plugin/plugin.json`, so it can use the URL you enter. `.mcp.json` holds the baserow.io URL for other tools that read Claude-format plugins, such as GitHub Copilot CLI.
The Claude Code plugin declares its server in `.claude-plugin/plugin.json`, so it can use the URL you enter. `.mcp.json` holds the baserow.io URL for other tools that read Claude-format plugins, such as GitHub Copilot CLI and Cursor.

## License

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ This repo holds manifests and a skill that point AI agents at Baserow's MCP serv

- Each connection reaches one workspace and only the tools you tick at sign-in. Tick the read tools only unless the agent needs to change data.
- Data in your tables reaches the agent as tool output. A row can contain text written to steer the agent (prompt injection), so review what the agent wants to change before you approve it.
- Disconnect apps you no longer use under Settings → MCP server in Baserow.
- Disconnect apps you no longer use under My settings → MCP server in Baserow.
- Endpoint keys give full access to their workspace. Keep them out of repos; the README's `add-mcp` commands use `-g` for this reason.
43 changes: 23 additions & 20 deletions scripts/check-manifests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -134,40 +134,43 @@ export function checkSync(m) {
return errors;
}

// Returns the server URL an install link in the README points at.
function linkUrl(link) {
const parsed = new URL(link);
if (parsed.protocol === "cursor:") {
return JSON.parse(Buffer.from(parsed.searchParams.get("config") ?? "", "base64").toString()).url;
}
if (parsed.protocol === "vscode:") return JSON.parse(decodeURIComponent(parsed.search.slice(1))).url;
if (parsed.protocol === "goose:") return parsed.searchParams.get("url");
return undefined;
}
// Install links in the README, and how to read the server URL out of each.
// GitHub strips cursor:, vscode: and goose: links, so the README uses the https forms.
const INSTALL_LINKS = [
["cursor.com", "/install-mcp", (p) => JSON.parse(Buffer.from(p.get("config") ?? "", "base64").toString()).url],
["vscode.dev", "/redirect/mcp/install", (p) => JSON.parse(p.get("config") ?? "").url],
["goose-docs.ai", "/extension", (p) => p.get("url")],
];

const installLink = (parsed) =>
INSTALL_LINKS.find(([host, pathname]) => parsed.hostname === host && parsed.pathname.replace(/\/$/, "") === pathname);

export function checkReadme(text, saasUrl) {
const errors = [];
for (let [url] of text.matchAll(/https:\/\/[^\s"'`<>()\]]+/g)) {
for (let [url] of text.matchAll(/https?:\/\/[^\s"'`<>()\]|*!]+/g)) {
url = url.replace(/[.,;:]+$/, "");
let parsed;
try {
parsed = new URL(url);
} catch {
continue;
}
if (/\/mcp(\/|$)/.test(parsed.pathname) && !parsed.hostname.endsWith("example.com") && url !== saasUrl) {
const link = installLink(parsed);
if (link) {
let target;
try {
target = link[2](parsed.searchParams);
} catch (e) {
errors.push(`${README}: can't read install link ${url} (${e.message})`);
continue;
}
if (target !== saasUrl) errors.push(`${README}: install link ${url} points at ${target}, not ${saasUrl}`);
} else if (!parsed.hostname.endsWith("example.com") && /\/mcp(\/|$)/.test(parsed.pathname) && url !== saasUrl) {
errors.push(`${README}: ${url} differs from the SaaS URL ${saasUrl}`);
}
}
for (const [, link] of text.matchAll(/\]\(((?:cursor|vscode|goose):[^)\s]+)\)/g)) {
let url;
try {
url = linkUrl(link);
} catch (e) {
errors.push(`${README}: can't read install link ${link} (${e.message})`);
continue;
}
if (url !== saasUrl) errors.push(`${README}: install link ${link} points at ${url}, not ${saasUrl}`);
errors.push(`${README}: ${link} isn't clickable on GitHub; use the https install link`);
}
return errors;
}
Expand Down
27 changes: 21 additions & 6 deletions scripts/check-manifests.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -226,26 +226,41 @@ test("malformed marketplace plugins are reported without crashing", () => {
});

const cursorLink = (url) =>
`cursor://anysphere.cursor-deeplink/mcp/install?name=baserow&config=${Buffer.from(JSON.stringify({ url })).toString("base64")}`;
const vscodeLink = (url) => `vscode:mcp/install?${encodeURIComponent(JSON.stringify({ name: "baserow", type: "http", url }))}`;
const gooseLink = (url) => `goose://extension?url=${encodeURIComponent(url)}&id=baserow`;
`https://cursor.com/install-mcp?name=baserow&config=${Buffer.from(JSON.stringify({ url })).toString("base64")}`;
const vscodeLink = (url) =>
`https://vscode.dev/redirect/mcp/install?name=baserow&config=${encodeURIComponent(JSON.stringify({ type: "http", url }))}`;
const gooseLink = (url) => `https://goose-docs.ai/extension?url=${encodeURIComponent(url)}&id=baserow`;

test("a README that matches the SaaS URL passes", () => {
const readme = [
`Use \`${URL}\` or \`https://baserow.example.com/mcp\`.`,
"See [the docs](https://github.com/baserow/baserow/blob/develop/docs/installation/mcp-oauth.md).",
`[Add to Cursor](${cursorLink(URL)})`,
`[Add to VS Code](${vscodeLink(URL)})`,
`| [Add to VS Code](${vscodeLink(URL)}) |`,
`[Add to Goose](${gooseLink(URL)})`,
].join("\n");
assert.deepEqual(checkReadme(readme, URL), []);
});

test("a stale URL or install link in the README is reported", () => {
const old = "https://old.baserow.io/mcp";
const readme = [`{ "url": "${old}" }`, `${URL}/`, `[a](${cursorLink(old)})`, `[b](${vscodeLink(old)})`, `[c](${gooseLink(old)})`].join("\n");
const readme = [
`{ "url": "${old}" }`,
`${URL}/`,
`**${old}**`,
`| ${old}|`,
`http://api.baserow.io/mcp`,
`[a](${cursorLink(old)})`,
`[b](${vscodeLink(old)})`,
`[c](${gooseLink(old)})`,
].join("\n");
const errors = checkReadme(readme, URL);
assert.equal(errors.length, 5, errors.join("\n"));
assert.equal(errors.length, 8, errors.join("\n"));
});

test("app-scheme install links are reported, since GitHub strips them", () => {
const errors = checkReadme("[Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=baserow)", URL);
assert.match(errors.join("\n"), /isn't clickable on GitHub/);
});

test("a skill with CRLF line endings, a BOM and a quoted name passes", async (t) => {
Expand Down
Loading