-
Notifications
You must be signed in to change notification settings - Fork 16
Adding mTLS support #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…base cluster, including their bucket settings.
… must be named client.pem and client.key and reside in provided client_cert_path directory. Either client cert or user/pass to be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @Eyal-CB, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request significantly enhances the security and flexibility of the Couchbase MCP server by introducing support for mTLS authentication. It allows users to establish secure, certificate-based connections to their Couchbase clusters, alongside the existing basic authentication. The changes also refine the server's operational tools by making bucket specification explicit and adding new capabilities for cluster introspection and N1QL query optimization.
Highlights
- mTLS Authentication Support: I've added support for mTLS (mutual TLS) authentication, allowing the MCP server to connect to Couchbase clusters using client certificates and a CA certificate for trusted connections. This provides an alternative to traditional username/password basic authentication.
- Flexible Configuration: The server now accepts
CLIENT_CERT_PATH
(forclient.key
andclient.pem
) andCA_CERT_PATH
environment variables for mTLS setup. TheREADME.md
has been updated with clear configuration examples for both mTLS and basic authentication. - Authentication Method Validation: A new validation mechanism ensures that mTLS and basic authentication (username/password) are mutually exclusive. An error is thrown if both are attempted, preventing ambiguous configurations.
- Enhanced Tooling Parameters: All existing data-access tools (
get_schema_for_collection
,get_document_by_id
,upsert_document_by_id
,delete_document_by_id
,run_sql_plus_plus_query
) have been updated to explicitly accept abucket_name
parameter, improving clarity and control over operations. - New Query and Management Tools: Two new tools have been introduced:
get_list_of_buckets_with_settings
to retrieve information about all buckets in the cluster, andadvise_index_for_sql_plus_plus_query
to get N1QL index recommendations for a given query.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command>
or @gemini-code-assist <command>
. Below is a summary of the supported commands.
Feature | Command | Description |
---|---|---|
Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/
folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request adds mTLS support for connecting to Couchbase, which is a great feature. The changes are comprehensive, covering configuration, validation, and updates to the server tools.
My review focuses on improving correctness, robustness, and maintainability. Key areas for improvement include:
- Fixing a bug where user credentials with whitespace would fail authentication.
- Using platform-independent methods for constructing file paths.
- Improving exception handling to provide better debugging information by not swallowing stack traces.
- Correcting type hints and simplifying some logic for better code quality.
- Addressing some code duplication that has been introduced.
- Minor fixes in the README documentation for clarity and correctness.
Overall, the changes are in good shape, and addressing these points will make the implementation more robust and easier to maintain.
src/mcp_server.py
Outdated
if username is not None: | ||
username = username.strip() | ||
if password is not None: | ||
password = password.strip() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The username
and password
variables are stripped, but the changes are only local to this function. The original, unstripped values from the params
dictionary will be used for authentication in app_lifespan
, which could cause authentication to fail if the user provides credentials with leading or trailing whitespace.
To fix this, you should update the params
dictionary with the stripped values.
if username is not None: | |
username = username.strip() | |
if password is not None: | |
password = password.strip() | |
if username is not None: | |
username = username.strip() | |
params["username"] = username | |
if password is not None: | |
password = password.strip() | |
params["password"] = password |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Used only for validation that username and password are not empty strings, does not need to be saved globally.
src/mcp_server.py
Outdated
client_cert_path = client_cert_path[:-1] if client_cert_path.endswith('/') else client_cert_path | ||
tls_conf = { | ||
"cert_path" : f'{client_cert_path}/client.pem', | ||
"key_path" : f'{client_cert_path}/client.key', | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using f-strings to construct file paths is not platform-independent and can lead to issues on operating systems that use different path separators (e.g., Windows). You should use os.path.join()
for robust path construction.
tls_conf = {
"cert_path": os.path.join(client_cert_path, 'client.pem'),
"key_path": os.path.join(client_cert_path, 'client.key'),
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
src/mcp_server.py
Outdated
return result | ||
except Exception as e: | ||
logger.error(f"Error getting bucket names: {e}") | ||
raise e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
src/mcp_server.py
Outdated
try: | ||
bucket = cluster.bucket(bucket_name) | ||
except Exception as e: | ||
logger.error(f"Error accessing bucket: {e}") | ||
raise ValueError("Tool does not have access to bucket, or bucket does not exist.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This try...except
block for fetching a bucket instance is duplicated in several tools (get_document_by_id
, upsert_document_by_id
, etc.). This has a couple of implications:
- Maintainability: Duplicated code is harder to maintain. Consider refactoring this logic into a private helper function (e.g.,
_get_bucket(cluster, bucket_name)
) that can be called from each tool. - Exception Handling: Raising a new
ValueError
hides the original exception details. To preserve the original stack trace for easier debugging, you should chain the exceptions usingfrom e
, like this:raise ValueError(...) from e
.
src/mcp_server.py
Outdated
query = f"ADVISE {query}" | ||
result = run_sql_plus_plus_query(ctx, bucket_name, scope_name, query) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reassigning the query
parameter makes debugging more difficult. If an error occurs later, the error message will contain the modified query (ADVISE ...
) instead of the original user-provided query. It's better to use a new variable for the modified query.
advise_query = f"ADVISE {query}"
result = run_sql_plus_plus_query(ctx, bucket_name, scope_name, advise_query)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in parallel PR from another branch. Will leave to avoid conflict.
src/mcp_server.py
Outdated
return response | ||
except Exception as e: | ||
logger.error(f"Error running Advise on query: {e}") | ||
raise ValueError(f"Unable to run ADVISE on: {query} for keyspace {bucket_name}.{scope_name}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Raising a new ValueError
here hides the original exception's details. To provide better context for debugging, you should chain the original exception using from e
. This preserves the full stack trace.
raise ValueError(f"Unable to run ADVISE on: {query} for keyspace {bucket_name}.{scope_name}") from e
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
src/mcp_server.py
Outdated
except Exception as e: | ||
logger.error(f"Failed to validate auth method params: {e}") | ||
raise e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Catching a generic Exception
is too broad here. The validate_authentication_method
function is documented to raise click.BadParameter
on validation failure. Catching this specific exception makes the error handling more precise and avoids accidentally catching and masking other unexpected errors.
except Exception as e: | |
logger.error(f"Failed to validate auth method params: {e}") | |
raise e | |
except click.BadParameter as e: | |
logger.error(f"Failed to validate auth method params: {e}") | |
raise e |
README.md
Outdated
- `CB_USERNAME`: The username with access to the bucket to use to connect. Must be set if using Baisc Auth and unset if using mTLS. | ||
- `CB_PASSWORD`: The password for the username to connect. Must be set if using Baisc Auth and unset if using mTLS with client certificate. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a typo here. "Baisc Auth" should be "Basic Auth".
- `CB_USERNAME`: The username with access to the bucket to use to connect. Must be set if using Baisc Auth and unset if using mTLS. | |
- `CB_PASSWORD`: The password for the username to connect. Must be set if using Baisc Auth and unset if using mTLS with client certificate. | |
- `CB_USERNAME`: The username with access to the bucket to use to connect. Must be set if using Basic Auth and unset if using mTLS. | |
- `CB_PASSWORD`: The password for the username to connect. Must be set if using Basic Auth and unset if using mTLS with client certificate. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
…atination and changed raise exceptions to contain full trace.
…nd aligned pull request code to new structure
@nithishr I have updated this PR to align with the new code structure |
Adding mTLS support by accepting path to CA.crt file and path to client key and certificate. Client key and certificate must exist in the given path and be named client.key and client.pem respectively. If no CA.crt file is given, locally trusted CA is used.
mTLS and Basic Auth with username/password are mutually exclusive and handled as such (error is thrown if both are attempted - there is no dominant config).
mTLS config: