couchbase-mcp-server 0.4.0rc1__py3-none-any.whl → 0.5.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: couchbase-mcp-server
3
- Version: 0.4.0rc1
3
+ Version: 0.5.0
4
4
  Summary: Couchbase MCP Server - The Developer Data Platform for Critical Applications in Our AI World
5
5
  Author-email: Nithish Raghunandanan <devadvocates@couchbase.com>
6
6
  License-Expression: Apache-2.0
@@ -26,7 +26,7 @@ Dynamic: license-file
26
26
 
27
27
  An [MCP](https://modelcontextprotocol.io/) server implementation of Couchbase that allows LLMs to directly interact with Couchbase clusters.
28
28
 
29
- [![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)
29
+ [![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)
30
30
 
31
31
  <a href="https://glama.ai/mcp/servers/@Couchbase-Ecosystem/mcp-server-couchbase">
32
32
  <img width="380" height="200" src="https://glama.ai/mcp/servers/@Couchbase-Ecosystem/mcp-server-couchbase/badge" alt="Couchbase Server MCP server" />
@@ -34,7 +34,10 @@ An [MCP](https://modelcontextprotocol.io/) server implementation of Couchbase th
34
34
 
35
35
  ## Features
36
36
 
37
+ - Get a list of all the buckets in the cluster
37
38
  - Get a list of all the scopes and collections in the specified bucket
39
+ - Get a list of all the scopes in the specified bucket
40
+ - Get a list of all the collections in a specified scope and bucket. Note that this tool requires the cluster to have Query service.
38
41
  - Get the structure for a collection
39
42
  - Get a document by ID from a specified scope and collection
40
43
  - Upsert a document by ID to a specified scope and collection
@@ -53,7 +56,7 @@ An [MCP](https://modelcontextprotocol.io/) server implementation of Couchbase th
53
56
 
54
57
  ## Configuration
55
58
 
56
- The MCP server can be run either from the pre built PyPI package or the source using uv.
59
+ The MCP server can be run either from the prebuilt PyPI package or the source using uv.
57
60
 
58
61
  ### Running from PyPI
59
62
 
@@ -61,6 +64,8 @@ We publish a pre built [PyPI package](https://pypi.org/project/couchbase-mcp-ser
61
64
 
62
65
  #### Server Configuration using Pre built Package for MCP Clients
63
66
 
67
+ #### Basic Authentication
68
+
64
69
  ```json
65
70
  {
66
71
  "mcpServers": {
@@ -70,8 +75,27 @@ We publish a pre built [PyPI package](https://pypi.org/project/couchbase-mcp-ser
70
75
  "env": {
71
76
  "CB_CONNECTION_STRING": "couchbases://connection-string",
72
77
  "CB_USERNAME": "username",
73
- "CB_PASSWORD": "password",
74
- "CB_BUCKET_NAME": "bucket_name"
78
+ "CB_PASSWORD": "password"
79
+ }
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ or
86
+
87
+ #### mTLS
88
+
89
+ ```json
90
+ {
91
+ "mcpServers": {
92
+ "couchbase": {
93
+ "command": "uvx",
94
+ "args": ["couchbase-mcp-server"],
95
+ "env": {
96
+ "CB_CONNECTION_STRING": "couchbases://connection-string",
97
+ "CB_CLIENT_CERT_PATH": "/path/to/client-certificate.pem",
98
+ "CB_CLIENT_KEY_PATH": "/path/to/client.key"
75
99
  }
76
100
  }
77
101
  }
@@ -108,8 +132,7 @@ This is the common configuration for the MCP clients such as Claude Desktop, Cur
108
132
  "env": {
109
133
  "CB_CONNECTION_STRING": "couchbases://connection-string",
110
134
  "CB_USERNAME": "username",
111
- "CB_PASSWORD": "password",
112
- "CB_BUCKET_NAME": "bucket_name"
135
+ "CB_PASSWORD": "password"
113
136
  }
114
137
  }
115
138
  }
@@ -123,17 +146,21 @@ This is the common configuration for the MCP clients such as Claude Desktop, Cur
123
146
  ### Additional Configuration for MCP Server
124
147
 
125
148
  The server can be configured using environment variables or command line arguments:
126
-
127
- | Environment Variable | CLI Argument | Description | Default |
128
- | ----------------------------- | ------------------------ | ------------------------------------------ | ------------ |
129
- | `CB_CONNECTION_STRING` | `--connection-string` | Connection string to the Couchbase cluster | **Required** |
130
- | `CB_USERNAME` | `--username` | Username with bucket access | **Required** |
131
- | `CB_PASSWORD` | `--password` | Password for authentication | **Required** |
132
- | `CB_BUCKET_NAME` | `--bucket-name` | Name of the bucket to access | **Required** |
133
- | `CB_MCP_READ_ONLY_QUERY_MODE` | `--read-only-query-mode` | Prevent data modification queries | `true` |
134
- | `CB_MCP_TRANSPORT` | `--transport` | Transport mode: `stdio`, `http`, `sse` | `stdio` |
135
- | `CB_MCP_HOST` | `--host` | Host for HTTP/SSE transport modes | `127.0.0.1` |
136
- | `CB_MCP_PORT` | `--port` | Port for HTTP/SSE transport modes | `8000` |
149
+ | Environment Variable | CLI Argument | Description | Default |
150
+ |--------------------------------|--------------------------|---------------------------------------------------------------------------------------------|------------------------------------------|
151
+ | `CB_CONNECTION_STRING` | `--connection-string` | Connection string to the Couchbase cluster | **Required** |
152
+ | `CB_USERNAME` | `--username` | Username with access to required buckets for basic authentication | **Required (or Client Certificate and Key needed for mTLS)** |
153
+ | `CB_PASSWORD` | `--password` | Password for basic authentication | **Required (or Client Certificate and Key needed for mTLS)** |
154
+ | `CB_CLIENT_CERT_PATH` | `--client-cert-path` | Path to the client certificate file for mTLS authentication| **Required if using mTLS (or Username and Password required)** |
155
+ | `CB_CLIENT_KEY_PATH` | `--client-key-path` | Path to the client key file for mTLS authentication| **Required if using mTLS (or Username and Password required)** |
156
+ | `CB_CA_CERT_PATH` | `--ca-cert-path` | Path to server root certificate for TLS if server is configured with a self-signed/untrusted certificate. This will not be required if you are connecting to Capella | |
157
+ | `CB_MCP_READ_ONLY_QUERY_MODE` | `--read-only-query-mode` | Prevent data modification queries | `true` |
158
+ | `CB_MCP_TRANSPORT` | `--transport` | Transport mode: `stdio`, `http`, `sse` | `stdio` |
159
+ | `CB_MCP_HOST` | `--host` | Host for HTTP/SSE transport modes | `127.0.0.1` |
160
+ | `CB_MCP_PORT` | `--port` | Port for HTTP/SSE transport modes | `8000` |
161
+
162
+ > Note: For authentication, you need either the Username and Password or the Client Certificate and key paths. Optionally, you can specify the CA root certificate path that will be used to validate the server certificates.
163
+ > If both the Client Certificate & key path and the username and password are specified, the client certificates will be used for authentication.
137
164
 
138
165
  You can also check the version of the server using:
139
166
 
@@ -228,7 +255,12 @@ Check if your [MCP client](https://modelcontextprotocol.io/clients) supports str
228
255
  By default, the MCP server will run on port 8000 but this can be configured using the `--port` or `CB_MCP_PORT` environment variable.
229
256
 
230
257
  ```bash
231
- uvx couchbase-mcp-server --connection-string='<couchbase_connection_string>' --username='<database_username>' --password='<database_password>' --bucket-name='<couchbase_bucket_to_use>' --read-only-query-mode=true --transport=streamable-http
258
+ uvx couchbase-mcp-server \
259
+ --connection-string='<couchbase_connection_string>' \
260
+ --username='<database_username>' \
261
+ --password='<database_password>' \
262
+ --read-only-query-mode=true \
263
+ --transport=http
232
264
  ```
233
265
 
234
266
  The server will be available on http://localhost:8000/mcp. This can be used in MCP clients supporting streamable http transport mode such as Cursor.
@@ -256,7 +288,12 @@ There is an option to run the MCP server in [Server-Sent Events (SSE)](https://m
256
288
  By default, the MCP server will run on port 8000 but this can be configured using the `--port` or `CB_MCP_PORT` environment variable.
257
289
 
258
290
  ```bash
259
- uvx couchbase-mcp-server --connection-string='<couchbase_connection_string>' --username='<database_username>' --password='<database_password>' --bucket-name='<couchbase_bucket_to_use>' --read-only-query-mode=true --transport=sse
291
+ uvx couchbase-mcp-server \
292
+ --connection-string='<couchbase_connection_string>' \
293
+ --username='<database_username>' \
294
+ --password='<database_password>' \
295
+ --read-only-query-mode=true \
296
+ --transport=sse
260
297
  ```
261
298
 
262
299
  The server will be available on http://localhost:8000/sse. This can be used in MCP clients supporting SSE transport mode such as Cursor.
@@ -331,7 +368,6 @@ docker run --rm -i \
331
368
  -e CB_CONNECTION_STRING='<couchbase_connection_string>' \
332
369
  -e CB_USERNAME='<database_user>' \
333
370
  -e CB_PASSWORD='<database_password>' \
334
- -e CB_BUCKET_NAME='<bucket_name>' \
335
371
  -e CB_MCP_TRANSPORT='<http|sse|stdio>' \
336
372
  -e CB_MCP_READ_ONLY_QUERY_MODE='<true|false>' \
337
373
  -e CB_MCP_PORT=9001 \
@@ -360,8 +396,6 @@ The Docker image can be used in `stdio` transport mode with the following config
360
396
  "CB_USERNAME=<database_user>",
361
397
  "-e",
362
398
  "CB_PASSWORD=<database_password>",
363
- "-e",
364
- "CB_BUCKET_NAME=<bucket_name>",
365
399
  "mcp/couchbase"
366
400
  ]
367
401
  }
@@ -387,11 +421,11 @@ The Couchbase MCP server can also be used as a managed server in your agentic ap
387
421
  ## Troubleshooting Tips
388
422
 
389
423
  - Ensure the path to your MCP server repository is correct in the configuration if running from source.
390
- - Verify that your Couchbase connection string, database username, password and bucket name are correct.
424
+ - Verify that your Couchbase connection string, database username, password or the path to the certificates are correct.
391
425
  - 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.
392
- - Check that the database user has proper permissions to access the specified bucket.
393
- - 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.
394
- - Check the logs for any errors or warnings that may indicate issues with the MCP server. The server logs are under the name, `mcp-server-couchbase.log`.
426
+ - Check that the database user has proper permissions to access at least one bucket.
427
+ - 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.
428
+ - Check the logs for any errors or warnings that may indicate issues with the MCP server. The location of the logs depend on your MCP client.
395
429
  - If you are observing issues running your MCP server from source after updating your local MCP server repository, try running `uv sync` to update the [dependencies](https://docs.astral.sh/uv/concepts/projects/sync/#syncing-the-environment).
396
430
 
397
431
  ---
@@ -0,0 +1,16 @@
1
+ mcp_server.py,sha256=QGKbHL5fnQQnFkZZvK89eEWR6HcmF_0JqV_E6lke3M8,5031
2
+ couchbase_mcp_server-0.5.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
3
+ tools/__init__.py,sha256=1FRntOrRYPiITHteCiEu0DrIBEesNgpeeLkvzFpRNv4,1355
4
+ tools/kv.py,sha256=NGUs43iuXElj9rYe4RCyCStqoh5y1fUgbg1oWuU4WeQ,2493
5
+ tools/query.py,sha256=fSSG7XuxnND1lHwI-r8y_M3h6f82vCXxCwB_A2wHN-M,3598
6
+ tools/server.py,sha256=iv_UH4OiBnlPYSSEt1rs3LFeYmIZb_c5mEEYBToS8gU,5188
7
+ utils/__init__.py,sha256=eVRW08PAfJXrB3cYbmHIj3v6kfa1-a9dlNXsEOB0igU,1223
8
+ utils/config.py,sha256=B6H_JYDn6uxtu9juM924zdvNQgSaHh_u6rYME3_0_xQ,268
9
+ utils/connection.py,sha256=NtAU4pmHMZubSJcs_X_lai9o8dih5mW0RyrRdmyp1Po,2892
10
+ utils/constants.py,sha256=w0zvQ1zMzJBg44Yl3aQW8KfaaRPn0BgPOLEe8xLeLSE,487
11
+ utils/context.py,sha256=XZL4M70BMdFBptJ9sT0zxhEey-EvvoSKZJrP_sb7q-A,2286
12
+ couchbase_mcp_server-0.5.0.dist-info/METADATA,sha256=G_R6LVJ0LqtBhF4v58z1-qRms3YgHjDpLLDfGwlEI3Q,20630
13
+ couchbase_mcp_server-0.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ couchbase_mcp_server-0.5.0.dist-info/entry_points.txt,sha256=iU5pF4kIMTnNhoMPHhdH-k8o1Fmxb_iM9qJHCZcL6Ak,57
15
+ couchbase_mcp_server-0.5.0.dist-info/top_level.txt,sha256=cQeSKgjLtHXStynG6fJkj8o4tBep9OkDai_4h5bf_4I,23
16
+ couchbase_mcp_server-0.5.0.dist-info/RECORD,,
mcp_server.py CHANGED
@@ -78,9 +78,22 @@ async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
78
78
  help="Couchbase database password (required for operations)",
79
79
  )
80
80
  @click.option(
81
- "--bucket-name",
82
- envvar="CB_BUCKET_NAME",
83
- help="Couchbase bucket name (required for operations)",
81
+ "--ca-cert-path",
82
+ envvar="CB_CA_CERT_PATH",
83
+ default=None,
84
+ help="Path to the server trust store (CA certificate) file. The certificate at this path is used to verify the server certificate during the authentication process.",
85
+ )
86
+ @click.option(
87
+ "--client-cert-path",
88
+ envvar="CB_CLIENT_CERT_PATH",
89
+ default=None,
90
+ help="Path to the client certificate file used for mTLS authentication.",
91
+ )
92
+ @click.option(
93
+ "--client-key-path",
94
+ envvar="CB_CLIENT_KEY_PATH",
95
+ default=None,
96
+ help="Path to the client certificate key file used for mTLS authentication.",
84
97
  )
85
98
  @click.option(
86
99
  "--read-only-query-mode",
@@ -121,7 +134,9 @@ def main(
121
134
  connection_string,
122
135
  username,
123
136
  password,
124
- bucket_name,
137
+ ca_cert_path,
138
+ client_cert_path,
139
+ client_key_path,
125
140
  read_only_query_mode,
126
141
  transport,
127
142
  host,
@@ -133,7 +148,9 @@ def main(
133
148
  "connection_string": connection_string,
134
149
  "username": username,
135
150
  "password": password,
136
- "bucket_name": bucket_name,
151
+ "ca_cert_path": ca_cert_path,
152
+ "client_cert_path": client_cert_path,
153
+ "client_key_path": client_key_path,
137
154
  "read_only_query_mode": read_only_query_mode,
138
155
  "transport": transport,
139
156
  "host": host,
tools/__init__.py CHANGED
@@ -19,16 +19,22 @@ from .query import (
19
19
 
20
20
  # Server tools
21
21
  from .server import (
22
+ get_buckets_in_cluster,
23
+ get_collections_in_scope,
22
24
  get_scopes_and_collections_in_bucket,
25
+ get_scopes_in_bucket,
23
26
  get_server_configuration_status,
24
27
  test_cluster_connection,
25
28
  )
26
29
 
27
30
  # List of all tools for easy registration
28
31
  ALL_TOOLS = [
32
+ get_buckets_in_cluster,
29
33
  get_server_configuration_status,
30
34
  test_cluster_connection,
31
35
  get_scopes_and_collections_in_bucket,
36
+ get_collections_in_scope,
37
+ get_scopes_in_bucket,
32
38
  get_document_by_id,
33
39
  upsert_document_by_id,
34
40
  delete_document_by_id,
@@ -41,6 +47,9 @@ __all__ = [
41
47
  "get_server_configuration_status",
42
48
  "test_cluster_connection",
43
49
  "get_scopes_and_collections_in_bucket",
50
+ "get_collections_in_scope",
51
+ "get_scopes_in_bucket",
52
+ "get_buckets_in_cluster",
44
53
  "get_document_by_id",
45
54
  "upsert_document_by_id",
46
55
  "delete_document_by_id",
tools/kv.py CHANGED
@@ -9,18 +9,25 @@ from typing import Any
9
9
 
10
10
  from mcp.server.fastmcp import Context
11
11
 
12
+ from utils.connection import connect_to_bucket
12
13
  from utils.constants import MCP_SERVER_NAME
13
- from utils.context import ensure_bucket_connection
14
+ from utils.context import get_cluster_connection
14
15
 
15
16
  logger = logging.getLogger(f"{MCP_SERVER_NAME}.tools.kv")
16
17
 
17
18
 
18
19
  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,
20
25
  ) -> dict[str, Any]:
21
26
  """Get a document by its ID from the specified scope and collection.
22
27
  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)
24
31
  try:
25
32
  collection = bucket.scope(scope_name).collection(collection_name)
26
33
  result = collection.get(document_id)
@@ -32,6 +39,7 @@ def get_document_by_id(
32
39
 
33
40
  def upsert_document_by_id(
34
41
  ctx: Context,
42
+ bucket_name: str,
35
43
  scope_name: str,
36
44
  collection_name: str,
37
45
  document_id: str,
@@ -39,7 +47,8 @@ def upsert_document_by_id(
39
47
  ) -> bool:
40
48
  """Insert or update a document by its ID.
41
49
  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)
43
52
  try:
44
53
  collection = bucket.scope(scope_name).collection(collection_name)
45
54
  collection.upsert(document_id, document_content)
@@ -51,11 +60,16 @@ def upsert_document_by_id(
51
60
 
52
61
 
53
62
  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,
55
68
  ) -> bool:
56
69
  """Delete a document by its ID.
57
70
  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)
59
73
  try:
60
74
  collection = bucket.scope(scope_name).collection(collection_name)
61
75
  collection.remove(document_id)
tools/query.py CHANGED
@@ -10,22 +10,23 @@ from typing import Any
10
10
  from lark_sqlpp import modifies_data, modifies_structure, parse_sqlpp
11
11
  from mcp.server.fastmcp import Context
12
12
 
13
+ from utils.connection import connect_to_bucket
13
14
  from utils.constants import MCP_SERVER_NAME
14
- from utils.context import ensure_bucket_connection
15
+ from utils.context import get_cluster_connection
15
16
 
16
17
  logger = logging.getLogger(f"{MCP_SERVER_NAME}.tools.query")
17
18
 
18
19
 
19
20
  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
21
22
  ) -> dict[str, Any]:
22
23
  """Get the schema for a collection in the specified scope.
23
24
  Returns a dictionary with the collection name and the schema returned by running INFER query on the Couchbase collection.
24
25
  """
25
26
  schema = {"collection_name": collection_name, "schema": []}
26
27
  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)
29
30
  # Result is a list of list of schemas. We convert it to a list of schemas.
30
31
  if result:
31
32
  schema["schema"] = result[0]
@@ -36,10 +37,13 @@ def get_schema_for_collection(
36
37
 
37
38
 
38
39
  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
40
41
  ) -> list[dict[str, Any]]:
41
42
  """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
+
43
47
  app_context = ctx.request_context.lifespan_context
44
48
  read_only_query_mode = app_context.read_only_query_mode
45
49
  logger.info(f"Running SQL++ queries in read-only mode: {read_only_query_mode}")
@@ -75,3 +79,20 @@ def run_sql_plus_plus_query(
75
79
  except Exception as e:
76
80
  logger.error(f"Error running query: {e!s}", exc_info=True)
77
81
  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
tools/server.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Tools for server operations.
3
3
 
4
- This module contains tools for getting the server status, testing the connection, and getting the scopes and collections in the bucket.
4
+ This module contains tools for getting the server status, testing the connection, and getting the buckets in the cluster, the scopes and collections in the bucket.
5
5
  """
6
6
 
7
7
  import logging
@@ -9,9 +9,11 @@ from typing import Any
9
9
 
10
10
  from mcp.server.fastmcp import Context
11
11
 
12
+ from tools.query import run_cluster_query
12
13
  from utils.config import get_settings
14
+ from utils.connection import connect_to_bucket
13
15
  from utils.constants import MCP_SERVER_NAME
14
- from utils.context import ensure_bucket_connection
16
+ from utils.context import get_cluster_connection
15
17
 
16
18
  logger = logging.getLogger(f"{MCP_SERVER_NAME}.tools.server")
17
19
 
@@ -26,15 +28,16 @@ def get_server_configuration_status(ctx: Context) -> dict[str, Any]:
26
28
  configuration = {
27
29
  "connection_string": settings.get("connection_string", "Not set"),
28
30
  "username": settings.get("username", "Not set"),
29
- "bucket_name": settings.get("bucket_name", "Not set"),
30
31
  "read_only_query_mode": settings.get("read_only_query_mode", True),
31
32
  "password_configured": bool(settings.get("password")),
33
+ "ca_cert_path_configured": bool(settings.get("ca_cert_path")),
34
+ "client_cert_path_configured": bool(settings.get("client_cert_path")),
35
+ "client_key_path_configured": bool(settings.get("client_key_path")),
32
36
  }
33
37
 
34
38
  app_context = ctx.request_context.lifespan_context
35
39
  connection_status = {
36
40
  "cluster_connected": app_context.cluster is not None,
37
- "bucket_connected": app_context.bucket is not None,
38
41
  }
39
42
 
40
43
  return {
@@ -45,39 +48,46 @@ def get_server_configuration_status(ctx: Context) -> dict[str, Any]:
45
48
  }
46
49
 
47
50
 
48
- def test_cluster_connection(ctx: Context) -> dict[str, Any]:
49
- """Test the connection to Couchbase cluster and bucket.
51
+ def test_cluster_connection(
52
+ ctx: Context, bucket_name: str | None = None
53
+ ) -> dict[str, Any]:
54
+ """Test the connection to Couchbase cluster and optionally to a bucket.
50
55
  This tool verifies the connection to the Couchbase cluster and bucket by establishing the connection if it is not already established.
56
+ If bucket name is not provided, it will not try to connect to the bucket specified in the MCP server settings.
51
57
  Returns connection status and basic cluster information.
52
58
  """
53
59
  try:
54
- bucket = ensure_bucket_connection(ctx)
55
-
56
- # Test basic connectivity by getting bucket name
57
- bucket_name = bucket.name
60
+ cluster = get_cluster_connection(ctx)
61
+ bucket = None
62
+ if bucket_name:
63
+ bucket = connect_to_bucket(cluster, bucket_name)
58
64
 
59
65
  return {
60
66
  "status": "success",
61
- "cluster_connected": True,
62
- "bucket_connected": True,
67
+ "cluster_connected": cluster.connected,
68
+ "bucket_connected": bucket is not None,
63
69
  "bucket_name": bucket_name,
64
- "message": "Successfully connected to Couchbase cluster and bucket",
70
+ "message": "Successfully connected to Couchbase cluster",
65
71
  }
66
72
  except Exception as e:
67
73
  return {
68
74
  "status": "error",
69
75
  "cluster_connected": False,
70
76
  "bucket_connected": False,
77
+ "bucket_name": bucket_name,
71
78
  "error": str(e),
72
- "message": "Failed to connect to Couchbase",
79
+ "message": "Failed to connect to Couchbase cluster",
73
80
  }
74
81
 
75
82
 
76
- def get_scopes_and_collections_in_bucket(ctx: Context) -> dict[str, list[str]]:
83
+ def get_scopes_and_collections_in_bucket(
84
+ ctx: Context, bucket_name: str
85
+ ) -> dict[str, list[str]]:
77
86
  """Get the names of all scopes and collections in the bucket.
78
87
  Returns a dictionary with scope names as keys and lists of collection names as values.
79
88
  """
80
- bucket = ensure_bucket_connection(ctx)
89
+ cluster = get_cluster_connection(ctx)
90
+ bucket = connect_to_bucket(cluster, bucket_name)
81
91
  try:
82
92
  scopes_collections = {}
83
93
  collection_manager = bucket.collections()
@@ -89,3 +99,41 @@ def get_scopes_and_collections_in_bucket(ctx: Context) -> dict[str, list[str]]:
89
99
  except Exception as e:
90
100
  logger.error(f"Error getting scopes and collections: {e}")
91
101
  raise
102
+
103
+
104
+ def get_buckets_in_cluster(ctx: Context) -> list[str]:
105
+ """Get the names of all the accessible buckets in the cluster."""
106
+ cluster = get_cluster_connection(ctx)
107
+ bucket_manager = cluster.buckets()
108
+ buckets_with_settings = bucket_manager.get_all_buckets()
109
+
110
+ buckets = []
111
+ for bucket in buckets_with_settings:
112
+ buckets.append(bucket.name)
113
+
114
+ return buckets
115
+
116
+
117
+ def get_scopes_in_bucket(ctx: Context, bucket_name: str) -> list[str]:
118
+ """Get the names of all scopes in the given bucket."""
119
+ cluster = get_cluster_connection(ctx)
120
+ bucket = connect_to_bucket(cluster, bucket_name)
121
+ try:
122
+ scopes = bucket.collections().get_all_scopes()
123
+ return [scope.name for scope in scopes]
124
+ except Exception as e:
125
+ logger.error(f"Error getting scopes in the bucket {bucket_name}: {e}")
126
+ raise
127
+
128
+
129
+ def get_collections_in_scope(
130
+ ctx: Context, bucket_name: str, scope_name: str
131
+ ) -> list[str]:
132
+ """Get the names of all collections in the given scope and bucket."""
133
+
134
+ # Get the collections in the scope using system:all_keyspaces collection
135
+ query = "SELECT DISTINCT(name) as collection_name FROM system:all_keyspaces where `bucket`=$bucket_name and `scope`=$scope_name"
136
+ results = run_cluster_query(
137
+ ctx, query, bucket_name=bucket_name, scope_name=scope_name
138
+ )
139
+ return [result["collection_name"] for result in results]
utils/__init__.py CHANGED
@@ -7,8 +7,6 @@ This module contains utility functions for configuration, connection, and contex
7
7
  # Configuration utilities
8
8
  from .config import (
9
9
  get_settings,
10
- validate_connection_config,
11
- validate_required_param,
12
10
  )
13
11
 
14
12
  # Connection utilities
@@ -33,7 +31,7 @@ from .constants import (
33
31
  # Context utilities
34
32
  from .context import (
35
33
  AppContext,
36
- ensure_bucket_connection,
34
+ get_cluster_connection,
37
35
  )
38
36
 
39
37
  # Note: Individual modules create their own hierarchical loggers using:
@@ -42,14 +40,12 @@ from .context import (
42
40
  __all__ = [
43
41
  # Config
44
42
  "get_settings",
45
- "validate_required_param",
46
- "validate_connection_config",
47
43
  # Connection
48
44
  "connect_to_couchbase_cluster",
49
45
  "connect_to_bucket",
50
46
  # Context
51
47
  "AppContext",
52
- "ensure_bucket_connection",
48
+ "get_cluster_connection",
53
49
  # Constants
54
50
  "MCP_SERVER_NAME",
55
51
  "DEFAULT_READ_ONLY_MODE",
utils/config.py CHANGED
@@ -7,32 +7,7 @@ from .constants import MCP_SERVER_NAME
7
7
  logger = logging.getLogger(f"{MCP_SERVER_NAME}.utils.config")
8
8
 
9
9
 
10
- def validate_required_param(
11
- ctx: click.Context, param: click.Parameter, value: str | None
12
- ) -> str:
13
- """Validate that a required parameter is not empty."""
14
- if not value or value.strip() == "":
15
- raise click.BadParameter(f"{param.name} cannot be empty")
16
- return value
17
-
18
-
19
10
  def get_settings() -> dict:
20
11
  """Get settings from Click context."""
21
12
  ctx = click.get_current_context()
22
13
  return ctx.obj or {}
23
-
24
-
25
- def validate_connection_config() -> None:
26
- """Validate that all required parameters for the MCP server are available when needed."""
27
- settings = get_settings()
28
- required_params = ["connection_string", "username", "password", "bucket_name"]
29
- missing_params = []
30
-
31
- for param in required_params:
32
- if not settings.get(param):
33
- missing_params.append(param)
34
-
35
- if missing_params:
36
- error_msg = f"Missing required parameters for the MCP server: {', '.join(missing_params)}"
37
- logger.error(error_msg)
38
- raise ValueError(error_msg)
utils/connection.py CHANGED
@@ -1,7 +1,8 @@
1
1
  import logging
2
+ import os
2
3
  from datetime import timedelta
3
4
 
4
- from couchbase.auth import PasswordAuthenticator
5
+ from couchbase.auth import CertificateAuthenticator, PasswordAuthenticator
5
6
  from couchbase.cluster import Bucket, Cluster
6
7
  from couchbase.options import ClusterOptions
7
8
 
@@ -11,15 +12,40 @@ logger = logging.getLogger(f"{MCP_SERVER_NAME}.utils.connection")
11
12
 
12
13
 
13
14
  def connect_to_couchbase_cluster(
14
- connection_string: str, username: str, password: str
15
+ connection_string: str,
16
+ username: str,
17
+ password: str,
18
+ ca_cert_path: str | None = None,
19
+ client_cert_path: str | None = None,
20
+ client_key_path: str | None = None,
15
21
  ) -> Cluster:
16
22
  """Connect to Couchbase cluster and return the cluster object if successful.
23
+ The connection can be established using the client certificate and key or the username and password. Optionally, the CA root certificate path can also be provided.
24
+ Either of the path to the client certificate and key or the username and password should be provided.
25
+ If the client certificate and key are provided, the username and password are not used.
26
+ If both the client certificate and key and the username and password are provided, the client certificate is used for authentication.
17
27
  If the connection fails, it will raise an exception.
18
28
  """
19
29
 
20
30
  try:
21
31
  logger.info("Connecting to Couchbase cluster...")
22
- auth = PasswordAuthenticator(username, password)
32
+ if client_cert_path and client_key_path:
33
+ logger.info("Connecting to Couchbase cluster with client certificate...")
34
+ if not os.path.exists(client_cert_path) or not os.path.exists(
35
+ client_key_path
36
+ ):
37
+ raise FileNotFoundError(
38
+ f"Client certificate files not found at {os.path.basename(client_cert_path)} or {os.path.basename(client_key_path)}."
39
+ )
40
+
41
+ auth = CertificateAuthenticator(
42
+ cert_path=client_cert_path,
43
+ key_path=client_key_path,
44
+ trust_store_path=ca_cert_path,
45
+ )
46
+ else:
47
+ logger.info("Connecting to Couchbase cluster with password...")
48
+ auth = PasswordAuthenticator(username, password, cert_path=ca_cert_path)
23
49
  options = ClusterOptions(auth)
24
50
  options.apply_profile("wan_development")
25
51
 
@@ -38,7 +64,6 @@ def connect_to_bucket(cluster: Cluster, bucket_name: str) -> Bucket:
38
64
  If the operation fails, it will raise an exception.
39
65
  """
40
66
  try:
41
- logger.info(f"Connecting to bucket: {bucket_name}")
42
67
  bucket = cluster.bucket(bucket_name)
43
68
  return bucket
44
69
  except Exception as e:
utils/context.py CHANGED
@@ -1,11 +1,11 @@
1
1
  import logging
2
2
  from dataclasses import dataclass
3
3
 
4
- from couchbase.cluster import Bucket, Cluster
4
+ from couchbase.cluster import Cluster
5
5
  from mcp.server.fastmcp import Context
6
6
 
7
- from utils.config import get_settings, validate_connection_config
8
- from utils.connection import connect_to_bucket, connect_to_couchbase_cluster
7
+ from utils.config import get_settings
8
+ from utils.connection import connect_to_couchbase_cluster
9
9
  from utils.constants import MCP_SERVER_NAME
10
10
 
11
11
  logger = logging.getLogger(f"{MCP_SERVER_NAME}.utils.context")
@@ -16,7 +16,6 @@ class AppContext:
16
16
  """Context for the MCP server."""
17
17
 
18
18
  cluster: Cluster | None = None
19
- bucket: Bucket | None = None
20
19
  read_only_query_mode: bool = True
21
20
 
22
21
 
@@ -30,51 +29,36 @@ def _set_cluster_in_lifespan_context(ctx: Context) -> None:
30
29
  connection_string = settings.get("connection_string")
31
30
  username = settings.get("username")
32
31
  password = settings.get("password")
32
+ ca_cert_path = settings.get("ca_cert_path")
33
+ client_cert_path = settings.get("client_cert_path")
34
+ client_key_path = settings.get("client_key_path")
35
+
33
36
  cluster = connect_to_couchbase_cluster(
34
37
  connection_string, # type: ignore
35
38
  username, # type: ignore
36
39
  password, # type: ignore
40
+ ca_cert_path,
41
+ client_cert_path,
42
+ client_key_path,
37
43
  )
38
44
  ctx.request_context.lifespan_context.cluster = cluster
39
45
  except Exception as e:
40
46
  logger.error(
41
- f"Failed to connect to Couchbase: {e} \n Please check your connection string, username and password"
47
+ "Failed to connect to Couchbase: %s\n"
48
+ "Verify connection string, and either:\n"
49
+ "- Username/password are correct, or\n"
50
+ "- Client certificate and key exist and match server mapping.\n"
51
+ "If using self-signed or custom CA, set CB_CA_CERT_PATH to the CA file.",
52
+ e,
42
53
  )
43
54
  raise
44
55
 
45
56
 
46
- def _set_bucket_in_lifespan_context(ctx: Context) -> None:
47
- """Set the bucket in the lifespan context.
48
- If the bucket is not set, it will try to connect to the bucket using the cluster object in the lifespan context.
57
+ def get_cluster_connection(ctx: Context) -> Cluster:
58
+ """Return the cluster connection from the lifespan context.
49
59
  If the cluster is not set, it will try to connect to the cluster using the connection string, username, and password.
50
- If the connection fails, it will raise an exception.
51
60
  """
52
- settings = get_settings()
53
- bucket_name = settings.get("bucket_name")
54
-
55
- # If the bucket is not set, try to connect to the bucket using the cluster object in the lifespan context
56
- app_context = ctx.request_context.lifespan_context
57
-
58
- try:
59
- # If the cluster is not set, try to connect to the cluster
60
- if not app_context.cluster:
61
- _set_cluster_in_lifespan_context(ctx)
62
- cluster = app_context.cluster
63
-
64
- # Try to connect to the bucket using the cluster object
65
- bucket = connect_to_bucket(cluster, bucket_name) # type: ignore
66
- app_context.bucket = bucket
67
- except Exception as e:
68
- logger.error(
69
- f"Failed to connect to bucket: {e} \n Please check your bucket name and credentials."
70
- )
71
- raise
72
-
73
-
74
- def ensure_bucket_connection(ctx: Context) -> Bucket:
75
- """Ensure bucket connection is established and return the bucket object."""
76
- validate_connection_config()
77
61
  app_context = ctx.request_context.lifespan_context
78
- if not app_context.bucket:
79
- _set_bucket_in_lifespan_context(ctx)
80
- return app_context.bucket
62
+ if not app_context.cluster:
63
+ _set_cluster_in_lifespan_context(ctx)
64
+ return app_context.cluster
@@ -1,16 +0,0 @@
1
- mcp_server.py,sha256=t-TFlzAPH0qUILFjX33n6VnEvTgWAjCRy3HrKkKosWE,4416
2
- couchbase_mcp_server-0.4.0rc1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
3
- tools/__init__.py,sha256=jVvVnpoSJrYFmxAnx2vkyAuKZ1-Ti1fzdIgbO0yqAcM,1097
4
- tools/kv.py,sha256=LDDASE4nD4oiTkDzSSYIrmvleqn7e8W-14UOql_SWK8,2199
5
- tools/query.py,sha256=BYpJkgiCAINqgreW22uk-6JjrKrZkswGYKRhU1l-jLI,2901
6
- tools/server.py,sha256=a4rDDDYO7WNTcKN8KjiQlnsIZ1_ydlAVgIy5nxxic5g,3251
7
- utils/__init__.py,sha256=E7Puxqate6J5xdNWSzLFU33UfsbSGE9B4aiMB696v04,1353
8
- utils/config.py,sha256=vCZfyhQXnbTPwcJi3YfLTXqqirCeHVTSyHui5_aGchs,1136
9
- utils/connection.py,sha256=2a_syAAMFE2zHEFMzsOJGz4Vhz6Tq3lgna3qfhKva0w,1520
10
- utils/constants.py,sha256=w0zvQ1zMzJBg44Yl3aQW8KfaaRPn0BgPOLEe8xLeLSE,487
11
- utils/context.py,sha256=pepc4sCUsmx0itpoJ28_TS03mSXJbQhgTZcN7NAvvTg,3013
12
- couchbase_mcp_server-0.4.0rc1.dist-info/METADATA,sha256=_hBu07b23dHcalDuRUAS_IM-3N_yxggsKbxGGn-WM6E,19329
13
- couchbase_mcp_server-0.4.0rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- couchbase_mcp_server-0.4.0rc1.dist-info/entry_points.txt,sha256=iU5pF4kIMTnNhoMPHhdH-k8o1Fmxb_iM9qJHCZcL6Ak,57
15
- couchbase_mcp_server-0.4.0rc1.dist-info/top_level.txt,sha256=cQeSKgjLtHXStynG6fJkj8o4tBep9OkDai_4h5bf_4I,23
16
- couchbase_mcp_server-0.4.0rc1.dist-info/RECORD,,