Skip to content

Commit b2bb3a7

Browse files
DA-1171 list index names and definitions (#63)
Co-authored-by: Nithish Raghunandanan <12782505+nithishr@users.noreply.github.com>
1 parent 28620e9 commit b2bb3a7

File tree

12 files changed

+1015
-448
lines changed

12 files changed

+1015
-448
lines changed

CONTRIBUTING.md

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -67,25 +67,30 @@ Our Ruff configuration includes:
6767
```
6868
mcp-server-couchbase/
6969
├── src/
70-
│ ├── mcp_server.py # MCP server entry point
71-
│ ├── tools/ # MCP tool implementations
72-
│ │ ├── __init__.py # Tool exports and ALL_TOOLS list
73-
│ │ ├── server.py # Server status and connection tools
74-
│ │ ├── kv.py # Key-value operations (CRUD)
75-
│ │ └── query.py # SQL++ query operations
76-
│ └── utils/ # Utility modules
77-
│ ├── __init__.py # Utility exports
78-
│ ├── constants.py # Project constants
79-
│ ├── config.py # Configuration management
80-
│ ├── connection.py # Couchbase connection handling
81-
│ └── context.py # Application context management
82-
├── scripts/ # Development scripts
83-
│ ├── lint.sh # Manual linting script
84-
│ └── lint_fix.sh # Auto-fix linting issues
85-
├── .pre-commit-config.yaml # Pre-commit hook configuration
86-
├── pyproject.toml # Project dependencies and Ruff config
87-
├── CONTRIBUTING.md # Contribution Guide
88-
└── README.md # Usage
70+
│ ├── mcp_server.py # MCP server entry point
71+
│ ├── certs/ # SSL/TLS certificates
72+
│ │ ├── __init__.py # Package marker
73+
│ │ └── capella_root_ca.pem # Capella root CA certificate (for Capella connections)
74+
│ ├── tools/ # MCP tool implementations
75+
│ │ ├── __init__.py # Tool exports and ALL_TOOLS list
76+
│ │ ├── server.py # Server status and connection tools
77+
│ │ ├── kv.py # Key-value operations (CRUD)
78+
│ │ ├── query.py # SQL++ query operations
79+
│ │ └── index.py # Index operations and recommendations
80+
│ └── utils/ # Utility modules
81+
│ ├── __init__.py # Utility exports
82+
│ ├── constants.py # Project constants
83+
│ ├── config.py # Configuration management
84+
│ ├── connection.py # Couchbase connection handling
85+
│ ├── context.py # Application context management
86+
│ └── index_utils.py # Index-related helper functions
87+
├── scripts/ # Development scripts
88+
│ ├── lint.sh # Manual linting script
89+
│ └── lint_fix.sh # Auto-fix linting issues
90+
├── .pre-commit-config.yaml # Pre-commit hook configuration
91+
├── pyproject.toml # Project dependencies and Ruff config
92+
├── CONTRIBUTING.md # Contribution Guide
93+
└── README.md # Usage
8994
```
9095

9196
## 🛠️ Development Workflow

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ ENV UV_COMPILE_BYTECODE=1 \
88
WORKDIR /build
99

1010
# Copy dependency files for caching
11-
COPY pyproject.toml ./
11+
COPY pyproject.toml README.md ./
1212
COPY src/ ./src/
1313

1414
# Create virtual environment and install dependencies

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ An [MCP](https://modelcontextprotocol.io/) server implementation of Couchbase th
2222
- There is an option in the MCP server, `CB_MCP_READ_ONLY_QUERY_MODE` that is set to true by default to disable running SQL++ queries that change the data or the underlying collection structure. Note that the documents can still be updated by ID.
2323
- Get the status of the MCP server
2424
- Check the cluster credentials by connecting to the cluster
25+
- List all indexes in the cluster with their definitions, with optional filtering by bucket, scope, and collection
26+
- Get index recommendations from Couchbase Index Advisor for a given SQL++ query to optimize query performance
2527
- Get cluster health status and list of all running services
2628

2729
## Prerequisites

pyproject.toml

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "couchbase-mcp-server"
33
version = "0.5.0"
44
description = "Couchbase MCP Server - The Developer Data Platform for Critical Applications in Our AI World"
55
readme = "README.md"
6-
requires-python = ">=3.10"
6+
requires-python = ">=3.10,<3.14"
77
license = "Apache-2.0"
88
authors = [
99
{ name="Nithish Raghunandanan", email="devadvocates@couchbase.com" },
@@ -15,10 +15,11 @@ classifiers = [
1515
]
1616

1717
dependencies = [
18-
"click==8.2.1",
19-
"couchbase==4.4.0",
20-
"lark-sqlpp==0.0.1",
21-
"mcp[cli]==1.12.0",
18+
"click>=8.2.1,<9.0.0",
19+
"couchbase>=4.4.0,<5.0.0",
20+
"lark-sqlpp>=0.0.1",
21+
"mcp[cli]>=1.20.0,<2.0.0",
22+
"urllib3>=2.0.0",
2223
]
2324

2425
[project.urls]
@@ -123,5 +124,23 @@ indent-style = "space"
123124
skip-magic-trailing-comma = false
124125
line-ending = "auto"
125126

127+
# Build system configuration
128+
[build-system]
129+
requires = ["hatchling"]
130+
build-backend = "hatchling.build"
131+
132+
# Configure hatchling to use src-layout
133+
[tool.hatch.build.targets.wheel]
134+
# Include all packages from src/ and map them to the root
135+
only-include = [
136+
"src/mcp_server.py",
137+
"src/tools",
138+
"src/utils",
139+
"src/certs"
140+
]
141+
142+
[tool.hatch.build.targets.wheel.sources]
143+
"src" = ""
144+
126145
[tool.uv]
127146
package = true

src/certs/__init__.py

Whitespace-only changes.

src/certs/capella_root_ca.pem

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDFTCCAf2gAwIBAgIRANLVkgOvtaXiQJi0V6qeNtswDQYJKoZIhvcNAQELBQAw
3+
JDESMBAGA1UECgwJQ291Y2hiYXNlMQ4wDAYDVQQLDAVDbG91ZDAeFw0xOTEyMDYy
4+
MjEyNTlaFw0yOTEyMDYyMzEyNTlaMCQxEjAQBgNVBAoMCUNvdWNoYmFzZTEOMAwG
5+
A1UECwwFQ2xvdWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfvOIi
6+
enG4Dp+hJu9asdxEMRmH70hDyMXv5ZjBhbo39a42QwR59y/rC/sahLLQuNwqif85
7+
Fod1DkqgO6Ng3vecSAwyYVkj5NKdycQu5tzsZkghlpSDAyI0xlIPSQjoORA/pCOU
8+
WOpymA9dOjC1bo6rDyw0yWP2nFAI/KA4Z806XeqLREuB7292UnSsgFs4/5lqeil6
9+
rL3ooAw/i0uxr/TQSaxi1l8t4iMt4/gU+W52+8Yol0JbXBTFX6itg62ppb/Eugmn
10+
mQRMgL67ccZs7cJ9/A0wlXencX2ohZQOR3mtknfol3FH4+glQFn27Q4xBCzVkY9j
11+
KQ20T1LgmGSngBInAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
12+
FJQOBPvrkU2In1Sjoxt97Xy8+cKNMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B
13+
AQsFAAOCAQEARgM6XwcXPLSpFdSf0w8PtpNGehmdWijPM3wHb7WZiS47iNen3oq8
14+
m2mm6V3Z57wbboPpfI+VEzbhiDcFfVnK1CXMC0tkF3fnOG1BDDvwt4jU95vBiNjY
15+
xdzlTP/Z+qr0cnVbGBSZ+fbXstSiRaaAVcqQyv3BRvBadKBkCyPwo+7svQnScQ5P
16+
Js7HEHKVms5tZTgKIw1fbmgR2XHleah1AcANB+MAPBCcTgqurqr5G7W2aPSBLLGA
17+
fRIiVzm7VFLc7kWbp7ENH39HVG6TZzKnfl9zJYeiklo5vQQhGSMhzBsO70z4RRzi
18+
DPFAN/4qZAgD5q3AFNIq2WWADFQGSwVJhg==
19+
-----END CERTIFICATE-----

src/mcp_server.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,6 @@ def main(
165165
{
166166
"host": host,
167167
"port": port,
168-
"transport": sdk_transport,
169168
}
170169
if transport in NETWORK_TRANSPORTS
171170
else {}

src/tools/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66

77
# Index tools
8-
from .index import get_index_advisor_recommendations
8+
from .index import get_index_advisor_recommendations, list_indexes
99

1010
# Key-Value tools
1111
from .kv import (
@@ -45,6 +45,7 @@
4545
get_schema_for_collection,
4646
run_sql_plus_plus_query,
4747
get_index_advisor_recommendations,
48+
list_indexes,
4849
get_cluster_health_and_services,
4950
]
5051

@@ -62,6 +63,7 @@
6263
"get_schema_for_collection",
6364
"run_sql_plus_plus_query",
6465
"get_index_advisor_recommendations",
66+
"list_indexes",
6567
"get_cluster_health_and_services",
6668
# Convenience
6769
"ALL_TOOLS",

src/tools/index.py

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
2-
Tools for index operations and optimization.
2+
Tools for index operations.
33
4-
This module contains tools for getting index recommendations using the Couchbase Index Advisor.
4+
This module contains tools for listing and managing indexes in the Couchbase cluster and getting index recommendations using the Couchbase Index Advisor.
55
"""
66

77
import logging
@@ -10,7 +10,14 @@
1010
from mcp.server.fastmcp import Context
1111

1212
from tools.query import run_sql_plus_plus_query
13+
from utils.config import get_settings
1314
from utils.constants import MCP_SERVER_NAME
15+
from utils.index_utils import (
16+
fetch_indexes_from_rest_api,
17+
process_index_data,
18+
validate_connection_settings,
19+
validate_filter_params,
20+
)
1421

1522
logger = logging.getLogger(f"{MCP_SERVER_NAME}.tools.index")
1623

@@ -39,7 +46,7 @@ def get_index_advisor_recommendations(
3946

4047
logger.info("Running Index Advisor for the provided query")
4148

42-
# Execute the ADVISOR function at cluster level using run_cluster_query
49+
# Execute the ADVISOR function at cluster level using run_sql_plus_plus_query
4350
advisor_results = run_sql_plus_plus_query(
4451
ctx, bucket_name, scope_name, advisor_query
4552
)
@@ -86,3 +93,80 @@ def get_index_advisor_recommendations(
8693
except Exception as e:
8794
logger.error(f"Error running Index Advisor: {e!s}", exc_info=True)
8895
raise
96+
97+
98+
def list_indexes(
99+
ctx: Context,
100+
bucket_name: str | None = None,
101+
scope_name: str | None = None,
102+
collection_name: str | None = None,
103+
index_name: str | None = None,
104+
include_raw_index_stats: bool = False,
105+
) -> list[dict[str, Any]]:
106+
"""List all indexes in the cluster with optional filtering by bucket, scope, collection, and index name.
107+
Returns a list of indexes with their names and CREATE INDEX definitions.
108+
Uses the Index Service REST API (/getIndexStatus) to retrieve index information directly.
109+
110+
Args:
111+
ctx: MCP context for cluster connection
112+
bucket_name: Optional bucket name to filter indexes
113+
scope_name: Optional scope name to filter indexes (requires bucket_name)
114+
collection_name: Optional collection name to filter indexes (requires bucket_name and scope_name)
115+
index_name: Optional index name to filter indexes (requires bucket_name, scope_name, and collection_name)
116+
include_raw_index_stats: If True, include raw index stats (as-is from API) in addition
117+
to cleaned-up version. Default is False.
118+
119+
Returns:
120+
List of dictionaries with keys:
121+
- name (str): Index name
122+
- definition (str): Cleaned-up CREATE INDEX statement
123+
- status (str): Current status of the index (e.g., "Ready", "Building", "Deferred")
124+
- isPrimary (bool): Whether this is a primary index
125+
- bucket (str): Bucket name where the index exists
126+
- scope (str): Scope name where the index exists
127+
- collection (str): Collection name where the index exists
128+
- raw_index_stats (dict, optional): Complete raw index status object from API including metadata,
129+
state, keyspace info, etc. (only if include_raw_index_stats=True)
130+
"""
131+
try:
132+
# Validate parameters
133+
validate_filter_params(bucket_name, scope_name, collection_name, index_name)
134+
135+
# Get and validate connection settings
136+
settings = get_settings()
137+
validate_connection_settings(settings)
138+
139+
# Fetch indexes from REST API
140+
logger.info(
141+
f"Fetching indexes from REST API for bucket={bucket_name}, "
142+
f"scope={scope_name}, collection={collection_name}, index={index_name}"
143+
)
144+
145+
raw_indexes = fetch_indexes_from_rest_api(
146+
settings["connection_string"],
147+
settings["username"],
148+
settings["password"],
149+
bucket_name=bucket_name,
150+
scope_name=scope_name,
151+
collection_name=collection_name,
152+
index_name=index_name,
153+
ca_cert_path=settings.get("ca_cert_path"),
154+
)
155+
156+
# Process and format the results
157+
indexes = [
158+
processed
159+
for idx in raw_indexes
160+
if (processed := process_index_data(idx, include_raw_index_stats))
161+
is not None
162+
]
163+
164+
logger.info(
165+
f"Found {len(indexes)} indexes from REST API "
166+
f"(include_raw_index_stats={include_raw_index_stats})"
167+
)
168+
return indexes
169+
170+
except Exception as e:
171+
logger.error(f"Error listing indexes: {e}", exc_info=True)
172+
raise

src/utils/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
get_cluster_connection,
3535
)
3636

37+
# Index utilities
38+
from .index_utils import (
39+
fetch_indexes_from_rest_api,
40+
)
41+
3742
# Note: Individual modules create their own hierarchical loggers using:
3843
# logger = logging.getLogger(f"{MCP_SERVER_NAME}.module.name")
3944

@@ -46,6 +51,8 @@
4651
# Context
4752
"AppContext",
4853
"get_cluster_connection",
54+
# Index utilities
55+
"fetch_indexes_from_rest_api",
4956
# Constants
5057
"MCP_SERVER_NAME",
5158
"DEFAULT_READ_ONLY_MODE",

0 commit comments

Comments
 (0)