diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..6d29fef --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,52 @@ +name: Build and Publish Package to PyPI + +on: + push: + tags: + - "v[0-9].[0-9]+.[0-9]+*" + workflow_dispatch: + +jobs: + build-and-publish: + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + cache: "pip" + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + pip install hatch ruff + + - name: Lint with ruff + run: | + ruff check . + + - name: Install package + run: | + pip install -e . + + - name: Build package + run: | + hatch build + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + files: dist/* + generate_release_notes: true + draft: false + prerelease: false diff --git a/pyproject.toml b/pyproject.toml index ee2be60..6f02351 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,18 @@ [project] -name = "mcp-couchbase" -version = "0.1.0" +name = "mcp-server-couchbase" +version = "0.2.0-rc.0" description = "Couchbase MCP Server - The Developer Data Platform for Critical Applications in Our AI World" readme = "README.md" requires-python = ">=3.10" license = "Apache-2.0" +authors = [ + { name="Nithish Raghunandanan", email="devadvocates@couchbase.com" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python :: 3", + "Topic :: Database", +] dependencies = [ "click>=8.1.8", @@ -18,3 +26,16 @@ dependencies = [ Homepage = "https://github.com/Couchbase-Ecosystem/mcp-server-couchbase" Documentation = "https://github.com/Couchbase-Ecosystem/mcp-server-couchbase#readme" Issues = "https://github.com/Couchbase-Ecosystem/mcp-server-couchbase/issues" + +[project.scripts] +mcp-couchbase-server = "mcp_server_couchbase:main" + +[build-system] +requires = ["hatchling >= 1.26"] +build-backend = "hatchling.build" + +[tool.ruff.lint] +select = ["E", "F", "I", "T201"] + +[tool.hatch.build] +packages = ["src/mcp_server_couchbase"] \ No newline at end of file diff --git a/src/mcp_server_couchbase/__init__.py b/src/mcp_server_couchbase/__init__.py new file mode 100644 index 0000000..9f5ec7a --- /dev/null +++ b/src/mcp_server_couchbase/__init__.py @@ -0,0 +1,3 @@ +from server import main + +__all__ = ["main"] diff --git a/src/mcp_server.py b/src/mcp_server_couchbase/server.py similarity index 94% rename from src/mcp_server.py rename to src/mcp_server_couchbase/server.py index 52660f3..23c9594 100644 --- a/src/mcp_server.py +++ b/src/mcp_server_couchbase/server.py @@ -1,15 +1,15 @@ +import logging +from contextlib import asynccontextmanager +from dataclasses import dataclass from datetime import timedelta -from typing import Any -from mcp.server.fastmcp import FastMCP, Context -from couchbase.cluster import Cluster +from typing import Any, AsyncIterator + +import click from couchbase.auth import PasswordAuthenticator +from couchbase.cluster import Cluster from couchbase.options import ClusterOptions -import logging -from dataclasses import dataclass -from contextlib import asynccontextmanager -from typing import AsyncIterator from lark_sqlpp import modifies_data, modifies_structure, parse_sqlpp -import click +from mcp.server.fastmcp import Context, FastMCP MCP_SERVER_NAME = "couchbase" @@ -75,7 +75,10 @@ def get_settings() -> dict: envvar="READ_ONLY_QUERY_MODE", type=bool, default=True, - help="Enable read-only query mode. Set to True (default) to allow only read-only queries. Can be set to False to allow data modification queries.", + help=( + "Enable read-only query mode. Set to True (default) to allow only read-only " + "queries. Can be set to False to allow data modification queries." + ), ) @click.option( "--transport", @@ -166,7 +169,8 @@ async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]: @mcp.tool() def get_scopes_and_collections_in_bucket(ctx: Context) -> dict[str, list[str]]: """Get the names of all scopes and collections in the bucket. - Returns a dictionary with scope names as keys and lists of collection names as values. + Returns a dictionary with scope names as keys and lists of + collection names as values. """ bucket = ctx.request_context.lifespan_context.bucket try: @@ -187,7 +191,8 @@ def get_schema_for_collection( ctx: Context, scope_name: str, collection_name: str ) -> dict[str, Any]: """Get the schema for a collection in the specified scope. - Returns a dictionary with the schema returned by running INFER on the Couchbase collection. + Returns a dictionary with the schema returned by running INFER on the Couchbase + collection. """ try: query = f"INFER {collection_name}" @@ -264,7 +269,7 @@ def run_sql_plus_plus_query( scope = bucket.scope(scope_name) results = [] - # If read-only mode is enabled, check if the query is a data or structure modification query + # If read-only mode is enabled, check if the query modifies data or structure if read_only_query_mode: data_modification_query = modifies_data(parse_sqlpp(query)) structure_modification_query = modifies_structure(parse_sqlpp(query))