Skip to content

Commit 157721b

Browse files
committed
cocalc-api/mcp: tool and resource descovery via api key or explicit project id
1 parent 747a07d commit 157721b

File tree

2 files changed

+50
-22
lines changed

2 files changed

+50
-22
lines changed

src/python/cocalc-api/src/cocalc_api/mcp/README.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,33 @@ Add to `~/.config/Claude/claude_desktop_config.json`:
5353
"mcpServers": {
5454
"cocalc": {
5555
"command": "uv",
56-
"args": ["--directory", "/path/to/cocalc-api", "run", "cocalc-mcp-server"],
57-
"env": {"COCALC_API_KEY": "sk-your-api-key"}
56+
"args": [
57+
"--directory",
58+
"/path/to/cocalc-api",
59+
"run",
60+
"cocalc-mcp-server"
61+
],
62+
"env": { "COCALC_API_KEY": "sk-your-api-key" }
5863
}
5964
}
6065
}
6166
```
6267

68+
## Setup with Codex CLI
69+
70+
```bash
71+
cd /path/to/cocalc-api
72+
export COCALC_API_KEY="sk-your-api-key"
73+
export COCALC_HOST="https://cocalc.com" # or your local hub, e.g. http://localhost:5000
74+
# optional (account keys only): export COCALC_PROJECT_ID="your-project-uuid"
75+
76+
codex mcp add --env COCALC_API_KEY="$COCALC_API_KEY" \
77+
--env COCALC_HOST="$COCALC_HOST" \
78+
cocalc -- uv --directory "$(pwd)" run cocalc-mcp-server
79+
```
80+
81+
The MCP name (`cocalc` above) is the first positional argument; use `--` before command flags like `--directory`.
82+
6383
## Allow Tools in Claude Code Settings
6484

6585
Add to `.claude/settings.json`:
@@ -77,23 +97,28 @@ The server automatically provides different tools based on your API key type:
7797
### Account-Scoped API Keys
7898

7999
**Tools:**
100+
80101
- `projects_search(query="")` - Search and list your projects with collaborator info
81102

82103
**Resources:**
104+
83105
- `cocalc://account-profile` - View your account info, settings, and preferences
84106

85107
### Project-Scoped API Keys
86108

87109
**Tools:**
110+
88111
- `exec(command)` - Execute shell commands in the project
89112
- `jupyter_execute(input, kernel="python3")` - Run code using Jupyter kernels
90113

91114
**Resources:**
115+
92116
- `cocalc://project-files` - Browse the project directory structure
93117

94118
## API Keys
95119

96120
Create API keys at:
121+
97122
- **Account-scoped**: CoCalc Settings → API keys → Create API key
98123
- **Project-scoped**: Project Settings → API keys → Create API key
99124

src/python/cocalc-api/src/cocalc_api/mcp/mcp_server.py

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,11 @@ def _initialize_config() -> None:
208208
if "account_id" in scope:
209209
account_id = cast(AccountScope, scope)["account_id"]
210210
print(f"✓ Connected with account-scoped API key (account: {account_id})", file=sys.stderr)
211-
# If a project_id is explicitly provided via env, prepare a client for it
211+
# If a project_id is explicitly provided via env, add it to scope
212212
if project_id_config:
213+
# Store project_id in scope so tools/resources can use it as fallback
214+
scope["project_id"] = project_id_config # type: ignore
215+
_api_key_scope = scope
213216
client = Project(api_key=_api_key, project_id=project_id_config, host=_host)
214217
_project_clients[project_id_config] = client
215218
print(
@@ -234,37 +237,31 @@ def get_project_client(project_id: Optional[str] = None) -> Project:
234237
"""
235238
Get or create a Project client for the given project.
236239
237-
For project-scoped API keys, project_id is optional (uses the key's project).
238-
For account-scoped API keys, project_id is required.
240+
Project ID resolution (in order of priority):
241+
1. Explicit project_id parameter
242+
2. project_id from _api_key_scope (for project-scoped keys or account-scoped keys with COCALC_PROJECT_ID)
243+
3. Project client extracts it from the API key (for project-scoped keys)
239244
240245
Args:
241-
project_id: The project UUID. If None, uses the project-scoped key's project.
246+
project_id: The project UUID. If None, uses the value from _api_key_scope or the API key itself.
242247
243248
Returns:
244249
Project client for the specified project
245250
246251
Raises:
247-
RuntimeError: If project_id cannot be determined or account-scoped key without project_id
252+
RuntimeError: If project_id cannot be determined
248253
"""
249254
global _project_clients
250255

251256
_initialize_config()
252257

253258
# Determine which project_id to use
254259
if project_id is None:
255-
# If no project_id provided, try to use the one from project-scoped key
260+
# Try to use project_id from scope (works for both project-scoped keys
261+
# and account-scoped keys with explicit COCALC_PROJECT_ID)
256262
scope = _api_key_scope
257263
if scope and "project_id" in scope:
258264
project_id = cast(ProjectScope, scope)["project_id"]
259-
else:
260-
# Account-scoped key requires explicit project_id
261-
raise RuntimeError("Account-scoped API key requires an explicit project_id argument. "
262-
"No project_id provided to get_project_client().")
263-
264-
# For project-scoped keys with None/empty project_id, the Project client will extract it from the API key
265-
# For account-scoped keys, project_id must be non-empty
266-
if not project_id and _api_key_scope and "account_id" in _api_key_scope:
267-
raise RuntimeError("Account-scoped API key requires a non-empty project_id")
268265

269266
# Use a cache key that handles None/empty project_id for project-scoped keys
270267
cache_key = project_id if project_id else "_default_project"
@@ -288,18 +285,24 @@ def _register_tools_and_resources() -> None:
288285

289286
_initialize_config()
290287

291-
# Determine which tools/resources to register based on API key scope
292-
if _api_key_scope and "account_id" in _api_key_scope:
293-
# Account-scoped key: register account-scoped tools/resources
288+
# Determine what needs to be registered
289+
register_account_tools = _api_key_scope and "account_id" in _api_key_scope
290+
register_project_tools = _api_key_scope and "project_id" in _api_key_scope
291+
# Also register project tools if account-scoped key has explicit project
292+
if register_account_tools and _project_clients:
293+
register_project_tools = True
294+
295+
# Register account-scoped tools/resources
296+
if register_account_tools:
294297
print("Registering account-scoped tools and resources...", file=sys.stderr)
295298
from .tools.projects_search import register_projects_search_tool
296299
from .resources.account_profile import register_account_profile_resource
297300

298301
register_projects_search_tool(mcp)
299302
register_account_profile_resource(mcp)
300303

301-
elif _api_key_scope and "project_id" in _api_key_scope:
302-
# Project-scoped key: register project-scoped tools/resources
304+
# Register project-scoped tools/resources
305+
if register_project_tools:
303306
print("Registering project-scoped tools and resources...", file=sys.stderr)
304307
from .tools.exec import register_exec_tool
305308
from .tools.jupyter import register_jupyter_tool

0 commit comments

Comments
 (0)