Skip to content

Commit 0c1a250

Browse files
author
Claude Bot
committed
Add in-memory heap storage implementation
- Implemented MemoryHeapStorage using Arc<RwLock<HashMap>> - Added --memory CLI flag for in-memory storage mode - Memory storage is ideal for testing and scenarios where persistence is not needed - Data is stored in process memory and lost when the process terminates - Updated AnyHeapStorage enum to include Memory variant - Exported MemoryHeapStorage from mcp module
1 parent 1423567 commit 0c1a250

File tree

3 files changed

+48
-7
lines changed

3 files changed

+48
-7
lines changed

server/src/main.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rmcp::transport::sse_server::{SseServer, SseServerConfig};
1212
use tokio_util::sync::CancellationToken;
1313

1414
mod mcp;
15-
use mcp::{StatelessService, StatefulService, initialize_v8};
15+
use mcp::{StatelessService, StatefulService, initialize_v8, MemoryHeapStorage};
1616
use mcp::heap_storage::{AnyHeapStorage, S3HeapStorage, FileHeapStorage};
1717

1818
/// Command line arguments for configuring heap storage
@@ -21,15 +21,19 @@ use mcp::heap_storage::{AnyHeapStorage, S3HeapStorage, FileHeapStorage};
2121
struct Cli {
2222

2323
/// S3 bucket name (required if --use-s3)
24-
#[arg(long, conflicts_with_all = ["directory_path", "stateless"])]
24+
#[arg(long, conflicts_with_all = ["directory_path", "stateless", "memory"])]
2525
s3_bucket: Option<String>,
2626

2727
/// Directory path for filesystem storage (required if --use-filesystem)
28-
#[arg(long, conflicts_with_all = ["s3_bucket", "stateless"])]
28+
#[arg(long, conflicts_with_all = ["s3_bucket", "stateless", "memory"])]
2929
directory_path: Option<String>,
3030

31+
/// Use in-memory storage for heap snapshots (data is lost when process terminates)
32+
#[arg(long, conflicts_with_all = ["s3_bucket", "directory_path", "stateless"])]
33+
memory: bool,
34+
3135
/// Run in stateless mode - no heap snapshots are saved or loaded
32-
#[arg(long, conflicts_with_all = ["s3_bucket", "directory_path"])]
36+
#[arg(long, conflicts_with_all = ["s3_bucket", "directory_path", "memory"])]
3337
stateless: bool,
3438

3539
/// HTTP port to listen on (if not specified, uses stdio transport)
@@ -81,6 +85,8 @@ async fn main() -> Result<()> {
8185
AnyHeapStorage::S3(S3HeapStorage::new(bucket).await)
8286
} else if let Some(dir) = cli.directory_path {
8387
AnyHeapStorage::File(FileHeapStorage::new(dir))
88+
} else if cli.memory {
89+
AnyHeapStorage::Memory(MemoryHeapStorage::new())
8490
} else {
8591
// default to file /tmp/mcp-v8-heaps
8692
AnyHeapStorage::File(FileHeapStorage::new("/tmp/mcp-v8-heaps"))

server/src/mcp.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use std::sync::Once;
1111
use v8::{self};
1212

1313
pub(crate) mod heap_storage;
14+
pub use crate::mcp::heap_storage::MemoryHeapStorage;
1415
use crate::mcp::heap_storage::{HeapStorage, AnyHeapStorage};
1516

1617

server/src/mcp/heap_storage.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use aws_sdk_s3::ByteStream;
55
use aws_config;
66
use std::sync::Arc;
77
use async_trait::async_trait;
8+
use std::collections::HashMap;
9+
use tokio::sync::RwLock;
810

911
#[async_trait]
1012
pub trait HeapStorage: Send + Sync + 'static {
@@ -98,28 +100,60 @@ impl HeapStorage for S3HeapStorage {
98100
}
99101
}
100102

103+
/// In-memory heap storage using a HashMap
104+
/// Data is stored in memory and will be lost when the process terminates
105+
#[derive(Clone)]
106+
pub struct MemoryHeapStorage {
107+
store: Arc<RwLock<HashMap<String, Vec<u8>>>>,
108+
}
109+
110+
impl MemoryHeapStorage {
111+
pub fn new() -> Self {
112+
Self {
113+
store: Arc::new(RwLock::new(HashMap::new())),
114+
}
115+
}
116+
}
117+
118+
#[async_trait]
119+
impl HeapStorage for MemoryHeapStorage {
120+
async fn put(&self, name: &str, data: &[u8]) -> Result<(), String> {
121+
let mut store = self.store.write().await;
122+
store.insert(name.to_string(), data.to_vec());
123+
Ok(())
124+
}
125+
126+
async fn get(&self, name: &str) -> Result<Vec<u8>, String> {
127+
let store = self.store.read().await;
128+
store
129+
.get(name)
130+
.cloned()
131+
.ok_or_else(|| format!("Heap '{}' not found in memory storage", name))
132+
}
133+
}
134+
101135
#[derive(Clone)]
102136
pub enum AnyHeapStorage {
103137
#[allow(dead_code)]
104138
File(FileHeapStorage),
105139
S3(S3HeapStorage),
140+
Memory(MemoryHeapStorage),
106141
}
107142

108-
109-
110-
111143
#[async_trait::async_trait]
112144
impl HeapStorage for AnyHeapStorage {
113145
async fn put(&self, name: &str, data: &[u8]) -> Result<(), String> {
114146
match self {
115147
AnyHeapStorage::File(inner) => inner.put(name, data).await,
116148
AnyHeapStorage::S3(inner) => inner.put(name, data).await,
149+
AnyHeapStorage::Memory(inner) => inner.put(name, data).await,
117150
}
118151
}
119152
async fn get(&self, name: &str) -> Result<Vec<u8>, String> {
120153
match self {
121154
AnyHeapStorage::File(inner) => inner.get(name).await,
122155
AnyHeapStorage::S3(inner) => inner.get(name).await,
156+
AnyHeapStorage::Memory(inner) => inner.get(name).await,
123157
}
124158
}
125159
}

0 commit comments

Comments
 (0)