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.
@@ -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
+ [![smithery badge](https://smithery.ai/badge/mcp-server-bigquery)](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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mcp-server-bigquery = mcp_server_bigquery:main
@@ -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
+ )