iflow-mcp_mcp-server-bigquery 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iflow_mcp_mcp_server_bigquery-0.3.1.dist-info/METADATA +139 -0
- iflow_mcp_mcp_server_bigquery-0.3.1.dist-info/RECORD +7 -0
- iflow_mcp_mcp_server_bigquery-0.3.1.dist-info/WHEEL +4 -0
- iflow_mcp_mcp_server_bigquery-0.3.1.dist-info/entry_points.txt +2 -0
- iflow_mcp_mcp_server_bigquery-0.3.1.dist-info/licenses/LICENSE +21 -0
- mcp_server_bigquery/__init__.py +30 -0
- mcp_server_bigquery/server.py +196 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: iflow-mcp_mcp-server-bigquery
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: A Model Context Protocol server that provides access to BigQuery. This server enables LLMs to inspect database schemas and execute queries.
|
|
5
|
+
Author: Lucas Hild
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.13
|
|
8
|
+
Requires-Dist: google-cloud-bigquery>=3.27.0
|
|
9
|
+
Requires-Dist: mcp>=1.0.0
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# BigQuery MCP server
|
|
13
|
+
|
|
14
|
+
[](https://smithery.ai/server/mcp-server-bigquery)
|
|
15
|
+
|
|
16
|
+
A Model Context Protocol server that provides access to BigQuery. This server enables LLMs to inspect database schemas and execute queries.
|
|
17
|
+
|
|
18
|
+
## Components
|
|
19
|
+
|
|
20
|
+
### Tools
|
|
21
|
+
|
|
22
|
+
The server implements one tool:
|
|
23
|
+
|
|
24
|
+
- `execute-query`: Executes a SQL query using BigQuery dialect
|
|
25
|
+
- `list-tables`: Lists all tables in the BigQuery database
|
|
26
|
+
- `describe-table`: Describes the schema of a specific table
|
|
27
|
+
|
|
28
|
+
## Configuration
|
|
29
|
+
|
|
30
|
+
The server can be configured either with command line arguments or environment variables.
|
|
31
|
+
|
|
32
|
+
| Argument | Environment Variable | Required | Description |
|
|
33
|
+
| ------------ | -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
34
|
+
| `--project` | `BIGQUERY_PROJECT` | Yes | The GCP project ID. |
|
|
35
|
+
| `--location` | `BIGQUERY_LOCATION` | Yes | The GCP location (e.g. `europe-west9`). |
|
|
36
|
+
| `--dataset` | `BIGQUERY_DATASETS` | No | Only take specific BigQuery datasets into consideration. Several datasets can be specified by repeating the argument (e.g. `--dataset my_dataset_1 --dataset my_dataset_2`) or by joining them with a comma in the environment variable (e.g. `BIGQUERY_DATASETS=my_dataset_1,my_dataset_2`). If not provided, all datasets in the project will be considered. |
|
|
37
|
+
| `--key-file` | `BIGQUERY_KEY_FILE` | No | Path to a service account key file for BigQuery. If not provided, the server will use the default credentials. |
|
|
38
|
+
|
|
39
|
+
## Quickstart
|
|
40
|
+
|
|
41
|
+
### Install
|
|
42
|
+
|
|
43
|
+
#### Installing via Smithery
|
|
44
|
+
|
|
45
|
+
To install BigQuery Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mcp-server-bigquery):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx -y @smithery/cli install mcp-server-bigquery --client claude
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### Claude Desktop
|
|
52
|
+
|
|
53
|
+
On MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
|
|
54
|
+
On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
|
|
55
|
+
|
|
56
|
+
##### Development/Unpublished Servers Configuration</summary>
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
"mcpServers": {
|
|
60
|
+
"bigquery": {
|
|
61
|
+
"command": "uv",
|
|
62
|
+
"args": [
|
|
63
|
+
"--directory",
|
|
64
|
+
"{{PATH_TO_REPO}}",
|
|
65
|
+
"run",
|
|
66
|
+
"mcp-server-bigquery",
|
|
67
|
+
"--project",
|
|
68
|
+
"{{GCP_PROJECT_ID}}",
|
|
69
|
+
"--location",
|
|
70
|
+
"{{GCP_LOCATION}}"
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
##### Published Servers Configuration
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
"mcpServers": {
|
|
80
|
+
"bigquery": {
|
|
81
|
+
"command": "uvx",
|
|
82
|
+
"args": [
|
|
83
|
+
"mcp-server-bigquery",
|
|
84
|
+
"--project",
|
|
85
|
+
"{{GCP_PROJECT_ID}}",
|
|
86
|
+
"--location",
|
|
87
|
+
"{{GCP_LOCATION}}"
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Replace `{{PATH_TO_REPO}}`, `{{GCP_PROJECT_ID}}`, and `{{GCP_LOCATION}}` with the appropriate values.
|
|
94
|
+
|
|
95
|
+
## Development
|
|
96
|
+
|
|
97
|
+
### Building and Publishing
|
|
98
|
+
|
|
99
|
+
To prepare the package for distribution:
|
|
100
|
+
|
|
101
|
+
1. Increase the version number in `pyproject.toml`
|
|
102
|
+
|
|
103
|
+
2. Sync dependencies and update lockfile:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
uv sync
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
3. Build package distributions:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
uv build
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
This will create source and wheel distributions in the `dist/` directory.
|
|
116
|
+
|
|
117
|
+
4. Publish to PyPI:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
uv publish
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Note: You'll need to set PyPI credentials via environment variables or command flags:
|
|
124
|
+
|
|
125
|
+
- Token: `--token` or `UV_PUBLISH_TOKEN`
|
|
126
|
+
- Or username/password: `--username`/`UV_PUBLISH_USERNAME` and `--password`/`UV_PUBLISH_PASSWORD`
|
|
127
|
+
|
|
128
|
+
### Debugging
|
|
129
|
+
|
|
130
|
+
Since MCP servers run over stdio, debugging can be challenging. For the best debugging
|
|
131
|
+
experience, we strongly recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector).
|
|
132
|
+
|
|
133
|
+
You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npx @modelcontextprotocol/inspector uv --directory {{PATH_TO_REPO}} run mcp-server-bigquery
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
mcp_server_bigquery/__init__.py,sha256=X1ta3m-alGK-SkOzY-wUxkFXFYsYWGaEiaBYymbdRBc,1295
|
|
2
|
+
mcp_server_bigquery/server.py,sha256=MzeLE4EgUrB8fY1zIEO973LaItaR-x2ZNCESidXvexk,7646
|
|
3
|
+
iflow_mcp_mcp_server_bigquery-0.3.1.dist-info/METADATA,sha256=OauDyIYiVMRiarJ_08a5nTD9EkwM7gJ3kMt6kOblQMo,5728
|
|
4
|
+
iflow_mcp_mcp_server_bigquery-0.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
5
|
+
iflow_mcp_mcp_server_bigquery-0.3.1.dist-info/entry_points.txt,sha256=cgwDx4ywZKfn6NKgfs_R27BLCJ4OLfiVOGDLnCmDAr0,65
|
|
6
|
+
iflow_mcp_mcp_server_bigquery-0.3.1.dist-info/licenses/LICENSE,sha256=Fwuj73c22MmMmJq8VG2FmoeIr5KSGDF5ePWjLXGWflk,1067
|
|
7
|
+
iflow_mcp_mcp_server_bigquery-0.3.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Lucas Hild
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from . import server
|
|
2
|
+
import asyncio
|
|
3
|
+
import argparse
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
"""Main entry point for the package."""
|
|
9
|
+
parser = argparse.ArgumentParser(description='BigQuery MCP Server')
|
|
10
|
+
parser.add_argument('--project', help='BigQuery project', required=False)
|
|
11
|
+
parser.add_argument('--location', help='BigQuery location', required=False)
|
|
12
|
+
parser.add_argument('--key-file', help='BigQuery Service Account', required=False)
|
|
13
|
+
parser.add_argument('--dataset', help='BigQuery dataset', required=False, action='append')
|
|
14
|
+
|
|
15
|
+
args = parser.parse_args()
|
|
16
|
+
|
|
17
|
+
# Get values from environment variables if not provided as arguments
|
|
18
|
+
project = args.project or os.environ.get('BIGQUERY_PROJECT')
|
|
19
|
+
location = args.location or os.environ.get('BIGQUERY_LOCATION')
|
|
20
|
+
key_file = args.key_file or os.environ.get('BIGQUERY_KEY_FILE')
|
|
21
|
+
|
|
22
|
+
datasets_filter = args.dataset if args.dataset else []
|
|
23
|
+
if not datasets_filter and 'BIGQUERY_DATASETS' in os.environ:
|
|
24
|
+
datasets_filter = os.environ.get('BIGQUERY_DATASETS', '').split(',')
|
|
25
|
+
datasets_filter = [d.strip() for d in datasets_filter if d.strip()]
|
|
26
|
+
|
|
27
|
+
asyncio.run(server.main(project, location, key_file, datasets_filter))
|
|
28
|
+
|
|
29
|
+
# Optionally expose other important items at package level
|
|
30
|
+
__all__ = ['main', 'server']
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
from google.cloud import bigquery
|
|
2
|
+
from google.oauth2 import service_account
|
|
3
|
+
import logging
|
|
4
|
+
from mcp.server.models import InitializationOptions
|
|
5
|
+
import mcp.types as types
|
|
6
|
+
from mcp.server import NotificationOptions, Server
|
|
7
|
+
import mcp.server.stdio
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
# Set up logging to both stdout and file
|
|
11
|
+
logger = logging.getLogger('mcp_bigquery_server')
|
|
12
|
+
handler_stdout = logging.StreamHandler()
|
|
13
|
+
handler_file = logging.FileHandler('/tmp/mcp_bigquery_server.log')
|
|
14
|
+
|
|
15
|
+
# Set format for both handlers
|
|
16
|
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
17
|
+
handler_stdout.setFormatter(formatter)
|
|
18
|
+
handler_file.setFormatter(formatter)
|
|
19
|
+
|
|
20
|
+
# Add both handlers to logger
|
|
21
|
+
logger.addHandler(handler_stdout)
|
|
22
|
+
logger.addHandler(handler_file)
|
|
23
|
+
|
|
24
|
+
# Set overall logging level
|
|
25
|
+
logger.setLevel(logging.DEBUG)
|
|
26
|
+
|
|
27
|
+
logger.info("Starting MCP BigQuery Server")
|
|
28
|
+
|
|
29
|
+
class BigQueryDatabase:
|
|
30
|
+
def __init__(self, project: str, location: str, key_file: Optional[str], datasets_filter: list[str]):
|
|
31
|
+
"""Initialize a BigQuery database client"""
|
|
32
|
+
logger.info(f"Initializing BigQuery client for project: {project}, location: {location}, key_file: {key_file}")
|
|
33
|
+
if not project:
|
|
34
|
+
raise ValueError("Project is required")
|
|
35
|
+
if not location:
|
|
36
|
+
raise ValueError("Location is required")
|
|
37
|
+
|
|
38
|
+
credentials: service_account.Credentials | None = None
|
|
39
|
+
if key_file:
|
|
40
|
+
try:
|
|
41
|
+
credentials_path = key_file
|
|
42
|
+
credentials = service_account.Credentials.from_service_account_file(
|
|
43
|
+
credentials_path,
|
|
44
|
+
scopes=["https://www.googleapis.com/auth/cloud-platform"],
|
|
45
|
+
)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
logger.error(f"Error loading service account credentials: {e}")
|
|
48
|
+
raise ValueError(f"Invalid key file: {e}")
|
|
49
|
+
|
|
50
|
+
self.client = bigquery.Client(credentials=credentials, project=project, location=location)
|
|
51
|
+
self.datasets_filter = datasets_filter
|
|
52
|
+
|
|
53
|
+
def execute_query(self, query: str, params: dict[str, Any] | None = None) -> list[dict[str, Any]]:
|
|
54
|
+
"""Execute a SQL query and return results as a list of dictionaries"""
|
|
55
|
+
logger.debug(f"Executing query: {query}")
|
|
56
|
+
try:
|
|
57
|
+
if params:
|
|
58
|
+
job = self.client.query(query, job_config=bigquery.QueryJobConfig(query_parameters=params))
|
|
59
|
+
else:
|
|
60
|
+
job = self.client.query(query)
|
|
61
|
+
|
|
62
|
+
results = job.result()
|
|
63
|
+
rows = [dict(row.items()) for row in results]
|
|
64
|
+
logger.debug(f"Query returned {len(rows)} rows")
|
|
65
|
+
return rows
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.error(f"Database error executing query: {e}")
|
|
68
|
+
raise
|
|
69
|
+
|
|
70
|
+
def list_tables(self) -> list[str]:
|
|
71
|
+
"""List all tables in the BigQuery database"""
|
|
72
|
+
logger.debug("Listing all tables")
|
|
73
|
+
|
|
74
|
+
if self.datasets_filter:
|
|
75
|
+
datasets = [self.client.dataset(dataset) for dataset in self.datasets_filter]
|
|
76
|
+
else:
|
|
77
|
+
datasets = list(self.client.list_datasets())
|
|
78
|
+
|
|
79
|
+
logger.debug(f"Found {len(datasets)} datasets")
|
|
80
|
+
|
|
81
|
+
tables = []
|
|
82
|
+
for dataset in datasets:
|
|
83
|
+
dataset_tables = self.client.list_tables(dataset.dataset_id)
|
|
84
|
+
tables.extend([
|
|
85
|
+
f"{dataset.dataset_id}.{table.table_id}" for table in dataset_tables
|
|
86
|
+
])
|
|
87
|
+
|
|
88
|
+
logger.debug(f"Found {len(tables)} tables")
|
|
89
|
+
return tables
|
|
90
|
+
|
|
91
|
+
def describe_table(self, table_name: str) -> list[dict[str, Any]]:
|
|
92
|
+
"""Describe a table in the BigQuery database"""
|
|
93
|
+
logger.debug(f"Describing table: {table_name}")
|
|
94
|
+
|
|
95
|
+
parts = table_name.split(".")
|
|
96
|
+
if len(parts) != 2 and len(parts) != 3:
|
|
97
|
+
raise ValueError(f"Invalid table name: {table_name}")
|
|
98
|
+
|
|
99
|
+
dataset_id = ".".join(parts[:-1])
|
|
100
|
+
table_id = parts[-1]
|
|
101
|
+
|
|
102
|
+
query = f"""
|
|
103
|
+
SELECT ddl
|
|
104
|
+
FROM {dataset_id}.INFORMATION_SCHEMA.TABLES
|
|
105
|
+
WHERE table_name = @table_name;
|
|
106
|
+
"""
|
|
107
|
+
return self.execute_query(query, params=[
|
|
108
|
+
bigquery.ScalarQueryParameter("table_name", "STRING", table_id),
|
|
109
|
+
])
|
|
110
|
+
|
|
111
|
+
async def main(project: str, location: str, key_file: Optional[str], datasets_filter: list[str]):
|
|
112
|
+
logger.info(f"Starting BigQuery MCP Server with project: {project} and location: {location}")
|
|
113
|
+
|
|
114
|
+
db = BigQueryDatabase(project, location, key_file, datasets_filter)
|
|
115
|
+
server = Server("bigquery-manager")
|
|
116
|
+
|
|
117
|
+
# Register handlers
|
|
118
|
+
logger.debug("Registering handlers")
|
|
119
|
+
|
|
120
|
+
@server.list_tools()
|
|
121
|
+
async def handle_list_tools() -> list[types.Tool]:
|
|
122
|
+
"""List available tools"""
|
|
123
|
+
return [
|
|
124
|
+
types.Tool(
|
|
125
|
+
name="execute-query",
|
|
126
|
+
description="Execute a SELECT query on the BigQuery database",
|
|
127
|
+
inputSchema={
|
|
128
|
+
"type": "object",
|
|
129
|
+
"properties": {
|
|
130
|
+
"query": {"type": "string", "description": "SELECT SQL query to execute using BigQuery dialect"},
|
|
131
|
+
},
|
|
132
|
+
"required": ["query"],
|
|
133
|
+
},
|
|
134
|
+
),
|
|
135
|
+
types.Tool(
|
|
136
|
+
name="list-tables",
|
|
137
|
+
description="List all tables in the BigQuery database",
|
|
138
|
+
inputSchema={
|
|
139
|
+
"type": "object",
|
|
140
|
+
"properties": {},
|
|
141
|
+
},
|
|
142
|
+
),
|
|
143
|
+
types.Tool(
|
|
144
|
+
name="describe-table",
|
|
145
|
+
description="Get the schema information for a specific table",
|
|
146
|
+
inputSchema={
|
|
147
|
+
"type": "object",
|
|
148
|
+
"properties": {
|
|
149
|
+
"table_name": {"type": "string", "description": "Name of the table to describe (e.g. my_dataset.my_table)"},
|
|
150
|
+
},
|
|
151
|
+
"required": ["table_name"],
|
|
152
|
+
},
|
|
153
|
+
),
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
@server.call_tool()
|
|
157
|
+
async def handle_call_tool(
|
|
158
|
+
name: str, arguments: dict[str, Any] | None
|
|
159
|
+
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
|
160
|
+
"""Handle tool execution requests"""
|
|
161
|
+
logger.debug(f"Handling tool execution request: {name}")
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
if name == "list-tables":
|
|
165
|
+
results = db.list_tables()
|
|
166
|
+
return [types.TextContent(type="text", text=str(results))]
|
|
167
|
+
|
|
168
|
+
elif name == "describe-table":
|
|
169
|
+
if not arguments or "table_name" not in arguments:
|
|
170
|
+
raise ValueError("Missing table_name argument")
|
|
171
|
+
results = db.describe_table(arguments["table_name"])
|
|
172
|
+
return [types.TextContent(type="text", text=str(results))]
|
|
173
|
+
|
|
174
|
+
if name == "execute-query":
|
|
175
|
+
results = db.execute_query(arguments["query"])
|
|
176
|
+
return [types.TextContent(type="text", text=str(results))]
|
|
177
|
+
|
|
178
|
+
else:
|
|
179
|
+
raise ValueError(f"Unknown tool: {name}")
|
|
180
|
+
except Exception as e:
|
|
181
|
+
return [types.TextContent(type="text", text=f"Error: {str(e)}")]
|
|
182
|
+
|
|
183
|
+
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
|
184
|
+
logger.info("Server running with stdio transport")
|
|
185
|
+
await server.run(
|
|
186
|
+
read_stream,
|
|
187
|
+
write_stream,
|
|
188
|
+
InitializationOptions(
|
|
189
|
+
server_name="bigquery",
|
|
190
|
+
server_version="0.3.0",
|
|
191
|
+
capabilities=server.get_capabilities(
|
|
192
|
+
notification_options=NotificationOptions(),
|
|
193
|
+
experimental_capabilities={},
|
|
194
|
+
),
|
|
195
|
+
),
|
|
196
|
+
)
|