返回市场
运行JS

运行JS

作者:CharlieDigital30 星标更新:2025-06-24

项目介绍

技术文档摘要

RunJS - 你需要的唯一MCP服务器

该项目包含一个MCP服务器,可以在隔离的沙箱中安全地执行JavaScript,并从脚本返回结果。它配备了一个使用System.Net.HttpClient实现的fetch模拟器,允许生成的JavaScript进行网络请求以及使用JSONPath处理结果 😎。还提供了一个Web API来安全存储密钥,以便可以使用API密钥进行API调用。有了这个MCP服务器,你可以基本上与任何REST API进行交互。

主要功能

  • 集成的密钥管理器,用于安全存储加密的API密钥
  • 不需要基础设施(例如部署容器或无服务器函数)即可在沙箱中执行JavaScript
  • 预装了用于发起HTTP请求的fetch模拟器
  • 预装了jsonpath-plus以处理JSON负载
  • 集成了通过Polly实现的重试机制,用于HTTP请求

RunJS MCP服务器快速介绍

# 本地测试
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服务器执行它 🚀

架构和流程

RunJS架构和流程

上图提供了架构和流程的概述。

  1. 一个Web API暴露了一个密钥管理器,用于安全存储密钥,以防止它们被暴露给LLM。LLM通过ID引用这些密钥,并在执行API调用时用实际值替换它们。
  2. 密钥被加密并存储在Postgres数据库中。
  3. 调用者会收到一个密钥ID;调用者存储这个值。
  4. 提示中有执行JavaScript或进行API调用的指令;如果需要任何密钥,它们将通过密钥ID引用。
  5. LLM生成JavaScript以进行API调用或操作数据,并使用RunJS MCP服务器执行JavaScript。实际上这是一个来自应用程序(而不是来自LLM)的工具调用,这就是为什么localhostURL有效的原因(并且为什么这将在你的私有网络上游工作)。
  6. SDK(例如Vercel AI SDK、Semantic Kernel)将调用发送到MCP服务器,包括生成的JavaScript和从中提取的任何密钥ID。
  7. 如果提供了密钥ID,服务器将用实际值交换它。
  8. 实际值被返回并传递给工具实现。
  9. 工具使用一个fetch模拟器(使用System.Net.HttpClient实现)现在使用注入的密钥值进行HTTP请求!
  10. 结果从工具返回给LLM进行进一步处理(实际上是间接通过SDK,意味着它会回到Nuxt应用,并作为工具调用响应传递给LLM)。

如果你第一次使用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文档

运行Web应用

/app目录有一个Nuxt应用:

# 复制.env.sample为.env
cd app
npm run dev

这将在http://localhost:3000启动应用。

RunJS Nuxt Web应用

运行CLI客户端

如果你更喜欢,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" }'

测试Fetch

要测试使用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(不包括密钥)。

OTEL追踪


密钥

你可能希望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"。'

构建容器

要构建容器,请遵循以下步骤:

  1. 修改./server/appsettings.json(或在容器上设置环境变量)。
  2. 查看./Dockerfile以确定是否需要更改。
  3. 运行./db-script.sh(或复制命令手动运行,如果你在Windows上)。

你的容器准备好仅部署到私有网络。