grok-cli-acp 0.1.2
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/.env.example +42 -0
- package/.github/workflows/ci.yml +30 -0
- package/.github/workflows/rust.yml +22 -0
- package/.grok/.env.example +85 -0
- package/.grok/COMPLETE_FIX_SUMMARY.md +466 -0
- package/.grok/ENV_CONFIG_GUIDE.md +173 -0
- package/.grok/QUICK_REFERENCE.md +180 -0
- package/.grok/README.md +104 -0
- package/.grok/TESTING_GUIDE.md +393 -0
- package/CHANGELOG.md +465 -0
- package/CODE_REVIEW_SUMMARY.md +414 -0
- package/COMPLETE_FIX_SUMMARY.md +415 -0
- package/CONFIGURATION.md +489 -0
- package/CONTEXT_FILES_GUIDE.md +419 -0
- package/CONTRIBUTING.md +55 -0
- package/CURSOR_POSITION_FIX.md +206 -0
- package/Cargo.toml +88 -0
- package/ERROR_HANDLING_REPORT.md +361 -0
- package/FINAL_FIX_SUMMARY.md +462 -0
- package/FIXES.md +37 -0
- package/FIXES_SUMMARY.md +87 -0
- package/GROK_API_MIGRATION_SUMMARY.md +111 -0
- package/LICENSE +22 -0
- package/MIGRATION_TO_GROK_API.md +223 -0
- package/README.md +504 -0
- package/REVIEW_COMPLETE.md +416 -0
- package/REVIEW_QUICK_REFERENCE.md +173 -0
- package/SECURITY.md +463 -0
- package/SECURITY_AUDIT.md +661 -0
- package/SETUP.md +287 -0
- package/TESTING_TOOLS.md +88 -0
- package/TESTING_TOOL_EXECUTION.md +239 -0
- package/TOOL_EXECUTION_FIX.md +491 -0
- package/VERIFICATION_CHECKLIST.md +419 -0
- package/docs/API.md +74 -0
- package/docs/CHAT_LOGGING.md +39 -0
- package/docs/CURSOR_FIX_DEMO.md +306 -0
- package/docs/ERROR_HANDLING_GUIDE.md +547 -0
- package/docs/FILE_OPERATIONS.md +449 -0
- package/docs/INTERACTIVE.md +401 -0
- package/docs/PROJECT_CREATION_GUIDE.md +570 -0
- package/docs/QUICKSTART.md +378 -0
- package/docs/QUICK_REFERENCE.md +691 -0
- package/docs/RELEASE_NOTES_0.1.2.md +240 -0
- package/docs/TOOLS.md +459 -0
- package/docs/TOOLS_QUICK_REFERENCE.md +210 -0
- package/docs/ZED_INTEGRATION.md +371 -0
- package/docs/extensions.md +464 -0
- package/docs/settings.md +293 -0
- package/examples/extensions/logging-hook/README.md +91 -0
- package/examples/extensions/logging-hook/extension.json +22 -0
- package/package.json +30 -0
- package/scripts/test_acp.py +252 -0
- package/scripts/test_acp.sh +143 -0
- package/scripts/test_acp_simple.sh +72 -0
- package/src/acp/mod.rs +741 -0
- package/src/acp/protocol.rs +323 -0
- package/src/acp/security.rs +298 -0
- package/src/acp/tools.rs +697 -0
- package/src/bin/banner_demo.rs +216 -0
- package/src/bin/docgen.rs +18 -0
- package/src/bin/installer.rs +217 -0
- package/src/cli/app.rs +310 -0
- package/src/cli/commands/acp.rs +721 -0
- package/src/cli/commands/chat.rs +485 -0
- package/src/cli/commands/code.rs +513 -0
- package/src/cli/commands/config.rs +394 -0
- package/src/cli/commands/health.rs +442 -0
- package/src/cli/commands/history.rs +421 -0
- package/src/cli/commands/mod.rs +14 -0
- package/src/cli/commands/settings.rs +1384 -0
- package/src/cli/mod.rs +166 -0
- package/src/config/mod.rs +2212 -0
- package/src/display/ascii_art.rs +139 -0
- package/src/display/banner.rs +289 -0
- package/src/display/components/input.rs +323 -0
- package/src/display/components/mod.rs +2 -0
- package/src/display/components/settings_list.rs +306 -0
- package/src/display/interactive.rs +1255 -0
- package/src/display/mod.rs +62 -0
- package/src/display/terminal.rs +42 -0
- package/src/display/tips.rs +316 -0
- package/src/grok_client_ext.rs +177 -0
- package/src/hooks/loader.rs +407 -0
- package/src/hooks/mod.rs +158 -0
- package/src/lib.rs +174 -0
- package/src/main.rs +65 -0
- package/src/mcp/client.rs +195 -0
- package/src/mcp/config.rs +20 -0
- package/src/mcp/mod.rs +6 -0
- package/src/mcp/protocol.rs +67 -0
- package/src/utils/auth.rs +41 -0
- package/src/utils/chat_logger.rs +568 -0
- package/src/utils/context.rs +390 -0
- package/src/utils/mod.rs +16 -0
- package/src/utils/network.rs +320 -0
- package/src/utils/rate_limiter.rs +166 -0
- package/src/utils/session.rs +73 -0
- package/src/utils/shell_permissions.rs +389 -0
- package/src/utils/telemetry.rs +41 -0
package/src/lib.rs
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
//! Grok CLI Library
|
|
2
|
+
//!
|
|
3
|
+
//! This library provides the core functionality for the Grok CLI,
|
|
4
|
+
//! including API integration, configuration management, and display utilities.
|
|
5
|
+
|
|
6
|
+
use clap::Subcommand;
|
|
7
|
+
|
|
8
|
+
pub mod acp;
|
|
9
|
+
pub mod cli;
|
|
10
|
+
pub mod config;
|
|
11
|
+
pub mod display;
|
|
12
|
+
pub mod grok_client_ext;
|
|
13
|
+
pub mod hooks;
|
|
14
|
+
pub mod mcp;
|
|
15
|
+
pub mod utils;
|
|
16
|
+
|
|
17
|
+
// Re-export grok_api types for use throughout the crate
|
|
18
|
+
pub use grok_api::{
|
|
19
|
+
ChatResponse, Message, ToolCall, FunctionCall, Choice, Usage,
|
|
20
|
+
Error as GrokApiError,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Re-export the extended GrokClient
|
|
24
|
+
pub use grok_client_ext::GrokClient;
|
|
25
|
+
|
|
26
|
+
#[derive(Subcommand, Clone, Debug)]
|
|
27
|
+
pub enum CodeAction {
|
|
28
|
+
/// Explain code functionality
|
|
29
|
+
Explain {
|
|
30
|
+
/// File path or code snippet
|
|
31
|
+
input: String,
|
|
32
|
+
/// Input is a file path (default: auto-detect)
|
|
33
|
+
#[arg(short, long)]
|
|
34
|
+
file: bool,
|
|
35
|
+
},
|
|
36
|
+
/// Review code for improvements
|
|
37
|
+
Review {
|
|
38
|
+
/// File path or code snippet
|
|
39
|
+
input: String,
|
|
40
|
+
/// Input is a file path (default: auto-detect)
|
|
41
|
+
#[arg(short, long)]
|
|
42
|
+
file: bool,
|
|
43
|
+
/// Focus on specific aspects (security, performance, style, etc.)
|
|
44
|
+
#[arg(long)]
|
|
45
|
+
focus: Option<String>,
|
|
46
|
+
},
|
|
47
|
+
/// Generate code from description
|
|
48
|
+
Generate {
|
|
49
|
+
/// Description of what to generate
|
|
50
|
+
description: Vec<String>,
|
|
51
|
+
/// Programming language
|
|
52
|
+
#[arg(short, long)]
|
|
53
|
+
language: Option<String>,
|
|
54
|
+
/// Output file path
|
|
55
|
+
#[arg(short, long)]
|
|
56
|
+
output: Option<String>,
|
|
57
|
+
},
|
|
58
|
+
/// Fix code issues
|
|
59
|
+
Fix {
|
|
60
|
+
/// File path containing code to fix
|
|
61
|
+
file: String,
|
|
62
|
+
/// Description of the issue to fix
|
|
63
|
+
issue: Vec<String>,
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#[derive(Subcommand, Clone, Debug)]
|
|
68
|
+
pub enum AcpAction {
|
|
69
|
+
/// Start ACP server for Zed integration
|
|
70
|
+
Server {
|
|
71
|
+
/// Port to bind to (default: auto-assign)
|
|
72
|
+
#[arg(short, long)]
|
|
73
|
+
port: Option<u16>,
|
|
74
|
+
/// Host to bind to
|
|
75
|
+
#[arg(short = 'H', long, default_value = "127.0.0.1")]
|
|
76
|
+
host: String,
|
|
77
|
+
},
|
|
78
|
+
/// Start ACP in stdio mode (default for Zed)
|
|
79
|
+
Stdio {
|
|
80
|
+
/// Model to use (overrides default)
|
|
81
|
+
#[arg(long)]
|
|
82
|
+
model: Option<String>,
|
|
83
|
+
},
|
|
84
|
+
/// Test ACP connection
|
|
85
|
+
Test {
|
|
86
|
+
/// ACP server address
|
|
87
|
+
#[arg(short, long)]
|
|
88
|
+
address: String,
|
|
89
|
+
},
|
|
90
|
+
/// Show ACP capabilities
|
|
91
|
+
Capabilities,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#[derive(Subcommand, Clone, Debug)]
|
|
95
|
+
pub enum ConfigAction {
|
|
96
|
+
/// Show current configuration
|
|
97
|
+
Show,
|
|
98
|
+
/// Set configuration value
|
|
99
|
+
Set {
|
|
100
|
+
/// Configuration key
|
|
101
|
+
key: String,
|
|
102
|
+
/// Configuration value
|
|
103
|
+
value: String,
|
|
104
|
+
},
|
|
105
|
+
/// Get configuration value
|
|
106
|
+
Get {
|
|
107
|
+
/// Configuration key
|
|
108
|
+
key: String,
|
|
109
|
+
},
|
|
110
|
+
/// Initialize configuration with defaults
|
|
111
|
+
Init {
|
|
112
|
+
/// Force overwrite existing config
|
|
113
|
+
#[arg(long)]
|
|
114
|
+
force: bool,
|
|
115
|
+
},
|
|
116
|
+
/// Validate configuration
|
|
117
|
+
Validate,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#[derive(Subcommand, Clone, Debug)]
|
|
121
|
+
pub enum SettingsAction {
|
|
122
|
+
/// Show interactive settings browser
|
|
123
|
+
Show,
|
|
124
|
+
/// Edit settings interactively
|
|
125
|
+
Edit,
|
|
126
|
+
/// Reset settings to defaults
|
|
127
|
+
Reset {
|
|
128
|
+
/// Category to reset (optional, resets all if not specified)
|
|
129
|
+
#[arg(short, long)]
|
|
130
|
+
category: Option<String>,
|
|
131
|
+
},
|
|
132
|
+
/// Export settings to file
|
|
133
|
+
Export {
|
|
134
|
+
/// Export file path
|
|
135
|
+
#[arg(short, long)]
|
|
136
|
+
path: Option<String>,
|
|
137
|
+
},
|
|
138
|
+
/// Import settings from file
|
|
139
|
+
Import {
|
|
140
|
+
/// Import file path
|
|
141
|
+
#[arg(short, long)]
|
|
142
|
+
path: String,
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
#[derive(Subcommand, Clone, Debug)]
|
|
147
|
+
pub enum HistoryAction {
|
|
148
|
+
/// List all chat sessions
|
|
149
|
+
List,
|
|
150
|
+
/// View a specific chat session
|
|
151
|
+
View {
|
|
152
|
+
/// Session ID to view
|
|
153
|
+
session_id: String,
|
|
154
|
+
},
|
|
155
|
+
/// Search through chat sessions
|
|
156
|
+
Search {
|
|
157
|
+
/// Search query
|
|
158
|
+
query: String,
|
|
159
|
+
},
|
|
160
|
+
/// Clear chat history
|
|
161
|
+
Clear {
|
|
162
|
+
/// Confirm deletion
|
|
163
|
+
#[arg(long)]
|
|
164
|
+
confirm: bool,
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Re-export commonly used types and functions
|
|
169
|
+
pub use config::{Config, ConfigSource, RateLimitConfig};
|
|
170
|
+
pub use display::{
|
|
171
|
+
ascii_art::{get_logo_for_width, print_grok_logo},
|
|
172
|
+
banner::{BannerConfig, BannerType, print_banner, print_welcome_banner},
|
|
173
|
+
get_terminal_size,
|
|
174
|
+
};
|
package/src/main.rs
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
use anyhow::Result;
|
|
2
|
+
use std::path::PathBuf;
|
|
3
|
+
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
|
4
|
+
|
|
5
|
+
// Import directly from the library crate to avoid duplicate compilation
|
|
6
|
+
use grok_cli::cli;
|
|
7
|
+
use grok_cli::utils::chat_logger;
|
|
8
|
+
|
|
9
|
+
#[tokio::main]
|
|
10
|
+
async fn main() -> Result<()> {
|
|
11
|
+
// Load environment variables from .env file
|
|
12
|
+
dotenvy::dotenv().ok();
|
|
13
|
+
|
|
14
|
+
// Initialize tracing
|
|
15
|
+
tracing_subscriber::registry()
|
|
16
|
+
.with(
|
|
17
|
+
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
18
|
+
.unwrap_or_else(|_| "grok_cli=info".into()),
|
|
19
|
+
)
|
|
20
|
+
.with(tracing_subscriber::fmt::layer())
|
|
21
|
+
.init();
|
|
22
|
+
|
|
23
|
+
// Initialize chat logger
|
|
24
|
+
let chat_logger_enabled = std::env::var("GROK_CHAT_LOGGING_ENABLED")
|
|
25
|
+
.unwrap_or_else(|_| "true".to_string())
|
|
26
|
+
.parse::<bool>()
|
|
27
|
+
.unwrap_or(true);
|
|
28
|
+
|
|
29
|
+
let chat_log_dir = std::env::var("GROK_CHAT_LOG_DIR")
|
|
30
|
+
.ok()
|
|
31
|
+
.map(PathBuf::from)
|
|
32
|
+
.unwrap_or_else(|| {
|
|
33
|
+
dirs::home_dir()
|
|
34
|
+
.unwrap_or_else(|| PathBuf::from("."))
|
|
35
|
+
.join(".grok")
|
|
36
|
+
.join("logs")
|
|
37
|
+
.join("chat_sessions")
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
let config = chat_logger::ChatLoggerConfig {
|
|
41
|
+
enabled: chat_logger_enabled,
|
|
42
|
+
log_dir: chat_log_dir,
|
|
43
|
+
json_format: true,
|
|
44
|
+
text_format: true,
|
|
45
|
+
max_file_size_mb: std::env::var("GROK_CHAT_LOG_MAX_SIZE_MB")
|
|
46
|
+
.ok()
|
|
47
|
+
.and_then(|s| s.parse().ok())
|
|
48
|
+
.unwrap_or(10),
|
|
49
|
+
rotation_count: std::env::var("GROK_CHAT_LOG_ROTATION_COUNT")
|
|
50
|
+
.ok()
|
|
51
|
+
.and_then(|s| s.parse().ok())
|
|
52
|
+
.unwrap_or(5),
|
|
53
|
+
include_system: std::env::var("GROK_CHAT_LOG_INCLUDE_SYSTEM")
|
|
54
|
+
.unwrap_or_else(|_| "true".to_string())
|
|
55
|
+
.parse::<bool>()
|
|
56
|
+
.unwrap_or(true),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if let Err(e) = chat_logger::init(config) {
|
|
60
|
+
eprintln!("Warning: Failed to initialize chat logger: {}", e);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Run the application
|
|
64
|
+
cli::app::run().await
|
|
65
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
use anyhow::{anyhow, Result};
|
|
2
|
+
use serde_json::{json, Value};
|
|
3
|
+
use std::collections::HashMap;
|
|
4
|
+
use std::process::Stdio;
|
|
5
|
+
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
|
6
|
+
use tokio::process::{Child, ChildStdin, ChildStdout, Command};
|
|
7
|
+
use tokio::sync::Mutex;
|
|
8
|
+
use tracing::{debug, error, info};
|
|
9
|
+
|
|
10
|
+
use crate::mcp::config::McpServerConfig;
|
|
11
|
+
use crate::mcp::protocol::{ClientCapabilities, ClientInfo, Tool};
|
|
12
|
+
|
|
13
|
+
pub struct McpClient {
|
|
14
|
+
servers: HashMap<String, ServerConnection>,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
struct ServerConnection {
|
|
18
|
+
process: Child,
|
|
19
|
+
stdin: Mutex<ChildStdin>,
|
|
20
|
+
reader: Mutex<BufReader<ChildStdout>>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
impl Default for McpClient {
|
|
24
|
+
fn default() -> Self {
|
|
25
|
+
Self::new()
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl McpClient {
|
|
30
|
+
pub fn new() -> Self {
|
|
31
|
+
Self {
|
|
32
|
+
servers: HashMap::new(),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub async fn connect(&mut self, name: &str, config: &McpServerConfig) -> Result<()> {
|
|
37
|
+
match config {
|
|
38
|
+
McpServerConfig::Stdio { command, args, env } => {
|
|
39
|
+
info!(
|
|
40
|
+
"Connecting to MCP server '{}' via stdio: {} {:?}",
|
|
41
|
+
name, command, args
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
let mut cmd = Command::new(command);
|
|
45
|
+
cmd.args(args);
|
|
46
|
+
cmd.envs(env);
|
|
47
|
+
cmd.stdin(Stdio::piped());
|
|
48
|
+
cmd.stdout(Stdio::piped());
|
|
49
|
+
cmd.stderr(Stdio::inherit()); // Log stderr to parent's stderr
|
|
50
|
+
|
|
51
|
+
let mut child = cmd
|
|
52
|
+
.spawn()
|
|
53
|
+
.map_err(|e| anyhow!("Failed to spawn MCP server: {}", e))?;
|
|
54
|
+
|
|
55
|
+
let stdin = child
|
|
56
|
+
.stdin
|
|
57
|
+
.take()
|
|
58
|
+
.ok_or_else(|| anyhow!("Failed to open stdin"))?;
|
|
59
|
+
let stdout = child
|
|
60
|
+
.stdout
|
|
61
|
+
.take()
|
|
62
|
+
.ok_or_else(|| anyhow!("Failed to open stdout"))?;
|
|
63
|
+
|
|
64
|
+
let connection = ServerConnection {
|
|
65
|
+
process: child,
|
|
66
|
+
stdin: Mutex::new(stdin),
|
|
67
|
+
reader: Mutex::new(BufReader::new(stdout)),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Initialize handshake
|
|
71
|
+
self.initialize_handshake(&connection).await?;
|
|
72
|
+
|
|
73
|
+
self.servers.insert(name.to_string(), connection);
|
|
74
|
+
info!("Connected to MCP server '{}'", name);
|
|
75
|
+
Ok(())
|
|
76
|
+
}
|
|
77
|
+
McpServerConfig::Sse { .. } => Err(anyhow!("SSE transport not yet implemented")),
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async fn initialize_handshake(&self, connection: &ServerConnection) -> Result<()> {
|
|
82
|
+
let init_msg = json!({
|
|
83
|
+
"jsonrpc": "2.0",
|
|
84
|
+
"id": 1,
|
|
85
|
+
"method": "initialize",
|
|
86
|
+
"params": {
|
|
87
|
+
"protocolVersion": "0.1.0",
|
|
88
|
+
"capabilities": {},
|
|
89
|
+
"clientInfo": {
|
|
90
|
+
"name": "grok-cli",
|
|
91
|
+
"version": env!("CARGO_PKG_VERSION")
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
self.send_message(connection, &init_msg).await?;
|
|
97
|
+
let response = self.read_response(connection).await?;
|
|
98
|
+
|
|
99
|
+
// TODO: Validate response?
|
|
100
|
+
debug!("Initialize response: {:?}", response);
|
|
101
|
+
|
|
102
|
+
// Send initialized notification
|
|
103
|
+
let initialized_msg = json!({
|
|
104
|
+
"jsonrpc": "2.0",
|
|
105
|
+
"method": "notifications/initialized"
|
|
106
|
+
});
|
|
107
|
+
self.send_message(connection, &initialized_msg).await?;
|
|
108
|
+
|
|
109
|
+
Ok(())
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async fn send_message(&self, connection: &ServerConnection, message: &Value) -> Result<()> {
|
|
113
|
+
let mut stdin = connection.stdin.lock().await;
|
|
114
|
+
let json_str = serde_json::to_string(message)?;
|
|
115
|
+
stdin.write_all(json_str.as_bytes()).await?;
|
|
116
|
+
stdin.write_all(b"\n").await?;
|
|
117
|
+
stdin.flush().await?;
|
|
118
|
+
Ok(())
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async fn read_response(&self, connection: &ServerConnection) -> Result<Value> {
|
|
122
|
+
let mut reader = connection.reader.lock().await;
|
|
123
|
+
let mut line = String::new();
|
|
124
|
+
reader.read_line(&mut line).await?;
|
|
125
|
+
|
|
126
|
+
if line.is_empty() {
|
|
127
|
+
return Err(anyhow!("MCP server closed connection"));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let value: Value = serde_json::from_str(&line)?;
|
|
131
|
+
Ok(value)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
pub async fn list_tools(&self, server_name: &str) -> Result<Vec<Tool>> {
|
|
135
|
+
let connection = self
|
|
136
|
+
.servers
|
|
137
|
+
.get(server_name)
|
|
138
|
+
.ok_or_else(|| anyhow!("Server not connected: {}", server_name))?;
|
|
139
|
+
|
|
140
|
+
let msg = json!({
|
|
141
|
+
"jsonrpc": "2.0",
|
|
142
|
+
"id": 2, // simple id gen needed
|
|
143
|
+
"method": "tools/list",
|
|
144
|
+
"params": {}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
self.send_message(connection, &msg).await?;
|
|
148
|
+
let response = self.read_response(connection).await?;
|
|
149
|
+
|
|
150
|
+
// Parse response
|
|
151
|
+
if let Some(result) = response.get("result")
|
|
152
|
+
&& let Some(tools_val) = result.get("tools") {
|
|
153
|
+
let tools: Vec<Tool> = serde_json::from_value(tools_val.clone())?;
|
|
154
|
+
return Ok(tools);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
Ok(Vec::new())
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
pub async fn call_tool(
|
|
161
|
+
&self,
|
|
162
|
+
server_name: &str,
|
|
163
|
+
tool_name: &str,
|
|
164
|
+
args: Value,
|
|
165
|
+
) -> Result<Value> {
|
|
166
|
+
let connection = self
|
|
167
|
+
.servers
|
|
168
|
+
.get(server_name)
|
|
169
|
+
.ok_or_else(|| anyhow!("Server not connected: {}", server_name))?;
|
|
170
|
+
|
|
171
|
+
let msg = json!({
|
|
172
|
+
"jsonrpc": "2.0",
|
|
173
|
+
"id": 3,
|
|
174
|
+
"method": "tools/call",
|
|
175
|
+
"params": {
|
|
176
|
+
"name": tool_name,
|
|
177
|
+
"arguments": args
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
self.send_message(connection, &msg).await?;
|
|
182
|
+
let response = self.read_response(connection).await?;
|
|
183
|
+
|
|
184
|
+
// Check for error
|
|
185
|
+
if let Some(error) = response.get("error") {
|
|
186
|
+
return Err(anyhow!("Tool call failed: {:?}", error));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if let Some(result) = response.get("result") {
|
|
190
|
+
return Ok(result.clone());
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
Err(anyhow!("Invalid response from tool call"))
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
use serde::{Deserialize, Serialize};
|
|
2
|
+
use std::collections::HashMap;
|
|
3
|
+
|
|
4
|
+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
5
|
+
pub struct McpConfig {
|
|
6
|
+
pub servers: HashMap<String, McpServerConfig>,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
10
|
+
#[serde(tag = "type")]
|
|
11
|
+
pub enum McpServerConfig {
|
|
12
|
+
#[serde(rename = "stdio")]
|
|
13
|
+
Stdio {
|
|
14
|
+
command: String,
|
|
15
|
+
args: Vec<String>,
|
|
16
|
+
env: HashMap<String, String>,
|
|
17
|
+
},
|
|
18
|
+
#[serde(rename = "sse")]
|
|
19
|
+
Sse { url: String },
|
|
20
|
+
}
|
package/src/mcp/mod.rs
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
use serde::{Deserialize, Serialize};
|
|
2
|
+
use serde_json::Value;
|
|
3
|
+
|
|
4
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
5
|
+
#[serde(tag = "method", content = "params")]
|
|
6
|
+
pub enum JsonRpcMessage {
|
|
7
|
+
#[serde(rename = "initialize")]
|
|
8
|
+
Initialize {
|
|
9
|
+
protocol_version: String,
|
|
10
|
+
capabilities: ClientCapabilities,
|
|
11
|
+
client_info: ClientInfo,
|
|
12
|
+
},
|
|
13
|
+
#[serde(rename = "tools/list")]
|
|
14
|
+
ListTools {},
|
|
15
|
+
#[serde(rename = "tools/call")]
|
|
16
|
+
CallTool { name: String, arguments: Value },
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
20
|
+
pub struct ClientCapabilities {
|
|
21
|
+
pub tools: Option<ToolsCapability>,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
25
|
+
pub struct ToolsCapability {
|
|
26
|
+
pub list_changed: Option<bool>,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
30
|
+
pub struct ClientInfo {
|
|
31
|
+
pub name: String,
|
|
32
|
+
pub version: String,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
36
|
+
pub struct Tool {
|
|
37
|
+
pub name: String,
|
|
38
|
+
pub description: Option<String>,
|
|
39
|
+
pub input_schema: Value,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
43
|
+
pub struct ListToolsResult {
|
|
44
|
+
pub tools: Vec<Tool>,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
48
|
+
pub struct CallToolResult {
|
|
49
|
+
pub content: Vec<ToolContent>,
|
|
50
|
+
pub is_error: Option<bool>,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
54
|
+
#[serde(tag = "type")]
|
|
55
|
+
pub enum ToolContent {
|
|
56
|
+
#[serde(rename = "text")]
|
|
57
|
+
Text { text: String },
|
|
58
|
+
#[serde(rename = "image")]
|
|
59
|
+
Image { data: String, mime_type: String },
|
|
60
|
+
#[serde(rename = "resource")]
|
|
61
|
+
Resource {
|
|
62
|
+
uri: String,
|
|
63
|
+
mime_type: Option<String>,
|
|
64
|
+
text: Option<String>,
|
|
65
|
+
blob: Option<String>,
|
|
66
|
+
},
|
|
67
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
use anyhow::{anyhow, Result};
|
|
2
|
+
use colored::*;
|
|
3
|
+
use std::process;
|
|
4
|
+
use tracing::error;
|
|
5
|
+
|
|
6
|
+
use crate::config::Config;
|
|
7
|
+
|
|
8
|
+
/// Resolves the API key from various sources (CLI arg, config, env vars).
|
|
9
|
+
pub fn resolve_api_key(cli_key: Option<String>, config: &Config) -> Option<String> {
|
|
10
|
+
cli_key
|
|
11
|
+
.or(config.api_key.clone())
|
|
12
|
+
.or_else(|| std::env::var("GROK_API_KEY").ok())
|
|
13
|
+
.or_else(|| std::env::var("X_API_KEY").ok())
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// Ensures an API key is present, or exits with a helpful error message.
|
|
17
|
+
///
|
|
18
|
+
/// Returns the API key if present.
|
|
19
|
+
pub fn require_api_key(
|
|
20
|
+
api_key: Option<String>,
|
|
21
|
+
hide_banner: bool,
|
|
22
|
+
show_banner_fn: impl FnOnce(),
|
|
23
|
+
) -> String {
|
|
24
|
+
if let Some(key) = api_key {
|
|
25
|
+
return key;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Show welcome banner even without API key if requested
|
|
29
|
+
if !hide_banner {
|
|
30
|
+
show_banner_fn();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
error!("No API key provided. Set GROK_API_KEY environment variable or use --api-key option");
|
|
34
|
+
eprintln!("{}", "Error: No API key provided".red());
|
|
35
|
+
eprintln!(
|
|
36
|
+
"Set the {} environment variable or use the {} option",
|
|
37
|
+
"GROK_API_KEY".yellow(),
|
|
38
|
+
"--api-key".yellow()
|
|
39
|
+
);
|
|
40
|
+
process::exit(1);
|
|
41
|
+
}
|