robodev 0.52.0 → 0.53.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robodev",
3
- "version": "0.52.0",
3
+ "version": "0.53.0",
4
4
  "description": "CLI for Robodev Starbase — create, auth, link, deploy, load, and run a project locally with robodev dev",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,8 +26,8 @@
26
26
  "embedded-postgres": "17.10.0-beta.17",
27
27
  "fastify": "^5.12.1",
28
28
  "tsx": "^4.20.5",
29
- "@robodev-ai/runtime": "0.12.0",
30
- "@robodev-ai/sdk": "0.16.0"
29
+ "@robodev-ai/runtime": "0.13.0",
30
+ "@robodev-ai/sdk": "0.17.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^24.3.0",
@@ -8,6 +8,7 @@ import {
8
8
  collectFiles,
9
9
  collectSourceFiles,
10
10
  mergeCollectedFiles,
11
+ mixedBackendLayoutError,
11
12
  shouldCollectPath,
12
13
  shouldCollectSourcePath,
13
14
  } from "./collect-files.js";
@@ -221,3 +222,13 @@ test("backend tests are not collected for deploy", () => {
221
222
  assert.equal(shouldCollectPath("robodev/api/_lib.ts"), true);
222
223
  assert.equal(shouldCollectPath("robodev/api/latest.ts"), true);
223
224
  });
225
+
226
+ test("robodev/mcp.ts (defineMcp) is collected beside the mcp/ tools directory", () => {
227
+ assert.equal(shouldCollectPath("robodev/mcp.ts"), true);
228
+ assert.equal(shouldCollectPath("robodev/mcp/ping.ts"), true);
229
+ assert.equal(shouldCollectPath("mcp.ts"), true);
230
+ assert.equal(shouldCollectPath("robodev/mcp.js"), false);
231
+ assert.ok(mixedBackendLayoutError(["robodev/database.ts", "mcp.ts"]));
232
+ assert.ok(mixedBackendLayoutError(["database.ts", "robodev/mcp.ts"]));
233
+ assert.equal(mixedBackendLayoutError(["robodev/database.ts", "robodev/mcp.ts"]), null);
234
+ });
@@ -145,6 +145,8 @@ function isBackendPath(path: string): boolean {
145
145
  if (inner === "database.ts" && (path === "database.ts" || path === "robodev/database.ts")) {
146
146
  return true;
147
147
  }
148
+ // `defineMcp` settings; exactly `mcp.ts`, never a tool under `mcp/`.
149
+ if (path === "mcp.ts" || path === "robodev/mcp.ts") return true;
148
150
  if (
149
151
  inner.startsWith("api/") ||
150
152
  inner.startsWith("jobs/") ||
@@ -180,14 +182,16 @@ export function mixedBackendLayoutError(paths: Iterable<string>): string | null
180
182
  path.startsWith("api/") ||
181
183
  path.startsWith("jobs/") ||
182
184
  path.startsWith("sockets/") ||
183
- path.startsWith("mcp/"),
185
+ path.startsWith("mcp/") ||
186
+ path === "mcp.ts",
184
187
  );
185
188
  const hasNewApiJobs = list.some(
186
189
  (path) =>
187
190
  path.startsWith("robodev/api/") ||
188
191
  path.startsWith("robodev/jobs/") ||
189
192
  path.startsWith("robodev/sockets/") ||
190
- path.startsWith("robodev/mcp/"),
193
+ path.startsWith("robodev/mcp/") ||
194
+ path === "robodev/mcp.ts",
191
195
  );
192
196
  if (hasNewDb && (hasLegacyDb || hasLegacyApiJobs)) {
193
197
  return "Do not mix robodev/ backend files with root database.ts, api/, or jobs/";
@@ -441,6 +441,6 @@ test("dashboard links the MCP endpoint only when the project has tools", async (
441
441
  inspect: createDevInspect(),
442
442
  runtime: { ...runtime, mcpTools: [{ name: "get_planets" }] },
443
443
  });
444
- assert.equal(withTools.overview.links.mcp, "/_robodev/mcp");
445
- assert.equal(isHistogramExcludedPath("/_robodev/mcp"), true);
444
+ assert.equal(withTools.overview.links.mcp, "/mcp");
445
+ assert.equal(isHistogramExcludedPath("/mcp"), true);
446
446
  });
@@ -42,7 +42,7 @@ const HISTOGRAM_EXCLUDED_PATHS = new Set([
42
42
  "/openapi.json",
43
43
  "/robodev-enterprise-spec.json",
44
44
  "/_robodev/ready",
45
- "/_robodev/mcp",
45
+ "/mcp",
46
46
  ]);
47
47
 
48
48
  export const RESERVED_AUTH_ROUTES: Array<{ method: string; path: string; file: string }> = [
@@ -611,7 +611,7 @@ export async function buildDashboardState(input: {
611
611
  scalar: "/_robodev/scalar",
612
612
  openapi: "/openapi.json",
613
613
  enterprise: "/robodev-enterprise-spec.json",
614
- ...(input.runtime?.mcpTools?.length ? { mcp: "/_robodev/mcp" } : {}),
614
+ ...(input.runtime?.mcpTools?.length ? { mcp: "/mcp" } : {}),
615
615
  },
616
616
  },
617
617
  routes,
@@ -64,6 +64,9 @@ test("isBackendDevPath accepts backend sources only", () => {
64
64
  assert.equal(isBackendDevPath("jobs/digest.ts"), true);
65
65
  assert.equal(isBackendDevPath("hooks/auth.ts"), true);
66
66
  assert.equal(isBackendDevPath("sockets/chat.ts"), true);
67
+ assert.equal(isBackendDevPath("mcp.ts"), true);
68
+ assert.equal(isBackendDevPath("mcp/ping.ts"), true);
69
+ assert.equal(isBackendDevPath("other.ts"), false);
67
70
  assert.equal(isBackendDevPath("src/main.tsx"), false);
68
71
  assert.equal(isBackendDevPath("api/planets.js"), false);
69
72
  assert.equal(isBackendDevPath("package.json"), false);
@@ -69,7 +69,7 @@ export function isBackendDevPath(path: string): boolean {
69
69
  const normalized = path.replaceAll("\\", "/");
70
70
  if (normalized.includes("..")) return false;
71
71
  if (isBackendTestPath(normalized)) return false;
72
- if (normalized === "database.ts") return true;
72
+ if (normalized === "database.ts" || normalized === "mcp.ts") return true;
73
73
  if (!normalized.endsWith(".ts")) return false;
74
74
  const top = normalized.split("/")[0] ?? "";
75
75
  return (BACKEND_DIRS as readonly string[]).includes(top);
@@ -112,6 +112,10 @@ export async function collectBackendFiles(backendDir: string): Promise<DevFile[]
112
112
  content: await readFile(join(backendDir, "database.ts"), "utf8"),
113
113
  });
114
114
  }
115
+ // `defineMcp` settings sit beside the `mcp/` tools directory.
116
+ if (await isFile(join(backendDir, "mcp.ts"))) {
117
+ files.push({ path: "mcp.ts", content: await readFile(join(backendDir, "mcp.ts"), "utf8") });
118
+ }
115
119
  for (const name of BACKEND_DIRS) {
116
120
  await walk(join(backendDir, name));
117
121
  }
package/src/dev-server.ts CHANGED
@@ -17,8 +17,10 @@ import {
17
17
  handleProjectMcpHttp,
18
18
  handleProjectOAuth,
19
19
  isLocalAuthPath,
20
- isProjectMcpPath,
20
+ isProjectMcpRequest,
21
21
  isProjectOAuthRequestPath,
22
+ PROJECT_MCP_PATH,
23
+ resolveConsentUrl,
22
24
  localPasswordSignIn,
23
25
  localPasswordSignUp,
24
26
  authenticateProjectMcpToken,
@@ -32,6 +34,7 @@ import {
32
34
  type McpCaller,
33
35
  type OAuthHttpResponse,
34
36
  type ProjectMcpHttpResponse,
37
+ type RuntimeMcpConfig,
35
38
  type RuntimeMcpTool,
36
39
  isReservedStoragePath,
37
40
  ENTERPRISE_SPEC_PATH,
@@ -73,6 +76,8 @@ export type DevRuntime = {
73
76
  jobs: RuntimeJob[];
74
77
  sockets: RuntimeSocket[];
75
78
  mcpTools: RuntimeMcpTool[];
79
+ /** `robodev/mcp.ts` (`defineMcp`). */
80
+ mcpConfig?: RuntimeMcpConfig;
76
81
  openapi: Record<string, unknown>;
77
82
  };
78
83
 
@@ -107,7 +112,7 @@ export function buildDevOpenApi(
107
112
 
108
113
  /** The URL MCP clients connect to, printed in the banner. */
109
114
  export function devMcpUrl(listenOrigin: string): string {
110
- return `${listenOrigin}/_robodev/mcp`;
115
+ return `${listenOrigin}${PROJECT_MCP_PATH}`;
111
116
  }
112
117
 
113
118
  function devOAuthSecret(options: DevServerOptions): string {
@@ -716,7 +721,14 @@ export async function startDevServer(
716
721
  return;
717
722
  }
718
723
 
719
- if (isProjectMcpPath(pathname)) {
724
+ // A browser opening /mcp falls through to the normal 404 (the FE runs on its own dev server).
725
+ if (
726
+ isProjectMcpRequest(
727
+ pathname,
728
+ request.method,
729
+ request.headers as Record<string, string | string[] | undefined>,
730
+ )
731
+ ) {
720
732
  const headers = request.headers as Record<string, string | string[] | undefined>;
721
733
  const clients = () => routeClients(options, getRuntime);
722
734
  const response = await handleProjectMcpHttp(
@@ -798,6 +810,7 @@ export async function startDevServer(
798
810
  projectName: options.projectName ?? "robodev dev",
799
811
  origin: options.listenOrigin,
800
812
  secret: devOAuthSecret(options),
813
+ consentUrl: resolveConsentUrl(runtime.mcpConfig?.consentUrl, options.listenOrigin),
801
814
  settings: devOAuthSettings(options),
802
815
  signIn: (input) => localPasswordSignIn(runtime.pool, input),
803
816
  signUp: (input) => localPasswordSignUp(runtime.pool, input),
package/src/dev.ts CHANGED
@@ -233,6 +233,8 @@ export async function main(args: string[]): Promise<void> {
233
233
  const loaded = await loadModules(distDir, files, undefined, {
234
234
  mode: "deploy",
235
235
  onWarning: (message) => log(`Warning: ${message}`),
236
+ // Locally the consent page may be the Vite dev server on http://localhost.
237
+ allowLocalConsentUrl: true,
236
238
  });
237
239
  assertJobCrons(loaded.jobs, { mode: "deploy" });
238
240
  // Any edit to database.ts can change columns, not just tables, so compare its source.
@@ -268,6 +270,7 @@ export async function main(args: string[]): Promise<void> {
268
270
  jobs: loaded.jobs,
269
271
  sockets: loaded.sockets,
270
272
  mcpTools: loaded.mcpTools,
273
+ ...(loaded.mcpConfig ? { mcpConfig: loaded.mcpConfig } : {}),
271
274
  openapi: buildDevOpenApi(loaded.database.name, loaded.routes, loaded.mcpTools),
272
275
  };
273
276
  projectName = loaded.database.name;
@@ -293,6 +296,9 @@ export async function main(args: string[]): Promise<void> {
293
296
  for (const tool of loaded.mcpTools) {
294
297
  log(` MCP ${tool.name}`);
295
298
  }
299
+ if (loaded.mcpConfig?.consentUrl) {
300
+ log(` MCP consent page ${loaded.mcpConfig.consentUrl}`);
301
+ }
296
302
  if (previous) {
297
303
  await previous.pool.end().catch(() => undefined);
298
304
  }
@@ -9,10 +9,10 @@
9
9
  "rules:gen": "rulesync generate"
10
10
  },
11
11
  "dependencies": {
12
- "@robodev-ai/sdk": "^0.16.0"
12
+ "@robodev-ai/sdk": "^0.17.0"
13
13
  },
14
14
  "devDependencies": {
15
- "robodev": "^0.52.0",
15
+ "robodev": "^0.53.0",
16
16
  "rulesync": "^8.18.0",
17
17
  "typescript": "^5.9.2"
18
18
  }
@@ -8,10 +8,10 @@
8
8
  "deploy": "robodev deploy"
9
9
  },
10
10
  "dependencies": {
11
- "@robodev-ai/sdk": "^0.16.0"
11
+ "@robodev-ai/sdk": "^0.17.0"
12
12
  },
13
13
  "devDependencies": {
14
- "robodev": "^0.52.0",
14
+ "robodev": "^0.53.0",
15
15
  "typescript": "^5.9.2"
16
16
  }
17
17
  }
@@ -10,13 +10,13 @@
10
10
  "build": "vite build"
11
11
  },
12
12
  "dependencies": {
13
- "@robodev-ai/sdk": "^0.16.0",
14
- "@robodev-ai/client": "^0.5.0",
13
+ "@robodev-ai/sdk": "^0.17.0",
14
+ "@robodev-ai/client": "^0.6.0",
15
15
  "react": "^18.3.1",
16
16
  "react-dom": "^18.3.1"
17
17
  },
18
18
  "devDependencies": {
19
- "robodev": "^0.52.0",
19
+ "robodev": "^0.53.0",
20
20
  "typescript": "^5.9.2",
21
21
  "vite": "^6.0.0"
22
22
  }
@@ -9,10 +9,10 @@
9
9
  "rules:gen": "rulesync generate"
10
10
  },
11
11
  "dependencies": {
12
- "@robodev-ai/sdk": "^0.16.0"
12
+ "@robodev-ai/sdk": "^0.17.0"
13
13
  },
14
14
  "devDependencies": {
15
- "robodev": "^0.52.0",
15
+ "robodev": "^0.53.0",
16
16
  "rulesync": "^8.18.0",
17
17
  "typescript": "^5.9.2"
18
18
  }
@@ -9,10 +9,10 @@
9
9
  "rules:gen": "rulesync generate"
10
10
  },
11
11
  "dependencies": {
12
- "@robodev-ai/sdk": "^0.16.0"
12
+ "@robodev-ai/sdk": "^0.17.0"
13
13
  },
14
14
  "devDependencies": {
15
- "robodev": "^0.52.0",
15
+ "robodev": "^0.53.0",
16
16
  "rulesync": "^8.18.0",
17
17
  "typescript": "^5.9.2"
18
18
  }
@@ -11,8 +11,8 @@
11
11
  "generate:api": "orval"
12
12
  },
13
13
  "dependencies": {
14
- "@robodev-ai/sdk": "^0.16.0",
15
- "@robodev-ai/client": "^0.5.0",
14
+ "@robodev-ai/sdk": "^0.17.0",
15
+ "@robodev-ai/client": "^0.6.0",
16
16
  "react": "^18.3.1",
17
17
  "react-dom": "^18.3.1",
18
18
  "react-router-dom": "^6.30.1"
@@ -21,7 +21,7 @@
21
21
  "@types/react": "^18.3.12",
22
22
  "@types/react-dom": "^18.3.1",
23
23
  "orval": "^7.11.2",
24
- "robodev": "^0.52.0",
24
+ "robodev": "^0.53.0",
25
25
  "typescript": "^5.9.2",
26
26
  "vite": "^6.0.0"
27
27
  }
@@ -9,10 +9,10 @@
9
9
  "rules:gen": "rulesync generate"
10
10
  },
11
11
  "dependencies": {
12
- "@robodev-ai/sdk": "^0.16.0"
12
+ "@robodev-ai/sdk": "^0.17.0"
13
13
  },
14
14
  "devDependencies": {
15
- "robodev": "^0.52.0",
15
+ "robodev": "^0.53.0",
16
16
  "rulesync": "^8.18.0",
17
17
  "typescript": "^5.9.2"
18
18
  }