codeocean-mcp-server 0.1.0__py3-none-any.whl

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.
File without changes
@@ -0,0 +1,21 @@
1
+ """File utilities for downloading and reading files."""
2
+
3
+ import requests
4
+
5
+ # Constants
6
+ MAX_FILE_CONTENT_LENGTH = 50_000 # Maximum length of content to read
7
+ DOWNLOAD_TIMEOUT = 30
8
+
9
+
10
+ def download_and_read_file(url: str) -> str:
11
+ """Download file from URL and return first MAX_FILE_CONTENT_LENGTH characters."""
12
+ try:
13
+ with requests.get(url, timeout=DOWNLOAD_TIMEOUT, stream=True) as response:
14
+ response.raise_for_status()
15
+ # Read the first 'bytes_to_read' bytes of the response
16
+ data = response.raw.read(MAX_FILE_CONTENT_LENGTH)
17
+ # Decode the data into a string using the response's encoding
18
+ return data.decode(response.encoding or "utf-8", errors="ignore")
19
+
20
+ except requests.exceptions.RequestException as e:
21
+ return f"Download error: {e}"
@@ -0,0 +1,89 @@
1
+ from dataclasses import fields, is_dataclass
2
+ from typing import Any, List, Type, get_args, get_origin, get_type_hints
3
+
4
+ from pydantic import BaseModel, Field, create_model
5
+
6
+
7
+ def dataclass_to_pydantic(
8
+ data_class: Type[Any], cache: dict[Type[Any], Type[BaseModel]] = None
9
+ ) -> Type[BaseModel]:
10
+ """Convert a dataclass to Pydantic model.
11
+
12
+ Recursively convert a frozen @dataclass (and nested dataclasses)
13
+ into validating Pydantic BaseModel subclasses — resolving all
14
+ forward/string annotations via get_type_hints().
15
+ """
16
+ if cache is None:
17
+ cache = {}
18
+ if data_class in cache:
19
+ return cache[data_class]
20
+ assert is_dataclass(data_class), (
21
+ f"{data_class.__name__} is not a dataclass"
22
+ )
23
+
24
+ # 1) Resolve all annotations to real types (no strings)
25
+ module_ns = vars(
26
+ __import__(data_class.__module__, fromlist=["*"])
27
+ )
28
+ type_hints = get_type_hints(
29
+ data_class, globalns=module_ns, localns=module_ns
30
+ )
31
+
32
+ definitions: dict[str, tuple[type, Any]] = {}
33
+ for field in fields(data_class):
34
+ # Use the evaluated hint if available, else the raw annotation
35
+ typ = type_hints.get(field.name, field.type)
36
+ default = field.default
37
+ field_type = typ
38
+ origin = get_origin(typ)
39
+ args = get_args(typ)
40
+
41
+ # 2) Nested dataclass → build or fetch nested model
42
+ if is_dataclass(typ):
43
+ nested_model = dataclass_to_pydantic(typ, cache)
44
+ field_type = nested_model
45
+
46
+ # 3) List[...] of dataclasses → List[NestedModel]
47
+ elif origin in (list, List) and args and is_dataclass(args[0]):
48
+ nested_model = dataclass_to_pydantic(args[0], cache)
49
+ field_type = List[nested_model]
50
+
51
+ # 4) Handle field with description from metadata
52
+ field_info = default
53
+ if field.metadata and "description" in field.metadata:
54
+ # Create a Pydantic Field with description
55
+ field_info = Field(
56
+ default=default, description=field.metadata["description"]
57
+ )
58
+
59
+ definitions[field.name] = (field_type, field_info)
60
+
61
+ # 5) Dynamically create the Pydantic model
62
+ model = create_model(
63
+ f"{data_class.__name__}Model",
64
+ __base__=BaseModel,
65
+ __doc__=data_class.__doc__,
66
+ **definitions
67
+ )
68
+
69
+ # 6) Override the schema generation to include description from docstring
70
+ if data_class.__doc__:
71
+ original_json_schema = model.model_json_schema
72
+
73
+ def custom_json_schema(*args, **kwargs):
74
+ schema = original_json_schema(*args, **kwargs)
75
+ schema["description"] = data_class.__doc__.strip()
76
+ return schema
77
+
78
+ model.model_json_schema = custom_json_schema
79
+
80
+ model.model_rebuild()
81
+
82
+ def to_dict_method(self):
83
+ return self.model_dump()
84
+
85
+ # 7) Add a method to convert the model instance to a dictionary
86
+ model.to_dict = to_dict_method
87
+
88
+ cache[data_class] = model
89
+ return model
@@ -0,0 +1,36 @@
1
+ import os
2
+
3
+ from codeocean import CodeOcean
4
+ from mcp.server.fastmcp import FastMCP
5
+
6
+ from codeocean_mcp_server.tools import capsules, computations, data_assets
7
+
8
+
9
+ def main():
10
+ """Run the MCP server."""
11
+ domain = os.getenv("CODEOCEAN_DOMAIN")
12
+ token = os.getenv("CODEOCEAN_TOKEN")
13
+ if not domain or not token:
14
+ raise ValueError(
15
+ "Environment variables CODEOCEAN_DOMAIN and "
16
+ "CODEOCEAN_TOKEN must be set."
17
+ )
18
+ client = CodeOcean(domain=domain, token=token)
19
+
20
+ mcp = FastMCP(
21
+ name="Code Ocean",
22
+ description=(
23
+ f"MCP server for Code Ocean: search & run capsules, "
24
+ f"pipelines, and assets using Code Ocean domain {domain}."
25
+ ),
26
+ )
27
+
28
+ capsules.add_tools(mcp, client)
29
+ data_assets.add_tools(mcp, client)
30
+ computations.add_tools(mcp, client)
31
+
32
+ mcp.run()
33
+
34
+
35
+ if __name__ == "__main__":
36
+ main()
File without changes
@@ -0,0 +1,75 @@
1
+ from codeocean import CodeOcean
2
+ from codeocean.capsule import (
3
+ Capsule,
4
+ CapsuleSearchParams,
5
+ CapsuleSearchResults,
6
+ Computation,
7
+ DataAssetAttachParams,
8
+ DataAssetAttachResults,
9
+ )
10
+ from mcp.server.fastmcp import FastMCP
11
+
12
+ from codeocean_mcp_server.models import dataclass_to_pydantic
13
+
14
+ CapsuleModel = dataclass_to_pydantic(Capsule)
15
+ CapsuleSearchParamsModel = dataclass_to_pydantic(CapsuleSearchParams)
16
+ CapsuleSearchResultsModel = dataclass_to_pydantic(CapsuleSearchResults)
17
+ ComputationModel = dataclass_to_pydantic(Computation)
18
+ DataAssetAttachParamsModel = dataclass_to_pydantic(DataAssetAttachParams)
19
+ DataAssetAttachResultsModel = dataclass_to_pydantic(DataAssetAttachResults)
20
+
21
+
22
+ def add_tools(mcp: FastMCP, client: CodeOcean):
23
+ """Add capsule tools to the MCP server."""
24
+
25
+ @mcp.tool(
26
+ description=(
27
+ str(client.capsules.search_capsules.__doc__)
28
+ + "Use only for capsule searches. "
29
+ "Provide only the minimal required parameters (e.g. limit=10); "
30
+ "do not include optional params "
31
+ "like sort_by or sort_order unless requested."
32
+ )
33
+ )
34
+ def search_capsules(
35
+ search_params: CapsuleSearchParamsModel,
36
+ ) -> CapsuleSearchResultsModel:
37
+ """Search for capsules matching specified criteria."""
38
+ params = CapsuleSearchParams(**search_params.model_dump(exclude_none=True))
39
+ return dataclass_to_pydantic(client.capsules.search_capsules(params))
40
+
41
+ @mcp.tool(
42
+ description=(
43
+ str(client.capsules.attach_data_assets.__doc__)
44
+ + "Accepts a list of parameter objects (e.g. [{'id': '...'}]), "
45
+ "not just a list of IDs."
46
+ )
47
+ )
48
+ def attach_data_assets(
49
+ capsule_id: str,
50
+ data_asset_ids: list[DataAssetAttachParamsModel],
51
+ ) -> list[DataAssetAttachResultsModel]:
52
+ """Attach data assets to a capsule."""
53
+ return [
54
+ dataclass_to_pydantic(result)
55
+ for result in client.capsules.attach_data_assets(capsule_id, data_asset_ids)
56
+ ]
57
+
58
+ @mcp.tool(
59
+ description=(
60
+ str(client.capsules.get_capsule.__doc__)
61
+ + "Use only to fetch metadata for a known capsule ID. "
62
+ "Do not use for searching."
63
+ )
64
+ )
65
+ def get_capsule(capsule_id: str) -> CapsuleModel:
66
+ """Retrieve a capsule by its ID."""
67
+ return dataclass_to_pydantic(client.capsules.get_capsule(capsule_id))
68
+
69
+ @mcp.tool(description=client.capsules.list_computations.__doc__)
70
+ def list_computations(capsule_id: str) -> list[ComputationModel]:
71
+ """List all computations for a capsule."""
72
+ return [
73
+ dataclass_to_pydantic(computation)
74
+ for computation in client.capsules.list_computations(capsule_id)
75
+ ]
@@ -0,0 +1,77 @@
1
+ from codeocean import CodeOcean
2
+ from codeocean.computation import (
3
+ Computation,
4
+ DownloadFileURL,
5
+ Folder,
6
+ RunParams,
7
+ )
8
+ from mcp.server.fastmcp import FastMCP
9
+
10
+ from codeocean_mcp_server.file_utils import download_and_read_file
11
+ from codeocean_mcp_server.models import dataclass_to_pydantic
12
+
13
+ ComputationModel = dataclass_to_pydantic(Computation)
14
+ DownloadFileURLModel = dataclass_to_pydantic(DownloadFileURL)
15
+ FolderModel = dataclass_to_pydantic(Folder)
16
+ RunParamsModel = dataclass_to_pydantic(RunParams)
17
+
18
+
19
+ def add_tools(mcp: FastMCP, client: CodeOcean):
20
+ """Add capsule tools to the MCP server."""
21
+
22
+ @mcp.tool(description=client.computations.get_computation.__doc__)
23
+ def get_computation(computation_id: str) -> list[ComputationModel]:
24
+ """Retrieve a specific computation by its unique identifier."""
25
+ return [
26
+ dataclass_to_pydantic(computation)
27
+ for computation in client.computations.get_computation(computation_id)
28
+ ]
29
+
30
+ @mcp.tool(
31
+ description=(
32
+ str(client.computations.run_capsule.__doc__)
33
+ + "Typical workflow: 1) run_capsule() to start execution "
34
+ "2) wait_until_completed() to monitor progress "
35
+ "3) list_computation_results() and get_result_file_download_url() "
36
+ "to retrieve outputs."
37
+ )
38
+ )
39
+ def run_capsule(run_params: RunParamsModel) -> ComputationModel:
40
+ """Execute a capsule or a pipeline in Code Ocean and don't wait."""
41
+ return dataclass_to_pydantic(client.computations.run_capsule(run_params))
42
+
43
+ @mcp.tool(description=client.computations.wait_until_completed.__doc__)
44
+ def wait_until_completed(computation_id: str) -> ComputationModel:
45
+ """Wait until a computation completes and return its details."""
46
+ # first get the computation based on the computation_id:
47
+ computation = client.computations.get_computation(computation_id)
48
+ return dataclass_to_pydantic(client.computations.wait_until_completed(computation))
49
+
50
+ @mcp.tool(
51
+ description=(
52
+ str(client.computations.list_computation_results.__doc__)
53
+ + " computation_id is required as string"
54
+ )
55
+ )
56
+ def list_computation_results(computation_id: str) -> FolderModel:
57
+ """List the output files generated by a completed computation."""
58
+ return dataclass_to_pydantic(
59
+ client.computations.list_computation_results(computation_id)
60
+ )
61
+
62
+ @mcp.tool(description=(client.computations.get_result_file_download_url.__doc__))
63
+ def get_result_file_download_url(computation_id: str, file_path: str) -> DownloadFileURLModel:
64
+ """Get download URL for a specific result file from computation."""
65
+ return dataclass_to_pydantic(
66
+ client.computations.get_result_file_download_url(computation_id, file_path),
67
+ )
68
+
69
+ @mcp.tool(
70
+ description=(
71
+ "Use when you want to read the content of a file from a computation"
72
+ )
73
+ )
74
+ def download_and_read_a_file_from_computation(computation_id: str, file_path: str) -> str:
75
+ """Download a file using the provided URL and return its content."""
76
+ file_url = client.computations.get_result_file_download_url(computation_id, file_path)
77
+ return download_and_read_file(file_url.url)
@@ -0,0 +1,123 @@
1
+ import os
2
+
3
+ from codeocean import CodeOcean
4
+ from codeocean.data_asset import (
5
+ DataAsset,
6
+ DataAssetAttachParams,
7
+ DataAssetParams,
8
+ DataAssetSearchParams,
9
+ DataAssetSearchResults,
10
+ DataAssetUpdateParams,
11
+ DownloadFileURL,
12
+ Folder,
13
+ )
14
+ from mcp.server.fastmcp import FastMCP
15
+
16
+ from codeocean_mcp_server.file_utils import download_and_read_file
17
+ from codeocean_mcp_server.models import dataclass_to_pydantic
18
+
19
+ DataAssetAttachParamsModel = dataclass_to_pydantic(DataAssetAttachParams)
20
+ DataAssetModel = dataclass_to_pydantic(DataAsset)
21
+ DataAssetParamsModel = dataclass_to_pydantic(DataAssetParams)
22
+ DataAssetSearchParamsModel = dataclass_to_pydantic(DataAssetSearchParams)
23
+ DataAssetSearchResultsModel = dataclass_to_pydantic(DataAssetSearchResults)
24
+ DataAssetUpdateParamsModel = dataclass_to_pydantic(DataAssetUpdateParams)
25
+ DownloadFileURLModel = dataclass_to_pydantic(DownloadFileURL)
26
+ FolderModel = dataclass_to_pydantic(Folder)
27
+
28
+
29
+ def add_tools(mcp: FastMCP, client: CodeOcean):
30
+ """Add data asset tools to the MCP server."""
31
+
32
+ @mcp.tool(
33
+ description=(
34
+ str(client.data_assets.search_data_assets.__doc__)
35
+ + "Search for data assets (external or internal). You may filter by "
36
+ "fields such as `origin`, tags, or other criteria supported by the "
37
+ "SDK."
38
+ )
39
+ )
40
+ def search_data_assets(search_params: DataAssetSearchParamsModel) -> DataAssetSearchResultsModel:
41
+ """Retrieve data assets matching search criteria for datasets."""
42
+ params = DataAssetSearchParams(**search_params.model_dump(exclude_none=True))
43
+ return dataclass_to_pydantic(client.data_assets.search_data_assets(params))
44
+
45
+ @mcp.tool(
46
+ description=(
47
+ str(client.data_assets.get_data_asset_file_download_url.__doc__)
48
+ + "Call only when the data asset is already created and in a ready "
49
+ "state. If the asset may not yet be ready, first use "
50
+ "`wait_until_ready` to poll until readiness, then retrieve the "
51
+ "download URL."
52
+ )
53
+ )
54
+ def get_data_asset_file_download_url(
55
+ data_asset_id: str,
56
+ file_path: str | None = None,
57
+ ) -> DownloadFileURLModel:
58
+ """Get a download URL for a specific file in a data asset."""
59
+ return dataclass_to_pydantic(
60
+ client.data_assets.get_data_asset_file_download_url(data_asset_id, file_path),
61
+ )
62
+
63
+ @mcp.tool(
64
+ description=(
65
+ "Use when you want to read the content of a file from a data asset"
66
+ )
67
+ )
68
+ def download_and_read_a_file_from_data_asset(data_asset_id: str, file_path: str) -> str:
69
+ """Download a file using the provided URL and return its content."""
70
+ file_url = client.data_assets.get_data_asset_file_download_url(data_asset_id, file_path)
71
+ return download_and_read_file(file_url.url)
72
+
73
+ @mcp.tool(description=client.data_assets.list_data_asset_files.__doc__)
74
+ def list_data_asset_files(data_asset_id: str) -> FolderModel:
75
+ """List files in a data asset."""
76
+ return dataclass_to_pydantic(
77
+ client.data_assets.list_data_asset_files(data_asset_id),
78
+ )
79
+
80
+ @mcp.tool(description=client.data_assets.update_metadata.__doc__)
81
+ def update_metadata(
82
+ data_asset_id: str,
83
+ update_params: DataAssetUpdateParamsModel,
84
+ ) -> DataAssetModel:
85
+ """Update metadata for a specific data asset."""
86
+ update_params = DataAssetUpdateParams(**update_params)
87
+ return dataclass_to_pydantic(
88
+ client.data_assets.update_metadata(data_asset_id, update_params)
89
+ )
90
+
91
+ @mcp.tool(
92
+ description=(
93
+ str(client.data_assets.wait_until_ready.__doc__)
94
+ + "Poll until the specified data asset becomes ready before "
95
+ "performing further operations (e.g., downloading files). You can "
96
+ "set `polling_interval` and optional `timeout`."
97
+ )
98
+ )
99
+ def wait_until_ready(
100
+ data_asset: DataAssetModel,
101
+ polling_interval: float = 5,
102
+ timeout: float | None = None,
103
+ ) -> DataAssetModel:
104
+ """Wait until a data asset is ready."""
105
+ result = client.data_assets.wait_until_ready(
106
+ DataAsset(**data_asset.model_dump(exclude_none=True)),
107
+ polling_interval=polling_interval,
108
+ timeout=timeout,
109
+ )
110
+
111
+ return dataclass_to_pydantic(result)
112
+
113
+ @mcp.tool(
114
+ description=(
115
+ str(client.data_assets.create_data_asset.__doc__)
116
+ + f"You can link to the created data assets with the 'data_asset_id' "
117
+ f"with the pattern: {os.getenv('CODEOCEAN_DOMAIN', 'unknown')} with /data-assets/<data_asset_id>."
118
+ )
119
+ )
120
+ def create_data_asset(data_asset_params: DataAssetParamsModel) -> DataAssetModel:
121
+ """Create a new data asset."""
122
+ params = DataAssetParams(**data_asset_params.model_dump(exclude_none=True))
123
+ return dataclass_to_pydantic(client.data_assets.create_data_asset(params))
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: codeocean-mcp-server
3
+ Version: 0.1.0
4
+ Summary: Code Ocean MCP Server
5
+ Project-URL: Homepage, https://github.com/codeocean/codeocean-mcp-server
6
+ Project-URL: Issues, https://github.com/codeocean/codeocean-mcp-server/issues
7
+ Project-URL: Changelog, https://github.com/codeocean/codeocean-mcp-server/blob/main/CHANGELOG.md
8
+ Author-email: Code Ocean <dev@codeocean.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Requires-Python: >=3.10
15
+ Requires-Dist: codeocean>=0.7.0
16
+ Requires-Dist: fastmcp>=2.9.2
17
+ Description-Content-Type: text/markdown
18
+
19
+ # Code Ocean MCP Server
20
+
21
+ Model Context Protocol (MCP) server for Code Ocean.
22
+
23
+ This MCP server provides tools to search and run capsules and pipelines, and manage data assets.
24
+
25
+ ## Table of Contents
26
+
27
+ - [Prerequisites](#prerequisites)
28
+ - [Installation](#installation)
29
+ - [Visual Studio Code](#visual-studio-code)
30
+ - [Claude Desktop](#claude-desktop)
31
+ - [Cline](#cline)
32
+ - [Roo Code](#roo-code)
33
+ - [Cursor](#cursor)
34
+ - [Windsurf](#windsurf)
35
+
36
+ ## Prerequisites
37
+
38
+ 1. Install `uv` from [Astral](https://docs.astral.sh/uv/getting-started/installation/) or the [GitHub README](https://github.com/astral-sh/uv#installation)
39
+ 2. Install Python 3.10 or newer using `uv python install 3.10` (or a more recent version)
40
+ 3. Generate a Code Ocean access token. Follow instructions in the [Code Ocean user guide](https://docs.codeocean.com/user-guide/code-ocean-api/authentication).
41
+
42
+ ## Installation
43
+
44
+ ## [Visual Studio Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers)
45
+
46
+ Here's an example VS Code MCP server configuration:
47
+ ```json
48
+ {
49
+ ...
50
+ "mcp": {
51
+ "inputs": [
52
+ {
53
+ "type": "promptString",
54
+ "id": "codeocean-token",
55
+ "description": "Code Ocean API Key",
56
+ "password": true
57
+ }
58
+ ],
59
+ "servers": {
60
+ "codeocean": {
61
+ "type": "stdio",
62
+ "command": "uvx",
63
+ "args": ["codeocean-mcp-server"],
64
+ "env": {
65
+ "CODEOCEAN_DOMAIN": "https://codeocean.acme.com",
66
+ "CODEOCEAN_TOKEN": "${input:codeocean-token}"
67
+ }
68
+ }
69
+ },
70
+ }
71
+ }
72
+ ```
73
+
74
+ ---
75
+
76
+ ## [Claude Desktop](https://modelcontextprotocol.io/quickstart/user)
77
+
78
+ 1. Open the `claude_desktop_config.json` file:
79
+ - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
80
+ - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
81
+ 2. Under the top-level "mcpServers" object, add a "codeocean" entry. For a stdio transport (child-process) it looks like this:
82
+
83
+ ```json
84
+ {
85
+ "mcpServers": {
86
+ "codeocean": {
87
+ "command": "uvx",
88
+ "args": ["codeocean-mcp-server"],
89
+ "env": {
90
+ "CODEOCEAN_DOMAIN": "https://codeocean.acme.com",
91
+ "CODEOCEAN_TOKEN": "<YOUR_API_KEY>"
92
+ }
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ ---
99
+
100
+ ## [Cline](https://docs.cline.bot/mcp/configuring-mcp-servers)
101
+
102
+ Cline stores all of its MCP settings in a JSON file called cline_mcp_settings.json. You can edit this either through the GUI (“Configure MCP Servers” in the MCP Servers pane) or by hand:
103
+ 1. Open Cline and click the MCP Servers icon in the sidebar.
104
+ 2. In the “Installed” tab, click Configure MCP Servers → this opens your cline_mcp_settings.json.
105
+ 3. Add a "codeocean" server under the "mcpServers" key. For stdio transport:
106
+ ```json
107
+ {
108
+ "mcpServers": {
109
+ "codeocean": {
110
+ "command": "uvx",
111
+ "args": ["codeocean-mcp-server"],
112
+ "env": {
113
+ "CODEOCEAN_DOMAIN": "https://codeocean.acme.com",
114
+ "CODEOCEAN_TOKEN": "<YOUR_API_KEY>"
115
+ },
116
+ "alwaysAllow": [], // optional: list of tools to auto-approve
117
+ "disabled": false // ensure it’s enabled
118
+ }
119
+ }
120
+ }
121
+ ```
122
+ 4. Save the file. Cline will automatically detect and launch the new server, making your Code Ocean tools available in chat .
123
+
124
+ ---
125
+
126
+ ## [Roo Code](https://docs.roocode.com/features/mcp/using-mcp-in-roo/)
127
+
128
+ Roo Code’s MCP support is configured globally across all workspaces via a JSON settings file or through its dedicated MCP Settings UI
129
+
130
+ ### Via the MCP Settings UI:
131
+ 1. Click the MCP icon in Roo Code’s sidebar. 
132
+ 2. Select Edit MCP Settings (opens cline_mcp_settings.json). 
133
+ 3. Under "mcpServers", add:
134
+
135
+ ```json
136
+ {
137
+ "mcpServers": {
138
+ "codeocean": {
139
+ "command": "uvx",
140
+ "args": ["codeocean-mcp-server"],
141
+ "env": {
142
+ "CODEOCEAN_DOMAIN": "https://codeocean.acme.com",
143
+ "CODEOCEAN_TOKEN": "<YOUR_API_KEY>"
144
+ }
145
+ }
146
+ }
147
+ }
148
+ ```
149
+ 4. Save and restart Roo Code; your Code Ocean tools will appear automatically.
150
+
151
+ ### Optional: Manually editing cline_mcp_settings.json
152
+ 1. Locate cline_mcp_settings.json (in your home directory or workspace). 
153
+ 2. Insert the same "codeocean" block under "mcpServers" as above.
154
+ 3. Save and restart.
155
+
156
+ ---
157
+
158
+ ## [Cursor](https://docs.cursor.com/context/model-context-protocol)
159
+
160
+ Cursor stores MCP servers in a JSON file at either ~/.cursor/mcp.json (global) or {project}/.cursor/mcp.json (project-specific) .
161
+ 1. Open .cursor/mcp.json (or create it if missing). 
162
+ 2. Add under "mcpServers":
163
+ ```json
164
+ {
165
+ "mcpServers": {
166
+ "codeocean": {
167
+ "command": "uvx",
168
+ "args": ["codeocean-mcp-server"],
169
+ "env": {
170
+ "CODEOCEAN_DOMAIN": "https://codeocean.acme.com",
171
+ "CODEOCEAN_TOKEN": "<YOUR_API_KEY>"
172
+ }
173
+ }
174
+ }
175
+ }
176
+ ```
177
+ 3. Save the file. Cursor will automatically detect and launch the new server on next start. 
178
+
179
+ ---
180
+
181
+ ## [Windsurf](https://docs.windsurf.com/windsurf/cascade/mcp)
182
+
183
+ Windsurf (Cascade) uses mcp_config.json under ~/.codeium/windsurf/ (or via the Cascade → MCP Servers UI) .
184
+ 1. Open your Windsurf Settings and navigate to Cascade → MCP Servers, then click View Raw Config to open mcp_config.json. 
185
+ 2. Insert the following under "mcpServers":
186
+ ```json
187
+ {
188
+ "mcpServers": {
189
+ "codeocean": {
190
+ "command": "uvx",
191
+ "args": ["codeocean-mcp-server"],
192
+ "env": {
193
+ "CODEOCEAN_DOMAIN": "https://codeocean.acme.com",
194
+ "CODEOCEAN_TOKEN": "<YOUR_API_KEY>"
195
+ }
196
+ }
197
+ }
198
+ }
199
+ ```
200
+
201
+ 3. Save and restart Windsurf (or hit “Refresh” in the MCP panel).
@@ -0,0 +1,13 @@
1
+ codeocean_mcp_server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ codeocean_mcp_server/file_utils.py,sha256=iXhgkJ5CkZrm-7_x3HVc_ucHF9ZJENHfbR7TZ_mmcPI,817
3
+ codeocean_mcp_server/models.py,sha256=n-wGlUwz94dlJmjvtOjYE4JfgHtlOoQHRVwME3FnnX8,2996
4
+ codeocean_mcp_server/server.py,sha256=JErl9kZgXvfn4OGm0kDkdq9h2r6_mQkMlPmTj099Z3Q,900
5
+ codeocean_mcp_server/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ codeocean_mcp_server/tools/capsules.py,sha256=Llf234em469WoBAg4krJMzNQqqi0at7TWGlHOEOtEm4,2792
7
+ codeocean_mcp_server/tools/computations.py,sha256=LGu_l9KCVIzJJKJJbdYMQDTAHVj17UawI8bVzi1AXr4,3318
8
+ codeocean_mcp_server/tools/data_assets.py,sha256=o4Ze1P1jygeN_glPQ4dP_o2Q_DElZHbYEUxTjlkJhto,5016
9
+ codeocean_mcp_server-0.1.0.dist-info/METADATA,sha256=8TerkbAP1iXu4B6yT0UySfPQmz7rumIoToll6Y2nv8E,6281
10
+ codeocean_mcp_server-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
+ codeocean_mcp_server-0.1.0.dist-info/entry_points.txt,sha256=MeJye6ajSv37_8SGIGRUuYx8v1Wyod-XtbcRS7UDXpo,74
12
+ codeocean_mcp_server-0.1.0.dist-info/licenses/LICENSE,sha256=JUAoUpOdro6pzi4jINXDMdsz420i1h9oZ5u_I_TCBKI,1067
13
+ codeocean_mcp_server-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ codeocean-mcp-server = codeocean_mcp_server.server:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Code Ocean
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.