Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8bb754b
DA-1171 Added Index Definitions Utils
AayushTyagi1 Oct 24, 2025
a1e4b19
List Indexes and Index Definitions
AayushTyagi1 Oct 24, 2025
c4c1fc9
DA-1171: Change using to index_type
AayushTyagi1 Oct 24, 2025
de29d21
DA-1171 Added Index Advisor in readme
AayushTyagi1 Oct 24, 2025
5949d2d
DA-1171 Fixed Size name
AayushTyagi1 Oct 24, 2025
f46d012
DA-1171 Fixed naming
AayushTyagi1 Oct 24, 2025
979fd3c
Index Definitions for GSI Vector Index
AayushTyagi1 Oct 24, 2025
e06d92c
DA-1171: Review fixed
AayushTyagi1 Oct 24, 2025
a93b279
DA-1171 Modified Provide the definition if it exists in metadata
AayushTyagi1 Oct 24, 2025
c28ba3b
DA-1171 Added Index Def from Rest Endpoint
AayushTyagi1 Oct 24, 2025
20b611c
UV-lock synced
AayushTyagi1 Oct 28, 2025
0753ac4
TLS issue review fixed
AayushTyagi1 Oct 28, 2025
644a784
DA-1171 Added Capella Certification and option to add cert path
AayushTyagi1 Oct 28, 2025
0ef2e85
Sync up with main
AayushTyagi1 Oct 28, 2025
a18bafa
Added Raw Index definition as optional field
AayushTyagi1 Oct 29, 2025
241cd07
DA-1171: Modified Changed correct certificate and proper conditioning
AayushTyagi1 Oct 30, 2025
bed8b15
DA-1171 Added Review comments
AayushTyagi1 Oct 31, 2025
732ae85
Update dependencies (#66)
nithishr Oct 31, 2025
59f4c0f
Rebase
AayushTyagi1 Nov 3, 2025
1acdb88
DA-1171: Modified use httpx than http
AayushTyagi1 Nov 3, 2025
4a31c74
Package Capella certs & restrict Python 3.14 (#67)
nithishr Nov 3, 2025
5856981
DA-1171 Modified Multi host fix
AayushTyagi1 Nov 3, 2025
439c4c0
DA-1171 Modified Added Index def as per name
AayushTyagi1 Nov 3, 2025
5f7a13d
review fix
AayushTyagi1 Nov 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 24 additions & 19 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,25 +67,30 @@ Our Ruff configuration includes:
```
mcp-server-couchbase/
├── src/
│ ├── mcp_server.py # MCP server entry point
│ ├── tools/ # MCP tool implementations
│ │ ├── __init__.py # Tool exports and ALL_TOOLS list
│ │ ├── server.py # Server status and connection tools
│ │ ├── kv.py # Key-value operations (CRUD)
│ │ └── query.py # SQL++ query operations
│ └── utils/ # Utility modules
│ ├── __init__.py # Utility exports
│ ├── constants.py # Project constants
│ ├── config.py # Configuration management
│ ├── connection.py # Couchbase connection handling
│ └── context.py # Application context management
├── scripts/ # Development scripts
│ ├── lint.sh # Manual linting script
│ └── lint_fix.sh # Auto-fix linting issues
├── .pre-commit-config.yaml # Pre-commit hook configuration
├── pyproject.toml # Project dependencies and Ruff config
├── CONTRIBUTING.md # Contribution Guide
└── README.md # Usage
│ ├── mcp_server.py # MCP server entry point
│ ├── certs/ # SSL/TLS certificates
│ │ ├── __init__.py # Package marker
│ │ └── capella_root_ca.pem # Capella root CA certificate (for Capella connections)
│ ├── tools/ # MCP tool implementations
│ │ ├── __init__.py # Tool exports and ALL_TOOLS list
│ │ ├── server.py # Server status and connection tools
│ │ ├── kv.py # Key-value operations (CRUD)
│ │ ├── query.py # SQL++ query operations
│ │ └── index.py # Index operations and recommendations
│ └── utils/ # Utility modules
│ ├── __init__.py # Utility exports
│ ├── constants.py # Project constants
│ ├── config.py # Configuration management
│ ├── connection.py # Couchbase connection handling
│ ├── context.py # Application context management
│ └── index_utils.py # Index-related helper functions
├── scripts/ # Development scripts
│ ├── lint.sh # Manual linting script
│ └── lint_fix.sh # Auto-fix linting issues
├── .pre-commit-config.yaml # Pre-commit hook configuration
├── pyproject.toml # Project dependencies and Ruff config
├── CONTRIBUTING.md # Contribution Guide
└── README.md # Usage
```

## 🛠️ Development Workflow
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ ENV UV_COMPILE_BYTECODE=1 \
WORKDIR /build

# Copy dependency files for caching
COPY pyproject.toml ./
COPY pyproject.toml README.md ./
COPY src/ ./src/

# Create virtual environment and install dependencies
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ An [MCP](https://modelcontextprotocol.io/) server implementation of Couchbase th
- 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.
- Get the status of the MCP server
- Check the cluster credentials by connecting to the cluster
- List all indexes in the cluster with their definitions, with optional filtering by bucket, scope, and collection
- Get index recommendations from Couchbase Index Advisor for a given SQL++ query to optimize query performance
- Get cluster health status and list of all running services

## Prerequisites
Expand Down
29 changes: 24 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "couchbase-mcp-server"
version = "0.5.0"
description = "Couchbase MCP Server - The Developer Data Platform for Critical Applications in Our AI World"
readme = "README.md"
requires-python = ">=3.10"
requires-python = ">=3.10,<3.14"
license = "Apache-2.0"
authors = [
{ name="Nithish Raghunandanan", email="devadvocates@couchbase.com" },
Expand All @@ -15,10 +15,11 @@ classifiers = [
]

dependencies = [
"click==8.2.1",
"couchbase==4.4.0",
"lark-sqlpp==0.0.1",
"mcp[cli]==1.12.0",
"click>=8.2.1,<9.0.0",
"couchbase>=4.4.0,<5.0.0",
"lark-sqlpp>=0.0.1",
"mcp[cli]>=1.20.0,<2.0.0",
"urllib3>=2.0.0",
]

[project.urls]
Expand Down Expand Up @@ -123,5 +124,23 @@ indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"

# Build system configuration
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

# Configure hatchling to use src-layout
[tool.hatch.build.targets.wheel]
# Include all packages from src/ and map them to the root
only-include = [
"src/mcp_server.py",
"src/tools",
"src/utils",
"src/certs"
]

[tool.hatch.build.targets.wheel.sources]
"src" = ""

[tool.uv]
package = true
Empty file added src/certs/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions src/certs/capella_root_ca.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDFTCCAf2gAwIBAgIRANLVkgOvtaXiQJi0V6qeNtswDQYJKoZIhvcNAQELBQAw
JDESMBAGA1UECgwJQ291Y2hiYXNlMQ4wDAYDVQQLDAVDbG91ZDAeFw0xOTEyMDYy
MjEyNTlaFw0yOTEyMDYyMzEyNTlaMCQxEjAQBgNVBAoMCUNvdWNoYmFzZTEOMAwG
A1UECwwFQ2xvdWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfvOIi
enG4Dp+hJu9asdxEMRmH70hDyMXv5ZjBhbo39a42QwR59y/rC/sahLLQuNwqif85
Fod1DkqgO6Ng3vecSAwyYVkj5NKdycQu5tzsZkghlpSDAyI0xlIPSQjoORA/pCOU
WOpymA9dOjC1bo6rDyw0yWP2nFAI/KA4Z806XeqLREuB7292UnSsgFs4/5lqeil6
rL3ooAw/i0uxr/TQSaxi1l8t4iMt4/gU+W52+8Yol0JbXBTFX6itg62ppb/Eugmn
mQRMgL67ccZs7cJ9/A0wlXencX2ohZQOR3mtknfol3FH4+glQFn27Q4xBCzVkY9j
KQ20T1LgmGSngBInAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FJQOBPvrkU2In1Sjoxt97Xy8+cKNMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B
AQsFAAOCAQEARgM6XwcXPLSpFdSf0w8PtpNGehmdWijPM3wHb7WZiS47iNen3oq8
m2mm6V3Z57wbboPpfI+VEzbhiDcFfVnK1CXMC0tkF3fnOG1BDDvwt4jU95vBiNjY
xdzlTP/Z+qr0cnVbGBSZ+fbXstSiRaaAVcqQyv3BRvBadKBkCyPwo+7svQnScQ5P
Js7HEHKVms5tZTgKIw1fbmgR2XHleah1AcANB+MAPBCcTgqurqr5G7W2aPSBLLGA
fRIiVzm7VFLc7kWbp7ENH39HVG6TZzKnfl9zJYeiklo5vQQhGSMhzBsO70z4RRzi
DPFAN/4qZAgD5q3AFNIq2WWADFQGSwVJhg==
-----END CERTIFICATE-----
1 change: 0 additions & 1 deletion src/mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ def main(
{
"host": host,
"port": port,
"transport": sdk_transport,
}
if transport in NETWORK_TRANSPORTS
else {}
Expand Down
4 changes: 3 additions & 1 deletion src/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""

# Index tools
from .index import get_index_advisor_recommendations
from .index import get_index_advisor_recommendations, list_indexes

# Key-Value tools
from .kv import (
Expand Down Expand Up @@ -45,6 +45,7 @@
get_schema_for_collection,
run_sql_plus_plus_query,
get_index_advisor_recommendations,
list_indexes,
get_cluster_health_and_services,
]

Expand All @@ -62,6 +63,7 @@
"get_schema_for_collection",
"run_sql_plus_plus_query",
"get_index_advisor_recommendations",
"list_indexes",
"get_cluster_health_and_services",
# Convenience
"ALL_TOOLS",
Expand Down
90 changes: 87 additions & 3 deletions src/tools/index.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Tools for index operations and optimization.
Tools for index operations.

This module contains tools for getting index recommendations using the Couchbase Index Advisor.
This module contains tools for listing and managing indexes in the Couchbase cluster and getting index recommendations using the Couchbase Index Advisor.
"""

import logging
Expand All @@ -10,7 +10,14 @@
from mcp.server.fastmcp import Context

from tools.query import run_sql_plus_plus_query
from utils.config import get_settings
from utils.constants import MCP_SERVER_NAME
from utils.index_utils import (
fetch_indexes_from_rest_api,
process_index_data,
validate_connection_settings,
validate_filter_params,
)

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

Expand Down Expand Up @@ -39,7 +46,7 @@ def get_index_advisor_recommendations(

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

# Execute the ADVISOR function at cluster level using run_cluster_query
# Execute the ADVISOR function at cluster level using run_sql_plus_plus_query
advisor_results = run_sql_plus_plus_query(
ctx, bucket_name, scope_name, advisor_query
)
Expand Down Expand Up @@ -86,3 +93,80 @@ def get_index_advisor_recommendations(
except Exception as e:
logger.error(f"Error running Index Advisor: {e!s}", exc_info=True)
raise


def list_indexes(
ctx: Context,
bucket_name: str | None = None,
scope_name: str | None = None,
collection_name: str | None = None,
index_name: str | None = None,
include_raw_index_stats: bool = False,
) -> list[dict[str, Any]]:
"""List all indexes in the cluster with optional filtering by bucket, scope, collection, and index name.
Returns a list of indexes with their names and CREATE INDEX definitions.
Uses the Index Service REST API (/getIndexStatus) to retrieve index information directly.

Args:
ctx: MCP context for cluster connection
bucket_name: Optional bucket name to filter indexes
scope_name: Optional scope name to filter indexes (requires bucket_name)
collection_name: Optional collection name to filter indexes (requires bucket_name and scope_name)
index_name: Optional index name to filter indexes (requires bucket_name, scope_name, and collection_name)
include_raw_index_stats: If True, include raw index stats (as-is from API) in addition
to cleaned-up version. Default is False.

Returns:
List of dictionaries with keys:
- name (str): Index name
- definition (str): Cleaned-up CREATE INDEX statement
- status (str): Current status of the index (e.g., "Ready", "Building", "Deferred")
- isPrimary (bool): Whether this is a primary index
- bucket (str): Bucket name where the index exists
- scope (str): Scope name where the index exists
- collection (str): Collection name where the index exists
- raw_index_stats (dict, optional): Complete raw index status object from API including metadata,
state, keyspace info, etc. (only if include_raw_index_stats=True)
"""
try:
# Validate parameters
validate_filter_params(bucket_name, scope_name, collection_name, index_name)

# Get and validate connection settings
settings = get_settings()
validate_connection_settings(settings)

# Fetch indexes from REST API
logger.info(
f"Fetching indexes from REST API for bucket={bucket_name}, "
f"scope={scope_name}, collection={collection_name}, index={index_name}"
)

raw_indexes = fetch_indexes_from_rest_api(
settings["connection_string"],
settings["username"],
settings["password"],
bucket_name=bucket_name,
scope_name=scope_name,
collection_name=collection_name,
index_name=index_name,
ca_cert_path=settings.get("ca_cert_path"),
)

# Process and format the results
indexes = [
processed
for idx in raw_indexes
if (processed := process_index_data(idx, include_raw_index_stats))
is not None
]

logger.info(
f"Found {len(indexes)} indexes from REST API "
f"(include_raw_index_stats={include_raw_index_stats})"
)
return indexes

except Exception as e:
logger.error(f"Error listing indexes: {e}", exc_info=True)
raise
7 changes: 7 additions & 0 deletions src/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
get_cluster_connection,
)

# Index utilities
from .index_utils import (
fetch_indexes_from_rest_api,
)

# Note: Individual modules create their own hierarchical loggers using:
# logger = logging.getLogger(f"{MCP_SERVER_NAME}.module.name")

Expand All @@ -46,6 +51,8 @@
# Context
"AppContext",
"get_cluster_connection",
# Index utilities
"fetch_indexes_from_rest_api",
# Constants
"MCP_SERVER_NAME",
"DEFAULT_READ_ONLY_MODE",
Expand Down
Loading