Skip to content

Commit 9756779

Browse files
committed
cocalc-api/mcp: refine authentication
1 parent 01cae0a commit 9756779

File tree

17 files changed

+546
-84
lines changed

17 files changed

+546
-84
lines changed

src/packages/conat/hub/api/system.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { type UserSearchResult } from "@cocalc/util/db-schema/accounts";
99
export const system = {
1010
getCustomize: noAuth,
1111
ping: noAuth,
12+
test: authFirst,
1213
terminate: authFirst,
1314
userTracking: authFirst,
1415
logClientError: authFirst,
@@ -31,6 +32,8 @@ export interface System {
3132
getCustomize: (fields?: string[]) => Promise<Customize>;
3233
// ping server and get back the current time
3334
ping: () => { now: number };
35+
// test API key and return scope information (account_id or project_id) and server time
36+
test: () => Promise<{ account_id?: string; project_id?: string; server_time: number }>;
3437
// terminate a service:
3538
// - only admin can do this.
3639
// - useful for development

src/packages/conat/project/api/system.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const system = {
2828
configuration: true,
2929

3030
ping: true,
31+
test: true,
3132
exec: true,
3233

3334
signal: true,
@@ -69,6 +70,8 @@ export interface System {
6970

7071
ping: () => Promise<{ now: number }>;
7172

73+
test: () => Promise<{ project_id: string; server_time: number }>;
74+
7275
exec: (opts: ExecuteCodeOptions) => Promise<ExecuteCodeOutput>;
7376

7477
signal: (opts: {

src/packages/next/pages/api/conat/project.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ export default async function handle(req, res) {
5858
args,
5959
timeout,
6060
});
61+
// For project-scoped API keys, include the project_id in the response
62+
// so the client can discover it
63+
if (project_id0 && !resp.project_id) {
64+
resp.project_id = project_id0;
65+
}
6166
res.json(resp);
6267
} catch (err) {
6368
res.json({ error: err.message });

src/packages/project/conat/api/system.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@ export async function ping() {
22
return { now: Date.now() };
33
}
44

5+
export async function test({
6+
project_id,
7+
}: {
8+
project_id?: string;
9+
} = {}) {
10+
return {
11+
project_id: project_id ?? "",
12+
server_time: Date.now(),
13+
};
14+
}
15+
516
export async function terminate() {}
617

718
import { handleExecShellCode } from "@cocalc/project/exec_shell_code";

src/packages/server/api/project-bridge.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ async function callProject({
5151
service: "api",
5252
});
5353
try {
54-
const data = { name, args };
54+
// For system.test(), inject project_id into args[0] if not already present
55+
let finalArgs = args;
56+
if (name === "system.test" && (!args || args.length === 0)) {
57+
finalArgs = [{ project_id }];
58+
}
59+
const data = { name, args: finalArgs };
5560
// we use waitForInterest because often the project hasn't
5661
// quite fully started.
5762
const resp = await client.request(subject, data, {

src/packages/server/conat/api/system.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,26 @@ export function ping() {
1818
return { now: Date.now() };
1919
}
2020

21+
export async function test({
22+
account_id,
23+
project_id,
24+
}: { account_id?: string; project_id?: string } = {}) {
25+
// Return API key scope information and server time
26+
// The authFirst decorator determines the scope from the API key and injects
27+
// either account_id (for account-scoped keys) or project_id (for project-scoped keys)
28+
// into this parameter object.
29+
const response: { account_id?: string; project_id?: string; server_time: number } = {
30+
server_time: Date.now(),
31+
};
32+
if (account_id) {
33+
response.account_id = account_id;
34+
}
35+
if (project_id) {
36+
response.project_id = project_id;
37+
}
38+
return response;
39+
}
40+
2141
export async function terminate() {}
2242

2343
export async function userTracking({

src/python/cocalc-api/Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ help:
1111
@echo " coverage - Run tests with coverage reporting (HTML + terminal)"
1212
@echo " coverage-report - Show coverage report in terminal"
1313
@echo " coverage-html - Generate HTML coverage report only"
14-
@echo " mcp - Start the MCP server (requires COCALC_API_KEY, COCALC_PROJECT_ID, and COCALC_HOST)"
14+
@echo " mcp - Start the MCP server (requires COCALC_API_KEY)"
15+
@echo " mcp-debug - Debug the running MCP server and show tools/resources"
1516
@echo " serve-docs - Serve documentation locally"
1617
@echo " build-docs - Build documentation"
1718
@echo " publish - Build and publish package"
@@ -51,6 +52,9 @@ coverage-html:
5152
mcp:
5253
uv run cocalc-mcp-server
5354

55+
mcp-debug:
56+
uv run cocalc-mcp-debug
57+
5458
serve-docs:
5559
uv run mkdocs serve
5660

src/python/cocalc-api/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Issues = "https://github.com/sagemathinc/cocalc/issues"
2121

2222
[project.scripts]
2323
cocalc-mcp-server = "cocalc_api.mcp.server:main"
24+
cocalc-mcp-debug = "cocalc_api.mcp.mcp_debug:main"
2425

2526
[tool.mypy]
2627
python_version = "3.13"

src/python/cocalc-api/src/cocalc_api/api_types.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ class PingResponse(TypedDict):
55
now: int
66

77

8+
class TestResponse(TypedDict, total=False):
9+
account_id: Optional[str]
10+
project_id: Optional[str]
11+
server_time: int
12+
13+
814
class ExecuteCodeOutput(TypedDict):
915
stdout: str
1016
stderr: str

src/python/cocalc-api/src/cocalc_api/hub.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import httpx
22
from typing import Any, Literal, Optional
33
from .util import api_method, handle_error
4-
from .api_types import PingResponse, UserSearchResult, MessageType
4+
from .api_types import PingResponse, TestResponse, UserSearchResult, MessageType
55
from .org import Organizations
66

77

@@ -83,6 +83,19 @@ def ping(self) -> PingResponse:
8383
"""
8484
raise NotImplementedError
8585

86+
@api_method("system.test")
87+
def test(self) -> TestResponse:
88+
"""
89+
Test the API key and get its scope information.
90+
91+
Returns:
92+
TestResponse: JSON object containing:
93+
- account_id (if account-scoped key)
94+
- project_id (if project-scoped key)
95+
- server_time (current server time in milliseconds since epoch)
96+
"""
97+
raise NotImplementedError
98+
8699
def get_names(self, account_ids: list[str]) -> list[str]:
87100
"""
88101
Get the displayed names of CoCalc accounts with given IDs.

0 commit comments

Comments
 (0)