内容
# Roblox Studio MCP(双上下文)
本地 AI 到 Studio 的桥梁。MCP 客户端(Claude Desktop / Claude Code / Cursor)调用 `run_luau` 或 `get_instance_tree` 等工具;调用通过 **MCP 客户端 →(标准输入/输出)→ Node MCP 服务器 → 进程内队列 → Express 桥接器 →(本地 HTTP)→ Roblox Studio → DataModel**,结果通过相同路径返回。
Roblox 插件无法打开监听套接字,因此 Studio 始终 **向外轮询**。这一限制决定了架构。
与现有工具相比,有两个不同点:
1. **可配置端口**(解决端口冲突问题),插件端自动发现。
2. **双上下文**:在 **F5 播放测试** 期间,桥梁保持连接,不仅限于编辑模式,通过在实时游戏中运行的第二个轮询循环(运行时代理)。
```
MCP 客户端 ──标准输入/输出──▶ Node 进程 ─┬─ MCP 服务器(标准输入/输出)
└─ Express 桥接器 127.0.0.1:<端口>
▲ ▲
GET /poll?context=edit GET /poll?context=server
POST /response POST /response
│ │
编辑插件(PluginSecurity) 运行时代理(ServerScriptService 中的脚本)— 在 F5 时唤醒,轮询正在运行的服务器
```
## 布局
| 路径 | 描述 |
|------|------|
| `src/index.ts` | 入口:解析配置,启动桥梁 + MCP 服务器 |
| `src/config.ts` | 端口优先级:`--port` > `ROBLOX_STUDIO_PORT`/`PORT` 环境变量 > `config.json` > `58741` |
| `src/types.ts` | 共享的 `Command` / `CommandResult` / `Context` 类型 |
| `src/queue.ts` | 按上下文的队列,相关 ID,超时,`chooseContext` 路由 |
| `src/bridge.ts` | Express `/poll` `/response` `/heartbeat` `/settings`,上下文感知,可选身份验证 |
| `src/settings.ts` | 内存工具门控 + 标志(插件是真实来源;服务器强制执行) |
| `src/mcp-server.ts` | MCP 服务器 + 工具(每个 `tools/call` 都受设置控制) |
| `plugin/src/*.luau` | 编辑插件:`init.server`,`Config`,`Settings`,`Serializer`,`Executor`,`StatusWidget`,`RuntimeAgentSource` |
| `plugin/plugin.project.json` | Rojo 项目(构建目标) |
| `scripts/install-plugin.{sh,ps1}` | 将 `.rbxmx` 构建到本地插件文件夹 |
## 工具
每个工具 **默认启用**,可以在停靠的 **设置** 选项卡中切换关闭(参见下文)。服务器强制执行切换:禁用的工具在到达 Studio 之前返回错误。“写入”工具也尊重 **只读模式**。
| 工具 | 类型 | 上下文 | 备注 |
|------|------|---------|-------|
| `run_luau` | 写入 | 自动 / 编辑 / 服务器 | 打印输出 + 返回值(序列化) |
| `get_instance_tree` | 读取 | 自动 / 编辑 / 服务器 | 从点路径的深度限制树 |
| `read_console` | 读取 | 自动 / 编辑 / 服务器 | 最近的输出通过 `LogService` 历史记录 + 实时环缓冲区;`count`,`levelFilter` |
| `get_selection` | 读取 | 编辑 | 当前的 `Selection:Get()` 作为路径 |
| `search_instances` | 读取 | 自动 / 编辑 / 服务器 | 按名称子字符串 / `className` / 标签在根下 |
| `get_script_source` | 读取 | 自动 / 编辑 / 服务器 | `GetEditorSource`,备用 `.Source` |
| `list_scripts` | 读取 | 自动 / 编辑 / 服务器 | 根下的所有 `LuaSourceContainer` |
| `get_place_info` | 读取 | 自动 / 编辑 / 服务器 | 位置/游戏 ID,服务,子项计数,选择,相机 |
| `get_tagged` | 读取 | 自动 / 编辑 / 服务器 | `CollectionService:GetTagged(tag)` |
| `get_properties` | 读取 | 自动 / 编辑 / 服务器 | **精选** 常用属性转储(非详尽);可选 `propertyNames` |
| `set_property` | 写入 | 自动 / 编辑 / 服务器 | 编辑上下文包装在撤销历史记录中 |
| `write_script` | 写入 | 仅编辑 | `ScriptEditorService:UpdateSourceAsync` |
| `set_selection` | 写入 | 编辑 | `Selection:Set(paths)` |
| `create_instance` | 写入 | 自动 / 编辑 / 服务器 | `{ className, parentPath, name?, properties? }` |
| `delete_instance` | 写入 | 自动 / 编辑 / 服务器 | 在路径中销毁 |
| `clone_instance` | 写入 | 自动 / 编辑 / 服务器 | `{ path, parentPath? }` |
| `rename_instance` | 写入 | 自动 / 编辑 / 服务器 | `{ path, name }` |
| `set_parent` | 写入 | 自动 / 编辑 / 服务器 | `{ path, parentPath }` |
| `move_instance` | 写入 | 自动 / 编辑 / 服务器 | `{ path, cframe \| position }`(模型使用 `PivotTo`) |
| `bulk_set_property` | 写入 | 自动 / 编辑 / 服务器 | 一个可撤销的批次,超过 `paths[]` |
| `tag_instance` / `untag_instance` | 写入 | 自动 / 编辑 / 服务器 | CollectionService 添加/删除 |
| `insert_asset` | 写入 | 自动 / 编辑 / 服务器 | `InsertService:LoadAsset(assetId)` 然后父级(仅限所有者/公共) |
| `enable_playtest_agent` | 写入 | 仅编辑 | 在 F5 之前启用运行时代理 |
| `get_attribute` / `get_attributes` | 读取 | 自动 / 编辑 / 服务器 | `Instance:GetAttribute(s)` |
| `set_attribute` / `set_attributes` | 写入 | 自动 / 编辑 / 服务器 | 设置一个/多个属性(一个撤销) |
| `delete_attribute` | 写入 | 自动 / 编辑 / 服务器 | `SetAttribute(name, nil)` |
| `edit_script_lines` | 写入 | 仅编辑 | 替换 1 为基于行范围 `[startLine,endLine]` |
| `insert_script_lines` | 写入 | 仅编辑 | 在 1 为基于行的行之前插入 |
| `delete_script_lines` | 写入 | 仅编辑 | 删除 1 为基于行的行范围 |
| `find_and_replace_in_scripts` | 写入 | 仅编辑 | 在根下的脚本中查找/替换(`regex?`) |
| `grep_scripts` | 读取 | 自动 / 编辑 / 服务器 | 匹配 `{ path, line, text }` 在根下(`regex?`) |
| `get_script_analysis` | 读取 | 自动 / 编辑 / 服务器 | 编译检查(loadstring)语法诊断 |
| `undo` / `redo` | 写入 | 仅编辑 | `ChangeHistoryService:Undo()/Redo()` |
| `mass_create_objects` | 写入 | 自动 / 编辑 / 服务器 | 在一个撤销中创建多个(`items[]`) |
| `mass_duplicate` | 写入 | 自动 / 编辑 / 服务器 | 克隆 `count` 次,累积 `offset?` |
| `smart_duplicate` | 写入 | 自动 / 编辑 / 服务器 | 网格/线克隆布局(`columns?`,`spacing?`) |
| `mass_get_property` | 读取 | 自动 / 编辑 / 服务器 | 跨 `paths[]` 的一个属性 |
| `mass_set_property` | 写入 | 自动 / 编辑 / 服务器 | `bulk_set_property` 的别名 |
| `get_class_info` | 读取 | 自动 / 编辑 / 服务器 | 尽力而为(可创建?+ 适用精选属性);**没有完整的反射**(没有 API 转储捆绑) |
| `get_services` | 读取 | 自动 / 编辑 / 服务器 | 加载的服务(`game` 的子项) |
| `get_descendants` | 读取 | 自动 / 编辑 / 服务器 | 平坦的后代路径(`maxDepth?`,上限 5000) |
| `get_connected_instances` | 读取 | 自动 / 编辑 / 服务器 | `GetConnectedParts(true)` + 对象属性 |
| `compare_instances` | 读取 | 自动 / 编辑 / 服务器 | 两个实例的精选属性差异 |
| `get_project_structure` | 读取 | 自动 / 编辑 / 服务器 | 按服务和按类划分的子项计数 |
| `get_file_tree` | 读取 | 自动 / 编辑 / 服务器 | 根下的嵌套脚本树 |
| `set_properties` | 写入 | 自动 / 编辑 / 服务器 | 一个实例上的多个属性(一个撤销) |
| `search_by_property` | 读取 | 自动 / 编辑 / 服务器 | 属性 == 值的实例 |
| `get_tags` | 读取 | 自动 / 编辑 / 服务器 | `CollectionService:GetTags(instance)` |
| `get_asset_details` | 读取 | 自动 / 编辑 / 服务器 | `MarketplaceService:GetProductInfo` |
| `get_asset_thumbnail` | 读取 | 自动 / 编辑 / 服务器 | 资产的 `rbxthumb://` 内容 ID |
| `preview_asset` | 读取 | 自动 / 编辑 / 服务器 | 产品信息 + 缩略图内容 ID |
| `search_materials` | 读取 | 自动 / 编辑 / 服务器 | `Enum.Material` 名称(`query?`) |
| `search_assets` | 读取 | — | **不支持从插件**(需要开放云/网络目录 API);明确错误 |
| `list_library` | 读取 | — | **不支持从插件**(需要开放云);明确错误 |
| `upload_decal` | 写入 | — | **不支持从插件**(需要开放云资产 API 密钥);明确错误 |
| `capture_viewport` | 实验性 | 编辑 | 通过 `CaptureService` + EditableImage 的 **真实 PNG**;需要游戏设置 > 安全 > **允许网格 / 图像 API**(否则明确启用此消息) |
| `playtest_control` | 实验性 | 编辑 | 启动/停止 Studio 内的模拟(`mode='run'` → `RunService:Run()/Stop()`);`mode='play'`(播放单独 / 玩家)不受支持 |
| `get_playtest_output` | 实验性 | 编辑 | 排干/窥视自启动以来捕获的播放测试日志缓冲区 |
| `simulate_keyboard_input` / `simulate_mouse_input` | 实验性 | — | `VirtualInputManager` **受 RobloxScriptSecurity 限制**;在被阻止时返回明确原因 |
| `character_navigation` | 实验性 | 服务器 | `Humanoid:MoveTo(position)`;需要正在运行的播放测试(使用 `context:"server"`) |
| `get_status` | 元 | — | `{ edit, server }` 连接性(无门控) |
`context: "auto"`(默认)以 **正在运行的 F5 服务器** 为目标,如果它存在,否则以编辑器为目标。
**上下文说明:** 每个工具都在 **编辑** `Executor` 中实现。在实时 F5 播放测试期间,`auto` 路由到 **服务器** 代理,它镜像 `run_luau` + **读取** 工具(实例树,控制台,搜索/列表,位置信息,属性,属性,后代,服务,标签,`search_by_property`)加上 `character_navigation`,并为编辑操作返回明确的“代理不支持命令”错误(选择,脚本编辑,撤销,资产插入,标记)— 那些是编辑时间操作。传递 `context: "edit"` 以显式定位编辑器。
## 设置
停靠有三个选项卡:**状态 / 设置 / 活动**。
- **设置**(可滚动):每个工具作为一个切换开关,分为 **读取 / 写入 / 实验性**(全部默认开启),加上 **只读模式**(主写入切换开关),**确认破坏性**(已持久化存根 - 暂无确认流程),**声音**,**活动日志**,和可选 **身份验证**(启用 + 令牌)。插件将设置持久化到 `plugin:SetSetting("NikMCP_Settings", …)` 并在连接和每次更改时将它们发布到桥梁的 `POST /settings`。服务器存储它们并控制每个 `tools/call`。
- **活动**:最近执行的 ~50 个命令 - 工具,目标(截断),确定/错误,往返毫秒,时间戳;确定绿色 / 错误红色。
**可选身份验证(默认关闭,本地主机安全保护)。** 启用时,插件在 `/poll` `/response` `/settings` 上发送 `x-mcp-token: <token>`。服务器在第一个启用它的 `/settings` 上采用令牌(**信任第一个使用**),然后拒绝缺少/不等于它的请求。停用它会清除已采用的令牌。F5 运行时代理通过其 `McpToken` 属性携带令牌。
## 安装
### 1. Node 服务器
```bash
npm install
npm run build
# 烟雾测试(不需要 Studio):
node dist/index.js --port 58741 &
curl "http://127.0.0.1:58741/heartbeat?context=edit" # -> {"ok":true,"edit":true,"server":false}
```
### 2. 构建和安装插件(人工安装)
```bash
# macOS / Linux
bash scripts/install-plugin.sh
```
```powershell
# Windows
./scripts/install-plugin.ps1
```
或直接:`rojo build plugin/plugin.project.json -o "<Roblox 插件文件夹>/RobloxStudioMCP.rbxmx"`。需要 [Rojo](https://rojo.space)。重启 Studio → 出现 **Studio MCP** 工具栏,带有 **MCP 状态** 和 **启用播放测试** 按钮。
### 3. 使用您的 MCP 客户端注册(无路径)
无需克隆,无需硬编码路径 - 服务器直接从 npm 运行,如 boshyxd 一样。默认端口是 **双方的 58741**,因此 **无需 `--port`**。
Claude Code(一条命令):
```bash
claude mcp add -s user nikmcp -- npx -y nikmcp@latest
```
Claude Desktop / Cursor(JSON):
```json
{
"mcpServers": {
"nikmcp": {
"command": "npx",
"args": ["-y", "nikmcp@latest"]
}
}
}
```
**本地开发(仅限此仓库)。** 当自己开发服务器时,跳过 npm 并指向您的构建,以便测试未提交的更改:
```json
{
"mcpServers": {
"nikmcp-dev": {
"command": "node",
"args": ["D:/GameProjects/NikMCP/dist/index.js"]
}
}
}
```
(在任一表单中添加 `"--port", "5874X"`,如果您已移出默认的 58741。)
### 4. 人类必须验证的 Studio 设置
- **允许 HTTP 请求**(游戏设置 → 安全):**对于正常编辑模式使用** 不需要此项。编辑插件通过 PluginSecurity 轮询本地主机,Studio 允许此操作,而不考虑此切换开关 - Rojo 同步无需此项。`HttpEnabled` 仅控制来自 **正在运行的体验**(游戏内脚本)的 HTTP 请求。**仅在 F5 播放测试** 时需要此项(它不会造成伤害,编辑模式保持正常运行)。
- 对于 **服务器**(F5)上下文中的 `run_luau`:**`ServerScriptService.LoadStringEnabled = true`**(代理使用 `loadstring`;如果关闭,则返回明确的编译错误)。
- 对于 `capture_viewport`:**允许网格 / 图像 API = 开启**(游戏设置 → 安全)。
## F5 播放测试流程
1. 在 **编辑模式** 下,点击 **启用播放测试**(或一次性调用 `enable_playtest_agent`)。插件将 `MCP_RuntimeAgent` 写入 `ServerScriptService` 并将所选端口作为 `McpPort` 属性标记在上面。
2. 按 **F5**。Studio 将该脚本复制到新的服务器 DataModel 中,在那里它看到 `RunService:IsRunning()` 为真,解析其端口(首先是属性,然后是探测),并打开其自己的 `context=server` 轮询 + 心跳循环。
3. `get_status` 现在显示 `{ edit: true, server: true }`。
4. `run_luau` 使用 `context:"auto"` 命中 **正在运行的游戏**;`context:"edit"` 仍然以编辑器为目标。
5. 停止播放测试 → 代理的循环结束 → `server` 上下文在 ~2 秒内变得陈旧 → 路由回落到 `edit`。
**运行按钮(非 F5)** 无需任何设置 - 运行在与编辑插件绑定到相同的 DataModel 中模拟服务器,因此 `run_luau` 直接到达,无需设置。只有 **播放 / 播放单独(F5)** 需要运行时代理。
**客户端上下文限制(诚实):** 代理在 **服务器** DataModel 中运行。播放测试的客户端方面的任意 Luau 超出范围 - `loadstring` 在客户端/本地脚本上下文中不起作用。客户端代理将是一个单独的、更狭窄的功能。
## 端口
默认基准是 **58741** - boshyxd 的 `robloxstudio-mcp` 端口 - 因此 NikMCP 是一个直接替换品(删除 boshy 以便没有任何竞争 58741)。范围 58741-58760。默认 `58741`。使用 `--port`,`ROBLOX_STUDIO_PORT` / `PORT` 或 `config.json` 覆盖(参见 `config.example.json`)。插件和代理探测 `58741..58760`; 如果您选择了范围之外的端口,请将其添加到 `CANDIDATE_PORTS` 中(在 `plugin/src/Config.luau` 中首先)。
## 设计不变性(不要“改善”而破坏)
- 一个 Node 进程承载 MCP 服务器和桥梁 - 无需跨进程管道。
- 短轮询 ~250ms(≈240 请求/分钟每循环;本地主机上限 ~2000/分钟)。仅在测量时进行长轮询。
- 仅绑定 `127.0.0.1`,而非 `0.0.0.0`。
- 所有日志输出到 **stderr** - stdout 保留用于 MCP JSON-RPC。
- 每个编辑上下文的突变都包装在 `ChangeHistoryService` 中;不在播放测试期间。
## 状态 / 范围
Node 端(桥梁 + MCP 服务器 + 所有六种工具 + 路由)已构建并端到端验证:一个真正的 MCP `tools/list` + `tools/call run_luau` 往返通过桥梁,并且 `auto` 路由在活动时切换到服务器上下文。Luau 插件已编写但 **必须由人类在 Studio 中安装和确认** - 工具栏/停靠,编辑往返和 F5 双上下文流程只能在那里验证。
## 安全说明
桥梁仅绑定 `127.0.0.1`。`run_luau` 在 **您的** Studio 中执行任意 Luau 代码 - 请像对待命令栏一样对待它。仅限本地安装;不可发布到 Creator Store(在分发资产中禁用 `loadstring`),这没问题 - 您永远不会发布它。
# NikMCP
## 尼克斯峰
```
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器