Skip to content

Commit 1e70c0f

Browse files
committed
Adding Cursor support
1 parent 7cf1acc commit 1e70c0f

File tree

1 file changed

+84
-44
lines changed

1 file changed

+84
-44
lines changed

src/install.rs

+84-44
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,32 @@ 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 claude_config_path = get_claude_config();
135+
let results = vec![
136+
install_to_config(claude_config_path, &this_exe, "Claude"),
137+
install_to_config(get_cursor_config(), &this_exe, "Cursor"),
138+
];
139+
140+
let successes: Vec<_> = results
141+
.into_iter()
142+
.filter_map(|r| r.map_err(|e| errors.push(e)).ok())
143+
.collect();
144+
145+
if successes.is_empty() {
146+
let error = errors.into_iter().fold(
147+
eyre!("Failed to install to either Claude or Cursor"),
148+
|report, e| report.note(e),
149+
);
150+
return Err(error);
151+
}
111152

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

118159
#[cfg(target_os = "windows")]
@@ -128,14 +169,13 @@ pub async fn install() -> Result<()> {
128169
#[cfg(target_os = "macos")]
129170
pub async fn install() -> Result<()> {
130171
use native_dialog::{DialogBuilder, MessageLevel};
131-
let alert_builder = if let Err(e) = install_internal().await {
132-
DialogBuilder::message()
172+
let alert_builder = match install_internal().await {
173+
Err(e) => DialogBuilder::message()
133174
.set_level(MessageLevel::Error)
134-
.set_text(format!("Errors occurred: {:#}", e))
135-
} else {
136-
DialogBuilder::message()
175+
.set_text(format!("Errors occurred: {:#}", e)),
176+
Ok(msg) => DialogBuilder::message()
137177
.set_level(MessageLevel::Info)
138-
.set_text(DISCLAIMER)
178+
.set_text(msg),
139179
};
140180
let _ = alert_builder.set_title("Roblox Studio MCP").alert().show();
141181
Ok(())

0 commit comments

Comments
 (0)