codeocean-mcp-server 0.9.0__tar.gz → 0.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/CHANGELOG.md +16 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/PKG-INFO +17 -3
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/README.md +14 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/pyproject.toml +2 -2
- codeocean_mcp_server-0.12.0/src/codeocean_mcp_server/client.py +54 -0
- codeocean_mcp_server-0.12.0/src/codeocean_mcp_server/server.py +70 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/src/codeocean_mcp_server/tools/capsules.py +24 -17
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/src/codeocean_mcp_server/tools/computations.py +6 -1
- codeocean_mcp_server-0.12.0/tests/test_http_transport.py +140 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/tests/test_tool_calling.py +10 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/uv.lock +67 -5
- codeocean_mcp_server-0.9.0/src/codeocean_mcp_server/server.py +0 -41
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/.gitignore +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/.python-version +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/LICENSE +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/RELEASE.md +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/src/codeocean_mcp_server/__init__.py +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/src/codeocean_mcp_server/file_utils.py +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/src/codeocean_mcp_server/logging_config.py +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/src/codeocean_mcp_server/models.py +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/src/codeocean_mcp_server/search.py +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/src/codeocean_mcp_server/tools/__init__.py +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/src/codeocean_mcp_server/tools/custom_metadata.py +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/src/codeocean_mcp_server/tools/data_assets.py +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/tests/bedrock_call.py +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/tests/bedrock_tools_converter.py +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/tests/conftest.py +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/tests/mcp_client.py +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/tests/test_multi_tool_calling.py +0 -0
- {codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/tests/test_search.py +0 -0
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
CHANGELOG
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
## 0.12.0 (2026-08-27)
|
|
5
|
+
|
|
6
|
+
- [#38](https://github.com/codeocean/codeocean-mcp-server/pull/38) feat: serve over streamable HTTP with per-request credentials
|
|
7
|
+
|
|
8
|
+
## 0.11.0 (2026-03-09)
|
|
9
|
+
|
|
10
|
+
- [#35](https://github.com/codeocean/codeocean-mcp-server/pull/35) feat: add `delete_computation` tool to stop and delete computations
|
|
11
|
+
|
|
12
|
+
## 0.10.0 (2026-02-24)
|
|
13
|
+
|
|
14
|
+
- [#33](https://github.com/codeocean/codeocean-mcp-server/pull/33) feat: upgrade mcp package
|
|
15
|
+
|
|
16
|
+
## 0.9.1 (2026-02-04)
|
|
17
|
+
|
|
18
|
+
- [#31](https://github.com/codeocean/codeocean-mcp-server/pull/31) feat: clarify data asset attach tool usage
|
|
19
|
+
|
|
4
20
|
## 0.9.0 (2026-01-30)
|
|
5
21
|
|
|
6
22
|
- [#29](https://github.com/codeocean/codeocean-mcp-server/pull/29) feat: upgrade codeocean SDK to v0.14.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: codeocean-mcp-server
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: Code Ocean MCP Server
|
|
5
5
|
Project-URL: Homepage, https://github.com/codeocean/codeocean-mcp-server
|
|
6
6
|
Project-URL: Issues, https://github.com/codeocean/codeocean-mcp-server/issues
|
|
@@ -13,7 +13,7 @@ Classifier: Operating System :: OS Independent
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Requires-Python: >=3.10
|
|
15
15
|
Requires-Dist: codeocean<0.15.0,>=0.14.0
|
|
16
|
-
Requires-Dist: mcp<1.
|
|
16
|
+
Requires-Dist: mcp<1.24.0,>=1.23.0
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
|
|
19
19
|
# Code Ocean MCP Server
|
|
@@ -33,6 +33,7 @@ This MCP server provides tools to search and run capsules and pipelines, and man
|
|
|
33
33
|
- [Roo Code](#roo-code)
|
|
34
34
|
- [Cursor](#cursor)
|
|
35
35
|
- [Windsurf](#windsurf)
|
|
36
|
+
- [Streamable HTTP Transport](#streamable-http-transport)
|
|
36
37
|
- [Local Testing](#local-testing)
|
|
37
38
|
|
|
38
39
|
## Prerequisites
|
|
@@ -215,6 +216,19 @@ Windsurf (Cascade) uses mcp_config.json under ~/.codeium/windsurf/ (or via the C
|
|
|
215
216
|
|
|
216
217
|
3. Save and restart Windsurf (or hit “Refresh” in the MCP panel).
|
|
217
218
|
|
|
219
|
+
## Streamable HTTP Transport
|
|
220
|
+
|
|
221
|
+
By default the server runs over stdio and authenticates with the `CODEOCEAN_TOKEN` environment variable, as
|
|
222
|
+
described above. It can also serve multiple users from a single process over streamable HTTP, taking each
|
|
223
|
+
caller's API token from the request instead:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
CODEOCEAN_DOMAIN=https://acmecorp.codeocean.com codeocean-mcp-server --transport streamable-http --host 127.0.0.1 --port 8000
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Clients then pass their own token as `Authorization: Bearer <YOUR_API_KEY>` on every request; `CODEOCEAN_TOKEN`
|
|
230
|
+
is not used, and a request without a token is refused. The endpoint is `http://<host>:<port>/mcp`.
|
|
231
|
+
|
|
218
232
|
## Local Testing
|
|
219
233
|
|
|
220
234
|
You can test the MCP server locally during development with [MCP Inspector](https://modelcontextprotocol.io/legacy/tools/inspector):
|
|
@@ -15,6 +15,7 @@ This MCP server provides tools to search and run capsules and pipelines, and man
|
|
|
15
15
|
- [Roo Code](#roo-code)
|
|
16
16
|
- [Cursor](#cursor)
|
|
17
17
|
- [Windsurf](#windsurf)
|
|
18
|
+
- [Streamable HTTP Transport](#streamable-http-transport)
|
|
18
19
|
- [Local Testing](#local-testing)
|
|
19
20
|
|
|
20
21
|
## Prerequisites
|
|
@@ -197,6 +198,19 @@ Windsurf (Cascade) uses mcp_config.json under ~/.codeium/windsurf/ (or via the C
|
|
|
197
198
|
|
|
198
199
|
3. Save and restart Windsurf (or hit “Refresh” in the MCP panel).
|
|
199
200
|
|
|
201
|
+
## Streamable HTTP Transport
|
|
202
|
+
|
|
203
|
+
By default the server runs over stdio and authenticates with the `CODEOCEAN_TOKEN` environment variable, as
|
|
204
|
+
described above. It can also serve multiple users from a single process over streamable HTTP, taking each
|
|
205
|
+
caller's API token from the request instead:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
CODEOCEAN_DOMAIN=https://acmecorp.codeocean.com codeocean-mcp-server --transport streamable-http --host 127.0.0.1 --port 8000
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Clients then pass their own token as `Authorization: Bearer <YOUR_API_KEY>` on every request; `CODEOCEAN_TOKEN`
|
|
212
|
+
is not used, and a request without a token is refused. The endpoint is `http://<host>:<port>/mcp`.
|
|
213
|
+
|
|
200
214
|
## Local Testing
|
|
201
215
|
|
|
202
216
|
You can test the MCP server locally during development with [MCP Inspector](https://modelcontextprotocol.io/legacy/tools/inspector):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codeocean-mcp-server"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.12.0"
|
|
4
4
|
authors = [{ name = "Code Ocean", email = "dev@codeocean.com" }]
|
|
5
5
|
description = "Code Ocean MCP Server"
|
|
6
6
|
readme = "README.md"
|
|
@@ -12,7 +12,7 @@ classifiers = [
|
|
|
12
12
|
]
|
|
13
13
|
dependencies = [
|
|
14
14
|
"codeocean>=0.14.0,<0.15.0",
|
|
15
|
-
"mcp>=1.
|
|
15
|
+
"mcp>=1.23.0,<1.24.0",
|
|
16
16
|
]
|
|
17
17
|
license = "MIT"
|
|
18
18
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Per-request Code Ocean client resolution for the streamable-HTTP transport."""
|
|
2
|
+
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
|
|
5
|
+
from codeocean import CodeOcean
|
|
6
|
+
from mcp.server.fastmcp import FastMCP
|
|
7
|
+
from starlette.requests import Request
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@lru_cache(maxsize=32)
|
|
11
|
+
def _cached_client(domain: str, token: str, agent_id: str | None) -> CodeOcean:
|
|
12
|
+
"""Return a client for the given credentials, reusing its HTTP connection pool across requests."""
|
|
13
|
+
return CodeOcean(domain=domain, token=token, agent_id=agent_id)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _bearer_token(request: Request) -> str | None:
|
|
17
|
+
"""Return the token from an 'Authorization: Bearer <token>' header, or None for any other scheme."""
|
|
18
|
+
scheme, _, token = request.headers.get("authorization", "").partition(" ")
|
|
19
|
+
if scheme.lower() != "bearer":
|
|
20
|
+
return None
|
|
21
|
+
return token.strip() or None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RequestScopedClient:
|
|
25
|
+
"""A `CodeOcean` stand-in that resolves to the calling request's own client on every attribute access.
|
|
26
|
+
|
|
27
|
+
Tools reach the SDK as `client.<sub_client>.<method>(...)`, evaluated when the tool runs, so wrapping
|
|
28
|
+
the client is enough to give each request its own credentials without touching the tools themselves.
|
|
29
|
+
|
|
30
|
+
Outside a request — tool registration, where descriptions are read from SDK docstrings — attribute
|
|
31
|
+
access resolves to a client built from the environment's token, which issues no network call.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, mcp: FastMCP, domain: str, token: str | None, agent_id: str | None):
|
|
35
|
+
"""Wrap the credentials that are fixed for the process, around the token that varies per request."""
|
|
36
|
+
self._mcp = mcp
|
|
37
|
+
self._domain = domain
|
|
38
|
+
self._token = token
|
|
39
|
+
self._agent_id = agent_id
|
|
40
|
+
|
|
41
|
+
def __getattr__(self, name: str):
|
|
42
|
+
"""Delegate to the calling request's client."""
|
|
43
|
+
return getattr(self._resolve(), name)
|
|
44
|
+
|
|
45
|
+
def _resolve(self) -> CodeOcean:
|
|
46
|
+
try:
|
|
47
|
+
request = self._mcp.get_context().request_context.request
|
|
48
|
+
except ValueError:
|
|
49
|
+
# Registration happens outside any request, so it needs no credential of its own.
|
|
50
|
+
return _cached_client(self._domain, self._token or "", self._agent_id)
|
|
51
|
+
token = _bearer_token(request) if request is not None else None
|
|
52
|
+
if not token:
|
|
53
|
+
raise ValueError("Missing Code Ocean API token: send it as an 'Authorization: Bearer <token>' header.")
|
|
54
|
+
return _cached_client(self._domain, token, self._agent_id)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from codeocean import CodeOcean
|
|
5
|
+
from mcp.server.fastmcp import FastMCP
|
|
6
|
+
|
|
7
|
+
from codeocean_mcp_server.client import RequestScopedClient
|
|
8
|
+
from codeocean_mcp_server.logging_config import configure_logging
|
|
9
|
+
from codeocean_mcp_server.tools import (
|
|
10
|
+
capsules,
|
|
11
|
+
computations,
|
|
12
|
+
custom_metadata,
|
|
13
|
+
data_assets,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
18
|
+
"""Parse command line arguments."""
|
|
19
|
+
parser = argparse.ArgumentParser(prog="codeocean-mcp-server", description="Code Ocean MCP Server")
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"--transport",
|
|
22
|
+
choices=["stdio", "streamable-http"],
|
|
23
|
+
default="stdio",
|
|
24
|
+
help="Transport to serve on. Over streamable-HTTP the API token is taken per request from the "
|
|
25
|
+
"'Authorization: Bearer <token>' header instead of from CODEOCEAN_TOKEN.",
|
|
26
|
+
)
|
|
27
|
+
parser.add_argument("--host", default="127.0.0.1", help="Host to bind to when serving over streamable-HTTP.")
|
|
28
|
+
parser.add_argument("--port", type=int, default=8000, help="Port to bind to when serving over streamable-HTTP.")
|
|
29
|
+
return parser.parse_args(argv)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main():
|
|
33
|
+
"""Run the MCP server."""
|
|
34
|
+
configure_logging()
|
|
35
|
+
args = parse_args()
|
|
36
|
+
stdio = args.transport == "stdio"
|
|
37
|
+
domain = os.getenv("CODEOCEAN_DOMAIN")
|
|
38
|
+
token = os.getenv("CODEOCEAN_TOKEN")
|
|
39
|
+
if not domain:
|
|
40
|
+
raise ValueError("Environment variable CODEOCEAN_DOMAIN must be set.")
|
|
41
|
+
agent_id = os.getenv("AGENT_ID", "AI Agent")
|
|
42
|
+
|
|
43
|
+
mcp = FastMCP(
|
|
44
|
+
name="Code Ocean",
|
|
45
|
+
instructions=(
|
|
46
|
+
f"MCP server for Code Ocean: search & run capsules, pipelines, and assets using Code Ocean domain {domain}."
|
|
47
|
+
),
|
|
48
|
+
host=args.host,
|
|
49
|
+
port=args.port,
|
|
50
|
+
stateless_http=True,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if stdio:
|
|
54
|
+
# Over stdio the process serves a single user, so the environment's client is used directly, as before.
|
|
55
|
+
if not token:
|
|
56
|
+
raise ValueError("Environment variable CODEOCEAN_TOKEN must be set when serving over stdio.")
|
|
57
|
+
client = CodeOcean(domain=domain, token=token, agent_id=agent_id)
|
|
58
|
+
else:
|
|
59
|
+
client = RequestScopedClient(mcp, domain, token, agent_id)
|
|
60
|
+
|
|
61
|
+
capsules.add_tools(mcp, client)
|
|
62
|
+
data_assets.add_tools(mcp, client)
|
|
63
|
+
computations.add_tools(mcp, client)
|
|
64
|
+
custom_metadata.add_tools(mcp, client)
|
|
65
|
+
|
|
66
|
+
mcp.run(transport=args.transport)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
main()
|
|
@@ -40,11 +40,27 @@ def add_tools(mcp: FastMCP, client: CodeOcean):
|
|
|
40
40
|
results = client.pipelines.search_pipelines(params)
|
|
41
41
|
return CapsuleSearchResults.from_sdk_results(results, include_field_names)
|
|
42
42
|
|
|
43
|
+
@mcp.tool(
|
|
44
|
+
description=(
|
|
45
|
+
str(client.capsules.get_capsule.__doc__) + "Use only to fetch metadata for a known capsule ID. "
|
|
46
|
+
"Do not use for searching."
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
def get_capsule(capsule_id: str) -> Capsule:
|
|
50
|
+
"""Retrieve a capsule by its ID."""
|
|
51
|
+
return client.capsules.get_capsule(capsule_id)
|
|
52
|
+
|
|
53
|
+
@mcp.tool(description=client.capsules.list_computations.__doc__)
|
|
54
|
+
def list_computations(capsule_id: str) -> list[Computation]:
|
|
55
|
+
"""List all computations for a capsule."""
|
|
56
|
+
return client.capsules.list_computations(capsule_id)
|
|
57
|
+
|
|
43
58
|
@mcp.tool(
|
|
44
59
|
description=(
|
|
45
60
|
str(client.capsules.attach_data_assets.__doc__)
|
|
46
|
-
+ "
|
|
47
|
-
"
|
|
61
|
+
+ " Use when the capsule has no running cloud workstations. For active cloud workstation sessions"
|
|
62
|
+
" use attach_computation_data_assets instead."
|
|
63
|
+
" Accepts a list of parameter objects (e.g. [{'id': '...'}]), not just a list of IDs."
|
|
48
64
|
)
|
|
49
65
|
)
|
|
50
66
|
def attach_data_assets(
|
|
@@ -57,25 +73,16 @@ def add_tools(mcp: FastMCP, client: CodeOcean):
|
|
|
57
73
|
|
|
58
74
|
@mcp.tool(
|
|
59
75
|
description=(
|
|
60
|
-
str(client.capsules.
|
|
61
|
-
"
|
|
76
|
+
str(client.capsules.detach_data_assets.__doc__)
|
|
77
|
+
+ " Use when the capsule has no running cloud workstations. For active cloud workstation sessions"
|
|
78
|
+
" use detach_computation_data_assets instead."
|
|
62
79
|
)
|
|
63
80
|
)
|
|
64
|
-
def
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
@mcp.tool(description=client.capsules.list_computations.__doc__)
|
|
69
|
-
def list_computations(capsule_id: str) -> list[Computation]:
|
|
70
|
-
"""List all computations for a capsule."""
|
|
71
|
-
return client.capsules.list_computations(capsule_id)
|
|
81
|
+
def detach_data_assets(capsule_id: str, data_assets: list[str]) -> None:
|
|
82
|
+
"""Remove attached data assets from a capsule."""
|
|
83
|
+
client.capsules.detach_data_assets(capsule_id, data_assets)
|
|
72
84
|
|
|
73
85
|
@mcp.tool(description=client.capsules.get_capsule_app_panel.__doc__)
|
|
74
86
|
def get_capsule_app_panel(capsule_id: str, version: int | None = None) -> AppPanelModel:
|
|
75
87
|
"""Retrieve the app panel for a capsule, optionally for a specific version."""
|
|
76
88
|
return client.capsules.get_capsule_app_panel(capsule_id, version)
|
|
77
|
-
|
|
78
|
-
@mcp.tool(description=client.capsules.detach_data_assets.__doc__)
|
|
79
|
-
def detach_data_assets(capsule_id: str, data_assets: list[str]) -> None:
|
|
80
|
-
"""Remove attached data assets from a capsule."""
|
|
81
|
-
client.capsules.detach_data_assets(capsule_id, data_assets)
|
|
@@ -15,7 +15,7 @@ RunParamsModel = dataclass_to_pydantic(RunParams)
|
|
|
15
15
|
DataAssetAttachParamsModel = dataclass_to_pydantic(DataAssetAttachParams)
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def add_tools(mcp: FastMCP, client: CodeOcean):
|
|
18
|
+
def add_tools(mcp: FastMCP, client: CodeOcean): # noqa: C901
|
|
19
19
|
"""Add capsule tools to the MCP server."""
|
|
20
20
|
|
|
21
21
|
@mcp.tool(description=client.computations.get_computation.__doc__)
|
|
@@ -67,6 +67,11 @@ def add_tools(mcp: FastMCP, client: CodeOcean):
|
|
|
67
67
|
"""Rename an existing computation."""
|
|
68
68
|
client.computations.rename_computation(computation_id, name)
|
|
69
69
|
|
|
70
|
+
@mcp.tool(description=client.computations.delete_computation.__doc__)
|
|
71
|
+
def delete_computation(computation_id: str) -> None:
|
|
72
|
+
"""Delete a computation and stop it if currently running."""
|
|
73
|
+
client.computations.delete_computation(computation_id)
|
|
74
|
+
|
|
70
75
|
@mcp.tool(
|
|
71
76
|
description=(
|
|
72
77
|
str(client.computations.attach_data_assets.__doc__)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""End-to-end tests for the streamable-HTTP transport and its per-request credentials.
|
|
2
|
+
|
|
3
|
+
A stub Code Ocean API echoes back the token it was called with, so each assertion follows a token
|
|
4
|
+
from the MCP request header all the way to the outgoing API call.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import base64
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import socket
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import threading
|
|
15
|
+
import time
|
|
16
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
import pytest
|
|
20
|
+
from mcp import ClientSession, StdioServerParameters
|
|
21
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
22
|
+
from mcp_client import get_tools
|
|
23
|
+
|
|
24
|
+
SERVER_SCRIPT_PATH = str(Path(__file__).parent.parent / "src" / "codeocean_mcp_server" / "server.py")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _EchoTokenHandler(BaseHTTPRequestHandler):
|
|
28
|
+
"""Answer the custom metadata endpoint with the basic-auth user, which is the API token."""
|
|
29
|
+
|
|
30
|
+
def do_GET(self): # noqa: D102, N802
|
|
31
|
+
user = base64.b64decode(self.headers["Authorization"].split(" ")[1]).decode().split(":")[0]
|
|
32
|
+
body = json.dumps({"categories": [user]}).encode()
|
|
33
|
+
self.send_response(200)
|
|
34
|
+
self.send_header("Content-Type", "application/json")
|
|
35
|
+
self.send_header("Content-Length", str(len(body)))
|
|
36
|
+
self.end_headers()
|
|
37
|
+
self.wfile.write(body)
|
|
38
|
+
|
|
39
|
+
def log_message(self, *args): # noqa: D102
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _free_port() -> int:
|
|
44
|
+
with socket.socket() as sock:
|
|
45
|
+
sock.bind(("127.0.0.1", 0))
|
|
46
|
+
return sock.getsockname()[1]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _wait_until_listening(port: int, timeout: float = 30.0):
|
|
50
|
+
deadline = time.monotonic() + timeout
|
|
51
|
+
while time.monotonic() < deadline:
|
|
52
|
+
with socket.socket() as sock:
|
|
53
|
+
if sock.connect_ex(("127.0.0.1", port)) == 0:
|
|
54
|
+
return
|
|
55
|
+
time.sleep(0.1)
|
|
56
|
+
raise TimeoutError(f"MCP server did not start listening on port {port}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.fixture(scope="module")
|
|
60
|
+
def http_server():
|
|
61
|
+
"""Serve the MCP server over streamable-HTTP against a stub Code Ocean API, and yield URL and domain."""
|
|
62
|
+
api = ThreadingHTTPServer(("127.0.0.1", 0), _EchoTokenHandler)
|
|
63
|
+
threading.Thread(target=api.serve_forever, daemon=True).start()
|
|
64
|
+
|
|
65
|
+
port = _free_port()
|
|
66
|
+
env = {**os.environ, "CODEOCEAN_DOMAIN": f"http://127.0.0.1:{api.server_address[1]}"}
|
|
67
|
+
env.pop("CODEOCEAN_TOKEN", None)
|
|
68
|
+
process = subprocess.Popen(
|
|
69
|
+
[sys.executable, SERVER_SCRIPT_PATH, "--transport", "streamable-http", "--port", str(port)],
|
|
70
|
+
env=env,
|
|
71
|
+
)
|
|
72
|
+
try:
|
|
73
|
+
_wait_until_listening(port)
|
|
74
|
+
yield f"http://127.0.0.1:{port}/mcp", env["CODEOCEAN_DOMAIN"]
|
|
75
|
+
finally:
|
|
76
|
+
process.terminate()
|
|
77
|
+
process.wait(timeout=30)
|
|
78
|
+
api.shutdown()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _stdio_server(domain: str) -> StdioServerParameters:
|
|
82
|
+
"""Describe the same server run over stdio, against the same Code Ocean domain."""
|
|
83
|
+
return StdioServerParameters(
|
|
84
|
+
command=sys.executable,
|
|
85
|
+
args=[SERVER_SCRIPT_PATH],
|
|
86
|
+
env={"CODEOCEAN_DOMAIN": domain, "CODEOCEAN_TOKEN": "token"},
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def _call_get_custom_metadata(url: str, token: str | None, scheme: str = "Bearer"):
|
|
91
|
+
headers = {"Authorization": f"{scheme} {token}"} if token else None
|
|
92
|
+
async with streamablehttp_client(url, headers=headers) as (read_stream, write_stream, _):
|
|
93
|
+
async with ClientSession(read_stream, write_stream) as session:
|
|
94
|
+
await session.initialize()
|
|
95
|
+
return await session.call_tool("get_custom_metadata", {})
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_concurrent_requests_use_their_own_token(http_server):
|
|
99
|
+
"""Each request acts with the token from its own authorization header."""
|
|
100
|
+
url, _ = http_server
|
|
101
|
+
|
|
102
|
+
async def both():
|
|
103
|
+
return await asyncio.gather(
|
|
104
|
+
_call_get_custom_metadata(url, "alice-token"),
|
|
105
|
+
_call_get_custom_metadata(url, "bob-token"),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
alice, bob = asyncio.run(both())
|
|
109
|
+
assert alice.structuredContent["categories"] == ["alice-token"]
|
|
110
|
+
assert bob.structuredContent["categories"] == ["bob-token"]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_request_without_credential_is_refused(http_server):
|
|
114
|
+
"""A request carrying no token is refused rather than served with the environment's client."""
|
|
115
|
+
url, _ = http_server
|
|
116
|
+
result = asyncio.run(_call_get_custom_metadata(url, None))
|
|
117
|
+
assert result.isError
|
|
118
|
+
assert "Missing Code Ocean API token" in result.content[0].text
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_non_bearer_credential_is_refused(http_server):
|
|
122
|
+
"""A token offered under any scheme other than Bearer is refused rather than used."""
|
|
123
|
+
url, _ = http_server
|
|
124
|
+
result = asyncio.run(_call_get_custom_metadata(url, "alice-token", scheme="Basic"))
|
|
125
|
+
assert result.isError
|
|
126
|
+
assert "Missing Code Ocean API token" in result.content[0].text
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_tool_definitions_match_stdio(http_server):
|
|
130
|
+
"""Serving over HTTP leaves tool names, schemas and descriptions exactly as stdio serves them."""
|
|
131
|
+
url, domain = http_server
|
|
132
|
+
|
|
133
|
+
async def list_over_http():
|
|
134
|
+
async with streamablehttp_client(url) as (read_stream, write_stream, _):
|
|
135
|
+
async with ClientSession(read_stream, write_stream) as session:
|
|
136
|
+
await session.initialize()
|
|
137
|
+
return (await session.list_tools()).tools
|
|
138
|
+
|
|
139
|
+
over_stdio = get_tools(_stdio_server(domain))
|
|
140
|
+
assert [t.model_dump() for t in asyncio.run(list_over_http())] == [t.model_dump() for t in over_stdio]
|
|
@@ -475,6 +475,16 @@ test_response = [
|
|
|
475
475
|
},
|
|
476
476
|
},
|
|
477
477
|
# --------------------------------------------------------------
|
|
478
|
+
# delete_computation
|
|
479
|
+
{
|
|
480
|
+
"id": "delete_computation",
|
|
481
|
+
"prompt": "delete computation with id: comp123",
|
|
482
|
+
"expected": {
|
|
483
|
+
"name": "delete_computation",
|
|
484
|
+
"input": {"computation_id": "comp123"},
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
# --------------------------------------------------------------
|
|
478
488
|
# attach_computation_data_assets
|
|
479
489
|
{
|
|
480
490
|
"id": "attach_computation_data_assets",
|
|
@@ -367,6 +367,8 @@ dependencies = [
|
|
|
367
367
|
]
|
|
368
368
|
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
|
|
369
369
|
wheels = [
|
|
370
|
+
{ url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" },
|
|
371
|
+
{ url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" },
|
|
370
372
|
{ url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" },
|
|
371
373
|
{ url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" },
|
|
372
374
|
{ url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" },
|
|
@@ -375,6 +377,10 @@ wheels = [
|
|
|
375
377
|
{ url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" },
|
|
376
378
|
{ url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" },
|
|
377
379
|
{ url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" },
|
|
380
|
+
{ url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" },
|
|
381
|
+
{ url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" },
|
|
382
|
+
{ url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" },
|
|
383
|
+
{ url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" },
|
|
378
384
|
{ url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" },
|
|
379
385
|
{ url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" },
|
|
380
386
|
{ url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" },
|
|
@@ -383,6 +389,11 @@ wheels = [
|
|
|
383
389
|
{ url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" },
|
|
384
390
|
{ url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" },
|
|
385
391
|
{ url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" },
|
|
392
|
+
{ url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" },
|
|
393
|
+
{ url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" },
|
|
394
|
+
{ url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" },
|
|
395
|
+
{ url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" },
|
|
396
|
+
{ url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" },
|
|
386
397
|
{ url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" },
|
|
387
398
|
{ url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" },
|
|
388
399
|
{ url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" },
|
|
@@ -390,6 +401,11 @@ wheels = [
|
|
|
390
401
|
{ url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" },
|
|
391
402
|
{ url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" },
|
|
392
403
|
{ url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" },
|
|
404
|
+
{ url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" },
|
|
405
|
+
{ url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" },
|
|
406
|
+
{ url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" },
|
|
407
|
+
{ url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
|
|
408
|
+
{ url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
|
|
393
409
|
{ url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
|
|
394
410
|
{ url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
|
|
395
411
|
{ url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
|
|
@@ -397,18 +413,31 @@ wheels = [
|
|
|
397
413
|
{ url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
|
|
398
414
|
{ url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
|
|
399
415
|
{ url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
|
|
416
|
+
{ url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
|
|
417
|
+
{ url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
|
|
418
|
+
{ url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
|
|
419
|
+
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
|
|
420
|
+
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
|
|
400
421
|
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
|
|
401
422
|
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
|
|
402
423
|
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
|
|
403
424
|
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
|
|
404
425
|
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
|
|
405
426
|
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
|
|
427
|
+
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
|
|
428
|
+
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
|
|
429
|
+
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
|
|
430
|
+
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
|
|
431
|
+
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
|
|
406
432
|
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
|
|
407
433
|
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
|
|
408
434
|
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
|
|
409
435
|
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
|
|
410
436
|
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
|
|
411
437
|
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
|
|
438
|
+
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
|
|
439
|
+
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
|
|
440
|
+
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
|
|
412
441
|
]
|
|
413
442
|
|
|
414
443
|
[[package]]
|
|
@@ -529,7 +558,7 @@ wheels = [
|
|
|
529
558
|
|
|
530
559
|
[[package]]
|
|
531
560
|
name = "codeocean-mcp-server"
|
|
532
|
-
version = "0.
|
|
561
|
+
version = "0.12.0"
|
|
533
562
|
source = { editable = "." }
|
|
534
563
|
dependencies = [
|
|
535
564
|
{ name = "codeocean" },
|
|
@@ -549,7 +578,7 @@ dev = [
|
|
|
549
578
|
[package.metadata]
|
|
550
579
|
requires-dist = [
|
|
551
580
|
{ name = "codeocean", specifier = ">=0.14.0,<0.15.0" },
|
|
552
|
-
{ name = "mcp", specifier = ">=1.
|
|
581
|
+
{ name = "mcp", specifier = ">=1.23.0,<1.24.0" },
|
|
553
582
|
]
|
|
554
583
|
|
|
555
584
|
[package.metadata.requires-dev]
|
|
@@ -581,6 +610,7 @@ dependencies = [
|
|
|
581
610
|
]
|
|
582
611
|
sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" }
|
|
583
612
|
wheels = [
|
|
613
|
+
{ url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" },
|
|
584
614
|
{ url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" },
|
|
585
615
|
{ url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" },
|
|
586
616
|
{ url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" },
|
|
@@ -592,6 +622,10 @@ wheels = [
|
|
|
592
622
|
{ url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" },
|
|
593
623
|
{ url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" },
|
|
594
624
|
{ url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" },
|
|
625
|
+
{ url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" },
|
|
626
|
+
{ url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" },
|
|
627
|
+
{ url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" },
|
|
628
|
+
{ url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" },
|
|
595
629
|
{ url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" },
|
|
596
630
|
{ url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" },
|
|
597
631
|
{ url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" },
|
|
@@ -603,6 +637,10 @@ wheels = [
|
|
|
603
637
|
{ url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" },
|
|
604
638
|
{ url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" },
|
|
605
639
|
{ url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" },
|
|
640
|
+
{ url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" },
|
|
641
|
+
{ url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" },
|
|
642
|
+
{ url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" },
|
|
643
|
+
{ url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" },
|
|
606
644
|
{ url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" },
|
|
607
645
|
{ url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" },
|
|
608
646
|
{ url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" },
|
|
@@ -614,10 +652,17 @@ wheels = [
|
|
|
614
652
|
{ url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" },
|
|
615
653
|
{ url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" },
|
|
616
654
|
{ url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" },
|
|
655
|
+
{ url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" },
|
|
656
|
+
{ url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" },
|
|
657
|
+
{ url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" },
|
|
658
|
+
{ url = "https://files.pythonhosted.org/packages/d9/cd/1a8633802d766a0fa46f382a77e096d7e209e0817892929655fe0586ae32/cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32", size = 3689163, upload-time = "2025-10-15T23:18:13.821Z" },
|
|
659
|
+
{ url = "https://files.pythonhosted.org/packages/4c/59/6b26512964ace6480c3e54681a9859c974172fb141c38df11eadd8416947/cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c", size = 3429474, upload-time = "2025-10-15T23:18:15.477Z" },
|
|
660
|
+
{ url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" },
|
|
617
661
|
{ url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" },
|
|
618
662
|
{ url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" },
|
|
619
663
|
{ url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" },
|
|
620
664
|
{ url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" },
|
|
665
|
+
{ url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" },
|
|
621
666
|
]
|
|
622
667
|
|
|
623
668
|
[[package]]
|
|
@@ -1600,7 +1645,7 @@ wheels = [
|
|
|
1600
1645
|
|
|
1601
1646
|
[[package]]
|
|
1602
1647
|
name = "mcp"
|
|
1603
|
-
version = "1.
|
|
1648
|
+
version = "1.23.3"
|
|
1604
1649
|
source = { registry = "https://pypi.org/simple" }
|
|
1605
1650
|
dependencies = [
|
|
1606
1651
|
{ name = "anyio" },
|
|
@@ -1609,15 +1654,18 @@ dependencies = [
|
|
|
1609
1654
|
{ name = "jsonschema" },
|
|
1610
1655
|
{ name = "pydantic" },
|
|
1611
1656
|
{ name = "pydantic-settings" },
|
|
1657
|
+
{ name = "pyjwt", extra = ["crypto"] },
|
|
1612
1658
|
{ name = "python-multipart" },
|
|
1613
1659
|
{ name = "pywin32", marker = "sys_platform == 'win32'" },
|
|
1614
1660
|
{ name = "sse-starlette" },
|
|
1615
1661
|
{ name = "starlette" },
|
|
1662
|
+
{ name = "typing-extensions" },
|
|
1663
|
+
{ name = "typing-inspection" },
|
|
1616
1664
|
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
|
1617
1665
|
]
|
|
1618
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1666
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a7/a4/d06a303f45997e266f2c228081abe299bbcba216cb806128e2e49095d25f/mcp-1.23.3.tar.gz", hash = "sha256:b3b0da2cc949950ce1259c7bfc1b081905a51916fcd7c8182125b85e70825201", size = 600697, upload-time = "2025-12-09T16:04:37.351Z" }
|
|
1619
1667
|
wheels = [
|
|
1620
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1668
|
+
{ url = "https://files.pythonhosted.org/packages/32/c6/13c1a26b47b3f3a3b480783001ada4268917c9f42d78a079c336da2e75e5/mcp-1.23.3-py3-none-any.whl", hash = "sha256:32768af4b46a1b4f7df34e2bfdf5c6011e7b63d7f1b0e321d0fdef4cd6082031", size = 231570, upload-time = "2025-12-09T16:04:35.56Z" },
|
|
1621
1669
|
]
|
|
1622
1670
|
|
|
1623
1671
|
[[package]]
|
|
@@ -2215,6 +2263,20 @@ wheels = [
|
|
|
2215
2263
|
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
|
2216
2264
|
]
|
|
2217
2265
|
|
|
2266
|
+
[[package]]
|
|
2267
|
+
name = "pyjwt"
|
|
2268
|
+
version = "2.11.0"
|
|
2269
|
+
source = { registry = "https://pypi.org/simple" }
|
|
2270
|
+
sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" }
|
|
2271
|
+
wheels = [
|
|
2272
|
+
{ url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" },
|
|
2273
|
+
]
|
|
2274
|
+
|
|
2275
|
+
[package.optional-dependencies]
|
|
2276
|
+
crypto = [
|
|
2277
|
+
{ name = "cryptography" },
|
|
2278
|
+
]
|
|
2279
|
+
|
|
2218
2280
|
[[package]]
|
|
2219
2281
|
name = "pyproject-hooks"
|
|
2220
2282
|
version = "1.2.0"
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
from codeocean import CodeOcean
|
|
4
|
-
from mcp.server.fastmcp import FastMCP
|
|
5
|
-
|
|
6
|
-
from codeocean_mcp_server.logging_config import configure_logging
|
|
7
|
-
from codeocean_mcp_server.tools import (
|
|
8
|
-
capsules,
|
|
9
|
-
computations,
|
|
10
|
-
custom_metadata,
|
|
11
|
-
data_assets,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def main():
|
|
16
|
-
"""Run the MCP server."""
|
|
17
|
-
configure_logging()
|
|
18
|
-
domain = os.getenv("CODEOCEAN_DOMAIN")
|
|
19
|
-
token = os.getenv("CODEOCEAN_TOKEN")
|
|
20
|
-
if not domain or not token:
|
|
21
|
-
raise ValueError("Environment variables CODEOCEAN_DOMAIN and CODEOCEAN_TOKEN must be set.")
|
|
22
|
-
agent_id = os.getenv("AGENT_ID", "AI Agent")
|
|
23
|
-
client = CodeOcean(domain=domain, token=token, agent_id=agent_id)
|
|
24
|
-
|
|
25
|
-
mcp = FastMCP(
|
|
26
|
-
name="Code Ocean",
|
|
27
|
-
instructions=(
|
|
28
|
-
f"MCP server for Code Ocean: search & run capsules, pipelines, and assets using Code Ocean domain {domain}."
|
|
29
|
-
),
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
capsules.add_tools(mcp, client)
|
|
33
|
-
data_assets.add_tools(mcp, client)
|
|
34
|
-
computations.add_tools(mcp, client)
|
|
35
|
-
custom_metadata.add_tools(mcp, client)
|
|
36
|
-
|
|
37
|
-
mcp.run()
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if __name__ == "__main__":
|
|
41
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/src/codeocean_mcp_server/__init__.py
RENAMED
|
File without changes
|
{codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/src/codeocean_mcp_server/file_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/src/codeocean_mcp_server/models.py
RENAMED
|
File without changes
|
{codeocean_mcp_server-0.9.0 → codeocean_mcp_server-0.12.0}/src/codeocean_mcp_server/search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|