robodev 0.51.1 → 0.52.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.ts +9 -3
- package/src/dev-inspect.test.ts +15 -0
- package/src/dev-inspect.ts +14 -1
- package/src/dev-project.ts +2 -2
- package/src/dev-server.ts +189 -2
- package/src/dev.ts +12 -2
- package/templates/auth-chat/package.json +2 -2
- package/templates/backend/package.json +2 -2
- package/templates/empty/package.json +2 -2
- package/templates/marketplace/package.json +2 -2
- package/templates/povio/package.json +2 -2
- package/templates/shadcn/package.json +2 -2
- 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.52.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/
|
|
30
|
-
"@robodev-ai/
|
|
29
|
+
"@robodev-ai/runtime": "0.12.0",
|
|
30
|
+
"@robodev-ai/sdk": "0.16.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^24.3.0",
|
package/src/collect-files.ts
CHANGED
|
@@ -149,7 +149,8 @@ function isBackendPath(path: string): boolean {
|
|
|
149
149
|
inner.startsWith("api/") ||
|
|
150
150
|
inner.startsWith("jobs/") ||
|
|
151
151
|
inner.startsWith("hooks/") ||
|
|
152
|
-
inner.startsWith("sockets/")
|
|
152
|
+
inner.startsWith("sockets/") ||
|
|
153
|
+
inner.startsWith("mcp/")
|
|
153
154
|
) {
|
|
154
155
|
return inner.endsWith(".ts") && !isBackendTestPath(inner);
|
|
155
156
|
}
|
|
@@ -175,13 +176,18 @@ export function mixedBackendLayoutError(paths: Iterable<string>): string | null
|
|
|
175
176
|
const hasNewDb = list.includes("robodev/database.ts");
|
|
176
177
|
const hasLegacyDb = list.includes("database.ts");
|
|
177
178
|
const hasLegacyApiJobs = list.some(
|
|
178
|
-
(path) =>
|
|
179
|
+
(path) =>
|
|
180
|
+
path.startsWith("api/") ||
|
|
181
|
+
path.startsWith("jobs/") ||
|
|
182
|
+
path.startsWith("sockets/") ||
|
|
183
|
+
path.startsWith("mcp/"),
|
|
179
184
|
);
|
|
180
185
|
const hasNewApiJobs = list.some(
|
|
181
186
|
(path) =>
|
|
182
187
|
path.startsWith("robodev/api/") ||
|
|
183
188
|
path.startsWith("robodev/jobs/") ||
|
|
184
|
-
path.startsWith("robodev/sockets/")
|
|
189
|
+
path.startsWith("robodev/sockets/") ||
|
|
190
|
+
path.startsWith("robodev/mcp/"),
|
|
185
191
|
);
|
|
186
192
|
if (hasNewDb && (hasLegacyDb || hasLegacyApiJobs)) {
|
|
187
193
|
return "Do not mix robodev/ backend files with root database.ts, api/, or jobs/";
|
package/src/dev-inspect.test.ts
CHANGED
|
@@ -429,3 +429,18 @@ test("pdf inspect records byte length and format, not HTML text", () => {
|
|
|
429
429
|
assert.equal(event.error, "pdf_failed");
|
|
430
430
|
assert.equal(JSON.stringify(event).includes("secret invoice"), false);
|
|
431
431
|
});
|
|
432
|
+
|
|
433
|
+
test("dashboard links the MCP endpoint only when the project has tools", async () => {
|
|
434
|
+
const runtime = {
|
|
435
|
+
routes: [{ method: "get", path: "/api/planets", file: "api/planets.ts" }],
|
|
436
|
+
jobs: [],
|
|
437
|
+
};
|
|
438
|
+
const without = await buildDashboardState({ inspect: createDevInspect(), runtime });
|
|
439
|
+
assert.equal(without.overview.links.mcp, undefined);
|
|
440
|
+
const withTools = await buildDashboardState({
|
|
441
|
+
inspect: createDevInspect(),
|
|
442
|
+
runtime: { ...runtime, mcpTools: [{ name: "get_planets" }] },
|
|
443
|
+
});
|
|
444
|
+
assert.equal(withTools.overview.links.mcp, "/_robodev/mcp");
|
|
445
|
+
assert.equal(isHistogramExcludedPath("/_robodev/mcp"), true);
|
|
446
|
+
});
|
package/src/dev-inspect.ts
CHANGED
|
@@ -42,6 +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
46
|
]);
|
|
46
47
|
|
|
47
48
|
export const RESERVED_AUTH_ROUTES: Array<{ method: string; path: string; file: string }> = [
|
|
@@ -214,7 +215,14 @@ export type DashboardState = {
|
|
|
214
215
|
google: DashboardGoogle;
|
|
215
216
|
requestHistogram: RequestHistogramBucket[];
|
|
216
217
|
jobHistogram: JobHistogramBucket[];
|
|
217
|
-
links: {
|
|
218
|
+
links: {
|
|
219
|
+
swagger: string;
|
|
220
|
+
scalar: string;
|
|
221
|
+
openapi: string;
|
|
222
|
+
enterprise: string;
|
|
223
|
+
/** Present when the project has MCP tools. */
|
|
224
|
+
mcp?: string;
|
|
225
|
+
};
|
|
218
226
|
};
|
|
219
227
|
routes: Array<{ method: string; path: string; file: string }>;
|
|
220
228
|
calls: Array<{ at: number; method: string; path: string; status: number; durationMs: number }>;
|
|
@@ -246,6 +254,7 @@ export type DashboardRuntime = {
|
|
|
246
254
|
routes: Array<{ method: string; path: string; file: string }>;
|
|
247
255
|
jobs: Array<{ name: string; file: string; def: { cron?: string } }>;
|
|
248
256
|
sockets?: Array<{ name: string; path: string; file: string; def: { auth?: ApiAuth } }>;
|
|
257
|
+
mcpTools?: Array<{ name: string }>;
|
|
249
258
|
pool?: {
|
|
250
259
|
query: <R extends Record<string, unknown> = Record<string, unknown>>(
|
|
251
260
|
sql: string,
|
|
@@ -602,6 +611,7 @@ export async function buildDashboardState(input: {
|
|
|
602
611
|
scalar: "/_robodev/scalar",
|
|
603
612
|
openapi: "/openapi.json",
|
|
604
613
|
enterprise: "/robodev-enterprise-spec.json",
|
|
614
|
+
...(input.runtime?.mcpTools?.length ? { mcp: "/_robodev/mcp" } : {}),
|
|
605
615
|
},
|
|
606
616
|
},
|
|
607
617
|
routes,
|
|
@@ -1076,6 +1086,9 @@ export function inspectHtml(): string {
|
|
|
1076
1086
|
{ href: state.overview.links.openapi, label: "openapi.json" },
|
|
1077
1087
|
{ href: state.overview.links.enterprise, label: "Enterprise spec" }
|
|
1078
1088
|
];
|
|
1089
|
+
if (state.overview.links.mcp) {
|
|
1090
|
+
items.push({ href: state.overview.links.mcp, label: "MCP (POST)" });
|
|
1091
|
+
}
|
|
1079
1092
|
for (var i = 0; i < items.length; i++) {
|
|
1080
1093
|
var a = document.createElement("a");
|
|
1081
1094
|
a.href = items[i].href;
|
package/src/dev-project.ts
CHANGED
|
@@ -7,14 +7,14 @@ export type DevFile = { path: string; content: string };
|
|
|
7
7
|
export type DevProject = {
|
|
8
8
|
/** Where `.robodev/dev/` and the root `package.json` live. */
|
|
9
9
|
root: string;
|
|
10
|
-
/** Absolute directory holding `database.ts` plus `api/`, `jobs/`, `hooks/`, `sockets/`. */
|
|
10
|
+
/** Absolute directory holding `database.ts` plus `api/`, `jobs/`, `hooks/`, `sockets/`, `mcp/`. */
|
|
11
11
|
backendDir: string;
|
|
12
12
|
/** Backend layout that was matched, for the startup banner. */
|
|
13
13
|
layout: "root" | "robodev" | "packages/be";
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
/** Directories under the backend root that hold compilable project code. */
|
|
17
|
-
export const BACKEND_DIRS = ["api", "jobs", "hooks", "sockets"] as const;
|
|
17
|
+
export const BACKEND_DIRS = ["api", "jobs", "hooks", "sockets", "mcp"] as const;
|
|
18
18
|
|
|
19
19
|
const SKIP_DIRS = new Set(["node_modules", "dist", ".git", ".robodev"]);
|
|
20
20
|
const MAX_BACKEND_FILES = 400;
|
package/src/dev-server.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { Readable } from "node:stream";
|
|
2
3
|
import cors from "@fastify/cors";
|
|
3
4
|
import websocket from "@fastify/websocket";
|
|
@@ -13,7 +14,25 @@ import {
|
|
|
13
14
|
buildOpenApi,
|
|
14
15
|
buildEnterpriseSpec,
|
|
15
16
|
handleLocalAuth,
|
|
17
|
+
handleProjectMcpHttp,
|
|
18
|
+
handleProjectOAuth,
|
|
16
19
|
isLocalAuthPath,
|
|
20
|
+
isProjectMcpPath,
|
|
21
|
+
isProjectOAuthRequestPath,
|
|
22
|
+
localPasswordSignIn,
|
|
23
|
+
localPasswordSignUp,
|
|
24
|
+
authenticateProjectMcpToken,
|
|
25
|
+
isProjectMcpAccessToken,
|
|
26
|
+
projectGoogleOverrideMode,
|
|
27
|
+
projectMcpResource,
|
|
28
|
+
revokeRefresh,
|
|
29
|
+
runMcpOp,
|
|
30
|
+
runtimeVersion,
|
|
31
|
+
verifyProjectUserToken,
|
|
32
|
+
type McpCaller,
|
|
33
|
+
type OAuthHttpResponse,
|
|
34
|
+
type ProjectMcpHttpResponse,
|
|
35
|
+
type RuntimeMcpTool,
|
|
17
36
|
isReservedStoragePath,
|
|
18
37
|
ENTERPRISE_SPEC_PATH,
|
|
19
38
|
matchProjectRoute,
|
|
@@ -53,6 +72,7 @@ export type DevRuntime = {
|
|
|
53
72
|
routes: RuntimeRoute[];
|
|
54
73
|
jobs: RuntimeJob[];
|
|
55
74
|
sockets: RuntimeSocket[];
|
|
75
|
+
mcpTools: RuntimeMcpTool[];
|
|
56
76
|
openapi: Record<string, unknown>;
|
|
57
77
|
};
|
|
58
78
|
|
|
@@ -70,13 +90,76 @@ export type DevServerOptions = {
|
|
|
70
90
|
googleClientSecret?: string;
|
|
71
91
|
storageDir: string;
|
|
72
92
|
projectRoot?: string;
|
|
93
|
+
/** Shown on the MCP consent page and as the MCP server name. */
|
|
94
|
+
projectName?: string;
|
|
73
95
|
};
|
|
74
96
|
|
|
75
97
|
const SWAGGER_PATHS = new Set(["/_robodev/docs", "/_robodev/swagger", "/docs", "/swagger"]);
|
|
76
98
|
const SCALAR_PATHS = new Set(["/_robodev/scalar", "/scalar"]);
|
|
77
99
|
|
|
78
|
-
export function buildDevOpenApi(
|
|
79
|
-
|
|
100
|
+
export function buildDevOpenApi(
|
|
101
|
+
projectName: string,
|
|
102
|
+
routes: RuntimeRoute[],
|
|
103
|
+
mcpTools: RuntimeMcpTool[] = [],
|
|
104
|
+
) {
|
|
105
|
+
return buildOpenApi({ title: `${projectName} APIs`, projectId: "local", routes, mcpTools });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The URL MCP clients connect to, printed in the banner. */
|
|
109
|
+
export function devMcpUrl(listenOrigin: string): string {
|
|
110
|
+
return `${listenOrigin}/_robodev/mcp`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function devOAuthSecret(options: DevServerOptions): string {
|
|
114
|
+
return createHash("sha256")
|
|
115
|
+
.update(`${options.jwtSecret}:${options.projectId}:project-mcp-oauth`)
|
|
116
|
+
.digest("base64url");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function sendRuntimeResponse(
|
|
120
|
+
reply: FastifyReply,
|
|
121
|
+
response: ProjectMcpHttpResponse | OAuthHttpResponse,
|
|
122
|
+
): Promise<void> {
|
|
123
|
+
reply.code(response.status);
|
|
124
|
+
for (const [name, value] of Object.entries(response.headers)) reply.header(name, value);
|
|
125
|
+
await (response.body === undefined ? reply.send() : reply.send(response.body));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* `robodev dev` has no project Auth settings store: Auth is on, password sign-in and sign-up
|
|
130
|
+
* are on, and Google is on unless the project `.env` sets only one of its two keys.
|
|
131
|
+
*/
|
|
132
|
+
function devOAuthSettings(options: DevServerOptions) {
|
|
133
|
+
return {
|
|
134
|
+
enabled: true,
|
|
135
|
+
emailPasswordEnabled: true,
|
|
136
|
+
allowSignups: true,
|
|
137
|
+
googleEnabled:
|
|
138
|
+
projectGoogleOverrideMode(options.googleClientId, options.googleClientSecret) !== "one",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function devMcpCaller(
|
|
143
|
+
options: DevServerOptions,
|
|
144
|
+
runtime: DevRuntime,
|
|
145
|
+
token: string,
|
|
146
|
+
): Promise<McpCaller> {
|
|
147
|
+
if (isProjectMcpAccessToken(token)) {
|
|
148
|
+
const principal = await authenticateProjectMcpToken(
|
|
149
|
+
runtime.pool,
|
|
150
|
+
token,
|
|
151
|
+
projectMcpResource(options.listenOrigin),
|
|
152
|
+
);
|
|
153
|
+
return principal
|
|
154
|
+
? { user: principal.user, via: "mcp_token", invalid: false }
|
|
155
|
+
: { user: null, via: null, invalid: true };
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const user = await verifyProjectUserToken(token, options.projectId, options.jwtSecret);
|
|
159
|
+
return { user, via: "project_jwt", invalid: false };
|
|
160
|
+
} catch {
|
|
161
|
+
return { user: null, via: null, invalid: true };
|
|
162
|
+
}
|
|
80
163
|
}
|
|
81
164
|
|
|
82
165
|
function pathnameOf(url: string): string {
|
|
@@ -453,6 +536,8 @@ export async function startDevServer(
|
|
|
453
536
|
origin: true,
|
|
454
537
|
credentials: true,
|
|
455
538
|
methods: ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
539
|
+
// Browser MCP clients (MCP Inspector) read the 401 challenge to find the OAuth server.
|
|
540
|
+
exposedHeaders: ["WWW-Authenticate", "Mcp-Session-Id"],
|
|
456
541
|
});
|
|
457
542
|
await app.register(websocket);
|
|
458
543
|
app.get("/sockets/*", { websocket: true }, (socket, request) => {
|
|
@@ -631,6 +716,108 @@ export async function startDevServer(
|
|
|
631
716
|
return;
|
|
632
717
|
}
|
|
633
718
|
|
|
719
|
+
if (isProjectMcpPath(pathname)) {
|
|
720
|
+
const headers = request.headers as Record<string, string | string[] | undefined>;
|
|
721
|
+
const clients = () => routeClients(options, getRuntime);
|
|
722
|
+
const response = await handleProjectMcpHttp(
|
|
723
|
+
{ method: request.method, headers, body: request.body },
|
|
724
|
+
{
|
|
725
|
+
tools: runtime.mcpTools,
|
|
726
|
+
serverName: options.projectName ?? "robodev-dev",
|
|
727
|
+
serverVersion: runtimeVersion(),
|
|
728
|
+
authEnabled: true,
|
|
729
|
+
origin: options.listenOrigin,
|
|
730
|
+
resolveCaller: (token) => devMcpCaller(options, runtime, token),
|
|
731
|
+
verifyTools: async (names, requestHeaders) => {
|
|
732
|
+
const outcome = await runMcpOp({
|
|
733
|
+
db: runtime.db,
|
|
734
|
+
mcpTools: runtime.mcpTools,
|
|
735
|
+
op: { op: "verify", tools: names },
|
|
736
|
+
headers: requestHeaders,
|
|
737
|
+
clients,
|
|
738
|
+
});
|
|
739
|
+
const payload = outcome.kind === "ok" ? outcome.encoded.payload : null;
|
|
740
|
+
return Array.isArray(payload) ? payload.map(Boolean) : names.map(() => false);
|
|
741
|
+
},
|
|
742
|
+
invokeRoute: async (input) => {
|
|
743
|
+
const started = Date.now();
|
|
744
|
+
const outcome = await runRoute({
|
|
745
|
+
db: runtime.db,
|
|
746
|
+
routes: runtime.routes,
|
|
747
|
+
method: input.method,
|
|
748
|
+
pathname: input.pathname,
|
|
749
|
+
query: input.query,
|
|
750
|
+
body: input.body,
|
|
751
|
+
headers: input.headers,
|
|
752
|
+
authorization: request.headers.authorization,
|
|
753
|
+
resolveUser: async () => null,
|
|
754
|
+
user: input.user,
|
|
755
|
+
clients,
|
|
756
|
+
mapError: (error) => codedErrorBody(error),
|
|
757
|
+
});
|
|
758
|
+
const status = outcome.kind === "ok" ? outcome.encoded.status : outcome.status;
|
|
759
|
+
const durationMs = Date.now() - started;
|
|
760
|
+
options.inspect.record({
|
|
761
|
+
kind: "request",
|
|
762
|
+
method: input.method.toUpperCase(),
|
|
763
|
+
path: input.pathname,
|
|
764
|
+
status,
|
|
765
|
+
durationMs,
|
|
766
|
+
});
|
|
767
|
+
console.log(
|
|
768
|
+
`${input.method.toUpperCase()} ${input.pathname} ${status} ${durationMs}ms (mcp ${input.tool.name})`,
|
|
769
|
+
);
|
|
770
|
+
return outcome;
|
|
771
|
+
},
|
|
772
|
+
invokeTool: (input) =>
|
|
773
|
+
runMcpOp({
|
|
774
|
+
db: runtime.db,
|
|
775
|
+
mcpTools: runtime.mcpTools,
|
|
776
|
+
op: { op: "tool", tool: input.tool.name, input: input.input, user: input.user },
|
|
777
|
+
headers: input.headers,
|
|
778
|
+
clients,
|
|
779
|
+
mapError: (error) => codedErrorBody(error),
|
|
780
|
+
}),
|
|
781
|
+
},
|
|
782
|
+
);
|
|
783
|
+
await sendRuntimeResponse(reply, response);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (isProjectOAuthRequestPath(pathname)) {
|
|
788
|
+
const response = await handleProjectOAuth(
|
|
789
|
+
{
|
|
790
|
+
method: request.method,
|
|
791
|
+
pathname,
|
|
792
|
+
query: (request.query ?? {}) as Record<string, unknown>,
|
|
793
|
+
body: request.body,
|
|
794
|
+
headers: request.headers as Record<string, string | string[] | undefined>,
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
db: runtime.pool,
|
|
798
|
+
projectName: options.projectName ?? "robodev dev",
|
|
799
|
+
origin: options.listenOrigin,
|
|
800
|
+
secret: devOAuthSecret(options),
|
|
801
|
+
settings: devOAuthSettings(options),
|
|
802
|
+
signIn: (input) => localPasswordSignIn(runtime.pool, input),
|
|
803
|
+
signUp: (input) => localPasswordSignUp(runtime.pool, input),
|
|
804
|
+
verifyAppAccessToken: async (token) => {
|
|
805
|
+
try {
|
|
806
|
+
return await verifyProjectUserToken(token, options.projectId, options.jwtSecret);
|
|
807
|
+
} catch {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
revokeAppRefreshToken: (token) => revokeRefresh(runtime.pool, token),
|
|
812
|
+
clientIp: request.ip,
|
|
813
|
+
},
|
|
814
|
+
);
|
|
815
|
+
if (response) {
|
|
816
|
+
await sendRuntimeResponse(reply, response);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
634
821
|
if (isLocalAuthPath(pathname)) {
|
|
635
822
|
const result = await handleLocalAuth(
|
|
636
823
|
{
|
package/src/dev.ts
CHANGED
|
@@ -36,7 +36,7 @@ import { externalDevPostgres, startDevPostgres, type DevPostgres } from "./dev-p
|
|
|
36
36
|
import { starbaseUrl } from "./config.js";
|
|
37
37
|
import { devRouteClients } from "./dev-clients.js";
|
|
38
38
|
import { createDevInspect } from "./dev-inspect.js";
|
|
39
|
-
import { buildDevOpenApi, startDevServer, type DevRuntime } from "./dev-server.js";
|
|
39
|
+
import { buildDevOpenApi, devMcpUrl, startDevServer, type DevRuntime } from "./dev-server.js";
|
|
40
40
|
|
|
41
41
|
export type DevArgs = {
|
|
42
42
|
port: number;
|
|
@@ -162,6 +162,7 @@ export async function main(args: string[]): Promise<void> {
|
|
|
162
162
|
await mkdir(storageDir, { recursive: true });
|
|
163
163
|
|
|
164
164
|
let current: DevRuntime | null = null;
|
|
165
|
+
let projectName = "robodev dev";
|
|
165
166
|
let lastSchemaSource = "";
|
|
166
167
|
let closing = false;
|
|
167
168
|
|
|
@@ -266,8 +267,10 @@ export async function main(args: string[]): Promise<void> {
|
|
|
266
267
|
routes: loaded.routes,
|
|
267
268
|
jobs: loaded.jobs,
|
|
268
269
|
sockets: loaded.sockets,
|
|
269
|
-
|
|
270
|
+
mcpTools: loaded.mcpTools,
|
|
271
|
+
openapi: buildDevOpenApi(loaded.database.name, loaded.routes, loaded.mcpTools),
|
|
270
272
|
};
|
|
273
|
+
projectName = loaded.database.name;
|
|
271
274
|
setProjectSocketNames(
|
|
272
275
|
DEV_PROJECT_ID,
|
|
273
276
|
loaded.sockets.map((socket) => socket.name),
|
|
@@ -287,6 +290,9 @@ export async function main(args: string[]): Promise<void> {
|
|
|
287
290
|
for (const socket of loaded.sockets) {
|
|
288
291
|
log(` WS ${socket.path}`);
|
|
289
292
|
}
|
|
293
|
+
for (const tool of loaded.mcpTools) {
|
|
294
|
+
log(` MCP ${tool.name}`);
|
|
295
|
+
}
|
|
290
296
|
if (previous) {
|
|
291
297
|
await previous.pool.end().catch(() => undefined);
|
|
292
298
|
}
|
|
@@ -316,6 +322,7 @@ export async function main(args: string[]): Promise<void> {
|
|
|
316
322
|
googleClientSecret: env.GOOGLE_CLIENT_SECRET,
|
|
317
323
|
storageDir,
|
|
318
324
|
projectRoot: project.root,
|
|
325
|
+
projectName,
|
|
319
326
|
},
|
|
320
327
|
() => current,
|
|
321
328
|
);
|
|
@@ -340,6 +347,9 @@ export async function main(args: string[]): Promise<void> {
|
|
|
340
347
|
console.log(`API ${apiUrl}`);
|
|
341
348
|
console.log(`Swagger ${apiUrl}/_robodev/docs`);
|
|
342
349
|
console.log(`Dashboard ${apiUrl}/_robodev`);
|
|
350
|
+
if ((current as DevRuntime | null)?.mcpTools.length) {
|
|
351
|
+
console.log(`MCP ${devMcpUrl(apiUrl)}`);
|
|
352
|
+
}
|
|
343
353
|
console.log(
|
|
344
354
|
`Project ${relative(process.cwd(), project.backendDir) || "."} (${project.layout})`,
|
|
345
355
|
);
|
|
@@ -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.16.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.52.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.
|
|
13
|
+
"@robodev-ai/sdk": "^0.16.0",
|
|
14
14
|
"@robodev-ai/client": "^0.5.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.52.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.16.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.52.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.16.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.52.0",
|
|
16
16
|
"rulesync": "^8.18.0",
|
|
17
17
|
"typescript": "^5.9.2"
|
|
18
18
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"generate:api": "orval"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@robodev-ai/sdk": "^0.
|
|
14
|
+
"@robodev-ai/sdk": "^0.16.0",
|
|
15
15
|
"@robodev-ai/client": "^0.5.0",
|
|
16
16
|
"react": "^18.3.1",
|
|
17
17
|
"react-dom": "^18.3.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.52.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.16.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.52.0",
|
|
16
16
|
"rulesync": "^8.18.0",
|
|
17
17
|
"typescript": "^5.9.2"
|
|
18
18
|
}
|