conduit-mcp 2.0.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { parseArgs } from \"node:util\";\nimport { homedir, platform } from \"node:os\";\nimport { join } from \"node:path\";\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { startServer } from \"./index.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nconst { values } = parseArgs({\n options: {\n install: { type: \"boolean\", default: false },\n \"auto-config\": { type: \"boolean\", default: false },\n port: { type: \"string\", default: \"3200\" },\n mode: { type: \"string\", default: \"full\" },\n \"with-cloud\": { type: \"boolean\", default: false },\n \"with-rojo\": { type: \"boolean\", default: false },\n version: { type: \"boolean\", default: false },\n help: { type: \"boolean\", default: false },\n },\n strict: false,\n});\n\nif (values.version) {\n const pkg = JSON.parse(\n readFileSync(join(__dirname, \"..\", \"package.json\"), \"utf-8\"),\n );\n console.log(`conduit-mcp v${pkg.version}`);\n process.exit(0);\n}\n\nif (values.help) {\n console.log(`\nconduit-mcp — WebSocket-first MCP bridge for Roblox Studio\n\nUsage:\n npx conduit-mcp Start the MCP server (stdio transport)\n npx conduit-mcp --install Install the Studio plugin & show config\n npx conduit-mcp --version Print version\n\nOptions:\n --install Install the Conduit plugin to Roblox Studio\n --auto-config Also write config to detected AI clients\n --port <number> Override default bridge port (default: 3200)\n --mode <mode> Tool mode: 'full' (default) or 'inspector' (read-only tools only)\n --with-cloud Enable Cloud module (requires ROBLOX_CLOUD_API_KEY env var)\n --with-rojo Enable Rojo module (requires rojo on PATH)\n --help Show this help message\n`);\n process.exit(0);\n}\n\nif (values.install) {\n await install(values[\"auto-config\"] as boolean);\n process.exit(0);\n}\n\n// Validate mode\nconst mode = (values.mode as string) || \"full\";\nif (mode !== \"full\" && mode !== \"inspector\") {\n console.error(`Unknown mode: ${mode}. Expected 'full' or 'inspector'.`);\n process.exit(1);\n}\n\n// Default: start the MCP server\nconst port = parseInt(values.port as string) || 3200;\nawait startServer(port, {\n mode: mode as \"full\" | \"inspector\",\n withCloud: values[\"with-cloud\"] as boolean,\n withRojo: values[\"with-rojo\"] as boolean,\n});\n\n// ── Install logic ────────────────────────────────────────────────\n\nasync function install(autoConfig: boolean): Promise<void> {\n console.log(\"Conduit MCP — Installation\\n\");\n\n // Step 1: Install plugin\n const pluginInstalled = installPlugin();\n\n // Step 2: Detect & configure AI clients\n const clients = detectClients();\n\n if (clients.length === 0) {\n console.log(\"No AI clients detected. Add this to your client's MCP config:\\n\");\n printConfigSnippet();\n } else {\n console.log(`\\nDetected AI clients: ${clients.map((c) => c.name).join(\", \")}\\n`);\n\n for (const client of clients) {\n if (autoConfig) {\n writeClientConfig(client);\n } else {\n console.log(`${client.name} — add this to ${client.configPath}:\\n`);\n printConfigSnippet();\n }\n }\n }\n\n console.log(pluginInstalled ? \"\\nInstallation complete!\" : \"\\nPlugin installation failed — install manually from GitHub releases.\");\n console.log(\"\\nNext steps:\");\n console.log(\" 1. Open Roblox Studio\");\n console.log(\" 2. Enable HttpService (Game Settings > Security > Allow HTTP Requests)\");\n console.log(\" 3. The Conduit plugin will auto-connect when the MCP server starts\");\n}\n\nfunction installPlugin(): boolean {\n const pluginSrc = join(__dirname, \"..\", \"plugin\", \"Conduit.rbxm\");\n\n if (!existsSync(pluginSrc)) {\n console.log(\"Plugin file not found at: \" + pluginSrc);\n return false;\n }\n\n const pluginsDir = getPluginsDir();\n if (!pluginsDir) {\n console.log(\"Could not determine Roblox Studio plugins directory for this platform.\");\n return false;\n }\n\n if (!existsSync(pluginsDir)) {\n mkdirSync(pluginsDir, { recursive: true });\n }\n\n const dest = join(pluginsDir, \"Conduit.rbxm\");\n copyFileSync(pluginSrc, dest);\n console.log(`Plugin installed to: ${dest}`);\n return true;\n}\n\nfunction getPluginsDir(): string | null {\n const os = platform();\n if (os === \"win32\") {\n const localAppData = process.env.LOCALAPPDATA;\n if (localAppData) {\n return join(localAppData, \"Roblox\", \"Plugins\");\n }\n return join(homedir(), \"AppData\", \"Local\", \"Roblox\", \"Plugins\");\n }\n if (os === \"darwin\") {\n return join(homedir(), \"Documents\", \"Roblox\", \"Plugins\");\n }\n return null;\n}\n\ninterface ClientInfo {\n name: string;\n configPath: string;\n configKey: string;\n}\n\nfunction detectClients(): ClientInfo[] {\n const home = homedir();\n const candidates: ClientInfo[] = [\n {\n name: \"Claude Code\",\n configPath: join(home, \".claude\", \"settings.json\"),\n configKey: \"mcpServers\",\n },\n {\n name: \"Cursor\",\n configPath: join(home, \".cursor\", \"mcp.json\"),\n configKey: \"mcpServers\",\n },\n {\n name: \"Windsurf\",\n configPath: join(home, \".windsurf\", \"mcp.json\"),\n configKey: \"mcpServers\",\n },\n {\n name: \"Claude Desktop (Windows)\",\n configPath: join(\n process.env.APPDATA || join(home, \"AppData\", \"Roaming\"),\n \"Claude\",\n \"claude_desktop_config.json\",\n ),\n configKey: \"mcpServers\",\n },\n {\n name: \"Claude Desktop (macOS)\",\n configPath: join(\n home,\n \"Library\",\n \"Application Support\",\n \"Claude\",\n \"claude_desktop_config.json\",\n ),\n configKey: \"mcpServers\",\n },\n ];\n\n return candidates.filter((c) => {\n try {\n return existsSync(dirname(c.configPath));\n } catch {\n return false;\n }\n });\n}\n\nfunction printConfigSnippet(): void {\n const snippet = {\n conduit: {\n command: \"npx\",\n args: [\"-y\", \"conduit-mcp\"],\n },\n };\n console.log(JSON.stringify(snippet, null, 2));\n}\n\nfunction writeClientConfig(client: ClientInfo): void {\n try {\n let config: Record<string, unknown> = {};\n if (existsSync(client.configPath)) {\n config = JSON.parse(readFileSync(client.configPath, \"utf-8\"));\n }\n\n const servers = (config[client.configKey] as Record<string, unknown>) ?? {};\n servers[\"conduit\"] = {\n command: \"npx\",\n args: [\"-y\", \"conduit-mcp\"],\n };\n config[client.configKey] = servers;\n\n const dir = dirname(client.configPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(client.configPath, JSON.stringify(config, null, 2) + \"\\n\");\n console.log(`${client.name} — config written to ${client.configPath}`);\n } catch (err) {\n console.log(`${client.name} — failed to write config: ${err}`);\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,197 @@
1
+ // src/modules/cloud.ts
2
+ import { z } from "zod";
3
+ var BASE_URL = "https://apis.roblox.com";
4
+ function getApiKey() {
5
+ const key = process.env.ROBLOX_CLOUD_API_KEY;
6
+ if (!key) {
7
+ throw new Error(
8
+ "ROBLOX_CLOUD_API_KEY environment variable is not set. Get an API key from https://create.roblox.com/credentials"
9
+ );
10
+ }
11
+ return key;
12
+ }
13
+ async function cloudFetch(path, options = {}) {
14
+ const apiKey = getApiKey();
15
+ const url = `${BASE_URL}${path}`;
16
+ const res = await fetch(url, {
17
+ method: options.method ?? "GET",
18
+ headers: {
19
+ "x-api-key": apiKey,
20
+ "Content-Type": "application/json"
21
+ },
22
+ body: options.body ? JSON.stringify(options.body) : void 0
23
+ });
24
+ if (!res.ok) {
25
+ const text = await res.text();
26
+ throw new Error(`Cloud API error ${res.status}: ${text}`);
27
+ }
28
+ const contentType = res.headers.get("content-type") ?? "";
29
+ if (contentType.includes("application/json")) {
30
+ return res.json();
31
+ }
32
+ return res.text();
33
+ }
34
+ function register(server) {
35
+ server.registerTool(
36
+ "cloud",
37
+ {
38
+ title: "Roblox Open Cloud",
39
+ description: "Interact with the Roblox Open Cloud API. Requires ROBLOX_CLOUD_API_KEY env var.\n\nActions:\n- `datastore_get`: Read a key from a standard datastore.\n- `datastore_set`: Write a key to a standard datastore.\n- `datastore_list`: List keys in a standard datastore.\n- `messaging_publish`: Publish a message to a MessagingService topic.\n- `place_info`: Get information about a place.",
40
+ inputSchema: z.object({
41
+ action: z.enum([
42
+ "datastore_get",
43
+ "datastore_set",
44
+ "datastore_list",
45
+ "messaging_publish",
46
+ "place_info"
47
+ ]).describe("Cloud API action"),
48
+ universeId: z.number().int().describe("Roblox Universe ID"),
49
+ // Datastore params
50
+ datastoreName: z.string().optional().describe("Datastore name (for datastore actions)"),
51
+ key: z.string().optional().describe("Datastore entry key (for get/set)"),
52
+ value: z.unknown().optional().describe("Value to store (for datastore_set)"),
53
+ scope: z.string().optional().describe("Datastore scope (optional, default 'global')"),
54
+ prefix: z.string().optional().describe("Key prefix filter (for datastore_list)"),
55
+ limit: z.number().int().default(10).describe("Max results (for datastore_list)"),
56
+ // Messaging params
57
+ topic: z.string().optional().describe("MessagingService topic (for messaging_publish)"),
58
+ message: z.string().optional().describe("Message to publish (for messaging_publish)"),
59
+ // Place params
60
+ placeId: z.number().int().optional().describe("Place ID (for place_info)")
61
+ }),
62
+ annotations: {
63
+ readOnlyHint: false,
64
+ destructiveHint: false,
65
+ idempotentHint: false,
66
+ openWorldHint: true
67
+ }
68
+ },
69
+ async (params) => {
70
+ const { universeId } = params;
71
+ if (params.action === "datastore_get") {
72
+ if (!params.datastoreName || !params.key) {
73
+ return {
74
+ content: [
75
+ { type: "text", text: "datastore_get requires `datastoreName` and `key`." }
76
+ ]
77
+ };
78
+ }
79
+ const scope = params.scope ?? "global";
80
+ const path = `/datastores/v1/universes/${universeId}/standard-datastores/datastore/entries/entry?datastoreName=${encodeURIComponent(params.datastoreName)}&entryKey=${encodeURIComponent(params.key)}&scope=${encodeURIComponent(scope)}`;
81
+ const result = await cloudFetch(path);
82
+ return {
83
+ content: [
84
+ {
85
+ type: "text",
86
+ text: `**${params.datastoreName}/${params.key}:**
87
+ \`\`\`json
88
+ ${JSON.stringify(result, null, 2)}
89
+ \`\`\``
90
+ }
91
+ ]
92
+ };
93
+ }
94
+ if (params.action === "datastore_set") {
95
+ if (!params.datastoreName || !params.key) {
96
+ return {
97
+ content: [
98
+ { type: "text", text: "datastore_set requires `datastoreName`, `key`, and `value`." }
99
+ ]
100
+ };
101
+ }
102
+ const scope = params.scope ?? "global";
103
+ const path = `/datastores/v1/universes/${universeId}/standard-datastores/datastore/entries/entry?datastoreName=${encodeURIComponent(params.datastoreName)}&entryKey=${encodeURIComponent(params.key)}&scope=${encodeURIComponent(scope)}`;
104
+ const result = await cloudFetch(path, {
105
+ method: "POST",
106
+ body: params.value
107
+ });
108
+ return {
109
+ content: [
110
+ {
111
+ type: "text",
112
+ text: `Set **${params.datastoreName}/${params.key}** successfully.
113
+ \`\`\`json
114
+ ${JSON.stringify(result, null, 2)}
115
+ \`\`\``
116
+ }
117
+ ]
118
+ };
119
+ }
120
+ if (params.action === "datastore_list") {
121
+ if (!params.datastoreName) {
122
+ return {
123
+ content: [
124
+ { type: "text", text: "datastore_list requires `datastoreName`." }
125
+ ]
126
+ };
127
+ }
128
+ const scope = params.scope ?? "global";
129
+ let path = `/datastores/v1/universes/${universeId}/standard-datastores/datastore/entries?datastoreName=${encodeURIComponent(params.datastoreName)}&scope=${encodeURIComponent(scope)}&limit=${params.limit}`;
130
+ if (params.prefix) {
131
+ path += `&prefix=${encodeURIComponent(params.prefix)}`;
132
+ }
133
+ const result = await cloudFetch(path);
134
+ const keys = result.keys?.map((k) => k.key) ?? [];
135
+ return {
136
+ content: [
137
+ {
138
+ type: "text",
139
+ text: keys.length > 0 ? `**Keys in ${params.datastoreName}:**
140
+ ${keys.map((k) => `- \`${k}\``).join("\n")}` : `*No keys found in ${params.datastoreName}.*`
141
+ }
142
+ ]
143
+ };
144
+ }
145
+ if (params.action === "messaging_publish") {
146
+ if (!params.topic || !params.message) {
147
+ return {
148
+ content: [
149
+ { type: "text", text: "messaging_publish requires `topic` and `message`." }
150
+ ]
151
+ };
152
+ }
153
+ const path = `/messaging-service/v1/universes/${universeId}/topics/${encodeURIComponent(params.topic)}`;
154
+ await cloudFetch(path, {
155
+ method: "POST",
156
+ body: { message: params.message }
157
+ });
158
+ return {
159
+ content: [
160
+ {
161
+ type: "text",
162
+ text: `Message published to topic **${params.topic}**.`
163
+ }
164
+ ]
165
+ };
166
+ }
167
+ if (params.action === "place_info") {
168
+ const placeId = params.placeId;
169
+ if (!placeId) {
170
+ return {
171
+ content: [
172
+ { type: "text", text: "place_info requires `placeId`." }
173
+ ]
174
+ };
175
+ }
176
+ const path = `/universes/v1/${universeId}/places/${placeId}`;
177
+ const result = await cloudFetch(path);
178
+ return {
179
+ content: [
180
+ {
181
+ type: "text",
182
+ text: `**Place info:**
183
+ \`\`\`json
184
+ ${JSON.stringify(result, null, 2)}
185
+ \`\`\``
186
+ }
187
+ ]
188
+ };
189
+ }
190
+ return { content: [{ type: "text", text: "Unknown cloud action." }] };
191
+ }
192
+ );
193
+ }
194
+ export {
195
+ register
196
+ };
197
+ //# sourceMappingURL=cloud-HQIBAMRL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/modules/cloud.ts"],"sourcesContent":["import { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { log } from \"../utils/logger.js\";\n\nconst BASE_URL = \"https://apis.roblox.com\";\n\nfunction getApiKey(): string {\n const key = process.env.ROBLOX_CLOUD_API_KEY;\n if (!key) {\n throw new Error(\n \"ROBLOX_CLOUD_API_KEY environment variable is not set. \" +\n \"Get an API key from https://create.roblox.com/credentials\",\n );\n }\n return key;\n}\n\nasync function cloudFetch(\n path: string,\n options: { method?: string; body?: unknown } = {},\n): Promise<unknown> {\n const apiKey = getApiKey();\n const url = `${BASE_URL}${path}`;\n const res = await fetch(url, {\n method: options.method ?? \"GET\",\n headers: {\n \"x-api-key\": apiKey,\n \"Content-Type\": \"application/json\",\n },\n body: options.body ? JSON.stringify(options.body) : undefined,\n });\n\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Cloud API error ${res.status}: ${text}`);\n }\n\n const contentType = res.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"application/json\")) {\n return res.json();\n }\n return res.text();\n}\n\nexport function register(server: McpServer): void {\n server.registerTool(\n \"cloud\",\n {\n title: \"Roblox Open Cloud\",\n description:\n \"Interact with the Roblox Open Cloud API. Requires ROBLOX_CLOUD_API_KEY env var.\\n\\n\" +\n \"Actions:\\n\" +\n \"- `datastore_get`: Read a key from a standard datastore.\\n\" +\n \"- `datastore_set`: Write a key to a standard datastore.\\n\" +\n \"- `datastore_list`: List keys in a standard datastore.\\n\" +\n \"- `messaging_publish`: Publish a message to a MessagingService topic.\\n\" +\n \"- `place_info`: Get information about a place.\",\n inputSchema: z.object({\n action: z\n .enum([\n \"datastore_get\",\n \"datastore_set\",\n \"datastore_list\",\n \"messaging_publish\",\n \"place_info\",\n ])\n .describe(\"Cloud API action\"),\n universeId: z.number().int().describe(\"Roblox Universe ID\"),\n // Datastore params\n datastoreName: z\n .string()\n .optional()\n .describe(\"Datastore name (for datastore actions)\"),\n key: z\n .string()\n .optional()\n .describe(\"Datastore entry key (for get/set)\"),\n value: z\n .unknown()\n .optional()\n .describe(\"Value to store (for datastore_set)\"),\n scope: z\n .string()\n .optional()\n .describe(\"Datastore scope (optional, default 'global')\"),\n prefix: z\n .string()\n .optional()\n .describe(\"Key prefix filter (for datastore_list)\"),\n limit: z\n .number()\n .int()\n .default(10)\n .describe(\"Max results (for datastore_list)\"),\n // Messaging params\n topic: z\n .string()\n .optional()\n .describe(\"MessagingService topic (for messaging_publish)\"),\n message: z\n .string()\n .optional()\n .describe(\"Message to publish (for messaging_publish)\"),\n // Place params\n placeId: z\n .number()\n .int()\n .optional()\n .describe(\"Place ID (for place_info)\"),\n }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n },\n async (params) => {\n const { universeId } = params;\n\n if (params.action === \"datastore_get\") {\n if (!params.datastoreName || !params.key) {\n return {\n content: [\n { type: \"text\", text: \"datastore_get requires `datastoreName` and `key`.\" },\n ],\n };\n }\n const scope = params.scope ?? \"global\";\n const path = `/datastores/v1/universes/${universeId}/standard-datastores/datastore/entries/entry?datastoreName=${encodeURIComponent(params.datastoreName)}&entryKey=${encodeURIComponent(params.key)}&scope=${encodeURIComponent(scope)}`;\n const result = await cloudFetch(path);\n return {\n content: [\n {\n type: \"text\",\n text: `**${params.datastoreName}/${params.key}:**\\n\\`\\`\\`json\\n${JSON.stringify(result, null, 2)}\\n\\`\\`\\``,\n },\n ],\n };\n }\n\n if (params.action === \"datastore_set\") {\n if (!params.datastoreName || !params.key) {\n return {\n content: [\n { type: \"text\", text: \"datastore_set requires `datastoreName`, `key`, and `value`.\" },\n ],\n };\n }\n const scope = params.scope ?? \"global\";\n const path = `/datastores/v1/universes/${universeId}/standard-datastores/datastore/entries/entry?datastoreName=${encodeURIComponent(params.datastoreName)}&entryKey=${encodeURIComponent(params.key)}&scope=${encodeURIComponent(scope)}`;\n const result = await cloudFetch(path, {\n method: \"POST\",\n body: params.value,\n });\n return {\n content: [\n {\n type: \"text\",\n text: `Set **${params.datastoreName}/${params.key}** successfully.\\n\\`\\`\\`json\\n${JSON.stringify(result, null, 2)}\\n\\`\\`\\``,\n },\n ],\n };\n }\n\n if (params.action === \"datastore_list\") {\n if (!params.datastoreName) {\n return {\n content: [\n { type: \"text\", text: \"datastore_list requires `datastoreName`.\" },\n ],\n };\n }\n const scope = params.scope ?? \"global\";\n let path = `/datastores/v1/universes/${universeId}/standard-datastores/datastore/entries?datastoreName=${encodeURIComponent(params.datastoreName)}&scope=${encodeURIComponent(scope)}&limit=${params.limit}`;\n if (params.prefix) {\n path += `&prefix=${encodeURIComponent(params.prefix)}`;\n }\n const result = (await cloudFetch(path)) as { keys?: Array<{ key: string }> };\n const keys = result.keys?.map((k) => k.key) ?? [];\n return {\n content: [\n {\n type: \"text\",\n text: keys.length > 0\n ? `**Keys in ${params.datastoreName}:**\\n${keys.map((k) => `- \\`${k}\\``).join(\"\\n\")}`\n : `*No keys found in ${params.datastoreName}.*`,\n },\n ],\n };\n }\n\n if (params.action === \"messaging_publish\") {\n if (!params.topic || !params.message) {\n return {\n content: [\n { type: \"text\", text: \"messaging_publish requires `topic` and `message`.\" },\n ],\n };\n }\n const path = `/messaging-service/v1/universes/${universeId}/topics/${encodeURIComponent(params.topic)}`;\n await cloudFetch(path, {\n method: \"POST\",\n body: { message: params.message },\n });\n return {\n content: [\n {\n type: \"text\",\n text: `Message published to topic **${params.topic}**.`,\n },\n ],\n };\n }\n\n if (params.action === \"place_info\") {\n const placeId = params.placeId;\n if (!placeId) {\n return {\n content: [\n { type: \"text\", text: \"place_info requires `placeId`.\" },\n ],\n };\n }\n const path = `/universes/v1/${universeId}/places/${placeId}`;\n const result = await cloudFetch(path);\n return {\n content: [\n {\n type: \"text\",\n text: `**Place info:**\\n\\`\\`\\`json\\n${JSON.stringify(result, null, 2)}\\n\\`\\`\\``,\n },\n ],\n };\n }\n\n return { content: [{ type: \"text\", text: \"Unknown cloud action.\" }] };\n },\n );\n}\n"],"mappings":"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","names":[]}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ startServer
3
+ } from "./chunk-JDWBW44K.js";
4
+ export {
5
+ startServer
6
+ };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,112 @@
1
+ // src/modules/rojo.ts
2
+ import { z } from "zod";
3
+ import { execFile } from "child_process";
4
+ import { promisify } from "util";
5
+ var execFileAsync = promisify(execFile);
6
+ async function runRojo(args, cwd) {
7
+ try {
8
+ return await execFileAsync("rojo", args, {
9
+ cwd,
10
+ timeout: 3e4
11
+ });
12
+ } catch (err) {
13
+ if (err.code === "ENOENT") {
14
+ throw new Error(
15
+ "rojo command not found. Install it with: cargo install rojo, or via aftman/foreman."
16
+ );
17
+ }
18
+ throw new Error(`rojo ${args.join(" ")} failed: ${err.stderr || err.message}`);
19
+ }
20
+ }
21
+ function register(server) {
22
+ server.registerTool(
23
+ "rojo",
24
+ {
25
+ title: "Rojo Integration",
26
+ description: "Interact with Rojo for filesystem <-> Studio syncing. Requires `rojo` on PATH.\n\nActions:\n- `sourcemap`: Generate a sourcemap JSON showing Studio <-> filesystem mapping.\n- `build`: Build a .rbxm/.rbxmx/.rbxl file from a Rojo project.\n- `version`: Show installed Rojo version.",
27
+ inputSchema: z.object({
28
+ action: z.enum(["sourcemap", "build", "version"]).describe("Rojo action"),
29
+ project: z.string().optional().describe("Path to .project.json file (defaults to default.project.json in cwd)"),
30
+ output: z.string().optional().describe("Output file path (for 'build' action)")
31
+ }),
32
+ annotations: {
33
+ readOnlyHint: true,
34
+ destructiveHint: false,
35
+ idempotentHint: true,
36
+ openWorldHint: false
37
+ }
38
+ },
39
+ async (params) => {
40
+ if (params.action === "version") {
41
+ const { stdout } = await runRojo(["--version"]);
42
+ return {
43
+ content: [{ type: "text", text: stdout.trim() }]
44
+ };
45
+ }
46
+ if (params.action === "sourcemap") {
47
+ const args = ["sourcemap"];
48
+ if (params.project) {
49
+ args.push(params.project);
50
+ }
51
+ args.push("--output", "-");
52
+ const { stdout } = await runRojo(args);
53
+ try {
54
+ const sourcemap = JSON.parse(stdout);
55
+ const summary = formatSourcemap(sourcemap);
56
+ return {
57
+ content: [{ type: "text", text: summary }]
58
+ };
59
+ } catch {
60
+ return {
61
+ content: [{ type: "text", text: `**Raw sourcemap:**
62
+ \`\`\`json
63
+ ${stdout}
64
+ \`\`\`` }]
65
+ };
66
+ }
67
+ }
68
+ if (params.action === "build") {
69
+ if (!params.output) {
70
+ return {
71
+ content: [{ type: "text", text: "build action requires an `output` path." }]
72
+ };
73
+ }
74
+ const args = ["build"];
75
+ if (params.project) {
76
+ args.push(params.project);
77
+ }
78
+ args.push("--output", params.output);
79
+ const { stdout, stderr } = await runRojo(args);
80
+ const message = stdout || stderr || "Build completed.";
81
+ return {
82
+ content: [
83
+ { type: "text", text: `Rojo build complete: **${params.output}**
84
+ ${message.trim()}` }
85
+ ]
86
+ };
87
+ }
88
+ return { content: [{ type: "text", text: "Unknown rojo action." }] };
89
+ }
90
+ );
91
+ }
92
+ function formatSourcemap(sourcemap, depth = 0, indent = "") {
93
+ if (!sourcemap || !sourcemap.name) return "";
94
+ let line = `${indent}- **${sourcemap.name}** \`${sourcemap.className ?? ""}\``;
95
+ if (sourcemap.filePaths && sourcemap.filePaths.length > 0) {
96
+ line += ` \u2192 ${sourcemap.filePaths[0]}`;
97
+ }
98
+ let result = line + "\n";
99
+ if (sourcemap.children && depth < 3) {
100
+ for (const child of sourcemap.children) {
101
+ result += formatSourcemap(child, depth + 1, indent + " ");
102
+ }
103
+ } else if (sourcemap.children && sourcemap.children.length > 0) {
104
+ result += `${indent} *\u2026 ${sourcemap.children.length} children*
105
+ `;
106
+ }
107
+ return result;
108
+ }
109
+ export {
110
+ register
111
+ };
112
+ //# sourceMappingURL=rojo-TXV4K4PB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/modules/rojo.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { log } from \"../utils/logger.js\";\n\nconst execFileAsync = promisify(execFile);\n\nasync function runRojo(\n args: string[],\n cwd?: string,\n): Promise<{ stdout: string; stderr: string }> {\n try {\n return await execFileAsync(\"rojo\", args, {\n cwd,\n timeout: 30_000,\n });\n } catch (err: any) {\n if (err.code === \"ENOENT\") {\n throw new Error(\n \"rojo command not found. Install it with: cargo install rojo, or via aftman/foreman.\",\n );\n }\n throw new Error(`rojo ${args.join(\" \")} failed: ${err.stderr || err.message}`);\n }\n}\n\nexport function register(server: McpServer): void {\n server.registerTool(\n \"rojo\",\n {\n title: \"Rojo Integration\",\n description:\n \"Interact with Rojo for filesystem <-> Studio syncing. Requires `rojo` on PATH.\\n\\n\" +\n \"Actions:\\n\" +\n \"- `sourcemap`: Generate a sourcemap JSON showing Studio <-> filesystem mapping.\\n\" +\n \"- `build`: Build a .rbxm/.rbxmx/.rbxl file from a Rojo project.\\n\" +\n \"- `version`: Show installed Rojo version.\",\n inputSchema: z.object({\n action: z\n .enum([\"sourcemap\", \"build\", \"version\"])\n .describe(\"Rojo action\"),\n project: z\n .string()\n .optional()\n .describe(\"Path to .project.json file (defaults to default.project.json in cwd)\"),\n output: z\n .string()\n .optional()\n .describe(\"Output file path (for 'build' action)\"),\n }),\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async (params) => {\n if (params.action === \"version\") {\n const { stdout } = await runRojo([\"--version\"]);\n return {\n content: [{ type: \"text\", text: stdout.trim() }],\n };\n }\n\n if (params.action === \"sourcemap\") {\n const args = [\"sourcemap\"];\n if (params.project) {\n args.push(params.project);\n }\n args.push(\"--output\", \"-\"); // Output to stdout\n const { stdout } = await runRojo(args);\n try {\n const sourcemap = JSON.parse(stdout);\n const summary = formatSourcemap(sourcemap);\n return {\n content: [{ type: \"text\", text: summary }],\n };\n } catch {\n return {\n content: [{ type: \"text\", text: `**Raw sourcemap:**\\n\\`\\`\\`json\\n${stdout}\\n\\`\\`\\`` }],\n };\n }\n }\n\n if (params.action === \"build\") {\n if (!params.output) {\n return {\n content: [{ type: \"text\", text: \"build action requires an `output` path.\" }],\n };\n }\n const args = [\"build\"];\n if (params.project) {\n args.push(params.project);\n }\n args.push(\"--output\", params.output);\n const { stdout, stderr } = await runRojo(args);\n const message = stdout || stderr || \"Build completed.\";\n return {\n content: [\n { type: \"text\", text: `Rojo build complete: **${params.output}**\\n${message.trim()}` },\n ],\n };\n }\n\n return { content: [{ type: \"text\", text: \"Unknown rojo action.\" }] };\n },\n );\n}\n\nfunction formatSourcemap(sourcemap: any, depth = 0, indent = \"\"): string {\n if (!sourcemap || !sourcemap.name) return \"\";\n\n let line = `${indent}- **${sourcemap.name}** \\`${sourcemap.className ?? \"\"}\\``;\n if (sourcemap.filePaths && sourcemap.filePaths.length > 0) {\n line += ` → ${sourcemap.filePaths[0]}`;\n }\n\n let result = line + \"\\n\";\n\n if (sourcemap.children && depth < 3) {\n for (const child of sourcemap.children) {\n result += formatSourcemap(child, depth + 1, indent + \" \");\n }\n } else if (sourcemap.children && sourcemap.children.length > 0) {\n result += `${indent} *… ${sourcemap.children.length} children*\\n`;\n }\n\n return result;\n}\n"],"mappings":";AAAA,SAAS,SAAS;AAClB,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAI1B,IAAM,gBAAgB,UAAU,QAAQ;AAExC,eAAe,QACb,MACA,KAC6C;AAC7C,MAAI;AACF,WAAO,MAAM,cAAc,QAAQ,MAAM;AAAA,MACvC;AAAA,MACA,SAAS;AAAA,IACX,CAAC;AAAA,EACH,SAAS,KAAU;AACjB,QAAI,IAAI,SAAS,UAAU;AACzB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,IAAI,MAAM,QAAQ,KAAK,KAAK,GAAG,CAAC,YAAY,IAAI,UAAU,IAAI,OAAO,EAAE;AAAA,EAC/E;AACF;AAEO,SAAS,SAAS,QAAyB;AAChD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aACE;AAAA,MAKF,aAAa,EAAE,OAAO;AAAA,QACpB,QAAQ,EACL,KAAK,CAAC,aAAa,SAAS,SAAS,CAAC,EACtC,SAAS,aAAa;AAAA,QACzB,SAAS,EACN,OAAO,EACP,SAAS,EACT,SAAS,sEAAsE;AAAA,QAClF,QAAQ,EACL,OAAO,EACP,SAAS,EACT,SAAS,uCAAuC;AAAA,MACrD,CAAC;AAAA,MACD,aAAa;AAAA,QACX,cAAc;AAAA,QACd,iBAAiB;AAAA,QACjB,gBAAgB;AAAA,QAChB,eAAe;AAAA,MACjB;AAAA,IACF;AAAA,IACA,OAAO,WAAW;AAChB,UAAI,OAAO,WAAW,WAAW;AAC/B,cAAM,EAAE,OAAO,IAAI,MAAM,QAAQ,CAAC,WAAW,CAAC;AAC9C,eAAO;AAAA,UACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,OAAO,KAAK,EAAE,CAAC;AAAA,QACjD;AAAA,MACF;AAEA,UAAI,OAAO,WAAW,aAAa;AACjC,cAAM,OAAO,CAAC,WAAW;AACzB,YAAI,OAAO,SAAS;AAClB,eAAK,KAAK,OAAO,OAAO;AAAA,QAC1B;AACA,aAAK,KAAK,YAAY,GAAG;AACzB,cAAM,EAAE,OAAO,IAAI,MAAM,QAAQ,IAAI;AACrC,YAAI;AACF,gBAAM,YAAY,KAAK,MAAM,MAAM;AACnC,gBAAM,UAAU,gBAAgB,SAAS;AACzC,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,QAAQ,CAAC;AAAA,UAC3C;AAAA,QACF,QAAQ;AACN,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM;AAAA;AAAA,EAAmC,MAAM;AAAA,QAAW,CAAC;AAAA,UACvF;AAAA,QACF;AAAA,MACF;AAEA,UAAI,OAAO,WAAW,SAAS;AAC7B,YAAI,CAAC,OAAO,QAAQ;AAClB,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,0CAA0C,CAAC;AAAA,UAC7E;AAAA,QACF;AACA,cAAM,OAAO,CAAC,OAAO;AACrB,YAAI,OAAO,SAAS;AAClB,eAAK,KAAK,OAAO,OAAO;AAAA,QAC1B;AACA,aAAK,KAAK,YAAY,OAAO,MAAM;AACnC,cAAM,EAAE,QAAQ,OAAO,IAAI,MAAM,QAAQ,IAAI;AAC7C,cAAM,UAAU,UAAU,UAAU;AACpC,eAAO;AAAA,UACL,SAAS;AAAA,YACP,EAAE,MAAM,QAAQ,MAAM,0BAA0B,OAAO,MAAM;AAAA,EAAO,QAAQ,KAAK,CAAC,GAAG;AAAA,UACvF;AAAA,QACF;AAAA,MACF;AAEA,aAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,uBAAuB,CAAC,EAAE;AAAA,IACrE;AAAA,EACF;AACF;AAEA,SAAS,gBAAgB,WAAgB,QAAQ,GAAG,SAAS,IAAY;AACvE,MAAI,CAAC,aAAa,CAAC,UAAU,KAAM,QAAO;AAE1C,MAAI,OAAO,GAAG,MAAM,OAAO,UAAU,IAAI,QAAQ,UAAU,aAAa,EAAE;AAC1E,MAAI,UAAU,aAAa,UAAU,UAAU,SAAS,GAAG;AACzD,YAAQ,WAAM,UAAU,UAAU,CAAC,CAAC;AAAA,EACtC;AAEA,MAAI,SAAS,OAAO;AAEpB,MAAI,UAAU,YAAY,QAAQ,GAAG;AACnC,eAAW,SAAS,UAAU,UAAU;AACtC,gBAAU,gBAAgB,OAAO,QAAQ,GAAG,SAAS,IAAI;AAAA,IAC3D;AAAA,EACF,WAAW,UAAU,YAAY,UAAU,SAAS,SAAS,GAAG;AAC9D,cAAU,GAAG,MAAM,aAAQ,UAAU,SAAS,MAAM;AAAA;AAAA,EACtD;AAEA,SAAO;AACT;","names":[]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "conduit-mcp",
3
+ "version": "2.0.0",
4
+ "description": "WebSocket-first MCP bridge between AI coding assistants and Roblox Studio",
5
+ "type": "module",
6
+ "bin": {
7
+ "conduit-mcp": "dist/cli.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "files": [
11
+ "dist/",
12
+ "plugin/Conduit.rbxm"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsup",
16
+ "dev": "tsup --watch",
17
+ "test": "vitest run",
18
+ "test:watch": "vitest"
19
+ },
20
+ "dependencies": {
21
+ "@modelcontextprotocol/sdk": "^1.29.0",
22
+ "ws": "^8.18.0",
23
+ "zod": "^3.24.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^22.0.0",
27
+ "@types/ws": "^8.5.0",
28
+ "tsup": "^8.3.0",
29
+ "vitest": "^3.0.0"
30
+ },
31
+ "keywords": [
32
+ "mcp",
33
+ "roblox",
34
+ "roblox-studio",
35
+ "ai",
36
+ "websocket",
37
+ "claude",
38
+ "cursor",
39
+ "windsurf"
40
+ ],
41
+ "author": "Henri Elliott-Knight <henri@knightco.digital>",
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/henri-knightco/conduit-mcp.git"
46
+ },
47
+ "engines": {
48
+ "node": ">=18.0.0"
49
+ }
50
+ }