该项目包含一个MCP服务器,可以在隔离的沙箱中安全地执行JavaScript,并从脚本返回结果。它配备了一个使用System.Net.HttpClient实现的fetch模拟器,允许生成的JavaScript进行网络请求以及使用JSONPath处理结果 😎。还提供了一个Web API来安全存储密钥,以便可以使用API密钥进行API调用。有了这个MCP服务器,你可以基本上与任何REST API进行交互。
fetch模拟器jsonpath-plus以处理JSON负载# 本地测试
docker pull cdigs/runjs-mcp-server
# 其他选项也可以通过ENV设置
docker run -p 5000:8080 \
-e RunJSConfig__Jint__LimitMemory=10000000 \
-e RunJSConfig__Jint__TimeoutIntervalSeconds=30 \
cdigs/runjs-mcp-server
# 默认的密钥存储是临时的,在容器重启时会消失。
# 若要持久化存储,请设置连接字符串并切换到使用数据库(见下文)。
RunJS MCP服务器使用Jint——这是一个嵌入JavaScript运行时到.NET中的C#库,并允许通过指定以下内容来控制执行沙箱:
这非常强大,因为在许多情况下,你可能希望运行JavaScript,但因为JavaScript和生成代码的特性,安全地运行它是具有挑战性的。由于生成的JavaScript是在C#解释器中运行的,因此不会泄露环境变量,也不会有恶意包的风险,也不会导致JS崩溃服务器进程。
使用这种机制可以解锁许多需要JavaScript来处理一些JSON的用例,例如返回文本或对传入数据执行某些转换逻辑。
这里是一个使用Vercel AI SDK的示例调用:
const mcpClient = await createMCPClient({
transport: {
type: "sse",
url: "http://localhost:5000/sse",
},
});
const tools = await mcpClient.tools();
const prompt = `
生成并执行JavaScript,该JavaScript可以解析以下JSON
JavaScript应“返回”值
只返回name属性的值:
{ "id": 12345, "name": "Charles Chen", "handle": "chrlschn" }`;
try {
const { text } = await generateText({
model: openai("gpt-4.1-nano"),
prompt,
tools,
maxSteps: 10, // 👈 非常重要,否则你将得不到输出!
});
console.log("输出:", text);
} finally {
await mcpClient.close();
}
LLM将生成以下JavaScript:
const jsonString = '{ "id": 12345, "name": "Charles Chen", "handle": "chrlschn" }';
const obj = JSON.parse(jsonString);
return obj.name;
然后使用RunJS MCP服务器执行它 🚀

上图提供了架构和流程的概述。
localhostURL有效的原因(并且为什么这将在你的私有网络上游工作)。fetch模拟器(使用System.Net.HttpClient实现)现在使用注入的密钥值进行HTTP请求!如果你第一次使用MCP,这可能会显得复杂,但只需记住所有这些都可在本地运行,你应该开始理解调用是如何流动的;LLM永远不会看到实际的密钥。
项目按照以下结构设置:
📁 app # 一个示例Nuxt Web应用,便于测试
📁 cli # 使用Vercel AI SDK的示例客户端应用
📁 src
.env # 👈 根据.env.sample创建自己的
.env.sample # 示例.env文件;复制为.env
📁 server
📁 Data # 用于Secrets的数据库工件
📁 Endpoints # 注册和管理可用于HTTP客户端的Secrets的.NET Web API端点
📁 Mcp # 与MCP相关的工件
📁 Migrations # EF Core数据库的迁移
📁 Setup # DI容器设置
Program.cs # 暴露Jint工具的.NET MCP服务器
📁 tests # 小型集成测试套件,针对HTTP API调用和数据库访问
builder-server.sh # 简单的构建容器脚本(命令)
docker-compose.yaml # 启动Aspire Dashboard容器以支持OTEL和用于存储Secrets的Postgres服务器。
Dockerfile # .NET服务器CLI的Dockerfile
如果你还没有安装,请安装.NET SDK;它适用于Windows、Linux和macOS。
安装后,你可以运行以下命令启动服务器:
dotnet run --project server
这将在端口5000启动MCP服务器。对于本地使用或私有网络使用,无需做任何特殊操作。若要将本地MCP暴露给外部客户端(例如本地MCP和已部署的应用程序),你需要映射一个代理。
若要在远程源处使用此服务调用LLM API,你需要通过像ngrok这样的代理或VS Code端口转发工具将其暴露给OpenAI。
👉 确保将端口设置为公共
完成上述步骤后,你需要复制.env.sample文件并命名为.env,并设置你的OpenAI API密钥和URL:
OPENAI_API_KEY=sk-proj-kSZWV-M7.......K_MMv8JZRmIA
MCP_ENDPOINT=https://mhjt5hqd-5000.use.devtunnels.ms/sse
如果你仅使用本地调用(如/cli目录):
OPENAI_API_KEY=sk-proj-kSZWV-M7.......K_MMv8JZRmIA
MCP_ENDPOINT=http://localhost:5000/sse
从/cli目录运行以下命令:
cd cli
npm i
npm run cli -- "使用我的echo工具:Charles;写出你的回应"
这应该调用.NET MCP端点并输出你的名字!
🚨 目前没有认证 🚨
目前仅适合在私有网络中运行。
你为何可能使用这个?如果你的运行时应用程序是Python、JavaScript或其他语言,并且你需要一个快速、简单、安全且受控的上下文来运行生成的代码。
# 启动服务器
dotnet run --project server
# 启动带有热重载的服务器
dotnet watch run --project server --non-interactive
服务器限制通过appsettings.json文件配置。如果你选择部署(再次强调,👉 仅限私有网络 👈),你可以在容器中使用.NET环境变量覆盖这些设置:
RunJSConfig__Jint__LimitMemory=5000000
RunJSConfig__Jint__TimeoutIntervalSeconds=10
RunJSConfig__Jint__MaxStatements=1
RunJSConfig__Secrets__UseDatabase=false
RunJSConfig__Db__ConnectionString=YOUR_CONNECTION_STRING
更多关于运行时配置的详细信息,请参阅.NET文档。
在/app目录有一个Nuxt应用:
# 复制.env.sample为.env
cd app
npm run dev
这将在http://localhost:3000启动应用。

如果你更喜欢,CLI客户端是一种方便的方式,可以从CLI测试而无需UI:
# 复制.env.sample为.env
cd cli
npm run cli -- "我的提示在这里"
要测试这个,你可以运行两种类型的提示:
cd cli
# 测试echo
npm run cli -- "回声我的名字给我:Charles"
# 生成并执行JavaScript
npm run cli -- "生成一些JavaScript,将字符串'Hello, World'转换为小写并返回。给我结果;仅结果"
# 更复杂的例子
npm run cli -- '生成并执行JavaScript,解析以下JSON并返回name属性的值:{ "id": 12345, "name": "Charles Chen", "handle": "chrlschn" }'
要测试使用https://jsonplaceholder.typicode.com/端点的fetch模拟器,尝试以下提示:
cd cli
# 测试GET
npm run cli -- "生成一些JavaScript,从https://jsonplaceholder.typicode.com/posts/1 GET一个帖子并检索name属性"
# 测试POST
npm run cli -- '生成一些JavaScript,POST到https://jsonplaceholder.typicode.com/posts/并创建一个新的帖子:{ "title": "Hello", "body": "World!", "userId": 1 }。从结果中返回帖子的id'
后者生成并执行以下JavaScript:
(async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ title: 'Hello', body: 'World!', userId: 1 })
});
const data = await response.json();
return data.id;
})();
应返回结果101作为ID。
如果你运行以下命令:
docker compose up
你还将获得Aspire Dashboard,位于http://localhost:18888,用于追踪工具内部调用。
这是一个重要的工具,因为它会暴露实际生成和执行的JavaScript(不包括密钥)。

你可能希望RunJS进行需要API密钥的API调用。为了使这更安全,避免将API密钥传递给LLM,MCP服务器包含一个Web API端点,让你注册“密钥”。你会得到一个密钥ID,然后可以在运行时交换并注入实际API密钥。要使这工作,只需先进行正常的REST调用来注册你的密钥,然后使用返回的ID代替实际密钥。
如果负载包含一个密钥ID,那么当LLM调用RunJS时,该密钥将在后端加载并替换;密钥永远不会暴露给LLM。
要创建一个密钥:
# 持久密钥
curl -X POST http://localhost:5000/secrets \
-H "Content-Type: application/json" \
-d '{
"value": "abracadabra"
}'
# 一次性读取密钥
curl -X POST http://localhost:5000/secrets \
-H "Content-Type: application/json" \
-d '{
"value": "abracadabra",
"readOnce": true
}'
这将产生一个类似这样的密钥ID:
runjs:secret:fc719aab80ac402fa14e36038d948437
💡 一次性读取密钥可以在调用方拥有OAuth令牌的情况下使用,该令牌仅应在API调用中使用一次。一旦读取就会被丢弃。不过请注意:LLM可能会多次调用!
要测试是否在请求中被替换为实际值,你可以在负载的某个地方设置它(通常它只会替换在头部)。
然后使用以下提示进行测试:
npm run cli -- '生成一些JavaScript,POST到https://jsonplaceholder.typicode.com/posts/并创建一个新的帖子:{ "title": "Hello", "body": "runjs:secret:fc719aab80ac402fa14e36038d948437", "userId": 1 }。在Authorization头中包含密钥runjs:secret:fc719aab80ac402fa14e36038d948437。返回响应中是否包含短语"abracadabra"。'
要构建容器,请遵循以下步骤:
./server/appsettings.json(或在容器上设置环境变量)。./Dockerfile以确定是否需要更改。./db-script.sh(或复制命令手动运行,如果你在Windows上)。你的容器准备好仅部署到私有网络。