robodev 0.49.0 → 0.50.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/mcp-protocol.ts +23 -120
- package/src/mcp-tools.ts +14 -178
- package/src/mcp.ts +4 -4
- package/templates/auth-chat/package.json +1 -1
- package/templates/backend/package.json +1 -1
- package/templates/empty/package.json +1 -1
- package/templates/marketplace/package.json +1 -1
- package/templates/povio/package.json +1 -1
- package/templates/shadcn/package.json +1 -1
- package/templates/space/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "robodev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.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.10.0",
|
|
30
|
+
"@robodev-ai/sdk": "0.15.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^24.3.0",
|
package/src/mcp-protocol.ts
CHANGED
|
@@ -1,120 +1,23 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
id?: JsonRpcId;
|
|
25
|
-
method: string;
|
|
26
|
-
params?: Record<string, unknown>;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export type JsonRpcResponse = {
|
|
30
|
-
jsonrpc: string;
|
|
31
|
-
id: JsonRpcId;
|
|
32
|
-
result?: unknown;
|
|
33
|
-
error?: { code: number; message: string; data?: unknown };
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export type McpToolSchema = {
|
|
37
|
-
name: string;
|
|
38
|
-
title: string;
|
|
39
|
-
description: string;
|
|
40
|
-
inputSchema: Record<string, unknown>;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export function parseMessage(line: string): JsonRpcRequest | null {
|
|
44
|
-
const trimmed = line.trim();
|
|
45
|
-
if (!trimmed) return null;
|
|
46
|
-
let parsed: unknown;
|
|
47
|
-
try {
|
|
48
|
-
parsed = JSON.parse(trimmed);
|
|
49
|
-
} catch {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
53
|
-
const record = parsed as Record<string, unknown>;
|
|
54
|
-
if (typeof record.method !== "string") return null;
|
|
55
|
-
const request: JsonRpcRequest = {
|
|
56
|
-
jsonrpc: typeof record.jsonrpc === "string" ? record.jsonrpc : JSON_RPC_VERSION,
|
|
57
|
-
method: record.method,
|
|
58
|
-
};
|
|
59
|
-
if (typeof record.id === "string" || typeof record.id === "number") request.id = record.id;
|
|
60
|
-
if (record.params && typeof record.params === "object" && !Array.isArray(record.params)) {
|
|
61
|
-
request.params = record.params as Record<string, unknown>;
|
|
62
|
-
}
|
|
63
|
-
return request;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function success(id: JsonRpcId, result: unknown): JsonRpcResponse {
|
|
67
|
-
return { jsonrpc: JSON_RPC_VERSION, id, result };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function failure(
|
|
71
|
-
id: JsonRpcId,
|
|
72
|
-
code: number,
|
|
73
|
-
message: string,
|
|
74
|
-
data?: unknown,
|
|
75
|
-
): JsonRpcResponse {
|
|
76
|
-
return { jsonrpc: JSON_RPC_VERSION, id, error: { code, message, ...(data ? { data } : {}) } };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Echo a version the client asked for when it is one we speak; otherwise offer ours. */
|
|
80
|
-
export function negotiateProtocolVersion(requested: unknown): string {
|
|
81
|
-
if (typeof requested !== "string") return MCP_PROTOCOL_VERSION;
|
|
82
|
-
return (SUPPORTED_PROTOCOL_VERSIONS as readonly string[]).includes(requested)
|
|
83
|
-
? requested
|
|
84
|
-
: MCP_PROTOCOL_VERSION;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function initializeResult(input: {
|
|
88
|
-
requestedProtocolVersion?: unknown;
|
|
89
|
-
serverName: string;
|
|
90
|
-
serverVersion: string;
|
|
91
|
-
instructions?: string;
|
|
92
|
-
}): Record<string, unknown> {
|
|
93
|
-
return {
|
|
94
|
-
protocolVersion: negotiateProtocolVersion(input.requestedProtocolVersion),
|
|
95
|
-
capabilities: { tools: { listChanged: false } },
|
|
96
|
-
serverInfo: { name: input.serverName, version: input.serverVersion },
|
|
97
|
-
...(input.instructions ? { instructions: input.instructions } : {}),
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** A tools/call result. MCP reports tool-level failure in the payload, not as a JSON-RPC error. */
|
|
102
|
-
export function toolResult(text: string, isError = false): Record<string, unknown> {
|
|
103
|
-
return { content: [{ type: "text", text }], isError };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** A notification carries no id and must never be answered. */
|
|
107
|
-
export function isNotification(request: JsonRpcRequest): boolean {
|
|
108
|
-
return request.id === undefined;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** Splits a stdin chunk into complete lines, returning the incomplete remainder. */
|
|
112
|
-
export function takeCompleteLines(
|
|
113
|
-
buffer: string,
|
|
114
|
-
chunk: string,
|
|
115
|
-
): { lines: string[]; buffer: string } {
|
|
116
|
-
const combined = `${buffer}${chunk}`;
|
|
117
|
-
const parts = combined.split("\n");
|
|
118
|
-
const remainder = parts.pop() ?? "";
|
|
119
|
-
return { lines: parts.filter((line) => line.trim().length > 0), buffer: remainder };
|
|
120
|
-
}
|
|
1
|
+
/** Moved to `@robodev-ai/runtime` so the hosted Starbase MCP server speaks the same protocol. */
|
|
2
|
+
export {
|
|
3
|
+
INTERNAL_ERROR,
|
|
4
|
+
INVALID_PARAMS,
|
|
5
|
+
INVALID_REQUEST,
|
|
6
|
+
JSON_RPC_VERSION,
|
|
7
|
+
MCP_PROTOCOL_VERSION,
|
|
8
|
+
METHOD_NOT_FOUND,
|
|
9
|
+
PARSE_ERROR,
|
|
10
|
+
SUPPORTED_PROTOCOL_VERSIONS,
|
|
11
|
+
failure,
|
|
12
|
+
initializeResult,
|
|
13
|
+
isNotification,
|
|
14
|
+
negotiateProtocolVersion,
|
|
15
|
+
parseMessage,
|
|
16
|
+
success,
|
|
17
|
+
takeCompleteLines,
|
|
18
|
+
toolResult,
|
|
19
|
+
type JsonRpcId,
|
|
20
|
+
type JsonRpcRequest,
|
|
21
|
+
type JsonRpcResponse,
|
|
22
|
+
type McpToolSchema,
|
|
23
|
+
} from "@robodev-ai/runtime";
|
package/src/mcp-tools.ts
CHANGED
|
@@ -1,178 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
reconciled on publish, and a destructive change needs force: true.
|
|
16
|
-
|
|
17
|
-
Use list_backend_files and read_backend_file to see what is already deployed before overwriting it.
|
|
18
|
-
Frontend files are not publishable here — run \`robodev deploy\` for those.`;
|
|
19
|
-
|
|
20
|
-
const FILE_PROPERTIES = {
|
|
21
|
-
path: {
|
|
22
|
-
type: "string",
|
|
23
|
-
description:
|
|
24
|
-
"Project-relative backend path. Either layout works: a Tiny workspace path such as packages/be/api/todos.ts, or the deployed path api/todos.ts.",
|
|
25
|
-
},
|
|
26
|
-
content: { type: "string", description: "Full file content. This replaces the live file." },
|
|
27
|
-
} as const;
|
|
28
|
-
|
|
29
|
-
export const MCP_TOOLS: McpToolSchema[] = [
|
|
30
|
-
{
|
|
31
|
-
name: "publish_backend_file",
|
|
32
|
-
title: "Publish one backend file",
|
|
33
|
-
description:
|
|
34
|
-
"Write one backend file to the live project and redeploy. The rest of the live tree is kept, so only the file being changed has to be sent. Returns the live API and docs URLs.",
|
|
35
|
-
inputSchema: {
|
|
36
|
-
type: "object",
|
|
37
|
-
properties: {
|
|
38
|
-
...FILE_PROPERTIES,
|
|
39
|
-
force: {
|
|
40
|
-
type: "boolean",
|
|
41
|
-
description:
|
|
42
|
-
"Allow a destructive schema change (a dropped table or column). Off by default.",
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
required: ["path", "content"],
|
|
46
|
-
additionalProperties: false,
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
name: "publish_backend_files",
|
|
51
|
-
title: "Publish several backend files",
|
|
52
|
-
description:
|
|
53
|
-
"Same as publish_backend_file for a batch — one deploy for the whole set, which is what you want when a route and database.ts change together. At most 50 files.",
|
|
54
|
-
inputSchema: {
|
|
55
|
-
type: "object",
|
|
56
|
-
properties: {
|
|
57
|
-
files: {
|
|
58
|
-
type: "array",
|
|
59
|
-
minItems: 1,
|
|
60
|
-
maxItems: 50,
|
|
61
|
-
items: {
|
|
62
|
-
type: "object",
|
|
63
|
-
properties: FILE_PROPERTIES,
|
|
64
|
-
required: ["path", "content"],
|
|
65
|
-
additionalProperties: false,
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
force: {
|
|
69
|
-
type: "boolean",
|
|
70
|
-
description:
|
|
71
|
-
"Allow a destructive schema change (a dropped table or column). Off by default.",
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
required: ["files"],
|
|
75
|
-
additionalProperties: false,
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
name: "list_backend_files",
|
|
80
|
-
title: "List live backend files",
|
|
81
|
-
description:
|
|
82
|
-
"The backend files currently deployed, with their sizes. Use it before overwriting a file you have not read.",
|
|
83
|
-
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: "read_backend_file",
|
|
87
|
-
title: "Read a live backend file",
|
|
88
|
-
description: "The current content of one deployed backend file.",
|
|
89
|
-
inputSchema: {
|
|
90
|
-
type: "object",
|
|
91
|
-
properties: { path: FILE_PROPERTIES.path },
|
|
92
|
-
required: ["path"],
|
|
93
|
-
additionalProperties: false,
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
name: "project_info",
|
|
98
|
-
title: "Project info",
|
|
99
|
-
description:
|
|
100
|
-
"The linked project's id and its live API, app and docs URLs, plus how this server authenticated.",
|
|
101
|
-
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
102
|
-
},
|
|
103
|
-
];
|
|
104
|
-
|
|
105
|
-
export type PublishFile = { path: string; content: string };
|
|
106
|
-
|
|
107
|
-
export function parsePublishFile(value: unknown): PublishFile | string {
|
|
108
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
109
|
-
return "Each file must be an object with `path` and `content`.";
|
|
110
|
-
}
|
|
111
|
-
const record = value as Record<string, unknown>;
|
|
112
|
-
const path = typeof record.path === "string" ? record.path.trim() : "";
|
|
113
|
-
if (!path) return "`path` is required and must be a non-empty string.";
|
|
114
|
-
if (typeof record.content !== "string") return `\`content\` is required for ${path}.`;
|
|
115
|
-
return { path: normalizeDeployPath(path), content: record.content };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function parsePublishFiles(value: unknown): PublishFile[] | string {
|
|
119
|
-
if (!Array.isArray(value)) return "`files` must be an array.";
|
|
120
|
-
if (value.length === 0) return "`files` must contain at least one file.";
|
|
121
|
-
if (value.length > 50) return "`files` accepts at most 50 files per publish.";
|
|
122
|
-
const files: PublishFile[] = [];
|
|
123
|
-
for (const entry of value) {
|
|
124
|
-
const parsed = parsePublishFile(entry);
|
|
125
|
-
if (typeof parsed === "string") return parsed;
|
|
126
|
-
files.push(parsed);
|
|
127
|
-
}
|
|
128
|
-
return files;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* The deploy path a workspace path lands on. A Tiny project keeps its backend under
|
|
133
|
-
* `packages/be/`, and the deployed tree does not, so `packages/be/api/todos.ts` and the live
|
|
134
|
-
* `api/todos.ts` are the same file. Mirrors the server's mapping so reads line up with publishes.
|
|
135
|
-
*/
|
|
136
|
-
export function deployPathFor(path: string): string {
|
|
137
|
-
return mapPackagesBePath(path) ?? normalizeDeployPath(path);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** Backend paths only. Mirrors the server's own check so an agent gets the reason locally. */
|
|
141
|
-
export function backendPathError(path: string): string | null {
|
|
142
|
-
const normalized = normalizeDeployPath(path);
|
|
143
|
-
if (normalized.startsWith("apps/") || normalized === "apps") {
|
|
144
|
-
return `${normalized} is frontend source. Publish the frontend with \`robodev deploy\`.`;
|
|
145
|
-
}
|
|
146
|
-
if (normalized.startsWith("src/") || normalized === "index.html") {
|
|
147
|
-
return `${normalized} is a hosted frontend file. Publish the frontend with \`robodev deploy\`.`;
|
|
148
|
-
}
|
|
149
|
-
if (normalized.startsWith("public/")) {
|
|
150
|
-
return `${normalized} is a static asset. Publish it with \`robodev deploy\`.`;
|
|
151
|
-
}
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export function formatFileList(files: readonly PublishFile[]): string {
|
|
156
|
-
if (files.length === 0) {
|
|
157
|
-
return "No backend files are deployed yet. Run `robodev deploy` once to create the project's first deployment.";
|
|
158
|
-
}
|
|
159
|
-
const rows = files
|
|
160
|
-
.slice()
|
|
161
|
-
.sort((a, b) => a.path.localeCompare(b.path))
|
|
162
|
-
.map((file) => `${file.path} (${Buffer.byteLength(file.content, "utf8")} bytes)`);
|
|
163
|
-
return `${files.length} backend file${files.length === 1 ? "" : "s"} live:\n${rows.join("\n")}`;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function formatPublishResult(input: {
|
|
167
|
-
published: readonly string[];
|
|
168
|
-
apiUrl?: string;
|
|
169
|
-
docsUrl?: string;
|
|
170
|
-
}): string {
|
|
171
|
-
const lines = [
|
|
172
|
-
`Published ${input.published.length} file${input.published.length === 1 ? "" : "s"} and redeployed:`,
|
|
173
|
-
...input.published.map((path) => ` ${path}`),
|
|
174
|
-
];
|
|
175
|
-
if (input.apiUrl) lines.push(`Live API: ${input.apiUrl}`);
|
|
176
|
-
if (input.docsUrl) lines.push(`Swagger: ${input.docsUrl}`);
|
|
177
|
-
return lines.join("\n");
|
|
178
|
-
}
|
|
1
|
+
/** Moved to `@robodev-ai/runtime`; the CLI serves the `cli` host variant. */
|
|
2
|
+
export {
|
|
3
|
+
MCP_SERVER_INSTRUCTIONS,
|
|
4
|
+
MCP_TOOLS,
|
|
5
|
+
backendPathError,
|
|
6
|
+
deployPathFor,
|
|
7
|
+
formatFileList,
|
|
8
|
+
formatPublishResult,
|
|
9
|
+
mcpInstructionsFor,
|
|
10
|
+
mcpToolsFor,
|
|
11
|
+
parsePublishFile,
|
|
12
|
+
parsePublishFiles,
|
|
13
|
+
type PublishFile,
|
|
14
|
+
} from "@robodev-ai/runtime";
|
package/src/mcp.ts
CHANGED
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
deployPathFor,
|
|
21
21
|
formatFileList,
|
|
22
22
|
formatPublishResult,
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
mcpInstructionsFor,
|
|
24
|
+
mcpToolsFor,
|
|
25
25
|
parsePublishFile,
|
|
26
26
|
parsePublishFiles,
|
|
27
27
|
type PublishFile,
|
|
@@ -205,12 +205,12 @@ export async function handleRequest(
|
|
|
205
205
|
requestedProtocolVersion: request.params?.protocolVersion,
|
|
206
206
|
serverName: SERVER_NAME,
|
|
207
207
|
serverVersion: await serverVersion(),
|
|
208
|
-
instructions:
|
|
208
|
+
instructions: mcpInstructionsFor("cli"),
|
|
209
209
|
}),
|
|
210
210
|
);
|
|
211
211
|
}
|
|
212
212
|
if (request.method === "ping") return success(id, {});
|
|
213
|
-
if (request.method === "tools/list") return success(id, { tools:
|
|
213
|
+
if (request.method === "tools/list") return success(id, { tools: mcpToolsFor("cli") });
|
|
214
214
|
if (request.method === "tools/call") {
|
|
215
215
|
const name = typeof request.params?.name === "string" ? request.params.name : "";
|
|
216
216
|
if (!name) return failure(id, INVALID_PARAMS, "tools/call requires a tool name");
|