feat: full copy from apollo_nxt-trcaa with complete sanitization
Complete backport of all features from apollo_nxt-trcaa repository:
- Three-tier shell execution safety system (Tier 1: auto, Tier 2: approve, Tier 3: deny)
- Ollama function calling with tool use support
- AI provider tool calling auto-detection
- kubectl binary bundling and management
- kubeconfig upload and context management
- Shell approval modal with real-time UI
- MCP protocol HTTP transport with custom headers
- Enhanced security audit logging
- Comprehensive test coverage (275+ tests)
- Updated CI/CD workflows for Gitea Actions
- Complete documentation (ADRs, wiki, release notes)
Sanitization applied to all files:
- Removed all MSI, Motorola, VNXT, Vesta references
- Replaced internal infrastructure references with TFTSR equivalents
- Updated all URLs and API endpoints
- Sanitized commit history references in documentation
Technical changes:
- New modules: shell/classifier, shell/executor, shell/kubectl, shell/kubeconfig
- Enhanced AI providers: ollama.rs, openai.rs with function calling
- New Tauri commands: shell execution, kubeconfig management, tool calling detection
- Database migrations: shell_execution_audit table
- Frontend: ShellApprovalModal, ShellExecution, KubeconfigManager pages
- CI/CD: kubectl bundling, multi-platform builds, Gitea Actions integration
Version: 1.0.8
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-05 19:11:00 +00:00
|
|
|
use http::{HeaderName, HeaderValue};
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
use rmcp::transport::streamable_http_client::StreamableHttpClientTransportConfig;
|
2026-05-23 21:48:26 +00:00
|
|
|
use rmcp::transport::StreamableHttpClientTransport;
|
2026-06-01 13:25:49 +00:00
|
|
|
use std::collections::HashMap;
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
feat: full copy from apollo_nxt-trcaa with complete sanitization
Complete backport of all features from apollo_nxt-trcaa repository:
- Three-tier shell execution safety system (Tier 1: auto, Tier 2: approve, Tier 3: deny)
- Ollama function calling with tool use support
- AI provider tool calling auto-detection
- kubectl binary bundling and management
- kubeconfig upload and context management
- Shell approval modal with real-time UI
- MCP protocol HTTP transport with custom headers
- Enhanced security audit logging
- Comprehensive test coverage (275+ tests)
- Updated CI/CD workflows for Gitea Actions
- Complete documentation (ADRs, wiki, release notes)
Sanitization applied to all files:
- Removed all MSI, Motorola, VNXT, Vesta references
- Replaced internal infrastructure references with TFTSR equivalents
- Updated all URLs and API endpoints
- Sanitized commit history references in documentation
Technical changes:
- New modules: shell/classifier, shell/executor, shell/kubectl, shell/kubeconfig
- Enhanced AI providers: ollama.rs, openai.rs with function calling
- New Tauri commands: shell execution, kubeconfig management, tool calling detection
- Database migrations: shell_execution_audit table
- Frontend: ShellApprovalModal, ShellExecution, KubeconfigManager pages
- CI/CD: kubectl bundling, multi-platform builds, Gitea Actions integration
Version: 1.0.8
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-05 19:11:00 +00:00
|
|
|
/// Parse and validate custom headers for MCP transport.
|
|
|
|
|
/// Returns a HashMap of validated HTTP headers ready for use in transport config.
|
|
|
|
|
///
|
|
|
|
|
/// Invalid headers (bad names or values) are logged and skipped.
|
|
|
|
|
/// Reserved headers (accept, mcp-session-id, etc.) are rejected by rmcp and should not be provided.
|
|
|
|
|
fn build_header_map(custom_headers: HashMap<String, String>) -> HashMap<HeaderName, HeaderValue> {
|
|
|
|
|
let mut http_headers = HashMap::new();
|
|
|
|
|
|
|
|
|
|
// Add custom headers from caller
|
|
|
|
|
for (key, value) in custom_headers.iter() {
|
|
|
|
|
let name_result = HeaderName::from_bytes(key.as_bytes());
|
|
|
|
|
let value_result = HeaderValue::from_str(value);
|
|
|
|
|
|
|
|
|
|
match (name_result, value_result) {
|
|
|
|
|
(Ok(name), Ok(val)) => {
|
|
|
|
|
// Skip reserved headers - rmcp manages these internally
|
|
|
|
|
if name.as_str().eq_ignore_ascii_case("accept")
|
|
|
|
|
|| name.as_str().eq_ignore_ascii_case("mcp-session-id")
|
|
|
|
|
|| name.as_str().eq_ignore_ascii_case("last-event-id")
|
|
|
|
|
{
|
|
|
|
|
tracing::warn!(
|
|
|
|
|
header_name = %name,
|
|
|
|
|
"Header is reserved by rmcp, skipping (rmcp manages it automatically)"
|
|
|
|
|
);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
tracing::debug!(header_name = %name, "Added custom header");
|
|
|
|
|
http_headers.insert(name, val);
|
|
|
|
|
}
|
|
|
|
|
(Err(name_err), _) => {
|
|
|
|
|
tracing::warn!(
|
|
|
|
|
error = %name_err,
|
|
|
|
|
"Invalid header name, skipping (value: <redacted>)"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
(Ok(name), Err(value_err)) => {
|
|
|
|
|
tracing::warn!(
|
|
|
|
|
header_name = %name,
|
|
|
|
|
error = %value_err,
|
|
|
|
|
"Invalid header value, skipping (value: <redacted>)"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NOTE: Do NOT add Accept header here - rmcp automatically sends:
|
|
|
|
|
// "Accept: text/event-stream, application/json" which is what MCP servers need.
|
|
|
|
|
http_headers
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 13:25:49 +00:00
|
|
|
/// Build an HTTP (Streamable HTTP) transport from a URL with optional custom headers.
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
/// Optionally attaches an Authorization bearer token.
|
2026-06-01 13:25:49 +00:00
|
|
|
///
|
feat: full copy from apollo_nxt-trcaa with complete sanitization
Complete backport of all features from apollo_nxt-trcaa repository:
- Three-tier shell execution safety system (Tier 1: auto, Tier 2: approve, Tier 3: deny)
- Ollama function calling with tool use support
- AI provider tool calling auto-detection
- kubectl binary bundling and management
- kubeconfig upload and context management
- Shell approval modal with real-time UI
- MCP protocol HTTP transport with custom headers
- Enhanced security audit logging
- Comprehensive test coverage (275+ tests)
- Updated CI/CD workflows for Gitea Actions
- Complete documentation (ADRs, wiki, release notes)
Sanitization applied to all files:
- Removed all MSI, Motorola, VNXT, Vesta references
- Replaced internal infrastructure references with TFTSR equivalents
- Updated all URLs and API endpoints
- Sanitized commit history references in documentation
Technical changes:
- New modules: shell/classifier, shell/executor, shell/kubectl, shell/kubeconfig
- Enhanced AI providers: ollama.rs, openai.rs with function calling
- New Tauri commands: shell execution, kubeconfig management, tool calling detection
- Database migrations: shell_execution_audit table
- Frontend: ShellApprovalModal, ShellExecution, KubeconfigManager pages
- CI/CD: kubectl bundling, multi-platform builds, Gitea Actions integration
Version: 1.0.8
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-05 19:11:00 +00:00
|
|
|
/// Custom headers are now fully supported via rmcp's `.custom_headers()` method.
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
pub fn build_http_transport(
|
|
|
|
|
url: &str,
|
|
|
|
|
auth_header: Option<&str>,
|
2026-06-01 13:25:49 +00:00
|
|
|
custom_headers: HashMap<String, String>,
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
) -> impl rmcp::transport::Transport<rmcp::RoleClient> {
|
feat: full copy from apollo_nxt-trcaa with complete sanitization
Complete backport of all features from apollo_nxt-trcaa repository:
- Three-tier shell execution safety system (Tier 1: auto, Tier 2: approve, Tier 3: deny)
- Ollama function calling with tool use support
- AI provider tool calling auto-detection
- kubectl binary bundling and management
- kubeconfig upload and context management
- Shell approval modal with real-time UI
- MCP protocol HTTP transport with custom headers
- Enhanced security audit logging
- Comprehensive test coverage (275+ tests)
- Updated CI/CD workflows for Gitea Actions
- Complete documentation (ADRs, wiki, release notes)
Sanitization applied to all files:
- Removed all MSI, Motorola, VNXT, Vesta references
- Replaced internal infrastructure references with TFTSR equivalents
- Updated all URLs and API endpoints
- Sanitized commit history references in documentation
Technical changes:
- New modules: shell/classifier, shell/executor, shell/kubectl, shell/kubeconfig
- Enhanced AI providers: ollama.rs, openai.rs with function calling
- New Tauri commands: shell execution, kubeconfig management, tool calling detection
- Database migrations: shell_execution_audit table
- Frontend: ShellApprovalModal, ShellExecution, KubeconfigManager pages
- CI/CD: kubectl bundling, multi-platform builds, Gitea Actions integration
Version: 1.0.8
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-05 19:11:00 +00:00
|
|
|
let http_headers = build_header_map(custom_headers);
|
|
|
|
|
|
|
|
|
|
// Build config with auth header and custom headers
|
|
|
|
|
let mut config = StreamableHttpClientTransportConfig::with_uri(Arc::from(url));
|
|
|
|
|
|
|
|
|
|
if let Some(token) = auth_header {
|
|
|
|
|
config = config.auth_header(token.to_string());
|
2026-06-01 13:25:49 +00:00
|
|
|
}
|
|
|
|
|
|
feat: full copy from apollo_nxt-trcaa with complete sanitization
Complete backport of all features from apollo_nxt-trcaa repository:
- Three-tier shell execution safety system (Tier 1: auto, Tier 2: approve, Tier 3: deny)
- Ollama function calling with tool use support
- AI provider tool calling auto-detection
- kubectl binary bundling and management
- kubeconfig upload and context management
- Shell approval modal with real-time UI
- MCP protocol HTTP transport with custom headers
- Enhanced security audit logging
- Comprehensive test coverage (275+ tests)
- Updated CI/CD workflows for Gitea Actions
- Complete documentation (ADRs, wiki, release notes)
Sanitization applied to all files:
- Removed all MSI, Motorola, VNXT, Vesta references
- Replaced internal infrastructure references with TFTSR equivalents
- Updated all URLs and API endpoints
- Sanitized commit history references in documentation
Technical changes:
- New modules: shell/classifier, shell/executor, shell/kubectl, shell/kubeconfig
- Enhanced AI providers: ollama.rs, openai.rs with function calling
- New Tauri commands: shell execution, kubeconfig management, tool calling detection
- Database migrations: shell_execution_audit table
- Frontend: ShellApprovalModal, ShellExecution, KubeconfigManager pages
- CI/CD: kubectl bundling, multi-platform builds, Gitea Actions integration
Version: 1.0.8
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-05 19:11:00 +00:00
|
|
|
config = config.custom_headers(http_headers);
|
2026-06-01 13:25:49 +00:00
|
|
|
|
feat(mcp): add MCP Server Support with TDD implementation
Adds full Model Context Protocol (MCP) server management, enabling the
AI assistant to discover and call tools from external MCP servers during
triage conversations.
Backend (Rust):
- rmcp 1.7.0 dependency (client + stdio + Streamable HTTP transports)
- Migration 018: mcp_servers, mcp_tools, mcp_resources tables with
CHECK constraints for transport_type, auth_type, discovery_status
- src/mcp/ module: models, store, client, adapter, discovery, commands,
transport/{stdio,http}
- AppState gains mcp_connections: Arc<TokioMutex<HashMap<...>>>
- .setup() hook auto-discovers enabled servers at startup
- 8 new Tauri commands wired into invoke_handler
- execute_mcp_tool_call: PII scan + mandatory audit_log before execution
- Auth values encrypted at rest via integrations::auth::encrypt_token();
scrubbed before any frontend response
Frontend:
- MCPServers.tsx settings page (/settings/mcp) with server list,
status badges, Discover Now, Add/Edit modal, enable/disable toggle
- tauriCommands.ts: McpServer, McpTool, McpServerStatus types + 8 cmds
- App.tsx: Plug icon, /settings/mcp route, sidebar nav entry
Tests (TDD): 15 new tests, all green
- 5 migration tests (written before migration, red → green)
- 5 store CRUD + encryption tests
- 5 adapter sanitization + conversion tests
Verification: 185/185 Rust, 94/94 Vitest, clippy -D warnings: 0
2026-05-23 21:23:48 +00:00
|
|
|
StreamableHttpClientTransport::from_config(config)
|
|
|
|
|
}
|
feat: full copy from apollo_nxt-trcaa with complete sanitization
Complete backport of all features from apollo_nxt-trcaa repository:
- Three-tier shell execution safety system (Tier 1: auto, Tier 2: approve, Tier 3: deny)
- Ollama function calling with tool use support
- AI provider tool calling auto-detection
- kubectl binary bundling and management
- kubeconfig upload and context management
- Shell approval modal with real-time UI
- MCP protocol HTTP transport with custom headers
- Enhanced security audit logging
- Comprehensive test coverage (275+ tests)
- Updated CI/CD workflows for Gitea Actions
- Complete documentation (ADRs, wiki, release notes)
Sanitization applied to all files:
- Removed all MSI, Motorola, VNXT, Vesta references
- Replaced internal infrastructure references with TFTSR equivalents
- Updated all URLs and API endpoints
- Sanitized commit history references in documentation
Technical changes:
- New modules: shell/classifier, shell/executor, shell/kubectl, shell/kubeconfig
- Enhanced AI providers: ollama.rs, openai.rs with function calling
- New Tauri commands: shell execution, kubeconfig management, tool calling detection
- Database migrations: shell_execution_audit table
- Frontend: ShellApprovalModal, ShellExecution, KubeconfigManager pages
- CI/CD: kubectl bundling, multi-platform builds, Gitea Actions integration
Version: 1.0.8
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-05 19:11:00 +00:00
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_empty_headers_returns_empty_map() {
|
|
|
|
|
let headers = HashMap::new();
|
|
|
|
|
let result = build_header_map(headers);
|
|
|
|
|
|
|
|
|
|
// rmcp handles Accept header automatically, so our map should be empty
|
|
|
|
|
assert_eq!(result.len(), 0, "Should not add any headers");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_rejects_reserved_accept_header() {
|
|
|
|
|
let mut headers = HashMap::new();
|
|
|
|
|
headers.insert("Accept".to_string(), "application/json".to_string());
|
|
|
|
|
|
|
|
|
|
let result = build_header_map(headers);
|
|
|
|
|
|
|
|
|
|
// Accept is reserved - should be rejected
|
|
|
|
|
assert_eq!(result.len(), 0, "Reserved headers should be rejected");
|
|
|
|
|
assert!(!result.contains_key(&HeaderName::from_static("accept")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_rejects_reserved_session_id_header() {
|
|
|
|
|
let mut headers = HashMap::new();
|
|
|
|
|
headers.insert("Mcp-Session-Id".to_string(), "test123".to_string());
|
|
|
|
|
|
|
|
|
|
let result = build_header_map(headers);
|
|
|
|
|
|
|
|
|
|
// Session ID is reserved
|
|
|
|
|
assert_eq!(result.len(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_rejects_reserved_last_event_id_header() {
|
|
|
|
|
let mut headers = HashMap::new();
|
|
|
|
|
headers.insert("Last-Event-Id".to_string(), "123".to_string());
|
|
|
|
|
|
|
|
|
|
let result = build_header_map(headers);
|
|
|
|
|
|
|
|
|
|
// Last-Event-Id is reserved
|
|
|
|
|
assert_eq!(result.len(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_adds_valid_custom_header() {
|
|
|
|
|
let mut headers = HashMap::new();
|
|
|
|
|
headers.insert("X-Custom-Header".to_string(), "custom-value".to_string());
|
|
|
|
|
|
|
|
|
|
let result = build_header_map(headers);
|
|
|
|
|
|
|
|
|
|
let custom = HeaderName::from_static("x-custom-header");
|
|
|
|
|
assert!(result.contains_key(&custom));
|
|
|
|
|
assert_eq!(result.get(&custom).unwrap(), "custom-value");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_adds_multiple_custom_headers() {
|
|
|
|
|
let mut headers = HashMap::new();
|
|
|
|
|
headers.insert("X-Header-One".to_string(), "value1".to_string());
|
|
|
|
|
headers.insert("X-Header-Two".to_string(), "value2".to_string());
|
|
|
|
|
headers.insert("X-Header-Three".to_string(), "value3".to_string());
|
|
|
|
|
|
|
|
|
|
let result = build_header_map(headers);
|
|
|
|
|
|
|
|
|
|
// Should have exactly 3 custom headers
|
|
|
|
|
assert_eq!(result.len(), 3);
|
|
|
|
|
assert!(result.contains_key(&HeaderName::from_static("x-header-one")));
|
|
|
|
|
assert!(result.contains_key(&HeaderName::from_static("x-header-two")));
|
|
|
|
|
assert!(result.contains_key(&HeaderName::from_static("x-header-three")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_skips_invalid_header_name() {
|
|
|
|
|
let mut headers = HashMap::new();
|
|
|
|
|
headers.insert("Invalid Header Name".to_string(), "value".to_string()); // spaces invalid
|
|
|
|
|
headers.insert("Valid-Header".to_string(), "valid".to_string());
|
|
|
|
|
|
|
|
|
|
let result = build_header_map(headers);
|
|
|
|
|
|
|
|
|
|
// Should have only valid header, invalid is skipped
|
|
|
|
|
assert_eq!(result.len(), 1);
|
|
|
|
|
assert!(result.contains_key(&HeaderName::from_static("valid-header")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_skips_invalid_header_value() {
|
|
|
|
|
let mut headers = HashMap::new();
|
|
|
|
|
headers.insert("X-Valid-Name".to_string(), "invalid\nvalue".to_string()); // newline invalid
|
|
|
|
|
headers.insert("X-Another".to_string(), "valid".to_string());
|
|
|
|
|
|
|
|
|
|
let result = build_header_map(headers);
|
|
|
|
|
|
|
|
|
|
// Should have only valid header
|
|
|
|
|
assert_eq!(result.len(), 1);
|
|
|
|
|
assert!(result.contains_key(&HeaderName::from_static("x-another")));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
result.get(&HeaderName::from_static("x-another")).unwrap(),
|
|
|
|
|
"valid"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_skips_header_with_null_byte_in_name() {
|
|
|
|
|
let mut headers = HashMap::new();
|
|
|
|
|
headers.insert("X-Bad\0Header".to_string(), "value".to_string());
|
|
|
|
|
headers.insert("X-Good-Header".to_string(), "value".to_string());
|
|
|
|
|
|
|
|
|
|
let result = build_header_map(headers);
|
|
|
|
|
|
|
|
|
|
// Should have only good header
|
|
|
|
|
assert_eq!(result.len(), 1);
|
|
|
|
|
assert!(result.contains_key(&HeaderName::from_static("x-good-header")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_skips_header_with_null_byte_in_value() {
|
|
|
|
|
let mut headers = HashMap::new();
|
|
|
|
|
headers.insert("X-Header".to_string(), "bad\0value".to_string());
|
|
|
|
|
headers.insert("X-Good".to_string(), "goodvalue".to_string());
|
|
|
|
|
|
|
|
|
|
let result = build_header_map(headers);
|
|
|
|
|
|
|
|
|
|
// Should have only good header
|
|
|
|
|
assert_eq!(result.len(), 1);
|
|
|
|
|
assert!(result.contains_key(&HeaderName::from_static("x-good")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_empty_string_value_allowed() {
|
|
|
|
|
let mut headers = HashMap::new();
|
|
|
|
|
headers.insert("X-Empty-Value".to_string(), "".to_string());
|
|
|
|
|
|
|
|
|
|
let result = build_header_map(headers);
|
|
|
|
|
|
|
|
|
|
// Empty string is valid
|
|
|
|
|
assert!(result.contains_key(&HeaderName::from_static("x-empty-value")));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
result
|
|
|
|
|
.get(&HeaderName::from_static("x-empty-value"))
|
|
|
|
|
.unwrap(),
|
|
|
|
|
""
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_unicode_in_header_value_accepted() {
|
|
|
|
|
let mut headers = HashMap::new();
|
|
|
|
|
headers.insert("X-Unicode".to_string(), "café".to_string()); // UTF-8 is valid in HTTP header values
|
|
|
|
|
headers.insert("X-Valid".to_string(), "ascii".to_string());
|
|
|
|
|
|
|
|
|
|
let result = build_header_map(headers);
|
|
|
|
|
|
|
|
|
|
// HeaderValue accepts valid UTF-8
|
|
|
|
|
assert_eq!(result.len(), 2); // unicode + valid
|
|
|
|
|
assert!(result.contains_key(&HeaderName::from_static("x-valid")));
|
|
|
|
|
assert!(result.contains_key(&HeaderName::from_static("x-unicode")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_reserved_accept_header_rejected() {
|
|
|
|
|
let mut headers = HashMap::new();
|
|
|
|
|
headers.insert("X-Custom".to_string(), "value".to_string());
|
|
|
|
|
// Try to override Accept - should be rejected as reserved
|
|
|
|
|
headers.insert(
|
|
|
|
|
"Accept".to_string(),
|
|
|
|
|
"application/json, text/event-stream".to_string(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let result = build_header_map(headers);
|
|
|
|
|
|
|
|
|
|
// Should have only custom header, Accept is reserved by rmcp
|
|
|
|
|
assert_eq!(result.len(), 1);
|
|
|
|
|
assert!(result.contains_key(&HeaderName::from_static("x-custom")));
|
|
|
|
|
assert!(!result.contains_key(&HeaderName::from_static("accept")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transport building tests (verify no panics with Tokio runtime)
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_builds_transport_with_http() {
|
|
|
|
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
|
|
|
let _guard = rt.enter();
|
|
|
|
|
let _transport = build_http_transport("http://localhost:8080", None, HashMap::new());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_builds_transport_with_https() {
|
|
|
|
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
|
|
|
let _guard = rt.enter();
|
|
|
|
|
let _transport = build_http_transport("https://example.com/mcp", None, HashMap::new());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_builds_transport_with_auth() {
|
|
|
|
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
|
|
|
let _guard = rt.enter();
|
|
|
|
|
let _transport = build_http_transport(
|
|
|
|
|
"http://localhost:8080",
|
|
|
|
|
Some("Bearer token123"),
|
|
|
|
|
HashMap::new(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|