Skip to content

Commit

Permalink
fixed intellij interaction
Browse files Browse the repository at this point in the history
  • Loading branch information
Kvadratni committed Jan 8, 2025
1 parent 47ee30e commit df01349
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 27 deletions.
1 change: 1 addition & 0 deletions crates/goose-mcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ xcap = "0.0.14"
reqwest = { version = "0.11", features = ["json"] }
async-trait = "0.1"
parking_lot = "0.12"
chrono = "0.4"

[dev-dependencies]
sysinfo = "0.32.1"
Expand Down
1 change: 1 addition & 0 deletions crates/goose-mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

```bash
npx @modelcontextprotocol/inspector cargo run -p developer
npx @modelcontextprotocol/inspector cargo run -p jetbrains
```

Then visit the Inspector in the browser window and test the different endpoints.
113 changes: 96 additions & 17 deletions crates/goose-mcp/src/jetbrains/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ impl JetBrainsProxy {
debug!("Sending test request to {}/mcp/list_tools", endpoint);

let response = match self.client.get(&format!("{}/mcp/list_tools", endpoint)).send().await {
Ok(resp) => resp,
Ok(resp) => {
debug!("Got response with status: {}", resp.status());
resp
}
Err(e) => {
debug!("Error testing endpoint {}: {}", endpoint, e);
return Ok(false);
Expand All @@ -64,7 +67,13 @@ impl JetBrainsProxy {
}

let current_response = response.text().await?;
debug!("Received response: {:.100}...", current_response);
debug!("Received response: {}", current_response);

// Try to parse as JSON array to validate format
if serde_json::from_str::<Vec<Value>>(&current_response).is_err() {
debug!("Response is not a valid JSON array of tools");
return Ok(false);
}

let mut prev_response = self.previous_response.write().await;
if let Some(prev) = prev_response.as_ref() {
Expand All @@ -83,14 +92,19 @@ impl JetBrainsProxy {

// Check IDE_PORT environment variable first
if let Ok(port) = env::var("IDE_PORT") {
debug!("Found IDE_PORT environment variable: {}", port);
let test_endpoint = format!("http://127.0.0.1:{}/api", port);
if self.test_list_tools(&test_endpoint).await? {
debug!("IDE_PORT {} is working", port);
return Ok(test_endpoint);
}
debug!("IDE_PORT {} is not responding correctly", port);
return Err(anyhow!("Specified IDE_PORT={} is not responding correctly", port));
}

debug!("No IDE_PORT environment variable, scanning port range {}-{}",
PORT_RANGE_START, PORT_RANGE_END);

// Scan port range
for port in PORT_RANGE_START..=PORT_RANGE_END {
let candidate_endpoint = format!("http://127.0.0.1:{}/api", port);
Expand All @@ -102,55 +116,109 @@ impl JetBrainsProxy {
}
}

debug!("No working IDE endpoint found in port range");
Err(anyhow!("No working IDE endpoint found in range {}-{}",
PORT_RANGE_START, PORT_RANGE_END))
}

async fn update_ide_endpoint(&self) {
debug!("Updating IDE endpoint...");
match self.find_working_ide_endpoint().await {
Ok(endpoint) => {
let mut cached = self.cached_endpoint.write().await;
*cached = Some(endpoint);
debug!("Updated cached endpoint: {:?}", *cached);
*cached = Some(endpoint.clone());
debug!("Updated cached endpoint to: {}", endpoint);
}
Err(e) => {
debug!("Failed to update IDE endpoint: {}", e);
error!("Failed to update IDE endpoint: {}", e);
}
}
}

pub async fn list_tools(&self) -> Result<Vec<Tool>> {
let endpoint = self.cached_endpoint.read().await
.clone()
.ok_or_else(|| anyhow!("No working IDE endpoint available"))?;
debug!("Listing tools...");
let endpoint = {
let cached = self.cached_endpoint.read().await;
match cached.as_ref() {
Some(ep) => {
debug!("Using cached endpoint: {}", ep);
ep.clone()
}
None => {
debug!("No cached endpoint available");
return Ok(vec![]);
}
}
};

let response = self.client
debug!("Sending list_tools request to {}/mcp/list_tools", endpoint);
let response = match self.client
.get(&format!("{}/mcp/list_tools", endpoint))
.send()
.await?;
.await
{
Ok(resp) => {
debug!("Got response with status: {}", resp.status());
resp
}
Err(e) => {
debug!("Failed to send request: {}", e);
return Err(anyhow!("Failed to send request: {}", e));
}
};

if !response.status().is_success() {
debug!("Request failed with status: {}", response.status());
return Err(anyhow!("Failed to fetch tools with status {}", response.status()));
}

let tools_response: Value = response.json().await?;
let tools = tools_response
let response_text = response.text().await?;
debug!("Got response text: {}", response_text);

let tools_response: Value = serde_json::from_str(&response_text)
.map_err(|e| {
debug!("Failed to parse response as JSON: {}", e);
anyhow!("Failed to parse response as JSON: {}", e)
})?;

debug!("Parsed JSON response: {:?}", tools_response);

let tools: Vec<Tool> = tools_response
.as_array()
.ok_or_else(|| anyhow!("Invalid tools response format"))?
.ok_or_else(|| {
debug!("Response is not a JSON array");
anyhow!("Invalid tools response format: not an array")
})?
.iter()
.filter_map(|t| {
if let (Some(name), Some(description), Some(input_schema)) = (t["name"].as_str(), t["description"].as_str(), t["input_schema"].as_str()) {
if let (Some(name), Some(description)) = (
t["name"].as_str(),
t["description"].as_str()
) {
// Handle input_schema as either a string or an object
let input_schema = match &t["inputSchema"] {
Value::String(s) => Value::String(s.clone()),
Value::Object(o) => Value::Object(o.clone()),
_ => {
debug!("Invalid inputSchema format for tool {}: {:?}", name, t["inputSchema"]);
return None;
}
};

Some(Tool {
name: name.to_string(),
description: description.to_string(),
input_schema: serde_json::json!(input_schema),
input_schema,
})
} else {
debug!("Skipping invalid tool entry: {:?}", t);
None
}
})
.collect();

debug!("Collected {} tools", tools.len());
Ok(tools)
}

Expand All @@ -168,6 +236,7 @@ impl JetBrainsProxy {
.await?;

if !response.status().is_success() {
debug!("Response failed with status: {}", response.status());
return Err(anyhow!("Response failed: {}", response.status()));
}

Expand All @@ -180,10 +249,16 @@ impl JetBrainsProxy {
match (status, error) {
(Some(s), None) => (false, s.to_string()),
(None, Some(e)) => (true, e.to_string()),
_ => return Err(anyhow!("Invalid response format from IDE")),
_ => {
debug!("Invalid response format from IDE");
return Err(anyhow!("Invalid response format from IDE"));
}
}
}
_ => return Err(anyhow!("Unexpected response type from IDE")),
_ => {
debug!("Unexpected response type from IDE");
return Err(anyhow!("Unexpected response type from IDE"));
}
};

Ok(CallToolResult {
Expand All @@ -198,20 +273,24 @@ impl JetBrainsProxy {
}

pub async fn start(&self) -> Result<()> {
debug!("Initializing JetBrains Proxy...");
info!("Initializing JetBrains Proxy...");

// Initial endpoint check
debug!("Performing initial endpoint check...");
self.update_ide_endpoint().await;

// Schedule periodic endpoint checks
let proxy = self.clone();
tokio::spawn(async move {
loop {
tokio::time::sleep(ENDPOINT_CHECK_INTERVAL).await;
debug!("Performing periodic endpoint check...");
proxy.update_ide_endpoint().await;
}
});

debug!("JetBrains Proxy running");
info!("JetBrains Proxy running");
Ok(())
}
Expand All @@ -225,4 +304,4 @@ impl Clone for JetBrainsProxy {
client: Client::new(),
}
}
}
}
Loading

0 comments on commit df01349

Please sign in to comment.