Skip to content

Commit 72f7d31

Browse files
author
Your Name
committed
update tool description
1 parent 105b54e commit 72f7d31

File tree

3 files changed

+246
-1
lines changed

3 files changed

+246
-1
lines changed

server/src/mcp.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ impl StatelessService {
161161
}
162162

163163
/// Execute JavaScript code in a fresh, stateless V8 isolate. Each execution starts with a clean environment.
164-
#[tool(description = "Execute JavaScript code in a stateless V8 isolate. No state is preserved between executions.")]
164+
#[tool(description = include_str!("run_js_tool_stateless.md"))]
165165
pub async fn run_js(&self, #[tool(param)] code: String) -> RunJsStatelessResponse {
166166
let v8_result = tokio::task::spawn_blocking(move || execute_stateless(code)).await;
167167

File renamed without changes.
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
run javascript code in v8
2+
3+
params:
4+
- code: the javascript code to run
5+
6+
returns:
7+
- output: the output of the javascript code
8+
9+
10+
11+
12+
## Limitations
13+
14+
While `mcp-v8` provides a powerful and persistent JavaScript execution environment, there are limitations to its runtime.
15+
16+
- **No `async`/`await` or Promises**: Asynchronous JavaScript is not supported. All code must be synchronous.
17+
- **No `fetch` or network access**: There is no built-in way to make HTTP requests or access the network.
18+
- **No `console.log` or standard output**: Output from `console.log` or similar functions will not appear. To return results, ensure the value you want is the last line of your code.
19+
- **No file system access**: The runtime does not provide access to the local file system or environment variables.
20+
- **No `npm install` or external packages**: You cannot install or import npm packages. Only standard JavaScript (ECMAScript) built-ins are available.
21+
- **No timers**: Functions like `setTimeout` and `setInterval` are not available.
22+
- **No DOM or browser APIs**: This is not a browser environment; there is no access to `window`, `document`, or other browser-specific objects.
23+
24+
25+
The way the runtime works, is that there is no console.log. If you want the results of an execution, you must return it in the last line of code.
26+
27+
28+
eg:
29+
30+
```js
31+
const result = 1 + 1;
32+
result;
33+
```
34+
35+
would return:
36+
37+
```
38+
2
39+
```
40+
41+
you must also jsonify an object, and return it as a string to see its content.
42+
43+
eg:
44+
45+
```js
46+
const obj = {
47+
a: 1,
48+
b: 2,
49+
};
50+
JSON.stringify(obj);
51+
```
52+
53+
would return:
54+
55+
```
56+
{"a":1,"b":2}
57+
```
58+
59+
the source code of the runtime is this
60+
```rust
61+
use rmcp::{
62+
model::{ServerCapabilities, ServerInfo},
63+
64+
Error as McpError, RoleServer, ServerHandler, model::*, schemars,
65+
service::RequestContext, tool,
66+
};
67+
use serde_json::json;
68+
69+
70+
use std::sync::Once;
71+
use v8::{self};
72+
73+
pub(crate) mod heap_storage;
74+
use crate::mcp::heap_storage::{HeapStorage, AnyHeapStorage};
75+
76+
77+
78+
79+
fn eval<'s>(scope: &mut v8::HandleScope<'s>, code: &str) -> Result<v8::Local<'s, v8::Value>, String> {
80+
let scope = &mut v8::EscapableHandleScope::new(scope);
81+
let source = v8::String::new(scope, code).ok_or("Failed to create V8 string")?;
82+
let script = v8::Script::compile(scope, source, None).ok_or("Failed to compile script")?;
83+
let r = script.run(scope).ok_or("Failed to run script")?;
84+
Ok(scope.escape(r))
85+
}
86+
87+
static INIT: Once = Once::new();
88+
static mut PLATFORM: Option<v8::SharedRef<v8::Platform>> = None;
89+
90+
pub fn initialize_v8() {
91+
INIT.call_once(|| {
92+
let platform = v8::new_default_platform(0, false).make_shared();
93+
v8::V8::initialize_platform(platform.clone());
94+
v8::V8::initialize();
95+
unsafe {
96+
PLATFORM = Some(platform);
97+
}
98+
});
99+
}
100+
101+
102+
103+
#[allow(dead_code)]
104+
pub trait DataService: Send + Sync + 'static {
105+
fn get_data(&self) -> String;
106+
fn set_data(&mut self, data: String);
107+
}
108+
109+
#[derive(Clone)]
110+
pub struct GenericService {
111+
heap_storage: AnyHeapStorage,
112+
}
113+
114+
// response to run_js
115+
#[derive(Debug, Clone)]
116+
pub struct RunJsResponse {
117+
pub output: String,
118+
pub heap: String,
119+
}
120+
121+
impl IntoContents for RunJsResponse {
122+
fn into_contents(self) -> Vec<Content> {
123+
match Content::json(json!({
124+
"output": self.output,
125+
"heap": self.heap,
126+
})) {
127+
Ok(content) => vec![content],
128+
Err(e) => vec![Content::text(format!("Failed to convert run_js response to content: {}", e))],
129+
}
130+
}
131+
}
132+
133+
#[tool(tool_box)]
134+
impl GenericService {
135+
pub async fn new(
136+
heap_storage: AnyHeapStorage,
137+
) -> Self {
138+
Self {
139+
heap_storage,
140+
}
141+
}
142+
143+
#[tool(description = include_str!("run_js_tool_description.md"))]
144+
pub async fn run_js(&self, #[tool(param)] code: String, #[tool(param)] heap: String) -> RunJsResponse {
145+
let snapshot = self.heap_storage.get(&heap).await.ok();
146+
let code_clone = code.clone();
147+
let v8_result = tokio::task::spawn_blocking(move || {
148+
let mut snapshot_creator = match snapshot {
149+
Some(snapshot) => {
150+
eprintln!("creating isolate from snapshot...");
151+
v8::Isolate::snapshot_creator_from_existing_snapshot(snapshot, None, None)
152+
}
153+
None => {
154+
eprintln!("snapshot not found, creating new isolate...");
155+
v8::Isolate::snapshot_creator(Default::default(), Default::default())
156+
}
157+
};
158+
// Always call create_blob before dropping snapshot_creator
159+
let mut output_result: Result<String, String> = Err("Unknown error".to_string());
160+
{
161+
let scope = &mut v8::HandleScope::new(&mut snapshot_creator);
162+
let context = v8::Context::new(scope, Default::default());
163+
let scope = &mut v8::ContextScope::new(scope, context);
164+
let result = eval(scope, &code_clone);
165+
match result {
166+
Ok(result) => {
167+
let result_str = result
168+
.to_string(scope)
169+
.ok_or_else(|| "Failed to convert result to string".to_string());
170+
match result_str {
171+
Ok(s) => {
172+
output_result = Ok(s.to_rust_string_lossy(scope));
173+
}
174+
Err(e) => {
175+
output_result = Err(e);
176+
}
177+
}
178+
}
179+
Err(e) => {
180+
output_result = Err(e);
181+
}
182+
}
183+
scope.set_default_context(context);
184+
}
185+
// Always call create_blob before returning
186+
let startup_data = match snapshot_creator.create_blob(v8::FunctionCodeHandling::Clear) {
187+
Some(blob) => blob,
188+
None => return Ok::<_, std::convert::Infallible>((format!("V8 error: Failed to create V8 snapshot blob"), vec![])),
189+
};
190+
let startup_data_vec = startup_data.to_vec();
191+
match output_result {
192+
Ok(output) => Ok::<_, std::convert::Infallible>((output, startup_data_vec)),
193+
Err(e) => Ok::<_, std::convert::Infallible>((format!("V8 error: {}", e), startup_data_vec)),
194+
}
195+
}).await;
196+
match v8_result {
197+
Ok(Ok((output, startup_data))) => {
198+
if let Err(e) = self.heap_storage.put(&heap, &startup_data).await {
199+
return RunJsResponse {
200+
output: format!("Error saving heap: {}", e),
201+
heap,
202+
};
203+
}
204+
RunJsResponse { output, heap }
205+
}
206+
Ok(Err(e)) => RunJsResponse {
207+
output: format!("V8 error: {}", e),
208+
heap,
209+
},
210+
Err(e) => RunJsResponse {
211+
output: format!("Task join error: {}", e),
212+
heap,
213+
},
214+
}
215+
}
216+
217+
218+
}
219+
220+
#[tool(tool_box)]
221+
impl ServerHandler for GenericService {
222+
fn get_info(&self) -> ServerInfo {
223+
ServerInfo {
224+
instructions: Some("generic data service".into()),
225+
capabilities: ServerCapabilities::builder().enable_tools().build(),
226+
..Default::default()
227+
}
228+
}
229+
230+
231+
async fn initialize(
232+
&self,
233+
_request: InitializeRequestParam,
234+
context: RequestContext<RoleServer>,
235+
) -> Result<InitializeResult, McpError> {
236+
if let Some(http_request_part) = context.extensions.get::<axum::http::request::Parts>() {
237+
let initialize_headers = &http_request_part.headers;
238+
let initialize_uri = &http_request_part.uri;
239+
tracing::info!(?initialize_headers, %initialize_uri, "initialize from http server");
240+
}
241+
Ok(self.get_info())
242+
}
243+
}
244+
```
245+

0 commit comments

Comments
 (0)