diff --git a/CHANGELOG.md b/CHANGELOG.md index e12ec2c..c557e12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +## 0.13.0 (TBD) + +- [#37](https://github.com/codeocean/codeocean-mcp-server/pull/37) feat: add public release capsule/pipeline API +- **Requires `codeocean>=0.17.0`.** + ## 0.12.2 (2026-09-03) - [#42](https://github.com/codeocean/codeocean-mcp-server/pull/42) fix: run synchronous tool bodies in a worker thread diff --git a/README.md b/README.md index ecdcbde..43949fa 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Model Context Protocol (MCP) server for Code Ocean. -This MCP server provides tools to search and run capsules and pipelines, and manage data assets. +This MCP server provides tools to search, run, and release capsules and pipelines, and manage data assets. ## Table of Contents diff --git a/pyproject.toml b/pyproject.toml index c3d1af5..32303e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codeocean-mcp-server" -version = "0.12.2" +version = "0.13.0" authors = [{ name = "Code Ocean", email = "dev@codeocean.com" }] description = "Code Ocean MCP Server" readme = "README.md" @@ -12,7 +12,7 @@ classifiers = [ ] dependencies = [ "anyio>=4.5", - "codeocean>=0.14.0,<0.15.0", + "codeocean>=0.17.0,<0.18.0", "mcp>=1.23.0,<1.24.0", ] license = "MIT" diff --git a/src/codeocean_mcp_server/server.py b/src/codeocean_mcp_server/server.py index f12358d..f12ce1f 100644 --- a/src/codeocean_mcp_server/server.py +++ b/src/codeocean_mcp_server/server.py @@ -15,6 +15,7 @@ computations, custom_metadata, data_assets, + release, ) @@ -94,6 +95,7 @@ def main(): data_assets.add_tools(mcp, client) computations.add_tools(mcp, client) custom_metadata.add_tools(mcp, client) + release.add_tools(mcp, client) mcp.run(transport=args.transport) diff --git a/src/codeocean_mcp_server/tools/release.py b/src/codeocean_mcp_server/tools/release.py new file mode 100644 index 0000000..b1912ec --- /dev/null +++ b/src/codeocean_mcp_server/tools/release.py @@ -0,0 +1,39 @@ +from codeocean import CodeOcean +from codeocean.capsule import CapsuleReleaseJob +from mcp.server.fastmcp import FastMCP + + +def add_tools(mcp: FastMCP, client: CodeOcean): + """Add capsule and pipeline release tools to the MCP server.""" + + @mcp.tool(description=str(client.capsules.release_capsule.__doc__)) + def release_capsule(capsule_id: str) -> CapsuleReleaseJob: + """Start releasing a new version of an already-released capsule.""" + return client.capsules.release_capsule(capsule_id) + + @mcp.tool(description=str(client.pipelines.release_pipeline.__doc__)) + def release_pipeline(pipeline_id: str) -> CapsuleReleaseJob: + """Start releasing a new version of an already-released pipeline.""" + return client.pipelines.release_pipeline(pipeline_id) + + @mcp.tool(description=str(client.capsules.get_release_job.__doc__)) + def get_capsule_release_job(capsule_id: str, job_id: str) -> CapsuleReleaseJob: + """Get the status of a capsule release job.""" + return client.capsules.get_release_job(capsule_id, job_id) + + @mcp.tool(description=str(client.pipelines.get_release_job.__doc__)) + def get_pipeline_release_job(pipeline_id: str, job_id: str) -> CapsuleReleaseJob: + """Get the status of a pipeline release job.""" + return client.pipelines.get_release_job(pipeline_id, job_id) + + @mcp.tool(description=str(client.capsules.wait_until_release_completed.__doc__)) + def wait_until_capsule_release_completed(capsule_id: str, job_id: str) -> CapsuleReleaseJob: + """Wait until a capsule release job reaches a terminal state and return it.""" + job = client.capsules.get_release_job(capsule_id, job_id) + return client.capsules.wait_until_release_completed(capsule_id, job) + + @mcp.tool(description=str(client.pipelines.wait_until_release_completed.__doc__)) + def wait_until_pipeline_release_completed(pipeline_id: str, job_id: str) -> CapsuleReleaseJob: + """Wait until a pipeline release job reaches a terminal state and return it.""" + job = client.pipelines.get_release_job(pipeline_id, job_id) + return client.pipelines.wait_until_release_completed(pipeline_id, job) diff --git a/uv.lock b/uv.lock index d9b639b..b2c4374 100644 --- a/uv.lock +++ b/uv.lock @@ -543,7 +543,7 @@ wheels = [ [[package]] name = "codeocean" -version = "0.14.0" +version = "0.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backports-strenum", marker = "python_full_version < '3.11'" }, @@ -551,14 +551,14 @@ dependencies = [ { name = "requests" }, { name = "requests-toolbelt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/de/5d0ed25123b5e903eec6325249c23d3ae76d1ad5aceff000c9544e242700/codeocean-0.14.0.tar.gz", hash = "sha256:90d2a58a9c8d05edc53becc0b63773c5b8cd44423de19afa3b0ce1d82ebfadc6", size = 21461, upload-time = "2026-01-29T23:11:34.016Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/4b/ff631fe159251109d60623d0403a52fbc46e01e4ffd75b26f9ff833eee4d/codeocean-0.17.0.tar.gz", hash = "sha256:f3200f23df23c5b26b08e4377899ac8e9b7c3c67820207d93a9be5fb989191db", size = 25601, upload-time = "2026-09-09T17:15:45.646Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/d1/ef0027ce1a17eedd8c671495d780fffa0590d017061337e4ff86a0a0054e/codeocean-0.14.0-py3-none-any.whl", hash = "sha256:33ea9cde80df0c7d986e493a88edbe00f13b5d0cf92cfaed8a518f7314dbf4e1", size = 25201, upload-time = "2026-01-29T23:11:36.219Z" }, + { url = "https://files.pythonhosted.org/packages/5a/20/9c536f60b6c2b49094e23f14e300a739ebf6c19f02eec698e72f9b11045d/codeocean-0.17.0-py3-none-any.whl", hash = "sha256:bc636846188ed5f5f5da9510053c773bba0cc9f0e12af6c6523b9ae17eb956d7", size = 27958, upload-time = "2026-09-09T17:15:46.531Z" }, ] [[package]] name = "codeocean-mcp-server" -version = "0.12.2" +version = "0.13.0" source = { editable = "." } dependencies = [ { name = "anyio" }, @@ -579,7 +579,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "anyio", specifier = ">=4.5" }, - { name = "codeocean", specifier = ">=0.14.0,<0.15.0" }, + { name = "codeocean", specifier = ">=0.17.0,<0.18.0" }, { name = "mcp", specifier = ">=1.23.0,<1.24.0" }, ] @@ -715,7 +715,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -979,7 +979,7 @@ resolution-markers = [ "python_full_version >= '3.14'", ] dependencies = [ - { name = "typing-extensions", marker = "python_full_version >= '3.14'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182, upload-time = "2025-10-21T16:23:12.106Z" } wheels = [