Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
37d9fac
refactor: optimize secret redaction and preserve redaction maps
abd0-omar Nov 30, 2025
fad225a
Add benchmarks for SecretManager
abd0-omar Nov 26, 2025
040152a
Implement actor model for SecretManager
abd0-omar Nov 26, 2025
0405cf0
feat: add `secrecy` crate to wrap password in `SecretString` to avoid…
abd0-omar Dec 4, 2025
f032db5
refactor: use string bytes and use rand `choose()` fn on slices
abd0-omar Dec 7, 2025
7328fd1
test: add test case for password length validation
abd0-omar Dec 7, 2025
42721c3
feat: change `generate_password()` fn return type from `String` to `P…
abd0-omar Dec 8, 2025
8580312
refactor: simplify `redact_password()` to only redact passwords
abd0-omar Dec 8, 2025
7b8c8cb
refactor: remove unused fn
abd0-omar Dec 11, 2025
59a99c8
fix: add retry loop for passwords conflicts
abd0-omar Dec 8, 2025
920d2a2
feat: accept `Password` type in `redact_and_store_password()`
abd0-omar Dec 8, 2025
59b4e44
feat: add retry limit for password conflicts
abd0-omar Dec 8, 2025
6aee671
feat: enforce minimum 8 characters password constraint
abd0-omar Dec 8, 2025
df1d3b4
refactor: rename parameter `no_symbols` to `include_symbols` in `gene…
abd0-omar Dec 9, 2025
0ae4246
refactor: simplify logging as password conflicts
abd0-omar Dec 9, 2025
5beafac
refactor: improve password generation error handling
abd0-omar Dec 9, 2025
5ae0137
refactor: remove redundant empty password check
abd0-omar Dec 9, 2025
ca8cf55
feat: accept `Password` type in `redact_password()` fn
abd0-omar Dec 9, 2025
2c0fe2a
docs: add docs for the new `Password` type
abd0-omar Dec 11, 2025
f059292
chore: avoid using unsafe for creating UTF8 password and use proper e…
abd0-omar Dec 11, 2025
c162bb0
refactor: simplify password filling and shuffling
abd0-omar Dec 12, 2025
9624cb0
chore: remove unused fields and add fast-path optimization
abd0-omar Jan 2, 2026
a648bfd
fix: save to redaction_map when new secrets are added
abd0-omar Jan 2, 2026
448df71
fix: return only new mappings from `redact_secrets`
abd0-omar Jan 2, 2026
e48ed57
chore: add context to error messages
abd0-omar Jan 2, 2026
805368d
fix: fallback to unredacted output if channel is full
abd0-omar Jan 2, 2026
c0b44a1
password generate new one instead of shuffling
abd0-omar Jan 2, 2026
8438d6d
chore: refactor password population with random chars to it's own fun…
abd0-omar Jan 2, 2026
661e30c
deserialize password
abd0-omar Jan 2, 2026
c412def
early validation
abd0-omar Jan 2, 2026
d074865
feat: add rust mcp client integration
abd0-omar Jan 2, 2026
b7ed062
Merge branch 'main' into actor-model
abd0-omar Jan 2, 2026
71fc409
Merge branch 'main' into feature/improve-password-security
abd0-omar Jan 2, 2026
4f69a82
Merge branch 'main' into refactor-redact_secretes/passowrds
abd0-omar Jan 2, 2026
02a803c
fix: early return for already-redacted content
abd0-omar Jan 2, 2026
40c151a
fix tests
abd0-omar Jan 2, 2026
3e76389
chore: run cargo clippy
abd0-omar Jan 2, 2026
b296411
chore: use "with_symbols" instead of "no_symbols"
abd0-omar Jan 2, 2026
bde76ac
chore: replace ".unwrap()" in the example with simple "if let"
abd0-omar Jan 2, 2026
2bf3a69
feat: add mcp proxy example
abd0-omar Jan 2, 2026
702ddca
Merge pull request #352 from abd0-omar/refactor-redact_secretes/passo…
ahmedhesham6 Jan 4, 2026
3b4281a
Merge pull request #390 from abd0-omar/feature/add-Rust-MCP-client-in…
ahmedhesham6 Jan 5, 2026
c8c5fcc
Merge pull request #420 from stakpak/main
ahmedhesham6 Jan 5, 2026
35c9884
Merge branch 'beta' into actor-model
abd0-omar Jan 5, 2026
2035b47
refactor: take redaction map by reference in `redact_secrets` and `re…
abd0-omar Jan 5, 2026
e575435
Merge branch 'actor-model' into feature/improve-password-security
abd0-omar Jan 5, 2026
8b8b935
refactor: remove `content` field from `redact_secrets()` fn
abd0-omar Jan 5, 2026
baf6e42
refactor: move password generation to actor
abd0-omar Jan 5, 2026
4cd922e
Merge pull request #354 from abd0-omar/actor-model
ahmedhesham6 Jan 5, 2026
a008b69
Merge pull request #377 from abd0-omar/feature/improve-password-security
ahmedhesham6 Jan 5, 2026
a884939
Refactor: Remove secret handling from local tools
ahmedhesham6 Jan 6, 2026
05c733f
chore: bump version to 0.3.12-beta.1
ahmedhesham6 Jan 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
696 changes: 458 additions & 238 deletions Cargo.lock

Large diffs are not rendered by default.

23 changes: 12 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ members = [
"libs/mcp/client",
"libs/mcp/server",
"libs/mcp/proxy",
"examples/mcp-clients/rust-client"
]
default-members = ["cli"]


[workspace.package]
version = "0.3.11"
version = "0.3.12-beta.1"
edition = "2024"
description = "Stakpak: Your DevOps AI Agent. Generate infrastructure code, debug Kubernetes, configure CI/CD, automate deployments, without giving an LLM the keys to production."
license = "Apache-2.0"
Expand All @@ -23,14 +24,14 @@ homepage = "https://stakpak.io"


[workspace.dependencies]
stakai = { path = "libs/ai", version = "0.3.11" }
stakpak-api = { path = "libs/api", version = "0.3.11" }
stakpak-mcp-server = { path = "libs/mcp/server", version = "0.3.11" }
stakpak-mcp-client = { path = "libs/mcp/client", version = "0.3.11" }
stakpak-mcp-proxy = { path = "libs/mcp/proxy", version = "0.3.11" }
stakpak-tui = { path = "tui", version = "0.3.11" }
stakpak-shared = { path = "libs/shared", version = "0.3.11" }
popup-widget = { package = "stakpak-popup-widget", path = "libs/popup-widget", version = "0.3.11" }
stakai = { path = "libs/ai", version = "0.3.12-beta.1" }
stakpak-api = { path = "libs/api", version = "0.3.12-beta.1" }
stakpak-mcp-server = { path = "libs/mcp/server", version = "0.3.12-beta.1" }
stakpak-mcp-client = { path = "libs/mcp/client", version = "0.3.12-beta.1" }
stakpak-mcp-proxy = { path = "libs/mcp/proxy", version = "0.3.12-beta.1" }
stakpak-tui = { path = "tui", version = "0.3.12-beta.1" }
stakpak-shared = { path = "libs/shared", version = "0.3.12-beta.1" }
popup-widget = { package = "stakpak-popup-widget", path = "libs/popup-widget", version = "0.3.12-beta.1" }
serde = { version = "1.0.215", features = ["derive"] }
serde_json = "1.0.133"
uuid = { version = "1.10.0", features = ["serde", "v4"] }
Expand All @@ -56,7 +57,7 @@ futures = "0.3.31"
futures-util = "0.3.31"
regex = "1.11.1"
chrono = { version = "0.4.38", features = ["serde"] }
reqwest = { version = "=0.12.15", features = [
reqwest = { version = "0.12.26", features = [
"json",
"stream",
"rustls-tls",
Expand All @@ -82,7 +83,7 @@ rustls-platform-verifier = "0.5"
crossterm = "0.29"
tempfile = "3.0"
similar = { version = "2.7.0", features = ["inline"] }
schemars = { version = "1.1.0", features = ["chrono"] }
schemars = { version = "1.1.0", features = ["chrono04"] }
async-trait = "0.1"
open = "5.3.2"
log = "0.4"
Expand Down
84 changes: 65 additions & 19 deletions cli/src/commands/acp/server.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::commands::agent::run::helpers::{system_message, user_message};
use crate::config::ProviderType;
use crate::utils::network;
use crate::{commands::agent::run::helpers::convert_tools_with_filter, config::AppConfig};
use agent_client_protocol::{self as acp, Client as AcpClient, SessionNotification};
use futures_util::StreamExt;
Expand All @@ -10,6 +11,8 @@ use stakpak_api::{
remote::{ClientConfig, RemoteClient},
};
use stakpak_mcp_client::McpClient;
use stakpak_mcp_server::{EnabledToolsConfig, MCPServerConfig, ToolMode, start_server, tool_names};
use stakpak_shared::cert_utils::CertificateStrategy;
use stakpak_shared::models::integrations::mcp::CallToolResultExt;
use stakpak_shared::models::integrations::openai::{
AgentModel, ChatCompletionChoice, ChatCompletionResponse, ChatCompletionStreamResponse,
Expand Down Expand Up @@ -101,10 +104,10 @@ impl StakpakAcpAgent {

// Initialize MCP client and tools (optional for ACP)
let (mcp_client, mcp_tools, tools) =
match Self::initialize_mcp_server_and_tools(&config).await {
match Self::initialize_mcp_server_and_tools(&config, client.clone()).await {
Ok((client, mcp_tools, tool_list)) => {
log::info!("MCP client initialized successfully");
(Some(client), mcp_tools, tool_list)
(Some(Arc::new(client)), mcp_tools, tool_list)
}
Err(e) => {
log::warn!(
Expand Down Expand Up @@ -303,7 +306,6 @@ impl StakpakAcpAgent {

// Helper method to generate appropriate tool title based on tool type and arguments
fn generate_tool_title(&self, tool_name: &str, raw_input: &serde_json::Value) -> String {
use super::tool_names;
match tool_name {
tool_names::VIEW => {
// Extract path from arguments for view tool
Expand Down Expand Up @@ -393,7 +395,6 @@ impl StakpakAcpAgent {

// Helper method to get appropriate ToolKind based on tool name
fn get_tool_kind(&self, tool_name: &str) -> acp::ToolKind {
use super::tool_names;
if tool_names::is_fs_file_read(tool_name) || tool_name == tool_names::READ_RULEBOOK {
acp::ToolKind::Read
} else if tool_names::is_fs_file_write(tool_name) {
Expand All @@ -412,17 +413,17 @@ impl StakpakAcpAgent {

// Helper method to determine if a tool should use Diff content type
fn should_use_diff_content(&self, tool_name: &str) -> bool {
super::tool_names::is_fs_file_write(tool_name)
tool_names::is_fs_file_write(tool_name)
}

// Helper method to determine if a tool is a file creation tool
fn is_file_creation_tool(&self, tool_name: &str) -> bool {
tool_name == super::tool_names::CREATE || tool_name == super::tool_names::CREATE_FILE
tool_name == tool_names::CREATE || tool_name == tool_names::CREATE_FILE
}

// Helper method to determine if a tool should be auto-approved
fn is_auto_approved_tool(&self, tool_name: &str) -> bool {
super::tool_names::is_auto_approved(tool_name)
tool_names::is_auto_approved(tool_name)
}

// Helper method to create proper rawInput for tool calls
Expand Down Expand Up @@ -861,15 +862,15 @@ impl StakpakAcpAgent {

// Check if this is a filesystem tool that should use native ACP
// Decide if this should be handled by native ACP FS. Avoid read_text_file for directories.
let is_view_directory = if tool_call.function.name == super::tool_names::VIEW {
let tool_name = tool_call.function.name.as_str();
let is_view_directory = if tool_name == tool_names::VIEW {
Path::new(&abs_path).is_dir()
} else {
false
};

let tool_name = tool_call.function.name.as_str();
let is_read_tool = super::tool_names::is_fs_file_read(tool_name) && !is_view_directory;
let is_write_tool = super::tool_names::is_fs_file_write(tool_name);
let is_read_tool = tool_names::is_fs_file_read(tool_name) && !is_view_directory;
let is_write_tool = tool_names::is_fs_file_write(tool_name);

// Delegate fs operations to the client so it can access unsaved editor
// state and track modifications. Per ACP spec, both read and write
Expand Down Expand Up @@ -1065,13 +1066,58 @@ impl StakpakAcpAgent {

pub async fn initialize_mcp_server_and_tools(
config: &AppConfig,
) -> Result<(Arc<McpClient>, Vec<rmcp::model::Tool>, Vec<Tool>), String> {
// Initialize MCP client via stdio proxy
let mcp_client = Arc::new(
stakpak_mcp_client::connect(None) // progress_tx will be set later in run_stdio
.await
.map_err(|e| format!("Failed to connect to MCP proxy: {}", e))?,
client: Arc<dyn AgentProvider>,
) -> Result<(McpClient, Vec<rmcp::model::Tool>, Vec<Tool>), String> {
// Find available bind address
let (bind_address, listener) = network::find_available_bind_address_with_listener()
.await
.map_err(|e| e.to_string())?;

// Generate ephemeral certificates for mTLS
let strategy = CertificateStrategy::Ephemeral;
let certificate_chain = Some(Arc::new(
strategy
.get_certificate_chain()
.map_err(|e| e.to_string())?,
));

let protocol = "https";
let local_mcp_server_host = format!("{}://{}", protocol, bind_address);

// Start MCP server in background
let server_config_for_server = Some(
strategy
.load_server_config()
.map_err(|e| format!("Failed to create server config: {}", e))?,
);
let client_for_server = client.clone();

tokio::spawn(async move {
let _ = start_server(
MCPServerConfig {
client: Some(client_for_server),
redact_secrets: true,
privacy_mode: false,
enabled_tools: EnabledToolsConfig { slack: false },
tool_mode: ToolMode::Combined,
bind_address,
server_config: Arc::new(server_config_for_server),
subagent_configs: None,
},
Some(listener),
None,
)
.await;
});

// Initialize MCP client
let mcp_client = stakpak_mcp_client::connect_https(
&local_mcp_server_host,
certificate_chain.clone(),
None, // progress_tx will be set later in run_stdio
)
.await
.map_err(|e| format!("Failed to connect to MCP server: {}", e))?;

// Get tools from MCP client
let mcp_tools = stakpak_mcp_client::get_tools(&mcp_client)
Expand Down Expand Up @@ -1363,10 +1409,10 @@ impl StakpakAcpAgent {
let (progress_tx, mut progress_rx) = tokio::sync::mpsc::channel::<ToolCallResultProgress>(100);

// Reinitialize MCP client with progress channel
let (mcp_client, mcp_tools, tools) = match Self::initialize_mcp_server_and_tools(&self.config).await {
let (mcp_client, mcp_tools, tools) = match Self::initialize_mcp_server_and_tools(&self.config, self.client.clone()).await {
Ok((client, mcp_tools, tool_list)) => {
log::info!("MCP client reinitialized with progress channel");
(Some(client), mcp_tools, tool_list)
(Some(Arc::new(client)), mcp_tools, tool_list)
}
Err(e) => {
log::warn!("Failed to reinitialize MCP client with progress channel: {}, continuing without tools", e);
Expand Down
Loading