conduit-mcp 2.1.5 → 2.1.7

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/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startServer
4
- } from "./chunk-F5FJY3VP.js";
4
+ } from "./chunk-TUOCT4PR.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { parseArgs } from "util";
@@ -83,7 +83,7 @@ Detected AI clients: ${clients.map((c) => c.name).join(", ")}
83
83
  } else {
84
84
  console.log(`${client.name} \u2014 add this to ${client.configPath}:
85
85
  `);
86
- printConfigSnippet();
86
+ printConfigSnippet(client);
87
87
  }
88
88
  }
89
89
  }
@@ -134,6 +134,12 @@ function detectClients() {
134
134
  configPath: join(home, ".claude", "settings.json"),
135
135
  configKey: "mcpServers"
136
136
  },
137
+ {
138
+ name: "Codex",
139
+ configPath: join(home, ".codex", "config.toml"),
140
+ configKey: "mcp_servers",
141
+ format: "toml"
142
+ },
137
143
  {
138
144
  name: "Cursor",
139
145
  configPath: join(home, ".cursor", "mcp.json"),
@@ -173,35 +179,82 @@ function detectClients() {
173
179
  }
174
180
  });
175
181
  }
176
- function printConfigSnippet() {
177
- const snippet = {
178
- conduit: {
179
- command: "npx",
180
- args: ["-y", "conduit-mcp"]
181
- }
182
- };
183
- console.log(JSON.stringify(snippet, null, 2));
182
+ function serverSpec(client) {
183
+ const isCodex = client.format === "toml";
184
+ if (isCodex && platform() === "win32") {
185
+ return { command: "cmd", args: ["/c", "npx", "-y", "conduit-mcp"] };
186
+ }
187
+ return { command: "npx", args: ["-y", "conduit-mcp"] };
188
+ }
189
+ function printConfigSnippet(client) {
190
+ const spec = serverSpec(client ?? { name: "", configPath: "", configKey: "" });
191
+ if (client?.format === "toml") {
192
+ console.log(renderTomlSection("conduit", spec));
193
+ return;
194
+ }
195
+ console.log(JSON.stringify({ conduit: spec }, null, 2));
184
196
  }
185
197
  function writeClientConfig(client) {
186
198
  try {
187
- let config = {};
188
- if (existsSync(client.configPath)) {
189
- config = JSON.parse(readFileSync(client.configPath, "utf-8"));
190
- }
191
- const servers = config[client.configKey] ?? {};
192
- servers["conduit"] = {
193
- command: "npx",
194
- args: ["-y", "conduit-mcp"]
195
- };
196
- config[client.configKey] = servers;
197
199
  const dir = dirname(client.configPath);
198
200
  if (!existsSync(dir)) {
199
201
  mkdirSync(dir, { recursive: true });
200
202
  }
201
- writeFileSync(client.configPath, JSON.stringify(config, null, 2) + "\n");
203
+ if (client.format === "toml") {
204
+ writeTomlConfig(client);
205
+ } else {
206
+ writeJsonConfig(client);
207
+ }
202
208
  console.log(`${client.name} \u2014 config written to ${client.configPath}`);
203
209
  } catch (err) {
204
210
  console.log(`${client.name} \u2014 failed to write config: ${err}`);
205
211
  }
206
212
  }
213
+ function writeJsonConfig(client) {
214
+ let config = {};
215
+ if (existsSync(client.configPath)) {
216
+ config = JSON.parse(readFileSync(client.configPath, "utf-8"));
217
+ }
218
+ const servers = config[client.configKey] ?? {};
219
+ servers["conduit"] = serverSpec(client);
220
+ config[client.configKey] = servers;
221
+ writeFileSync(client.configPath, JSON.stringify(config, null, 2) + "\n");
222
+ }
223
+ function writeTomlConfig(client) {
224
+ const spec = serverSpec(client);
225
+ const section = renderTomlSection(`${client.configKey}.conduit`, spec);
226
+ const existing = existsSync(client.configPath) ? readFileSync(client.configPath, "utf-8") : "";
227
+ const header = `[${client.configKey}.conduit]`;
228
+ const updated = replaceOrAppendTomlSection(existing, header, section);
229
+ writeFileSync(client.configPath, updated);
230
+ }
231
+ function renderTomlSection(header, spec) {
232
+ const argsLiteral = spec.args.map((a) => JSON.stringify(a)).join(", ");
233
+ return `[${header}]
234
+ command = ${JSON.stringify(spec.command)}
235
+ args = [${argsLiteral}]
236
+ `;
237
+ }
238
+ function replaceOrAppendTomlSection(source, header, section) {
239
+ const headerLine = header;
240
+ const lines = source.split(/\r?\n/);
241
+ const startIdx = lines.findIndex((l) => l.trim() === headerLine);
242
+ if (startIdx === -1) {
243
+ const sep = source.length === 0 || source.endsWith("\n") ? "" : "\n";
244
+ return source + sep + (source.length ? "\n" : "") + section;
245
+ }
246
+ let endIdx = lines.length;
247
+ for (let i = startIdx + 1; i < lines.length; i++) {
248
+ const t = lines[i].trim();
249
+ if (t.startsWith("[") && t.endsWith("]")) {
250
+ endIdx = i;
251
+ break;
252
+ }
253
+ }
254
+ const before = lines.slice(0, startIdx).join("\n");
255
+ const after = lines.slice(endIdx).join("\n");
256
+ const joiner = before.length && !before.endsWith("\n") ? "\n" : "";
257
+ const trailing = after.length ? "\n" + after : "";
258
+ return before + joiner + section + trailing;
259
+ }
207
260
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +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":[]}
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(client);\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 format?: \"json\" | \"toml\";\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: \"Codex\",\n configPath: join(home, \".codex\", \"config.toml\"),\n configKey: \"mcp_servers\",\n format: \"toml\",\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\n// Codex spawns `command` directly via Node's child_process with no shell,\n// and on Windows that won't find `npx` (it's npx.cmd). Wrap with cmd /c.\nfunction serverSpec(client: ClientInfo): { command: string; args: string[] } {\n const isCodex = client.format === \"toml\";\n if (isCodex && platform() === \"win32\") {\n return { command: \"cmd\", args: [\"/c\", \"npx\", \"-y\", \"conduit-mcp\"] };\n }\n return { command: \"npx\", args: [\"-y\", \"conduit-mcp\"] };\n}\n\nfunction printConfigSnippet(client?: ClientInfo): void {\n const spec = serverSpec(client ?? { name: \"\", configPath: \"\", configKey: \"\" });\n if (client?.format === \"toml\") {\n console.log(renderTomlSection(\"conduit\", spec));\n return;\n }\n console.log(JSON.stringify({ conduit: spec }, null, 2));\n}\n\nfunction writeClientConfig(client: ClientInfo): void {\n try {\n const dir = dirname(client.configPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n if (client.format === \"toml\") {\n writeTomlConfig(client);\n } else {\n writeJsonConfig(client);\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\nfunction writeJsonConfig(client: ClientInfo): void {\n let config: Record<string, unknown> = {};\n if (existsSync(client.configPath)) {\n config = JSON.parse(readFileSync(client.configPath, \"utf-8\"));\n }\n const servers = (config[client.configKey] as Record<string, unknown>) ?? {};\n servers[\"conduit\"] = serverSpec(client);\n config[client.configKey] = servers;\n writeFileSync(client.configPath, JSON.stringify(config, null, 2) + \"\\n\");\n}\n\nfunction writeTomlConfig(client: ClientInfo): void {\n const spec = serverSpec(client);\n const section = renderTomlSection(`${client.configKey}.conduit`, spec);\n const existing = existsSync(client.configPath)\n ? readFileSync(client.configPath, \"utf-8\")\n : \"\";\n const header = `[${client.configKey}.conduit]`;\n const updated = replaceOrAppendTomlSection(existing, header, section);\n writeFileSync(client.configPath, updated);\n}\n\nfunction renderTomlSection(\n header: string,\n spec: { command: string; args: string[] },\n): string {\n const argsLiteral = spec.args.map((a) => JSON.stringify(a)).join(\", \");\n return `[${header}]\\ncommand = ${JSON.stringify(spec.command)}\\nargs = [${argsLiteral}]\\n`;\n}\n\n// Replace an existing `[header]` block (up to the next top-level `[` or EOF),\n// or append if absent. Keeps other sections intact without a full TOML parse.\nfunction replaceOrAppendTomlSection(\n source: string,\n header: string,\n section: string,\n): string {\n const headerLine = header;\n const lines = source.split(/\\r?\\n/);\n const startIdx = lines.findIndex((l) => l.trim() === headerLine);\n if (startIdx === -1) {\n const sep = source.length === 0 || source.endsWith(\"\\n\") ? \"\" : \"\\n\";\n return source + sep + (source.length ? \"\\n\" : \"\") + section;\n }\n let endIdx = lines.length;\n for (let i = startIdx + 1; i < lines.length; i++) {\n const t = lines[i].trim();\n if (t.startsWith(\"[\") && t.endsWith(\"]\")) {\n endIdx = i;\n break;\n }\n }\n const before = lines.slice(0, startIdx).join(\"\\n\");\n const after = lines.slice(endIdx).join(\"\\n\");\n const joiner = before.length && !before.endsWith(\"\\n\") ? \"\\n\" : \"\";\n const trailing = after.length ? \"\\n\" + after : \"\";\n return before + joiner + section + trailing;\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  startServer
3
- } from "./chunk-F5FJY3VP.js";
3
+ } from "./chunk-TUOCT4PR.js";
4
4
  export {
5
5
  startServer
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduit-mcp",
3
- "version": "2.1.5",
3
+ "version": "2.1.7",
4
4
  "description": "WebSocket-first MCP bridge between AI coding assistants and Roblox Studio",
5
5
  "type": "module",
6
6
  "bin": {
Binary file