1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-10 23:50:12 +00:00
This commit is contained in:
Bram Wiepjes 2025-04-03 09:48:45 +02:00
parent 88e50db871
commit d2e7bd3f00
13 changed files with 246 additions and 97 deletions
backend
requirements
src/baserow
config
contrib/database
core
docker-compose.dev.yml
integrations/mcp

View file

@ -81,3 +81,4 @@ icalendar==5.0.12
jira2markdown==0.3.7
openpyxl==3.1.5
zipstream-ng==1.8.0
mcp==1.6.0

View file

@ -18,7 +18,10 @@ anyio==4.8.0
# via
# anthropic
# httpx
# mcp
# openai
# sse-starlette
# starlette
# watchfiles
asgiref==3.8.1
# via
@ -224,9 +227,12 @@ httpx==0.27.2
# via
# anthropic
# langsmith
# mcp
# mistralai
# ollama
# openai
httpx-sse==0.4.0
# via mcp
huggingface-hub==0.27.1
# via tokenizers
humanize==4.11.0
@ -290,6 +296,8 @@ loguru==0.7.2
# via -r base.in
markdown-it-py==3.0.0
# via rich
mcp==1.6.0
# via -r base.in
mdurl==0.1.2
# via markdown-it-py
mistralai==1.1.0
@ -467,10 +475,14 @@ pydantic==2.9.2
# langchain
# langchain-core
# langsmith
# mcp
# mistralai
# openai
# pydantic-settings
pydantic-core==2.23.4
# via pydantic
pydantic-settings==2.8.1
# via mcp
pygments==2.19.1
# via rich
pyjwt==2.10.1
@ -501,7 +513,9 @@ python-dateutil==2.8.2
# pysaml2
# python-crontab
python-dotenv==1.0.1
# via uvicorn
# via
# pydantic-settings
# uvicorn
pytz==2024.2
# via
# flower
@ -571,6 +585,12 @@ sqlalchemy==2.0.36
# via langchain
sqlparse==0.5.3
# via django
sse-starlette==2.2.1
# via mcp
starlette==0.46.1
# via
# mcp
# sse-starlette
tenacity==8.5.0
# via
# celery-redbeat
@ -627,7 +647,9 @@ urllib3==1.26.20
# requests
# sentry-sdk
uvicorn[standard]==0.29.0
# via -r base.in
# via
# -r base.in
# mcp
uvloop==0.21.0
# via uvicorn
validators==0.28.1

View file

@ -1,11 +1,13 @@
from django.conf import settings
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import re_path
from baserow.config.helpers import ConcurrencyLimiterASGI
from baserow.core.telemetry.telemetry import setup_logging, setup_telemetry
from baserow.ws.routers import websocket_router
from baserow.core.mcp import baserow_mcp
# The telemetry instrumentation library setup needs to run prior to django's setup.
setup_telemetry(add_django_instrumentation=True)
@ -20,7 +22,13 @@ setup_logging()
application = ProtocolTypeRouter(
{
"http": ConcurrencyLimiterASGI(
django_asgi_app, max_concurrency=settings.ASGI_HTTP_MAX_CONCURRENCY
URLRouter(
[
re_path(r"^mcp", baserow_mcp.sse_app()),
# re_path(r"", django_asgi_app),
]
),
max_concurrency=settings.ASGI_HTTP_MAX_CONCURRENCY
),
"websocket": websocket_router,
}

View file

@ -1029,6 +1029,7 @@ class DatabaseConfig(AppConfig):
import baserow.contrib.database.table.receivers # noqa: F401
import baserow.contrib.database.views.receivers # noqa: F401
import baserow.contrib.database.views.tasks # noqa: F401
import baserow.contrib.database.mcp.tools # noqa: F401
# noinspection PyPep8Naming

View file

@ -0,0 +1,20 @@
from typing import List
from baserow.core.mcp import baserow_mcp
@baserow_mcp.resource("echo://{message}")
def echo_resource(message: str) -> str:
"""Echo a message as a resource"""
return f"Resource echo: {message}"
@baserow_mcp.tool()
def echo_tool(message: str) -> str:
"""Echo a message as a tool"""
return f"Tool echo: {message}"
@baserow_mcp.prompt()
def echo_prompt(message: str) -> str:
"""Create an echo prompt"""
return f"Please process this message: {message}"

View file

@ -0,0 +1,8 @@
from mcp.server.fastmcp import FastMCP
baserow_mcp = FastMCP(
"Baserow MCP",
sse_path="/mcp/sse",
message_path="/mcp/messages/"
)

View file

@ -62,7 +62,7 @@ services:
UID: $UID
GID: $GID
ports:
- "${HOST_PUBLISH_IP:-127.0.0.1}:3000:3000"
- "${HOST_PUBLISH_IP:-127.0.0.1}:3001:3000"
volumes:
- ./web-frontend:/baserow/web-frontend
# Override the above mounts for node_modules so we use the node_modules built

View file

@ -1,5 +1,66 @@
# Baserow's official MCP
## Quick Start
- Get your Baserow Database Token. Note that Baserow version 1.33 is the minimum
requirement because it allows listing of all tables that the token has access to.
- Sign into your Baserow account.
- Click on the workspace in the top left corner.
- Then on "My settings", "Database tokens", "Create token", and fill out the form.
- Then click on the three dots next to the name of the created token and copy the
token.
- Configure Claude Desktop.
- Open ~/Library/Application Support/Claude/claude_desktop_config.json
- Add the following configuration:
```json
{
"mcpServers": {
"Baserow MCP": {
"command": "uv",
"args": [
"run",
"--with",
"mcp[cli]",
"--with",
"requests",
"mcp",
"run",
"@TODO/server.py"
],
"env": {
"BASEROW_DATABASE_TOKEN": "YOUR_TOKEN",
"BASEROW_BASE_URL": "https://api.baserow.io"
}
}
}
}
```
- Replace `YOUR_TOKEN` with the token that you created.
- Replace `BASEROW_BASE_URL` with the URL of your Baserow instance. Leave to
api.baserow.io if you're using the cloud version.
- Save and restart Claude Desktop.
## Features
- **Table management**: List all the tables that the token has access to.
- **Fields**: List all the fields of a specific table.
- **Data access**: Create, read, update, and delete rows.
- **User files**: Upload files directly to the user files.
## Available tools
| Tool name | Description | Example |
|-------------|---------------------------------------------------|-----------------------------------------------------------------|
| list_tables | List all the tables that the token has access to. | "Show me all Baserow tables" |
| list_fields | List all the fields of the provided table. | "Which fields does table Projects have?" |
| list_rows | Lists the rows of the provided table. | "List all rows in the Project table." |
| get_row | Get a specific row based on the ID. | "Get row with ID 432 from the Projects table." |
| create_rows | Create a new row. | "Create a new row in the contacts table with the title 'Jane'" |
| update_rows | Update existing rows. | "Update the status of row 432 in the Tasks table to completed." |
| delete_rows | Delete existing rows. | "Delete row with id 432 in the Tasks table." |
## Development
You must install Claude Desktop (https://claude.ai/download) first.
This MCP is based on build with Python using

View file

@ -1,92 +0,0 @@
import os
import requests
from typing import Any, Dict, List
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, Field
BASEROW_DATABASE_TOKEN = os.getenv('BASEROW_DATABASE_TOKEN')
BASEROW_BASE_URL = os.getenv('BASEROW_BASE_URL', 'https://api.baserow.io')
headers = {
"Authorization": f"Token {BASEROW_DATABASE_TOKEN}",
"Content-Type": "application/json"
}
mcp = FastMCP("Baserow MCP", dependencies=["requests", ""])
class Row(BaseModel):
id: int
order: str
class Table(BaseModel):
id: int
order: int
name: str
database_id: int
class Field(BaseModel):
id: int
table_id: int
name: str
order: int
type: str
primary: bool
read_only: bool
description: str
@mcp.tool()
def list_tables() -> List[Table]:
"""List all tables the token has access to."""
url = f"{BASEROW_BASE_URL}/api/database/tables/all-tables/"
response = requests.get(
url,
headers=headers
)
return response.json()
@mcp.tool()
def list_rows(table_id: int, search: str = "") -> List[Row]:
"""List or search rows in a table"""
url = f"{BASEROW_BASE_URL}/api/database/rows/table/{table_id}/"
response = requests.get(
url,
params={
"user_field_names": True,
"search": search
},
headers=headers
)
return response.json().get("results", [])
@mcp.resource("tables://")
def get_tables() -> List[Table]:
"""Get all the tables that the token has access to."""
url = f"{BASEROW_BASE_URL}/api/database/tables/all-tables/"
return requests.get(
url,
headers=headers,
).json()
@mcp.resource("fields://table/{table_id}")
def get_fields(table_id: int) -> List[Field]:
"""Get all the fields of a table."""
url = f"{BASEROW_BASE_URL}/api/database/fields/table/{table_id}/"
return requests.get(
url,
headers=headers,
).json()
if __name__ == "__main__":
mcp.run()

View file

@ -0,0 +1,24 @@
from pydantic import BaseModel, Field
class Row(BaseModel):
id: int
order: str
class Table(BaseModel):
id: int
order: int
name: str
database_id: int
class Field(BaseModel):
id: int
table_id: int
name: str
order: int
type: str
primary: bool
read_only: bool
description: str

View file

@ -0,0 +1,75 @@
from typing import Any, Dict, List
from mcp.server.fastmcp import FastMCP
from server.utils import make_request
from server.models import Row, Table, Field
mcp = FastMCP("Baserow MCP", dependencies=["requests"])
@mcp.tool()
def list_tables() -> List[Table]:
"""List all tables the token has access to."""
return make_request("get", "/api/database/tables/all-tables/")
@mcp.tool()
def list_fields(table_id: int) -> List[Field]:
"""List all fields in the provided table."""
return make_request("get", f"/api/database/fields/table/{table_id}/")
@mcp.tool()
def list_rows(table_id: int, search: str = "", page: int = 1) -> List[Row]:
"""
List 100 rows of the given table. Optionally accepts a search parameter and
the page that must be requested.
"""
return make_request(
"get",
"/api/database/rows/table/{table_id}/",
params={
"user_field_names": True,
"search": search,
"size": 100,
"page": page,
},
)
@mcp.tool()
def get_id(table_id: int, row_id: int) -> List[Row]:
"""
Returns the row with the given row ID in the provided table.
"""
return make_request(
"get",
f"/api/database/rows/table/{table_id}/{row_id}/",
params={
"user_field_names": True,
},
)
@mcp.tool()
def get_id(table_id: int, row_id: int) -> List[Row]:
"""
Returns the row with the given row ID in the provided table.
"""
return make_request(
"get",
f"/api/database/rows/table/{table_id}/{row_id}/",
params={
"user_field_names": True,
},
)
if __name__ == "__main__":
mcp.run()

View file

@ -0,0 +1,21 @@
import os
from requests import request
BASEROW_DATABASE_TOKEN = os.getenv('BASEROW_DATABASE_TOKEN')
BASEROW_BASE_URL = os.getenv('BASEROW_BASE_URL', 'https://api.baserow.io')
headers = {
"Authorization": f"Token {BASEROW_DATABASE_TOKEN}",
"Content-Type": "application/json"
}
def make_request(method, url, **kwargs):
url = f"{BASEROW_BASE_URL}{url}"
response = request(
method,
url,
headers=headers,
**kwargs
)
response.raise_for_status()
return response.json()