mirror of
https://github.com/samvallad33/vestige.git
synced 2026-05-08 23:32:37 +02:00
feat: add MCP Streamable HTTP transport with Bearer auth
Adds a second transport layer alongside stdio — Streamable HTTP on port 3928. Enables Claude.ai, remote clients, and web integrations to connect to Vestige over HTTP with per-session McpServer instances. - POST /mcp (JSON-RPC) + DELETE /mcp (session cleanup) - Bearer token auth with constant-time comparison (subtle crate) - Auto-generated UUID v4 token persisted with 0o600 permissions - Per-session McpServer instances with 30-min idle reaper - 100 max sessions, 50 concurrency limit, 256KB body limit - --http-port flag + VESTIGE_HTTP_PORT / VESTIGE_HTTP_BIND env vars - Module exports moved from binary to lib.rs for reusability - vestige CLI gains `serve` subcommand via shared lib Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
070889ef26
commit
816b577f69
11 changed files with 849 additions and 170 deletions
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::{NaiveDate, Utc};
|
||||
use clap::{Parser, Subcommand};
|
||||
|
|
@ -109,6 +110,19 @@ enum Commands {
|
|||
#[arg(long)]
|
||||
source: Option<String>,
|
||||
},
|
||||
|
||||
/// Start standalone HTTP MCP server (no stdio, for remote access)
|
||||
Serve {
|
||||
/// HTTP transport port
|
||||
#[arg(long, default_value = "3928")]
|
||||
port: u16,
|
||||
/// Also start the dashboard
|
||||
#[arg(long)]
|
||||
dashboard: bool,
|
||||
/// Dashboard port
|
||||
#[arg(long, default_value = "3927")]
|
||||
dashboard_port: u16,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
|
|
@ -139,6 +153,11 @@ fn main() -> anyhow::Result<()> {
|
|||
node_type,
|
||||
source,
|
||||
} => run_ingest(content, tags, node_type, source),
|
||||
Commands::Serve {
|
||||
port,
|
||||
dashboard,
|
||||
dashboard_port,
|
||||
} => run_serve(port, dashboard, dashboard_port),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -967,6 +986,82 @@ fn run_dashboard(port: u16, open_browser: bool) -> anyhow::Result<()> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Start standalone HTTP MCP server (no stdio transport)
|
||||
fn run_serve(port: u16, with_dashboard: bool, dashboard_port: u16) -> anyhow::Result<()> {
|
||||
use vestige_mcp::cognitive::CognitiveEngine;
|
||||
|
||||
println!("{}", "=== Vestige HTTP Server ===".cyan().bold());
|
||||
println!();
|
||||
|
||||
let storage = Storage::new(None)?;
|
||||
|
||||
#[cfg(feature = "embeddings")]
|
||||
{
|
||||
if let Err(e) = storage.init_embeddings() {
|
||||
println!(
|
||||
" {} Embeddings unavailable: {} (search will use keyword-only)",
|
||||
"!".yellow(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let storage = Arc::new(storage);
|
||||
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
rt.block_on(async move {
|
||||
let cognitive = Arc::new(tokio::sync::Mutex::new(CognitiveEngine::new()));
|
||||
{
|
||||
let mut cog = cognitive.lock().await;
|
||||
cog.hydrate(&storage);
|
||||
}
|
||||
|
||||
let (event_tx, _) =
|
||||
tokio::sync::broadcast::channel::<vestige_mcp::dashboard::events::VestigeEvent>(1024);
|
||||
|
||||
// Optionally start dashboard
|
||||
if with_dashboard {
|
||||
let ds = Arc::clone(&storage);
|
||||
let dc = Arc::clone(&cognitive);
|
||||
let dtx = event_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
match vestige_mcp::dashboard::start_background_with_event_tx(ds, Some(dc), dtx, dashboard_port).await {
|
||||
Ok(_) => println!(" {} Dashboard: http://127.0.0.1:{}", ">".cyan(), dashboard_port),
|
||||
Err(e) => eprintln!(" {} Dashboard failed: {}", "!".yellow(), e),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Get auth token
|
||||
let token = vestige_mcp::protocol::auth::get_or_create_auth_token()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to create auth token: {}", e))?;
|
||||
|
||||
let bind = std::env::var("VESTIGE_HTTP_BIND").unwrap_or_else(|_| "127.0.0.1".to_string());
|
||||
println!(" {} HTTP transport: http://{}:{}/mcp", ">".cyan(), bind, port);
|
||||
println!(" {} Auth token: {}...", ">".cyan(), &token[..8]);
|
||||
println!();
|
||||
println!("{}", "Press Ctrl+C to stop.".dimmed());
|
||||
|
||||
// Start HTTP transport (blocks on the server, no stdio)
|
||||
vestige_mcp::protocol::http::start_http_transport(
|
||||
Arc::clone(&storage),
|
||||
Arc::clone(&cognitive),
|
||||
event_tx,
|
||||
token,
|
||||
port,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("HTTP transport failed: {}", e))?;
|
||||
|
||||
// Keep the process alive (the HTTP server runs in a spawned task)
|
||||
tokio::signal::ctrl_c().await.ok();
|
||||
println!();
|
||||
println!("{}", "Shutting down...".dimmed());
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Truncate a string for display (UTF-8 safe)
|
||||
fn truncate(s: &str, max_chars: usize) -> String {
|
||||
let s = s.replace('\n', " ");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue