Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions crates/kmp-mcp/src/serving/kernel_mcp_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@ impl KernelMcpServer {
server
}

/// Keeps a remote store's agents and salt under `data_home` instead of
/// the user data home, the way a hermetic caller (a test) needs (#910).
pub fn with_remote_state_home(mut self, data_home: &std::path::Path) -> Self {
let relocate = |path: Option<std::path::PathBuf>| {
path.and_then(|path| {
path.file_name()
.map(|name| data_home.join("agent-users").join(name))
})
};
self.agent_directory_path = relocate(self.agent_directory_path.take());
self.telemetry_salt_path = relocate(self.telemetry_salt_path.take());
self
}

pub fn embedded(data_dir: &std::path::Path) -> Result<Self, String> {
Self::embedded_with_engine(
data_dir,
Expand Down
6 changes: 4 additions & 2 deletions crates/kmp-mcp/tests/cli_surface_parity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
//! `KMP_BLESS_CLI_SURFACE=1 cargo test -p kmp-mcp --test cli_surface_parity`
//! and review the diff as the interface change it is.

#[path = "support/isolated_home.rs"]
mod isolated_home;

use std::path::{Path, PathBuf};
use std::process::Command;

const BLESS: &str = "KMP_BLESS_CLI_SURFACE";

Expand Down Expand Up @@ -43,7 +45,7 @@ fn fixtures() -> PathBuf {
/// Runs the binary with a clean environment, so the pinned text is the
/// executable's own and not a reflection of this machine.
fn help(arguments: &[&str]) -> String {
let mut command = Command::new(env!("CARGO_BIN_EXE_kmp-mcp"));
let mut command = isolated_home::command(env!("CARGO_BIN_EXE_kmp-mcp"));
command.args(arguments);
for name in ["KMP_MCP_DATA_DIR", "KMP_MCP_BACKEND", "KMP_VIEWER_ADDR"] {
command.env_remove(name);
Expand Down
9 changes: 4 additions & 5 deletions crates/kmp-mcp/tests/consolidation_cli.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
#[path = "support/isolated_home.rs"]
mod isolated_home;
use kmp_domain::{NodeDetailProjection, NodeProjection, ProjectionMutation, ProjectionWriter};
use serde_json::{Value, json};
use std::{
io::Write,
process::{Command, Stdio},
};
use std::{io::Write, process::Stdio};

fn run(store: &std::path::Path, verb: &str, input: &Value) -> std::process::Output {
run_raw(store, verb, input.to_string().as_bytes())
}

fn run_raw(store: &std::path::Path, verb: &str, input: &[u8]) -> std::process::Output {
let mut child = Command::new(env!("CARGO_BIN_EXE_kmp-mcp"))
let mut child = isolated_home::command(env!("CARGO_BIN_EXE_kmp-mcp"))
.args(["consolidation", verb])
.env("KMP_MCP_DATA_DIR", store)
.stdin(Stdio::piped())
Expand Down
7 changes: 5 additions & 2 deletions crates/kmp-mcp/tests/context_cli.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
//! Captured-context transforms must work without touching a memory backend.

#[path = "support/isolated_home.rs"]
mod isolated_home;
use serde_json::{Value, json};
use std::io::Write;
use std::process::{Command, Stdio};
use std::process::Stdio;

fn run(directory: &std::path::Path, args: &[&str], input: &Value) -> std::process::Output {
let mut child = Command::new(env!("CARGO_BIN_EXE_kmp-mcp"))
let mut child = isolated_home::command(env!("CARGO_BIN_EXE_kmp-mcp"))
.args(args)
.current_dir(directory)
.env("KMP_MCP_BACKEND", "must-not-open")
Expand Down
23 changes: 16 additions & 7 deletions crates/kmp-mcp/tests/grpc_backend.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[path = "support/isolated_home.rs"]
mod isolated_home;
use std::path::PathBuf;
use std::sync::Arc;

Expand Down Expand Up @@ -30,7 +32,8 @@ mod dependency_checks;
async fn grpc_backend_maps_kernel_memory_service_responses_to_kmp_tools() {
let recorded = RecordedMemoryRequests::default();
let endpoint = spawn_fake_memory_server(recorded.clone()).await;
let server = KernelMcpServer::grpc(endpoint);
let server =
KernelMcpServer::grpc(endpoint).with_remote_state_home(&isolated_home::data_home());

let wake = call_tool(
&server,
Expand Down Expand Up @@ -204,7 +207,8 @@ async fn grpc_backend_maps_kernel_memory_service_responses_to_kmp_tools() {
async fn grpc_backend_maps_temporal_tools_to_kernel_memory_service() {
let recorded = RecordedMemoryRequests::default();
let endpoint = spawn_fake_memory_server(recorded.clone()).await;
let server = KernelMcpServer::grpc(endpoint);
let server =
KernelMcpServer::grpc(endpoint).with_remote_state_home(&isolated_home::data_home());

let forward = call_tool(
&server,
Expand Down Expand Up @@ -309,7 +313,8 @@ async fn grpc_backend_maps_temporal_tools_to_kernel_memory_service() {
async fn grpc_backend_maps_temporal_raw_refs_to_kernel_memory_service() {
let recorded = RecordedMemoryRequests::default();
let endpoint = spawn_fake_memory_server(recorded.clone()).await;
let server = KernelMcpServer::grpc(endpoint);
let server =
KernelMcpServer::grpc(endpoint).with_remote_state_home(&isolated_home::data_home());

let forward = call_tool(
&server,
Expand Down Expand Up @@ -347,7 +352,8 @@ async fn grpc_backend_maps_temporal_raw_refs_to_kernel_memory_service() {
async fn grpc_backend_maps_kmp_ingest_to_kernel_memory_service() {
let recorded = RecordedMemoryRequests::default();
let endpoint = spawn_fake_memory_server(recorded.clone()).await;
let server = KernelMcpServer::grpc(endpoint);
let server =
KernelMcpServer::grpc(endpoint).with_remote_state_home(&isolated_home::data_home());

let ingest = call_tool(
&server,
Expand Down Expand Up @@ -479,7 +485,8 @@ async fn grpc_backend_maps_kmp_ingest_to_kernel_memory_service() {
async fn grpc_backend_forwards_incremental_ingest_with_empty_dimensions() {
let recorded = RecordedMemoryRequests::default();
let endpoint = spawn_fake_memory_server(recorded.clone()).await;
let server = KernelMcpServer::grpc(endpoint);
let server =
KernelMcpServer::grpc(endpoint).with_remote_state_home(&isolated_home::data_home());

let ingest = call_tool(
&server,
Expand Down Expand Up @@ -524,7 +531,8 @@ async fn grpc_backend_forwards_incremental_ingest_with_empty_dimensions() {
async fn grpc_backend_dry_run_ingest_reaches_the_kernel_with_writes_disabled() {
let recorded = RecordedMemoryRequests::default();
let endpoint = spawn_fake_memory_server(recorded.clone()).await;
let server = KernelMcpServer::grpc(endpoint);
let server =
KernelMcpServer::grpc(endpoint).with_remote_state_home(&isolated_home::data_home());

let ingest = call_tool(
&server,
Expand Down Expand Up @@ -575,7 +583,8 @@ async fn grpc_backend_connects_to_mutual_tls_kernel_memory_service() {
let certs = TestTlsFiles::write();
let endpoint =
spawn_fake_memory_server_with_mutual_tls(RecordedMemoryRequests::default()).await;
let server = KernelMcpServer::grpc_with_tls(endpoint, certs.client_config());
let server = KernelMcpServer::grpc_with_tls(endpoint, certs.client_config())
.with_remote_state_home(&isolated_home::data_home());

let inspect = call_tool(
&server,
Expand Down
5 changes: 4 additions & 1 deletion crates/kmp-mcp/tests/grpc_caller_forwarding.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! An MCP server on a gRPC backend names its session's host to the kernel:
//! the kernel's Ask and Wake lines log that host, not this server's
//! `user-agent`. Its own binary, with a global subscriber for both sides.
#[path = "support/isolated_home.rs"]
mod isolated_home;

use std::collections::BTreeMap;
use std::io::Write;
Expand Down Expand Up @@ -112,7 +114,8 @@ async fn the_kernel_logs_the_mcp_host_the_call_was_made_for() {
.serve_with_incoming(TcpListenerStream::new(listener)),
);

let server = KernelMcpServer::grpc(endpoint);
let server =
KernelMcpServer::grpc(endpoint).with_remote_state_home(&isolated_home::data_home());
let session = McpSession::new();
let initialize = json!({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {
"protocolVersion": "2025-06-18", "capabilities": {},
Expand Down
4 changes: 3 additions & 1 deletion crates/kmp-mcp/tests/guide_repair.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[path = "support/isolated_home.rs"]
mod isolated_home;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
Expand All @@ -9,7 +11,7 @@ fn plugin_root() -> PathBuf {
}

fn command(store: &Path) -> Command {
let mut command = Command::new(env!("CARGO_BIN_EXE_kmp-mcp"));
let mut command = isolated_home::command(env!("CARGO_BIN_EXE_kmp-mcp"));
command
.env("KMP_MCP_BACKEND", "embedded")
.env("KMP_MCP_DATA_DIR", store)
Expand Down
8 changes: 5 additions & 3 deletions crates/kmp-mcp/tests/guide_sync.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#[path = "support/isolated_home.rs"]
mod isolated_home;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::process::Output;

use serde_json::Value;

Expand All @@ -8,7 +10,7 @@ fn plugin_root() -> PathBuf {
}

fn run_guide(store: &Path, extra: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_kmp-mcp"))
isolated_home::command(env!("CARGO_BIN_EXE_kmp-mcp"))
.args(["guide", "sync", "--plugin-root"])
.arg(plugin_root())
.args(extra)
Expand Down Expand Up @@ -36,7 +38,7 @@ fn guide_sync_is_an_explicit_idempotent_two_about_write() {
}

let bundle = scratch.path().join("memory.jsonl");
let output = Command::new(env!("CARGO_BIN_EXE_kmp-mcp"))
let output = isolated_home::command(env!("CARGO_BIN_EXE_kmp-mcp"))
.arg("export")
.arg(&bundle)
.env("KMP_MCP_DATA_DIR", &store)
Expand Down
7 changes: 5 additions & 2 deletions crates/kmp-mcp/tests/import_from_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
//! binary, on a hermetic machine (#903): HOME, XDG and the store are all
//! temporary, and the working directory is outside any repository.

#[path = "support/isolated_home.rs"]
mod isolated_home;

use std::collections::BTreeMap;
use std::path::Path;
use std::process::{Command, Output};
use std::process::Output;

use kmp_application::{
MemoryCoordinateData, MemoryData, MemoryDimensionData, MemoryEntryData, MemoryEvidenceData,
Expand Down Expand Up @@ -91,7 +94,7 @@ impl Machine {
}

fn run(&self, store: &Path, args: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_kmp-mcp"))
isolated_home::command(env!("CARGO_BIN_EXE_kmp-mcp"))
.args(args)
.current_dir(self.cwd.path())
.env("HOME", self.home.path())
Expand Down
5 changes: 3 additions & 2 deletions crates/kmp-mcp/tests/lifecycle_cli.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::process::Command;
#[path = "support/isolated_home.rs"]
mod isolated_home;

#[test]
fn lifecycle_http_adapter_never_drops_a_nested_runtime_on_the_async_cli() {
let scratch = tempfile::tempdir().expect("scratch home");
let output = Command::new(env!("CARGO_BIN_EXE_kmp-mcp"))
let output = isolated_home::command(env!("CARGO_BIN_EXE_kmp-mcp"))
.args(["setup", "--dry-run", "--version", "0.5.1"])
.env("HOME", scratch.path())
.env("PATH", "")
Expand Down
4 changes: 3 additions & 1 deletion crates/kmp-mcp/tests/lifecycle_real_hosts.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[path = "support/isolated_home.rs"]
mod isolated_home;
use std::collections::BTreeSet;
use std::fs;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -365,7 +367,7 @@ impl RealHostLifecycleHarness {
let stamp = source.join("FORMAT_VERSION");
let before = fs::read(&stamp).expect("baseline store stamp");
let refused_export = self.root.path().join("unsupported-export.jsonl");
let output = Command::new(self.shared_binary())
let output = isolated_home::command(self.shared_binary())
.args(["export", self.path(&refused_export)])
.env("KMP_MCP_DATA_DIR", &source)
.env("KMP_MCP_BACKEND", "embedded")
Expand Down
5 changes: 4 additions & 1 deletion crates/kmp-mcp/tests/memories_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
//! it: write memory from one project, export from another, name an old store
//! by hand, then stand somewhere else and ask.

#[path = "support/isolated_home.rs"]
mod isolated_home;

use std::collections::BTreeMap;
use std::fs;
use std::io::Write;
Expand Down Expand Up @@ -36,7 +39,7 @@ impl Machine {
}

fn command(&self, working_dir: &str) -> Command {
let mut command = Command::new(env!("CARGO_BIN_EXE_kmp-mcp"));
let mut command = isolated_home::command(env!("CARGO_BIN_EXE_kmp-mcp"));
command
.current_dir(self.at(working_dir))
.env("HOME", self.at("home"))
Expand Down
5 changes: 4 additions & 1 deletion crates/kmp-mcp/tests/memory_selection_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
//! precedence, both refusals, and the promise that an old store is never
//! touched.

#[path = "support/isolated_home.rs"]
mod isolated_home;

use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -53,7 +56,7 @@ impl Machine {
}

fn command(&self, working_dir: &str, data_dir: Option<&Path>) -> Command {
let mut command = Command::new(env!("CARGO_BIN_EXE_kmp-mcp"));
let mut command = isolated_home::command(env!("CARGO_BIN_EXE_kmp-mcp"));
command
.current_dir(self.at(working_dir))
.env("HOME", self.at("home"))
Expand Down
5 changes: 4 additions & 1 deletion crates/kmp-mcp/tests/plugin_bootstrap.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#![cfg(unix)]

#[path = "support/isolated_home.rs"]
mod isolated_home;

use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -297,7 +300,7 @@ exit 1
vec!["update", "--version", "9.9.9"],
),
] {
let output = Command::new(
let output = isolated_home::command(
self.root
.path()
.join("plugin/scripts/kmp-install-binary.sh"),
Expand Down
6 changes: 4 additions & 2 deletions crates/kmp-mcp/tests/plugin_notice.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#[path = "support/isolated_home.rs"]
mod isolated_home;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::process::Output;

use serde_json::json;

Expand Down Expand Up @@ -48,7 +50,7 @@ impl PluginNoticeHarness {
}

fn notice(&self, plugin: &Path, latest: &str) -> Output {
Command::new(&self.binary)
isolated_home::command(&self.binary)
.args([
"plugin",
"notice",
Expand Down
5 changes: 4 additions & 1 deletion crates/kmp-mcp/tests/relation_write_grpc.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//! The same attachment, source correction and replay through embedded SQLite and real gRPC.
#[path = "support/isolated_home.rs"]
mod isolated_home;

#[path = "support/relation_write_fixture.rs"]
pub mod fixture;
Expand Down Expand Up @@ -34,7 +36,8 @@ async fn a_link_declared_over_grpc_commits_exactly_what_the_embedded_path_commit
},
),
);
let server = KernelMcpServer::grpc(endpoint);
let server =
KernelMcpServer::grpc(endpoint).with_remote_state_home(&isolated_home::data_home());
let (remote_alias, remote_notice) = sources(&server).await;
assert_eq!(
remote_alias, alias,
Expand Down
8 changes: 6 additions & 2 deletions crates/kmp-mcp/tests/search_expansions_grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
//! that proposes expansions gets the same lint as `kmp_write_memory`, and,
//! since the kernel serves no judge, the same outcome as `kmp_write_memory`
//! on a backend that cannot judge: nothing is stored and the report says why.
#[path = "support/isolated_home.rs"]
mod isolated_home;

use kmp_application::SearchExpansionReport;
use kmp_mcp::KernelMcpServer;
Expand Down Expand Up @@ -180,7 +182,8 @@ async fn the_wire_and_kmp_write_memory_read_a_proposal_alike_and_store_none_with
let kernel = kernel().await;

// kmp_write_memory through the same kernel: its backend cannot judge.
let server = KernelMcpServer::grpc(kernel.endpoint.clone());
let server = KernelMcpServer::grpc(kernel.endpoint.clone())
.with_remote_state_home(&isolated_home::data_home());
let written = call(
&server,
"kmp_write_memory",
Expand Down Expand Up @@ -324,7 +327,8 @@ fn kmp_ingest(about: &str, key: &str, expansions: Option<&[&str]>) -> Value {
#[tokio::test]
async fn kmp_ingest_reports_what_the_wire_and_kmp_write_memory_report() {
let kernel = kernel().await;
let remote = KernelMcpServer::grpc(kernel.endpoint.clone());
let remote = KernelMcpServer::grpc(kernel.endpoint.clone())
.with_remote_state_home(&isolated_home::data_home());
let dir = tempfile::tempdir().expect("dir");
let embedded = KernelMcpServer::embedded(dir.path()).expect("embedded");

Expand Down
Loading
Loading