Skip to content

Commit 37fe8e1

Browse files
authored
Support multiple buckets (#47)
* Add support for the MCP server to work with multiple buckets - Introduced `connect_to_bucket` utility to establish bucket connections using the cluster context. - Updated relevant functions to accept `bucket_name` as an argument where necessary, improving flexibility in handling Couchbase operations. - Enhanced documentation and logging for better clarity on connection status and error handling. * Update README.md to enhance feature list and clarify environment variable usage - Added new features for retrieving bucket, scope, and collection lists in the MCP server. - Removed references to `CB_BUCKET_NAME` from environment variable sections * Remove `validate_connection_config` function and its references from the utils module * Add the bucket configuration back in to the MCP server * Add bucket name retrieval to server configuration and update connection logic - Included 'bucket_name' in the server configuration status. - Updated the connection logic to resolve the bucket name before attempting to connect, improving error handling and clarity in connection status. * Use Query based retrieval for getting the collections in a scope * Fix typo in docstring of run_cluster_query function in query.py * Handle review comments - Typo in docstring. - Enhanced error handling by removing redundant checks for empty strings. - Improved the retrieval of bucket names in connection status responses. * Update README.md to clarify environment variable usage and enhance feature descriptions - Added note regarding the requirement of Query services for retrieving collections in a specified scope and bucket. - Included `CB_BUCKET_NAME` in the environment variable section with a description for its usage. * Update error handling in cluster connection response to always indicate disconnection status * Update README.md to add a note on database credential requirements for default bucket in MCP server configuration * Refactor bucket name resolution in KV, Query, and Server modules - Updated the logic to use `resolved_bucket_name` instead of `bucket_name` for improved clarity and consistency across functions. - Enhanced error logging in the server module to specify the resolved bucket name when encountering issues. * Remove bucket_name from MCP server initialization - Removed the `resolve_bucket_name` function and updated the code to directly use `bucket_name` in KV, Query, and Server modules for improved clarity. - Adjusted function signatures to require `bucket_name` as a mandatory parameter, enhancing consistency in bucket handling. - Updated error logging to reflect the direct use of `bucket_name` in connection status responses. * Update README.md to reflect removal of CB_BUCKET_NAME from configuration examples - Removed references to `CB_BUCKET_NAME` in environment variable examples and usage notes for clarity. - Adjusted descriptions to ensure consistency with recent changes in MCP server initialization. * Handle error in case of unspecified bucket_name for test_cluster_connection tool * Update README.md to remove note on database credential limitations for MCP server configuration
1 parent 355b8ce commit 37fe8e1

File tree

10 files changed

+135
-118
lines changed

10 files changed

+135
-118
lines changed

README.md

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22

33
An [MCP](https://modelcontextprotocol.io/) server implementation of Couchbase that allows LLMs to directly interact with Couchbase clusters.
44

5-
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/) [![PyPI version](https://badge.fury.io/py/couchbase-mcp-server.svg)](https://pypi.org/project/couchbase-mcp-server/) [![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/13fce476-0e74-4b1e-ab82-1df2a3204809)
6-
7-
[![Trust Score](https://archestra.ai/mcp-catalog/api/badge/quality/Couchbase-Ecosystem/mcp-server-couchbase)](https://archestra.ai/mcp-catalog/couchbase-ecosystem__mcp-server-couchbase)
5+
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/) [![PyPI version](https://badge.fury.io/py/couchbase-mcp-server.svg)](https://pypi.org/project/couchbase-mcp-server/) [![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/13fce476-0e74-4b1e-ab82-1df2a3204809) [![Trust Score](https://archestra.ai/mcp-catalog/api/badge/quality/Couchbase-Ecosystem/mcp-server-couchbase)](https://archestra.ai/mcp-catalog/couchbase-ecosystem__mcp-server-couchbase)
86

97
<a href="https://glama.ai/mcp/servers/@Couchbase-Ecosystem/mcp-server-couchbase">
108
<img width="380" height="200" src="https://glama.ai/mcp/servers/@Couchbase-Ecosystem/mcp-server-couchbase/badge" alt="Couchbase Server MCP server" />
119
</a>
1210

1311
## Features
1412

13+
- Get a list of all the buckets in the cluster
1514
- Get a list of all the scopes and collections in the specified bucket
15+
- Get a list of all the scopes in the specified bucket
16+
- Get a list of all the collections in a specified scope and bucket. Note that this tool requires the cluster to have Query service.
1617
- Get the structure for a collection
1718
- Get a document by ID from a specified scope and collection
1819
- Upsert a document by ID to a specified scope and collection
@@ -48,8 +49,7 @@ We publish a pre built [PyPI package](https://pypi.org/project/couchbase-mcp-ser
4849
"env": {
4950
"CB_CONNECTION_STRING": "couchbases://connection-string",
5051
"CB_USERNAME": "username",
51-
"CB_PASSWORD": "password",
52-
"CB_BUCKET_NAME": "bucket_name"
52+
"CB_PASSWORD": "password"
5353
}
5454
}
5555
}
@@ -86,8 +86,7 @@ This is the common configuration for the MCP clients such as Claude Desktop, Cur
8686
"env": {
8787
"CB_CONNECTION_STRING": "couchbases://connection-string",
8888
"CB_USERNAME": "username",
89-
"CB_PASSWORD": "password",
90-
"CB_BUCKET_NAME": "bucket_name"
89+
"CB_PASSWORD": "password"
9190
}
9291
}
9392
}
@@ -105,9 +104,8 @@ The server can be configured using environment variables or command line argumen
105104
| Environment Variable | CLI Argument | Description | Default |
106105
| ----------------------------- | ------------------------ | ------------------------------------------ | ------------ |
107106
| `CB_CONNECTION_STRING` | `--connection-string` | Connection string to the Couchbase cluster | **Required** |
108-
| `CB_USERNAME` | `--username` | Username with bucket access | **Required** |
107+
| `CB_USERNAME` | `--username` | Username with access to required buckets | **Required** |
109108
| `CB_PASSWORD` | `--password` | Password for authentication | **Required** |
110-
| `CB_BUCKET_NAME` | `--bucket-name` | Name of the bucket to access | **Required** |
111109
| `CB_MCP_READ_ONLY_QUERY_MODE` | `--read-only-query-mode` | Prevent data modification queries | `true` |
112110
| `CB_MCP_TRANSPORT` | `--transport` | Transport mode: `stdio`, `http`, `sse` | `stdio` |
113111
| `CB_MCP_HOST` | `--host` | Host for HTTP/SSE transport modes | `127.0.0.1` |
@@ -210,7 +208,6 @@ uvx couchbase-mcp-server \
210208
--connection-string='<couchbase_connection_string>' \
211209
--username='<database_username>' \
212210
--password='<database_password>' \
213-
--bucket-name='<couchbase_bucket_to_use>' \
214211
--read-only-query-mode=true \
215212
--transport=http
216213
```
@@ -244,7 +241,6 @@ uvx couchbase-mcp-server \
244241
--connection-string='<couchbase_connection_string>' \
245242
--username='<database_username>' \
246243
--password='<database_password>' \
247-
--bucket-name='<couchbase_bucket_to_use>' \
248244
--read-only-query-mode=true \
249245
--transport=sse
250246
```
@@ -321,7 +317,6 @@ docker run --rm -i \
321317
-e CB_CONNECTION_STRING='<couchbase_connection_string>' \
322318
-e CB_USERNAME='<database_user>' \
323319
-e CB_PASSWORD='<database_password>' \
324-
-e CB_BUCKET_NAME='<bucket_name>' \
325320
-e CB_MCP_TRANSPORT='<http|sse|stdio>' \
326321
-e CB_MCP_READ_ONLY_QUERY_MODE='<true|false>' \
327322
-e CB_MCP_PORT=9001 \
@@ -350,8 +345,6 @@ The Docker image can be used in `stdio` transport mode with the following config
350345
"CB_USERNAME=<database_user>",
351346
"-e",
352347
"CB_PASSWORD=<database_password>",
353-
"-e",
354-
"CB_BUCKET_NAME=<bucket_name>",
355348
"mcp/couchbase"
356349
]
357350
}
@@ -377,7 +370,7 @@ The Couchbase MCP server can also be used as a managed server in your agentic ap
377370
## Troubleshooting Tips
378371

379372
- Ensure the path to your MCP server repository is correct in the configuration if running from source.
380-
- Verify that your Couchbase connection string, database username, password and bucket name are correct.
373+
- Verify that your Couchbase connection string, database username, password are correct.
381374
- If using Couchbase Capella, ensure that the cluster is [accessible](https://docs.couchbase.com/cloud/clusters/allow-ip-address.html) from the machine where the MCP server is running.
382375
- Check that the database user has proper permissions to access the specified bucket.
383376
- Confirm that the `uv` package manager is properly installed and accessible. You may need to provide absolute path to `uv`/`uvx` in the `command` field in the configuration.

src/mcp_server.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,6 @@ async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
7777
envvar="CB_PASSWORD",
7878
help="Couchbase database password (required for operations)",
7979
)
80-
@click.option(
81-
"--bucket-name",
82-
envvar="CB_BUCKET_NAME",
83-
help="Couchbase bucket name (required for operations)",
84-
)
8580
@click.option(
8681
"--read-only-query-mode",
8782
envvar=[
@@ -121,7 +116,6 @@ def main(
121116
connection_string,
122117
username,
123118
password,
124-
bucket_name,
125119
read_only_query_mode,
126120
transport,
127121
host,
@@ -133,7 +127,6 @@ def main(
133127
"connection_string": connection_string,
134128
"username": username,
135129
"password": password,
136-
"bucket_name": bucket_name,
137130
"read_only_query_mode": read_only_query_mode,
138131
"transport": transport,
139132
"host": host,

src/tools/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,22 @@
1919

2020
# Server tools
2121
from .server import (
22+
get_buckets_in_cluster,
23+
get_collections_in_scope,
2224
get_scopes_and_collections_in_bucket,
25+
get_scopes_in_bucket,
2326
get_server_configuration_status,
2427
test_cluster_connection,
2528
)
2629

2730
# List of all tools for easy registration
2831
ALL_TOOLS = [
32+
get_buckets_in_cluster,
2933
get_server_configuration_status,
3034
test_cluster_connection,
3135
get_scopes_and_collections_in_bucket,
36+
get_collections_in_scope,
37+
get_scopes_in_bucket,
3238
get_document_by_id,
3339
upsert_document_by_id,
3440
delete_document_by_id,
@@ -41,6 +47,9 @@
4147
"get_server_configuration_status",
4248
"test_cluster_connection",
4349
"get_scopes_and_collections_in_bucket",
50+
"get_collections_in_scope",
51+
"get_scopes_in_bucket",
52+
"get_buckets_in_cluster",
4453
"get_document_by_id",
4554
"upsert_document_by_id",
4655
"delete_document_by_id",

src/tools/kv.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,25 @@
99

1010
from mcp.server.fastmcp import Context
1111

12+
from utils.connection import connect_to_bucket
1213
from utils.constants import MCP_SERVER_NAME
13-
from utils.context import ensure_bucket_connection
14+
from utils.context import get_cluster_connection
1415

1516
logger = logging.getLogger(f"{MCP_SERVER_NAME}.tools.kv")
1617

1718

1819
def get_document_by_id(
19-
ctx: Context, scope_name: str, collection_name: str, document_id: str
20+
ctx: Context,
21+
bucket_name: str,
22+
scope_name: str,
23+
collection_name: str,
24+
document_id: str,
2025
) -> dict[str, Any]:
2126
"""Get a document by its ID from the specified scope and collection.
2227
If the document is not found, it will raise an exception."""
23-
bucket = ensure_bucket_connection(ctx)
28+
29+
cluster = get_cluster_connection(ctx)
30+
bucket = connect_to_bucket(cluster, bucket_name)
2431
try:
2532
collection = bucket.scope(scope_name).collection(collection_name)
2633
result = collection.get(document_id)
@@ -32,14 +39,16 @@ def get_document_by_id(
3239

3340
def upsert_document_by_id(
3441
ctx: Context,
42+
bucket_name: str,
3543
scope_name: str,
3644
collection_name: str,
3745
document_id: str,
3846
document_content: dict[str, Any],
3947
) -> bool:
4048
"""Insert or update a document by its ID.
4149
Returns True on success, False on failure."""
42-
bucket = ensure_bucket_connection(ctx)
50+
cluster = get_cluster_connection(ctx)
51+
bucket = connect_to_bucket(cluster, bucket_name)
4352
try:
4453
collection = bucket.scope(scope_name).collection(collection_name)
4554
collection.upsert(document_id, document_content)
@@ -51,11 +60,16 @@ def upsert_document_by_id(
5160

5261

5362
def delete_document_by_id(
54-
ctx: Context, scope_name: str, collection_name: str, document_id: str
63+
ctx: Context,
64+
bucket_name: str,
65+
scope_name: str,
66+
collection_name: str,
67+
document_id: str,
5568
) -> bool:
5669
"""Delete a document by its ID.
5770
Returns True on success, False on failure."""
58-
bucket = ensure_bucket_connection(ctx)
71+
cluster = get_cluster_connection(ctx)
72+
bucket = connect_to_bucket(cluster, bucket_name)
5973
try:
6074
collection = bucket.scope(scope_name).collection(collection_name)
6175
collection.remove(document_id)

src/tools/query.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,23 @@
1010
from lark_sqlpp import modifies_data, modifies_structure, parse_sqlpp
1111
from mcp.server.fastmcp import Context
1212

13+
from utils.connection import connect_to_bucket
1314
from utils.constants import MCP_SERVER_NAME
14-
from utils.context import ensure_bucket_connection
15+
from utils.context import get_cluster_connection
1516

1617
logger = logging.getLogger(f"{MCP_SERVER_NAME}.tools.query")
1718

1819

1920
def get_schema_for_collection(
20-
ctx: Context, scope_name: str, collection_name: str
21+
ctx: Context, bucket_name: str, scope_name: str, collection_name: str
2122
) -> dict[str, Any]:
2223
"""Get the schema for a collection in the specified scope.
2324
Returns a dictionary with the collection name and the schema returned by running INFER query on the Couchbase collection.
2425
"""
2526
schema = {"collection_name": collection_name, "schema": []}
2627
try:
27-
query = f"INFER {collection_name}"
28-
result = run_sql_plus_plus_query(ctx, scope_name, query)
28+
query = f"INFER `{collection_name}`"
29+
result = run_sql_plus_plus_query(ctx, bucket_name, scope_name, query)
2930
# Result is a list of list of schemas. We convert it to a list of schemas.
3031
if result:
3132
schema["schema"] = result[0]
@@ -36,10 +37,13 @@ def get_schema_for_collection(
3637

3738

3839
def run_sql_plus_plus_query(
39-
ctx: Context, scope_name: str, query: str
40+
ctx: Context, bucket_name: str, scope_name: str, query: str
4041
) -> list[dict[str, Any]]:
4142
"""Run a SQL++ query on a scope and return the results as a list of JSON objects."""
42-
bucket = ensure_bucket_connection(ctx)
43+
cluster = get_cluster_connection(ctx)
44+
45+
bucket = connect_to_bucket(cluster, bucket_name)
46+
4347
app_context = ctx.request_context.lifespan_context
4448
read_only_query_mode = app_context.read_only_query_mode
4549
logger.info(f"Running SQL++ queries in read-only mode: {read_only_query_mode}")
@@ -75,3 +79,20 @@ def run_sql_plus_plus_query(
7579
except Exception as e:
7680
logger.error(f"Error running query: {e!s}", exc_info=True)
7781
raise
82+
83+
84+
# Don't expose this function to the MCP server until we have a use case
85+
def run_cluster_query(ctx: Context, query: str, **kwargs: Any) -> list[dict[str, Any]]:
86+
"""Run a query on the cluster object and return the results as a list of JSON objects."""
87+
88+
cluster = get_cluster_connection(ctx)
89+
results = []
90+
91+
try:
92+
result = cluster.query(query, **kwargs)
93+
for row in result:
94+
results.append(row)
95+
return results
96+
except Exception as e:
97+
logger.error(f"Error running query: {e}")
98+
raise

0 commit comments

Comments
 (0)