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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robodev",
3
- "version": "0.49.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/sdk": "0.15.0",
30
- "@robodev-ai/runtime": "0.9.0"
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",
@@ -1,120 +1,23 @@
1
- /**
2
- * The MCP stdio wire format, hand-rolled.
3
- *
4
- * `robodev` is installed globally by people who just want a CLI, so the surface it needs from MCP
5
- * — `initialize`, `tools/list`, `tools/call`, `ping` over newline-delimited JSON-RPC 2.0 on
6
- * stdio — is not worth a dependency on the SDK in a published package. Everything here is pure so
7
- * the protocol can be tested without a transport.
8
- */
9
-
10
- export const MCP_PROTOCOL_VERSION = "2025-06-18";
11
- export const SUPPORTED_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"] as const;
12
-
13
- export const JSON_RPC_VERSION = "2.0";
14
- export const PARSE_ERROR = -32700;
15
- export const INVALID_REQUEST = -32600;
16
- export const METHOD_NOT_FOUND = -32601;
17
- export const INVALID_PARAMS = -32602;
18
- export const INTERNAL_ERROR = -32603;
19
-
20
- export type JsonRpcId = string | number;
21
-
22
- export type JsonRpcRequest = {
23
- jsonrpc: string;
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
- import { mapPackagesBePath, normalizeDeployPath } from "@robodev-ai/runtime";
2
- import type { McpToolSchema } from "./mcp-protocol.js";
3
-
4
- /**
5
- * The tool surface an external coding agent needs to work on a Robodev backend: see what is live,
6
- * read one file, and push a file it just wrote. Frontend files are out of scope here — a hosted
7
- * frontend is a built artifact, so it goes through `robodev deploy`.
8
- */
9
- export const MCP_SERVER_INSTRUCTIONS = `Publish backend files to a live Robodev project.
10
-
11
- Write a file locally, then call publish_backend_file with its path and content — it merges over the
12
- live deployment and redeploys, so the route is live when the call returns. Only backend paths are
13
- accepted: database.ts, api/**, jobs/**, hooks/**, sockets/** and backend config such as
14
- package.json. Adding or changing a table means publishing database.ts too; the schema is
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
- MCP_SERVER_INSTRUCTIONS,
24
- MCP_TOOLS,
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: MCP_SERVER_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: MCP_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");
@@ -12,7 +12,7 @@
12
12
  "@robodev-ai/sdk": "^0.15.0"
13
13
  },
14
14
  "devDependencies": {
15
- "robodev": "^0.49.0",
15
+ "robodev": "^0.50.0",
16
16
  "rulesync": "^8.18.0",
17
17
  "typescript": "^5.9.2"
18
18
  }
@@ -11,7 +11,7 @@
11
11
  "@robodev-ai/sdk": "^0.15.0"
12
12
  },
13
13
  "devDependencies": {
14
- "robodev": "^0.49.0",
14
+ "robodev": "^0.50.0",
15
15
  "typescript": "^5.9.2"
16
16
  }
17
17
  }
@@ -16,7 +16,7 @@
16
16
  "react-dom": "^18.3.1"
17
17
  },
18
18
  "devDependencies": {
19
- "robodev": "^0.49.0",
19
+ "robodev": "^0.50.0",
20
20
  "typescript": "^5.9.2",
21
21
  "vite": "^6.0.0"
22
22
  }
@@ -12,7 +12,7 @@
12
12
  "@robodev-ai/sdk": "^0.15.0"
13
13
  },
14
14
  "devDependencies": {
15
- "robodev": "^0.49.0",
15
+ "robodev": "^0.50.0",
16
16
  "rulesync": "^8.18.0",
17
17
  "typescript": "^5.9.2"
18
18
  }
@@ -12,7 +12,7 @@
12
12
  "@robodev-ai/sdk": "^0.15.0"
13
13
  },
14
14
  "devDependencies": {
15
- "robodev": "^0.49.0",
15
+ "robodev": "^0.50.0",
16
16
  "rulesync": "^8.18.0",
17
17
  "typescript": "^5.9.2"
18
18
  }
@@ -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.49.0",
24
+ "robodev": "^0.50.0",
25
25
  "typescript": "^5.9.2",
26
26
  "vite": "^6.0.0"
27
27
  }
@@ -12,7 +12,7 @@
12
12
  "@robodev-ai/sdk": "^0.15.0"
13
13
  },
14
14
  "devDependencies": {
15
- "robodev": "^0.49.0",
15
+ "robodev": "^0.50.0",
16
16
  "rulesync": "^8.18.0",
17
17
  "typescript": "^5.9.2"
18
18
  }