mirror of
https://github.com/aljazceru/goose.git
synced 2025-12-19 23:24:23 +01:00
feat: deprecate jetbrains extension in favor of public one (#2589)
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use goose_mcp::{
|
use goose_mcp::{
|
||||||
ComputerControllerRouter, DeveloperRouter, GoogleDriveRouter, JetBrainsRouter, MemoryRouter,
|
ComputerControllerRouter, DeveloperRouter, GoogleDriveRouter, MemoryRouter, TutorialRouter,
|
||||||
TutorialRouter,
|
|
||||||
};
|
};
|
||||||
use mcp_server::router::RouterService;
|
use mcp_server::router::RouterService;
|
||||||
use mcp_server::{BoundedService, ByteTransport, Server};
|
use mcp_server::{BoundedService, ByteTransport, Server};
|
||||||
@@ -26,7 +25,6 @@ pub async fn run_server(name: &str) -> Result<()> {
|
|||||||
let router: Option<Box<dyn BoundedService>> = match name {
|
let router: Option<Box<dyn BoundedService>> = match name {
|
||||||
"developer" => Some(Box::new(RouterService(DeveloperRouter::new()))),
|
"developer" => Some(Box::new(RouterService(DeveloperRouter::new()))),
|
||||||
"computercontroller" => Some(Box::new(RouterService(ComputerControllerRouter::new()))),
|
"computercontroller" => Some(Box::new(RouterService(ComputerControllerRouter::new()))),
|
||||||
"jetbrains" => Some(Box::new(RouterService(JetBrainsRouter::new()))),
|
|
||||||
"google_drive" | "googledrive" => {
|
"google_drive" | "googledrive" => {
|
||||||
let router = GoogleDriveRouter::new().await;
|
let router = GoogleDriveRouter::new().await;
|
||||||
Some(Box::new(RouterService(router)))
|
Some(Box::new(RouterService(router)))
|
||||||
|
|||||||
@@ -1,236 +0,0 @@
|
|||||||
mod proxy;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use mcp_core::{
|
|
||||||
handler::{PromptError, ResourceError, ToolError},
|
|
||||||
prompt::Prompt,
|
|
||||||
protocol::{JsonRpcMessage, ServerCapabilities},
|
|
||||||
resource::Resource,
|
|
||||||
role::Role,
|
|
||||||
tool::Tool,
|
|
||||||
};
|
|
||||||
use mcp_server::router::CapabilitiesBuilder;
|
|
||||||
use mcp_server::Router;
|
|
||||||
use rmcp::model::Content;
|
|
||||||
use serde_json::Value;
|
|
||||||
use std::future::Future;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tokio::sync::{mpsc, Mutex};
|
|
||||||
use tokio::time::{sleep, Duration};
|
|
||||||
use tracing::error;
|
|
||||||
|
|
||||||
use self::proxy::JetBrainsProxy;
|
|
||||||
|
|
||||||
pub struct JetBrainsRouter {
|
|
||||||
tools: Arc<Mutex<Vec<Tool>>>,
|
|
||||||
proxy: Arc<JetBrainsProxy>,
|
|
||||||
instructions: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for JetBrainsRouter {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JetBrainsRouter {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let tools = Arc::new(Mutex::new(Vec::new()));
|
|
||||||
let proxy = Arc::new(JetBrainsProxy::new());
|
|
||||||
let instructions = "JetBrains IDE integration".to_string();
|
|
||||||
|
|
||||||
// Initialize the proxy
|
|
||||||
let proxy_clone = Arc::clone(&proxy);
|
|
||||||
tokio::spawn(async move {
|
|
||||||
if let Err(e) = proxy_clone.start().await {
|
|
||||||
error!("Failed to start JetBrains proxy: {}", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start the background task to update tools
|
|
||||||
let tools_clone = Arc::clone(&tools);
|
|
||||||
let proxy_clone = Arc::clone(&proxy);
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let mut interval = tokio::time::interval(Duration::from_secs(5));
|
|
||||||
loop {
|
|
||||||
interval.tick().await;
|
|
||||||
match proxy_clone.list_tools().await {
|
|
||||||
Ok(new_tools) => {
|
|
||||||
let mut tools = tools_clone.lock().await;
|
|
||||||
*tools = new_tools;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to update tools: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
tools,
|
|
||||||
proxy,
|
|
||||||
instructions,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn call_proxy_tool(
|
|
||||||
&self,
|
|
||||||
tool_name: String,
|
|
||||||
arguments: Value,
|
|
||||||
) -> Result<Vec<Content>, ToolError> {
|
|
||||||
let result = self
|
|
||||||
.proxy
|
|
||||||
.call_tool(&tool_name, arguments)
|
|
||||||
.await
|
|
||||||
.map_err(|e| ToolError::ExecutionError(e.to_string()))?;
|
|
||||||
|
|
||||||
// Create a success message for the assistant
|
|
||||||
let mut contents = vec![
|
|
||||||
Content::text(format!("Tool {} executed successfully", tool_name))
|
|
||||||
.with_audience(vec![Role::Assistant]),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Add the tool's result contents
|
|
||||||
contents.extend(result.content);
|
|
||||||
|
|
||||||
Ok(contents)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn ensure_tools(&self) -> Result<(), ToolError> {
|
|
||||||
let mut retry_count = 0;
|
|
||||||
let max_retries = 50; // 5 second total wait time
|
|
||||||
let retry_delay = Duration::from_millis(100);
|
|
||||||
|
|
||||||
while retry_count < max_retries {
|
|
||||||
let tools = self.tools.lock().await;
|
|
||||||
if !tools.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
drop(tools); // Release the lock before sleeping
|
|
||||||
|
|
||||||
sleep(retry_delay).await;
|
|
||||||
retry_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(ToolError::ExecutionError("Failed to get tools list from IDE. Make sure the IDE is running and the plugin is installed.".to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Router for JetBrainsRouter {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
"jetbrains".to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn instructions(&self) -> String {
|
|
||||||
self.instructions.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn capabilities(&self) -> ServerCapabilities {
|
|
||||||
CapabilitiesBuilder::new().with_tools(true).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_tools(&self) -> Vec<Tool> {
|
|
||||||
// Use block_in_place to avoid blocking the runtime
|
|
||||||
tokio::task::block_in_place(|| {
|
|
||||||
let rt = tokio::runtime::Builder::new_current_thread()
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
rt.block_on(async {
|
|
||||||
let tools = self.tools.lock().await;
|
|
||||||
if tools.is_empty() {
|
|
||||||
drop(tools);
|
|
||||||
if let Err(e) = self.ensure_tools().await {
|
|
||||||
error!("Failed to ensure tools: {}", e);
|
|
||||||
vec![]
|
|
||||||
} else {
|
|
||||||
self.tools.lock().await.clone()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tools.clone()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call_tool(
|
|
||||||
&self,
|
|
||||||
tool_name: &str,
|
|
||||||
arguments: Value,
|
|
||||||
_notifier: mpsc::Sender<JsonRpcMessage>,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<Vec<Content>, ToolError>> + Send + 'static>> {
|
|
||||||
let this = self.clone();
|
|
||||||
let tool_name = tool_name.to_string();
|
|
||||||
Box::pin(async move {
|
|
||||||
this.ensure_tools().await?;
|
|
||||||
this.call_proxy_tool(tool_name, arguments).await
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_resources(&self) -> Vec<Resource> {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_resource(
|
|
||||||
&self,
|
|
||||||
_uri: &str,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<String, ResourceError>> + Send + 'static>> {
|
|
||||||
Box::pin(async { Err(ResourceError::NotFound("Resource not found".into())) })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_prompts(&self) -> Vec<Prompt> {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_prompt(
|
|
||||||
&self,
|
|
||||||
prompt_name: &str,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<String, PromptError>> + Send + 'static>> {
|
|
||||||
let prompt_name = prompt_name.to_string();
|
|
||||||
Box::pin(async move {
|
|
||||||
Err(PromptError::NotFound(format!(
|
|
||||||
"Prompt {} not found",
|
|
||||||
prompt_name
|
|
||||||
)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for JetBrainsRouter {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
tools: Arc::clone(&self.tools),
|
|
||||||
proxy: Arc::clone(&self.proxy),
|
|
||||||
instructions: self.instructions.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use tokio::sync::OnceCell;
|
|
||||||
|
|
||||||
static JETBRAINS_ROUTER: OnceCell<JetBrainsRouter> = OnceCell::const_new();
|
|
||||||
|
|
||||||
async fn get_router() -> &'static JetBrainsRouter {
|
|
||||||
JETBRAINS_ROUTER
|
|
||||||
.get_or_init(|| async { JetBrainsRouter::new() })
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_router_creation() {
|
|
||||||
let router = get_router().await;
|
|
||||||
assert_eq!(router.name(), "jetbrains");
|
|
||||||
assert!(!router.instructions().is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_capabilities() {
|
|
||||||
let router = get_router().await;
|
|
||||||
let capabilities = router.capabilities();
|
|
||||||
assert!(capabilities.tools.is_some());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,343 +0,0 @@
|
|||||||
use anyhow::{anyhow, Result};
|
|
||||||
use mcp_core::Tool;
|
|
||||||
use reqwest::Client;
|
|
||||||
use rmcp::model::Content;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::Value;
|
|
||||||
use std::env;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
|
||||||
use tokio::sync::RwLock;
|
|
||||||
use tracing::{debug, error, info};
|
|
||||||
|
|
||||||
const PORT_RANGE_START: u16 = 63342;
|
|
||||||
const PORT_RANGE_END: u16 = 63352;
|
|
||||||
const ENDPOINT_CHECK_INTERVAL: Duration = Duration::from_secs(10);
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct IDEResponseOk {
|
|
||||||
status: String,
|
|
||||||
error: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct IDEResponseErr {
|
|
||||||
status: Option<String>,
|
|
||||||
error: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
pub struct CallToolResult {
|
|
||||||
pub content: Vec<Content>,
|
|
||||||
pub is_error: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct JetBrainsProxy {
|
|
||||||
cached_endpoint: Arc<RwLock<Option<String>>>,
|
|
||||||
previous_response: Arc<RwLock<Option<String>>>,
|
|
||||||
client: Client,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JetBrainsProxy {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
cached_endpoint: Arc::new(RwLock::new(None)),
|
|
||||||
previous_response: Arc::new(RwLock::new(None)),
|
|
||||||
client: Client::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn test_list_tools(&self, endpoint: &str) -> Result<bool> {
|
|
||||||
debug!("Sending test request to {}/mcp/list_tools", endpoint);
|
|
||||||
|
|
||||||
let response = match self
|
|
||||||
.client
|
|
||||||
.get(format!("{}/mcp/list_tools", endpoint))
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(resp) => {
|
|
||||||
debug!("Got response with status: {}", resp.status());
|
|
||||||
resp
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!("Error testing endpoint {}: {}", endpoint, e);
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !response.status().is_success() {
|
|
||||||
debug!("Test request failed with status {}", response.status());
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_response = response.text().await?;
|
|
||||||
debug!("Received response: {}", current_response);
|
|
||||||
|
|
||||||
// Try to parse as JSON array to validate format
|
|
||||||
if serde_json::from_str::<Vec<Value>>(¤t_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() {
|
|
||||||
if prev != ¤t_response {
|
|
||||||
debug!("Response changed since last check");
|
|
||||||
self.send_tools_changed().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*prev_response = Some(current_response);
|
|
||||||
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn find_working_ide_endpoint(&self) -> Result<String> {
|
|
||||||
debug!("Attempting to find working IDE endpoint...");
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
debug!("Testing port {}...", port);
|
|
||||||
|
|
||||||
if self.test_list_tools(&candidate_endpoint).await? {
|
|
||||||
debug!("Found working IDE endpoint at {}", candidate_endpoint);
|
|
||||||
return Ok(candidate_endpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.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>> {
|
|
||||||
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![]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!("Sending list_tools request to {}/mcp/list_tools", endpoint);
|
|
||||||
let response = match self
|
|
||||||
.client
|
|
||||||
.get(format!("{}/mcp/list_tools", endpoint))
|
|
||||||
.send()
|
|
||||||
.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 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(|| {
|
|
||||||
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)) =
|
|
||||||
(t["name"].as_str(), t["description"].as_str())
|
|
||||||
{
|
|
||||||
// Get just the first sentence of the description
|
|
||||||
let first_sentence = description
|
|
||||||
.split('.')
|
|
||||||
.next()
|
|
||||||
.unwrap_or(description)
|
|
||||||
.trim()
|
|
||||||
.to_string()
|
|
||||||
+ ".";
|
|
||||||
|
|
||||||
// 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: first_sentence,
|
|
||||||
input_schema,
|
|
||||||
annotations: None,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
debug!("Skipping invalid tool entry: {:?}", t);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
debug!("Collected {} tools", tools.len());
|
|
||||||
Ok(tools)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn call_tool(&self, name: &str, args: Value) -> Result<CallToolResult> {
|
|
||||||
let endpoint = self
|
|
||||||
.cached_endpoint
|
|
||||||
.read()
|
|
||||||
.await
|
|
||||||
.clone()
|
|
||||||
.ok_or_else(|| anyhow!("No working IDE endpoint available"))?;
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
"ENDPOINT: {} | Tool name: {} | args: {}",
|
|
||||||
endpoint, name, args
|
|
||||||
);
|
|
||||||
|
|
||||||
let response = self
|
|
||||||
.client
|
|
||||||
.post(format!("{}/mcp/{}", endpoint, name))
|
|
||||||
.json(&args)
|
|
||||||
.send()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if !response.status().is_success() {
|
|
||||||
debug!("Response failed with status: {}", response.status());
|
|
||||||
return Err(anyhow!("Response failed: {}", response.status()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let ide_response: Value = response.json().await?;
|
|
||||||
let (is_error, text) = match ide_response {
|
|
||||||
Value::Object(map) => {
|
|
||||||
let status = map.get("status").and_then(|v| v.as_str());
|
|
||||||
let error = map.get("error").and_then(|v| v.as_str());
|
|
||||||
|
|
||||||
match (status, error) {
|
|
||||||
(Some(s), None) => (false, s.to_string()),
|
|
||||||
(None, Some(e)) => (true, e.to_string()),
|
|
||||||
_ => {
|
|
||||||
debug!("Invalid response format from IDE");
|
|
||||||
return Err(anyhow!("Invalid response format from IDE"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
debug!("Unexpected response type from IDE");
|
|
||||||
return Err(anyhow!("Unexpected response type from IDE"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(CallToolResult {
|
|
||||||
content: vec![Content::text(text)],
|
|
||||||
is_error,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_tools_changed(&self) {
|
|
||||||
debug!("Sending tools changed notification");
|
|
||||||
// TODO: Implement notification mechanism when needed
|
|
||||||
}
|
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for JetBrainsProxy {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
cached_endpoint: Arc::clone(&self.cached_endpoint),
|
|
||||||
previous_response: Arc::clone(&self.previous_response),
|
|
||||||
client: Client::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,13 +10,11 @@ pub static APP_STRATEGY: Lazy<AppStrategyArgs> = Lazy::new(|| AppStrategyArgs {
|
|||||||
pub mod computercontroller;
|
pub mod computercontroller;
|
||||||
mod developer;
|
mod developer;
|
||||||
pub mod google_drive;
|
pub mod google_drive;
|
||||||
mod jetbrains;
|
|
||||||
mod memory;
|
mod memory;
|
||||||
mod tutorial;
|
mod tutorial;
|
||||||
|
|
||||||
pub use computercontroller::ComputerControllerRouter;
|
pub use computercontroller::ComputerControllerRouter;
|
||||||
pub use developer::DeveloperRouter;
|
pub use developer::DeveloperRouter;
|
||||||
pub use google_drive::GoogleDriveRouter;
|
pub use google_drive::GoogleDriveRouter;
|
||||||
pub use jetbrains::JetBrainsRouter;
|
|
||||||
pub use memory::MemoryRouter;
|
pub use memory::MemoryRouter;
|
||||||
pub use tutorial::TutorialRouter;
|
pub use tutorial::TutorialRouter;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use goose_mcp::{
|
use goose_mcp::{
|
||||||
ComputerControllerRouter, DeveloperRouter, GoogleDriveRouter, JetBrainsRouter, MemoryRouter,
|
ComputerControllerRouter, DeveloperRouter, GoogleDriveRouter, MemoryRouter, TutorialRouter,
|
||||||
TutorialRouter,
|
|
||||||
};
|
};
|
||||||
use mcp_server::router::RouterService;
|
use mcp_server::router::RouterService;
|
||||||
use mcp_server::{BoundedService, ByteTransport, Server};
|
use mcp_server::{BoundedService, ByteTransport, Server};
|
||||||
@@ -15,7 +14,6 @@ pub async fn run(name: &str) -> Result<()> {
|
|||||||
let router: Option<Box<dyn BoundedService>> = match name {
|
let router: Option<Box<dyn BoundedService>> = match name {
|
||||||
"developer" => Some(Box::new(RouterService(DeveloperRouter::new()))),
|
"developer" => Some(Box::new(RouterService(DeveloperRouter::new()))),
|
||||||
"computercontroller" => Some(Box::new(RouterService(ComputerControllerRouter::new()))),
|
"computercontroller" => Some(Box::new(RouterService(ComputerControllerRouter::new()))),
|
||||||
"jetbrains" => Some(Box::new(RouterService(JetBrainsRouter::new()))),
|
|
||||||
"google_drive" | "googledrive" => {
|
"google_drive" | "googledrive" => {
|
||||||
let router = GoogleDriveRouter::new().await;
|
let router = GoogleDriveRouter::new().await;
|
||||||
Some(Box::new(RouterService(router)))
|
Some(Box::new(RouterService(router)))
|
||||||
|
|||||||
@@ -1827,6 +1827,11 @@
|
|||||||
"format": "double",
|
"format": "double",
|
||||||
"description": "Cost per token for output (optional)",
|
"description": "Cost per token for output (optional)",
|
||||||
"nullable": true
|
"nullable": true
|
||||||
|
},
|
||||||
|
"supports_cache_control": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether this model supports cache control",
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,15 +26,6 @@
|
|||||||
"env_keys": [],
|
"env_keys": [],
|
||||||
"timeout": 300
|
"timeout": 300
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "jetbrains",
|
|
||||||
"name": "Jetbrains",
|
|
||||||
"description": "Integration with any Jetbrains IDE",
|
|
||||||
"enabled": false,
|
|
||||||
"type": "builtin",
|
|
||||||
"env_keys": [],
|
|
||||||
"timeout": 300
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "tutorial",
|
"id": "tutorial",
|
||||||
"name": "Tutorial",
|
"name": "Tutorial",
|
||||||
|
|||||||
@@ -32,17 +32,6 @@
|
|||||||
"timeout": 300,
|
"timeout": 300,
|
||||||
"bundled": true
|
"bundled": true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "jetbrains",
|
|
||||||
"display_name": "Jetbrains",
|
|
||||||
"name": "jetbrains",
|
|
||||||
"description": "Integration with any Jetbrains IDE",
|
|
||||||
"enabled": false,
|
|
||||||
"type": "builtin",
|
|
||||||
"env_keys": [],
|
|
||||||
"timeout": 300,
|
|
||||||
"bundled": true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "tutorial",
|
"id": "tutorial",
|
||||||
"name": "tutorial",
|
"name": "tutorial",
|
||||||
|
|||||||
Reference in New Issue
Block a user