Skip to content

Commit 3fa6f32

Browse files
Adding Cursor support (#4)
* Adding Cursor support * fixing linux clippy build * fixing codeowners * Update src/install.rs Co-authored-by: danielangelrbx <[email protected]> * removing spaces since apparently MCP can`t do spaces * missed lipo * bumping rmcp Fixes #6 Fixes #1 * readme update --------- Co-authored-by: danielangelrbx <[email protected]>
1 parent cad0f4e commit 3fa6f32

File tree

7 files changed

+123
-61
lines changed

7 files changed

+123
-61
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
rustup target add x86_64-apple-darwin aarch64-apple-darwin
2323
cargo build --release --target aarch64-apple-darwin --target x86_64-apple-darwin
2424
cargo bundle --release --target aarch64-apple-darwin
25-
lipo -create target/aarch64-apple-darwin/release/rbx-studio-mcp target/x86_64-apple-darwin/release/rbx-studio-mcp -output "target/aarch64-apple-darwin/release/bundle/osx/Roblox Studio MCP.app/Contents/MacOS/rbx-studio-mcp"
25+
lipo -create target/aarch64-apple-darwin/release/rbx-studio-mcp target/x86_64-apple-darwin/release/rbx-studio-mcp -output "target/aarch64-apple-darwin/release/bundle/osx/RobloxStudioMCP.app/Contents/MacOS/rbx-studio-mcp"
2626
- name: Sign and Notarize macOS binary
2727
run: ./util/sign.macos.sh
2828
env:

CODEOWNERS

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
/* @Roblox/rust-mcp-maintainers
2-
1+
@Roblox/rust-mcp-maintainers

Cargo.lock

Lines changed: 5 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ publish = false
66
license = "MIT"
77

88
[dependencies]
9-
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", rev = "b1542a6153493d078d49b465c1498deac9141e68", features = ["server", "transport-io"] }
9+
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", rev = "db03f63e76b5b32f65d34a1bd08ae56dab595f60", features = ["server", "transport-io"] }
1010
tokio = { version = "1", features = ["full"] }
1111
serde = { version = "1.0", features = ["derive"] }
1212
serde_json = "1.0"
@@ -28,6 +28,6 @@ core-foundation = "0.10.0"
2828
rojo = "7.4.4"
2929

3030
[package.metadata.bundle]
31-
name = "Roblox Studio MCP"
31+
name = "RobloxStudioMCP"
3232
description = "Model Context Protocol server for Roblox Studio"
3333
identifier = "com.rbx-mcp.server"

README.md

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Roblox Studio MCP Server
22

33
This repository contains a reference implementation of the Model Context Protocol (MCP) that enables
4-
communication between Roblox Studio via a plugin and [Claude Desktop](https://claude.ai/download).
4+
communication between Roblox Studio via a plugin and [Claude Desktop](https://claude.ai/download) or [Cursor](https://www.cursor.com/).
55
It consists of the following Rust-based components, which communicate through internal shared
66
objects.
77

@@ -23,16 +23,37 @@ The setup process also contains a short plugin installation and Claude Desktop c
2323

2424
### Install with release binaries
2525

26-
To set up the reference implementation:
26+
This MCP Server supports pretty much any MCP Client but will automatically set up only [Claude Desktop](https://claude.ai/download) and [Cursor](https://www.cursor.com/) if found.
2727

28-
1. Ensure you have [Roblox Studio](https://create.roblox.com/docs/en-us/studio/setup)
29-
and [Claude Desktop](https://claude.ai/download) installed and started at least once.
30-
1. Exit Claude and Roblox Studio if they are running.
28+
To set up automatically:
29+
30+
1. Ensure you have [Roblox Studio](https://create.roblox.com/docs/en-us/studio/setup),
31+
and [Claude Desktop](https://claude.ai/download)/[Cursor](https://www.cursor.com/) installed and started at least once.
32+
1. Exit MCP Clients and Roblox Studio if they are running.
3133
1. Download and run the installer:
3234
1. Go to the [releases](https://github.com/Roblox/studio-rust-mcp-server/releases) page and
3335
download the latest release for your platform.
3436
1. Unzip the downloaded file if necessary and run the installer.
35-
1. Restart Claude and Roblox Studio if they are running.
37+
1. Restart Claude/Cursor and Roblox Studio if they are running.
38+
39+
### Setting up manually
40+
41+
To set up manually add following to your MCP Client config:
42+
43+
```json
44+
{
45+
"mcpServers": {
46+
"Roblox Studio": {
47+
"args": [
48+
"--stdio"
49+
],
50+
"command": "Path-to-downloaded\\rbx-studio-mcp.exe"
51+
}
52+
}
53+
}
54+
```
55+
56+
On macOS the path would be something like `"/Applications/RobloxStudioMCP.app/Contents/MacOS/rbx-studio-mcp"` if you move the app to the Applications directory.
3657

3758
### Build from source
3859

src/install.rs

Lines changed: 85 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
use color_eyre::eyre::{eyre, Result, WrapErr};
2+
use color_eyre::Help;
23
use roblox_install::RobloxStudio;
34
use serde_json::{json, Value};
45
use std::fs::File;
56
use std::io::BufReader;
67
use std::io::Write;
78
use std::path::Path;
89
use std::path::PathBuf;
10+
use std::vec;
911
use std::{env, fs, io};
1012

11-
const DISCLAIMER: &str = "Roblox Studio MCP is ready to go.
12-
13-
Please restart Studio and Claude to apply the changes.
13+
fn get_message(successes: String) -> String {
14+
format!("Roblox Studio MCP is ready to go.
15+
Please restart Studio and MCP clients to apply the changes.
16+
17+
MCP Clients set up:
18+
{successes}
1419
1520
Note: connecting a third-party LLM to Roblox Studio via an MCP server will share your data with that external service provider. Please review their privacy practices carefully before proceeding.
16-
To uninstall, delete the MCPStudioPlugin.rbxm from your Plugins directory.";
21+
To uninstall, delete the MCPStudioPlugin.rbxm from your Plugins directory.")
22+
}
1723

1824
// returns OS dependant claude_desktop_config.json path
1925
fn get_claude_config() -> Result<PathBuf> {
@@ -35,6 +41,13 @@ fn get_claude_config() -> Result<PathBuf> {
3541
Ok(config_path)
3642
}
3743

44+
fn get_cursor_config() -> Result<PathBuf> {
45+
let home_dir = env::var_os("HOME")
46+
.or_else(|| env::var_os("USERPROFILE"))
47+
.unwrap();
48+
Ok(Path::new(&home_dir).join(".cursor").join("mcp.json"))
49+
}
50+
3851
#[cfg(target_os = "macos")]
3952
fn get_exe_path() -> Result<PathBuf> {
4053
use core_foundation::url::CFURL;
@@ -52,7 +65,46 @@ fn get_exe_path() -> io::Result<PathBuf> {
5265
env::current_exe()
5366
}
5467

55-
async fn install_internal() -> Result<()> {
68+
pub fn install_to_config<'a>(
69+
config_path: Result<PathBuf>,
70+
exe_path: &Path,
71+
name: &'a str,
72+
) -> Result<&'a str> {
73+
let config_path = config_path?;
74+
let mut config: serde_json::Map<String, Value> = {
75+
if !config_path.exists() {
76+
let mut file = File::create(&config_path).map_err(|e| {
77+
eyre!("Could not create {name} config file at {config_path:?}: {e:#?}")
78+
})?;
79+
file.write_all(serde_json::to_string(&serde_json::Map::new())?.as_bytes())?;
80+
}
81+
let config_file = File::open(&config_path)
82+
.map_err(|error| eyre!("Could not read or create {name} config file: {error:#?}"))?;
83+
let reader = BufReader::new(config_file);
84+
serde_json::from_reader(reader)?
85+
};
86+
87+
if !matches!(config.get("mcpServers"), Some(Value::Object(_))) {
88+
config.insert("mcpServers".to_string(), json!({}));
89+
}
90+
91+
config["mcpServers"]["Roblox Studio"] = json!({
92+
"command": &exe_path,
93+
"args": [
94+
"--stdio"
95+
]
96+
});
97+
98+
let mut file = File::create(&config_path)?;
99+
file.write_all(serde_json::to_string_pretty(&config)?.as_bytes())
100+
.map_err(|e| eyre!("Could not write to {name} config file at {config_path:?}: {e:#?}"))?;
101+
102+
println!("Installed MCP Studio plugin to {name} config {config_path:?}");
103+
104+
Ok(name)
105+
}
106+
107+
async fn install_internal() -> Result<String> {
56108
let plugin_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/MCPStudioPlugin.rbxm"));
57109
let studio = RobloxStudio::locate()?;
58110
let plugins = studio.plugins_path();
@@ -76,43 +128,31 @@ async fn install_internal() -> Result<()> {
76128
output_plugin.display()
77129
);
78130

79-
let claude_config_path = get_claude_config()?;
80-
81-
let mut config: serde_json::Map<String, Value> = {
82-
if !claude_config_path.exists() {
83-
let mut file = File::create(&claude_config_path).map_err(|e| {
84-
eyre!("Could not create Claude config file at {claude_config_path:?}: {e:#?}")
85-
})?;
86-
file.write_all(serde_json::to_string(&serde_json::Map::new())?.as_bytes())?;
87-
}
88-
let claude_config_file = File::open(&claude_config_path)
89-
.map_err(|error| eyre!("Could not read or create claude config file: {error:#?}"))?;
90-
let reader = BufReader::new(claude_config_file);
91-
serde_json::from_reader(reader)?
92-
};
93-
94-
if !matches!(config.get("mcpServers"), Some(Value::Object(_))) {
95-
config.insert("mcpServers".to_string(), json!({}));
96-
}
97-
98131
let this_exe = get_exe_path()?;
99-
config["mcpServers"]["Roblox Studio"] = json!({
100-
"command": &this_exe,
101-
"args": [
102-
"--stdio"
103-
]
104-
});
105132

106-
let mut file = File::create(&claude_config_path)?;
107-
file.write_all(serde_json::to_string_pretty(&config)?.as_bytes())
108-
.map_err(|e| {
109-
eyre!("Could not write to Claude config file at {claude_config_path:?}: {e:#?}")
110-
})?;
133+
let mut errors = vec![];
134+
let results = vec![
135+
install_to_config(get_claude_config(), &this_exe, "Claude"),
136+
install_to_config(get_cursor_config(), &this_exe, "Cursor"),
137+
];
138+
139+
let successes: Vec<_> = results
140+
.into_iter()
141+
.filter_map(|r| r.map_err(|e| errors.push(e)).ok())
142+
.collect();
143+
144+
if successes.is_empty() {
145+
let error = errors.into_iter().fold(
146+
eyre!("Failed to install to either Claude or Cursor"),
147+
|report, e| report.note(e),
148+
);
149+
return Err(error);
150+
}
111151

112-
println!("Installed MCP Studio plugin to Claude config {claude_config_path:?}");
113152
println!();
114-
println!("{DISCLAIMER}");
115-
Ok(())
153+
let msg = get_message(successes.join("\n"));
154+
println!("{}", msg);
155+
Ok(msg)
116156
}
117157

118158
#[cfg(target_os = "windows")]
@@ -128,20 +168,20 @@ pub async fn install() -> Result<()> {
128168
#[cfg(target_os = "macos")]
129169
pub async fn install() -> Result<()> {
130170
use native_dialog::{DialogBuilder, MessageLevel};
131-
let alert_builder = if let Err(e) = install_internal().await {
132-
DialogBuilder::message()
171+
let alert_builder = match install_internal().await {
172+
Err(e) => DialogBuilder::message()
133173
.set_level(MessageLevel::Error)
134-
.set_text(format!("Errors occurred: {:#}", e))
135-
} else {
136-
DialogBuilder::message()
174+
.set_text(format!("Errors occurred: {:#}", e)),
175+
Ok(msg) => DialogBuilder::message()
137176
.set_level(MessageLevel::Info)
138-
.set_text(DISCLAIMER)
177+
.set_text(msg),
139178
};
140179
let _ = alert_builder.set_title("Roblox Studio MCP").alert().show();
141180
Ok(())
142181
}
143182

144183
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
145184
pub async fn install() -> Result<()> {
146-
install_internal().await
185+
install_internal().await?;
186+
Ok(())
147187
}

util/sign.macos.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ then
2424
echo "$APPLE_API_KEY_CONTENT" > "$APPLE_API_KEY"
2525

2626

27-
codesign -s "$IDENTITY" -v -f -o runtime --deep -i com.rbx-mcp.server --timestamp --entitlements util/App.entitlements --generate-entitlement-der "$BUNDLE_DIR/osx/Roblox Studio MCP.app"
27+
codesign -s "$IDENTITY" -v -f -o runtime --deep -i com.rbx-mcp.server --timestamp --entitlements util/App.entitlements --generate-entitlement-der "$BUNDLE_DIR/osx/RobloxStudioMCP.app"
2828
ditto -c -k $BUNDLE_DIR/osx $BUNDLE_DIR/bund.zip
2929
xcrun notarytool submit -k "$APPLE_API_KEY" -d "$APPLE_API_KEY_ID" -i "$APPLE_API_ISSUER" --wait --progress $BUNDLE_DIR/bund.zip
30-
xcrun stapler staple "$BUNDLE_DIR/osx/Roblox Studio MCP.app"
30+
xcrun stapler staple "$BUNDLE_DIR/osx/RobloxStudioMCP.app"
3131
fi
3232

3333
ditto -c -k $BUNDLE_DIR/osx output/macOS-rbx-studio-mcp.zip

0 commit comments

Comments
 (0)