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 +3 -3
- package/src/collect-files.test.ts +11 -0
- package/src/collect-files.ts +6 -2
- package/src/dev-inspect.test.ts +2 -2
- package/src/dev-inspect.ts +2 -2
- package/src/dev-project.test.ts +3 -0
- package/src/dev-project.ts +5 -1
- package/src/dev-server.ts +16 -3
- package/src/dev.ts +6 -0
- package/templates/auth-chat/package.json +2 -2
- package/templates/backend/package.json +2 -2
- package/templates/empty/package.json +3 -3
- package/templates/marketplace/package.json +2 -2
- package/templates/povio/package.json +2 -2
- package/templates/shadcn/package.json +3 -3
- package/templates/space/package.json +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "robodev",
|
|
3
|
-
"version": "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.
|
|
30
|
-
"@robodev-ai/sdk": "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
|
+
});
|
package/src/collect-files.ts
CHANGED
|
@@ -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/";
|
package/src/dev-inspect.test.ts
CHANGED
|
@@ -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, "/
|
|
445
|
-
assert.equal(isHistogramExcludedPath("/
|
|
444
|
+
assert.equal(withTools.overview.links.mcp, "/mcp");
|
|
445
|
+
assert.equal(isHistogramExcludedPath("/mcp"), true);
|
|
446
446
|
});
|
package/src/dev-inspect.ts
CHANGED
|
@@ -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
|
-
"/
|
|
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: "/
|
|
614
|
+
...(input.runtime?.mcpTools?.length ? { mcp: "/mcp" } : {}),
|
|
615
615
|
},
|
|
616
616
|
},
|
|
617
617
|
routes,
|
package/src/dev-project.test.ts
CHANGED
|
@@ -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);
|
package/src/dev-project.ts
CHANGED
|
@@ -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
|
-
|
|
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}
|
|
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
|
-
|
|
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.
|
|
12
|
+
"@robodev-ai/sdk": "^0.17.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.53.0",
|
|
16
16
|
"rulesync": "^8.18.0",
|
|
17
17
|
"typescript": "^5.9.2"
|
|
18
18
|
}
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
"build": "vite build"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@robodev-ai/sdk": "^0.
|
|
14
|
-
"@robodev-ai/client": "^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.
|
|
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.
|
|
12
|
+
"@robodev-ai/sdk": "^0.17.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"robodev": "^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.
|
|
12
|
+
"@robodev-ai/sdk": "^0.17.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"robodev": "^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.
|
|
15
|
-
"@robodev-ai/client": "^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.
|
|
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.
|
|
12
|
+
"@robodev-ai/sdk": "^0.17.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.53.0",
|
|
16
16
|
"rulesync": "^8.18.0",
|
|
17
17
|
"typescript": "^5.9.2"
|
|
18
18
|
}
|