mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-10 23:50:12 +00:00
wip
This commit is contained in:
parent
88e50db871
commit
d2e7bd3f00
13 changed files with 246 additions and 97 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
0
backend/src/baserow/contrib/database/mcp/__init__.py
Normal file
0
backend/src/baserow/contrib/database/mcp/__init__.py
Normal file
20
backend/src/baserow/contrib/database/mcp/tools.py
Normal file
20
backend/src/baserow/contrib/database/mcp/tools.py
Normal 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}"
|
8
backend/src/baserow/core/mcp.py
Normal file
8
backend/src/baserow/core/mcp.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
|
||||
baserow_mcp = FastMCP(
|
||||
"Baserow MCP",
|
||||
sse_path="/mcp/sse",
|
||||
message_path="/mcp/messages/"
|
||||
)
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
24
integrations/mcp/server/models.py
Normal file
24
integrations/mcp/server/models.py
Normal 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
|
75
integrations/mcp/server/server.py
Normal file
75
integrations/mcp/server/server.py
Normal 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()
|
21
integrations/mcp/server/utils.py
Normal file
21
integrations/mcp/server/utils.py
Normal 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()
|
Loading…
Add table
Reference in a new issue