handoff-mcp-server 0.17.0 → 0.17.3
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/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/package.json +1 -1
- package/src/mcp/handlers/mod.rs +7 -2
- package/src/setup.rs +3 -3
- package/src/storage/sessions.rs +28 -12
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "handoff-mcp-server",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.3",
|
|
4
4
|
"description": "MCP server that gives AI coding agents persistent memory across sessions",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "AlphaElements <66808803+alphaelements@users.noreply.github.com>",
|
package/src/mcp/handlers/mod.rs
CHANGED
|
@@ -35,11 +35,16 @@ use serde_json::Value;
|
|
|
35
35
|
use crate::mcp::types::JsonRpcResponse;
|
|
36
36
|
|
|
37
37
|
pub fn resolve_project_dir(arguments: &Value) -> Result<PathBuf> {
|
|
38
|
-
let raw = match arguments
|
|
38
|
+
let raw = match arguments
|
|
39
|
+
.get("project_dir")
|
|
40
|
+
.and_then(|v| v.as_str())
|
|
41
|
+
.filter(|s| !s.is_empty() && !s.starts_with("${"))
|
|
42
|
+
{
|
|
39
43
|
Some(dir) => PathBuf::from(dir),
|
|
40
44
|
None => std::env::current_dir().context("Failed to get current directory")?,
|
|
41
45
|
};
|
|
42
|
-
std::fs::canonicalize(&raw)
|
|
46
|
+
std::fs::canonicalize(&raw)
|
|
47
|
+
.with_context(|| format!("Invalid project path: {raw}", raw = raw.display()))
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
pub fn handle_tool_call(name: &str, arguments: &Value) -> JsonRpcResponse {
|
package/src/setup.rs
CHANGED
|
@@ -56,7 +56,7 @@ fn build_hooks_config() -> BTreeMap<&'static str, Value> {
|
|
|
56
56
|
"UserPromptSubmit",
|
|
57
57
|
serde_json::json!([{
|
|
58
58
|
"hooks": [mcp_tool_hook(HOOK_TOOL_QUERY, serde_json::json!({
|
|
59
|
-
"project_dir": "${
|
|
59
|
+
"project_dir": "${CLAUDE_PROJECT_DIR}",
|
|
60
60
|
"session_id": "${session_id}",
|
|
61
61
|
"text": "${prompt}"
|
|
62
62
|
}))]
|
|
@@ -68,7 +68,7 @@ fn build_hooks_config() -> BTreeMap<&'static str, Value> {
|
|
|
68
68
|
serde_json::json!([{
|
|
69
69
|
"matcher": "Edit|Write|MultiEdit",
|
|
70
70
|
"hooks": [mcp_tool_hook(HOOK_TOOL_QUERY, serde_json::json!({
|
|
71
|
-
"project_dir": "${
|
|
71
|
+
"project_dir": "${CLAUDE_PROJECT_DIR}",
|
|
72
72
|
"session_id": "${session_id}",
|
|
73
73
|
"tool_name": "${tool_name}",
|
|
74
74
|
"text": "${tool_input.file_path}",
|
|
@@ -81,7 +81,7 @@ fn build_hooks_config() -> BTreeMap<&'static str, Value> {
|
|
|
81
81
|
"SessionStart",
|
|
82
82
|
serde_json::json!([{
|
|
83
83
|
"hooks": [mcp_tool_hook(HOOK_TOOL_CLEANUP, serde_json::json!({
|
|
84
|
-
"project_dir": "${
|
|
84
|
+
"project_dir": "${CLAUDE_PROJECT_DIR}"
|
|
85
85
|
}))]
|
|
86
86
|
}]),
|
|
87
87
|
);
|
package/src/storage/sessions.rs
CHANGED
|
@@ -87,19 +87,34 @@ fn summary_to_slug(summary: &str) -> String {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
fn
|
|
91
|
-
|
|
92
|
-
let
|
|
93
|
-
|
|
94
|
-
.
|
|
95
|
-
.replace('Z', "");
|
|
96
|
-
if ts_compact.len() >= 15 {
|
|
97
|
-
ts_compact[..15].to_string()
|
|
90
|
+
fn extract_timestamp_from_id(id: &str) -> Option<String> {
|
|
91
|
+
// "s-20260704-003557-792065" → "20260704-003557"
|
|
92
|
+
let rest = id.strip_prefix("s-")?;
|
|
93
|
+
if rest.len() >= 15 {
|
|
94
|
+
Some(rest[..15].to_string())
|
|
98
95
|
} else {
|
|
99
|
-
|
|
96
|
+
None
|
|
100
97
|
}
|
|
101
98
|
}
|
|
102
99
|
|
|
100
|
+
fn compact_timestamp(data: &SessionData) -> String {
|
|
101
|
+
data.ended_at
|
|
102
|
+
.as_deref()
|
|
103
|
+
.map(|ts| {
|
|
104
|
+
let compact = ts
|
|
105
|
+
.replace(['-', ':'], "")
|
|
106
|
+
.replace('T', "-")
|
|
107
|
+
.replace('Z', "");
|
|
108
|
+
if compact.len() >= 15 {
|
|
109
|
+
compact[..15].to_string()
|
|
110
|
+
} else {
|
|
111
|
+
compact
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
.or_else(|| data.id.as_deref().and_then(extract_timestamp_from_id))
|
|
115
|
+
.unwrap_or_else(|| "00000000-000000".to_string())
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
fn synthesize_id_from_filename(filename: &str) -> String {
|
|
104
119
|
// Old format: YYYYMMDD-HHMMSS-slug.status.json
|
|
105
120
|
// Extract timestamp part and create s-YYYYMMDD-HHMMSS-000000
|
|
@@ -443,7 +458,7 @@ fn find_and_update_active_session(
|
|
|
443
458
|
);
|
|
444
459
|
}
|
|
445
460
|
|
|
446
|
-
if let Some((path,
|
|
461
|
+
if let Some((path, _, _)) = matches.into_iter().next() {
|
|
447
462
|
let content = std::fs::read_to_string(&path)
|
|
448
463
|
.with_context(|| format!("Failed to read session: {}", path.display()))?;
|
|
449
464
|
let mut data: SessionData = serde_json::from_str(&content)
|
|
@@ -457,8 +472,9 @@ fn find_and_update_active_session(
|
|
|
457
472
|
.with_context(|| format!("Failed to write session: {}", path.display()))?;
|
|
458
473
|
|
|
459
474
|
if let Some(target_status) = transition_to {
|
|
460
|
-
let
|
|
461
|
-
let
|
|
475
|
+
let ts_part = compact_timestamp(&data);
|
|
476
|
+
let base = generate_session_filename(&data.summary, &ts_part);
|
|
477
|
+
let new_name = format!("{base}.{target_status}.json");
|
|
462
478
|
let new_path = sessions_dir.join(&new_name);
|
|
463
479
|
std::fs::rename(&path, &new_path).with_context(|| {
|
|
464
480
|
format!(
|