flow-codeblock-rust-mcp 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/dist/index.js +262 -78
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Flow Codeblock Rust+Bun 的本地 stdio MCP Server。它只调用服务端 Rust
|
|
|
7
7
|
需要 Bun 1.4.0 或更高版本:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
bunx --bun flow-codeblock-rust-mcp@0.1.
|
|
10
|
+
bunx --bun flow-codeblock-rust-mcp@0.1.1
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
配置环境变量:
|
|
@@ -26,7 +26,7 @@ export FLOW_CODEBLOCK_TOKEN='<YOUR_INTERNAL_ACCESS_TOKEN>'
|
|
|
26
26
|
"mcpServers": {
|
|
27
27
|
"flow-codeblock-rust": {
|
|
28
28
|
"command": "bunx",
|
|
29
|
-
"args": ["--bun", "flow-codeblock-rust-mcp@0.1.
|
|
29
|
+
"args": ["--bun", "flow-codeblock-rust-mcp@0.1.1"],
|
|
30
30
|
"env": {
|
|
31
31
|
"FLOW_CODEBLOCK_BASE_URL": "https://flow.example.com",
|
|
32
32
|
"FLOW_CODEBLOCK_TOKEN": "<YOUR_INTERNAL_ACCESS_TOKEN>"
|
|
@@ -42,6 +42,14 @@ export FLOW_CODEBLOCK_TOKEN='<YOUR_INTERNAL_ACCESS_TOKEN>'
|
|
|
42
42
|
|
|
43
43
|
MCP 不提供脚本删除、紧急恢复解锁、Token 查询、执行统计、所有权转移或任意 HTTP 代理工具。当前版本只提供本地 stdio 连接,不提供远程 HTTP、SSE 或 Streamable HTTP 连接。
|
|
44
44
|
|
|
45
|
+
## MCP 工具契约
|
|
46
|
+
|
|
47
|
+
服务器初始化时会通过 MCP `instructions` 下发完整的工具选择、脚本预览/确认流程、代码运行时约束和接口文档规则,因此不依赖额外 Skill 也可以直接调用工具。客户端应优先使用工具 description 和 input schema 中的字段说明,不要通过试错猜测参数。
|
|
48
|
+
|
|
49
|
+
接口文档必须包含 `schema_version`、`title`、`summary`、`endpoint`、`request`、`responses`、`logic_description`;`endpoint` 必须有 `methods` 和 `description`,`request.query` 与 `request.headers` 必须存在(没有参数时传 `[]`)。POST 还必须提供 `request.body`,其 `content_type`、`schema`、`example` 均必填;每个响应的 `status`、`description`、`content_type`、`schema`、`example` 均必填;查询参数和请求头的每个字段必须有 `name`、`type`、`required`、`description`、`example`。
|
|
50
|
+
|
|
51
|
+
接口文档的 `endpoint.path` 使用相对路径:创建时省略,更新时使用 `/flow/codeblock/<实际脚本ID>`。对外展示完整请求地址时,将用户提供的服务域名与 `/flow/codeblock/{{脚本ID}}` 拼接;不要把真实令牌、密码、Cookie 或 Authorization 值写入代码、文档、示例或 URL。
|
|
52
|
+
|
|
45
53
|
## 安全行为
|
|
46
54
|
|
|
47
55
|
- 管理请求使用 `Authorization: Bearer`,执行已发布脚本时不会把 MCP Token 转发到脚本输入。
|
package/dist/index.js
CHANGED
|
@@ -27877,22 +27877,29 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
27877
27877
|
var interfaceDocRequiredFields = {
|
|
27878
27878
|
document: ["schema_version", "title", "summary", "endpoint", "request", "responses", "logic_description"],
|
|
27879
27879
|
endpoint: ["methods", "description"],
|
|
27880
|
-
request: ["query", "headers", "body (POST only)"],
|
|
27880
|
+
request: ["query", "headers", "body (required for POST; omit for GET-only)"],
|
|
27881
27881
|
parameter: ["name", "type", "required", "description", "example"],
|
|
27882
27882
|
body: ["content_type", "schema", "example"],
|
|
27883
27883
|
response: ["status", "description", "content_type", "schema", "example"],
|
|
27884
|
-
optional: ["endpoint.path (omit on create; actual path on update)", "usage_refs"]
|
|
27884
|
+
optional: ["endpoint.path (omit on create; actual relative path on update)", "usage_refs"]
|
|
27885
27885
|
};
|
|
27886
27886
|
var interfaceDocNestedRules = [
|
|
27887
|
+
"title is the document title; summary is a one-sentence summary; endpoint.description explains the callable interface; logic_description explains the business logic.",
|
|
27888
|
+
"request.query and request.headers are always present arrays; use [] when the interface has no query or header parameters.",
|
|
27889
|
+
"Each query/header parameter requires name, type, required, description, and example. Do not use the internal input.query/input.header/input.body/input.cookies names in the caller-facing document.",
|
|
27890
|
+
"For POST, request.body is required and content_type must be application/json; for GET-only documents, omit request.body.",
|
|
27891
|
+
"Each response requires status, description, content_type, schema, and example. Describe every response shape returned by the script, including errors when applicable.",
|
|
27887
27892
|
"Every JSON Schema node declares type. Object schemas use properties for fixed fields or a complete additionalProperties schema for dynamic keys.",
|
|
27888
27893
|
"Every array schema has items. Object array items have complete properties, and every example item covers those properties.",
|
|
27889
27894
|
"At every nesting level, schema properties and example fields cover each other. Optional properties still appear in the complete example.",
|
|
27890
27895
|
"required contains only runtime-required fields. Use separate responses when success and error payloads have different shapes."
|
|
27891
27896
|
];
|
|
27892
27897
|
var interfaceDocInputDescription = [
|
|
27893
|
-
"A complete script-interface-doc.v1 document is required when creating a script or
|
|
27898
|
+
"A complete script-interface-doc.v1 document is required when creating a script, changing code, or saving documentation.",
|
|
27894
27899
|
`Required structure: ${Object.entries(interfaceDocRequiredFields).map(([key, fields]) => `${key}=[${fields.join(", ")}]`).join("; ")}.`,
|
|
27895
|
-
...interfaceDocNestedRules
|
|
27900
|
+
...interfaceDocNestedRules,
|
|
27901
|
+
"endpoint.path is relative and must be /flow/codeblock/<actual-script-id> on update; the final public URL is the caller-provided domain followed by /flow/codeblock/<script-id>.",
|
|
27902
|
+
"Never include real tokens, passwords, cookies, Authorization values, or other credentials in the document or examples."
|
|
27896
27903
|
].join(" ");
|
|
27897
27904
|
function isObject2(value) {
|
|
27898
27905
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -27941,6 +27948,8 @@ function validateSchemaExampleCoverage(schema, example, schemaPath, examplePath,
|
|
|
27941
27948
|
issues.push(`${examplePath} is missing ${key}`);
|
|
27942
27949
|
} else if (isObject2(propertySchema)) {
|
|
27943
27950
|
validateSchemaExampleCoverage(propertySchema, example[key], `${schemaPath}.properties.${key}`, `${examplePath}.${key}`, issues);
|
|
27951
|
+
} else {
|
|
27952
|
+
issues.push(`${schemaPath}.properties.${key} must be a JSON Schema object`);
|
|
27944
27953
|
}
|
|
27945
27954
|
}
|
|
27946
27955
|
}
|
|
@@ -27973,6 +27982,9 @@ function validateParameters(value, path, issues) {
|
|
|
27973
27982
|
issues.push(`${path} must be present; use [] when there are no parameters`);
|
|
27974
27983
|
return;
|
|
27975
27984
|
}
|
|
27985
|
+
if (value.length > 100)
|
|
27986
|
+
issues.push(`${path} must contain at most 100 parameters`);
|
|
27987
|
+
const names = new Set;
|
|
27976
27988
|
value.forEach((parameter, index) => {
|
|
27977
27989
|
const itemPath = `${path}[${index}]`;
|
|
27978
27990
|
if (!isObject2(parameter)) {
|
|
@@ -27981,6 +27993,13 @@ function validateParameters(value, path, issues) {
|
|
|
27981
27993
|
}
|
|
27982
27994
|
requireText(parameter, "name", itemPath, issues);
|
|
27983
27995
|
requireText(parameter, "description", itemPath, issues);
|
|
27996
|
+
if (typeof parameter.name === "string") {
|
|
27997
|
+
const name = parameter.name.trim();
|
|
27998
|
+
if (name && names.has(name))
|
|
27999
|
+
issues.push(`${itemPath}.name must be unique within ${path}`);
|
|
28000
|
+
if (name)
|
|
28001
|
+
names.add(name);
|
|
28002
|
+
}
|
|
27984
28003
|
if (!["string", "integer", "number", "boolean", "array", "object"].includes(String(parameter.type))) {
|
|
27985
28004
|
issues.push(`${itemPath}.type is unsupported`);
|
|
27986
28005
|
}
|
|
@@ -28027,6 +28046,9 @@ function interfaceDocCompletenessIssues(document, operation) {
|
|
|
28027
28046
|
if (methods.some((method) => method !== "GET" && method !== "POST")) {
|
|
28028
28047
|
issues.push("interface_doc.endpoint.methods may contain only GET or POST");
|
|
28029
28048
|
}
|
|
28049
|
+
if (new Set(methods).size !== methods.length) {
|
|
28050
|
+
issues.push("interface_doc.endpoint.methods must not contain duplicates");
|
|
28051
|
+
}
|
|
28030
28052
|
}
|
|
28031
28053
|
if (operation === "update") {
|
|
28032
28054
|
if (typeof document.endpoint.path !== "string" || !document.endpoint.path.startsWith("/flow/codeblock/") || document.endpoint.path.includes("{script_id}")) {
|
|
@@ -28053,6 +28075,8 @@ function interfaceDocCompletenessIssues(document, operation) {
|
|
|
28053
28075
|
if (!Array.isArray(document.responses) || document.responses.length === 0) {
|
|
28054
28076
|
issues.push("interface_doc.responses must contain at least one response");
|
|
28055
28077
|
} else {
|
|
28078
|
+
if (document.responses.length > 50)
|
|
28079
|
+
issues.push("interface_doc.responses must contain at most 50 responses");
|
|
28056
28080
|
document.responses.forEach((response, index) => {
|
|
28057
28081
|
const path = `interface_doc.responses[${index}]`;
|
|
28058
28082
|
if (!isObject2(response)) {
|
|
@@ -28110,7 +28134,11 @@ var interfaceDocSchema = {
|
|
|
28110
28134
|
additionalProperties: false,
|
|
28111
28135
|
properties: {
|
|
28112
28136
|
methods: { type: "array", minItems: 1, maxItems: 2, uniqueItems: true, items: { enum: ["GET", "POST"] } },
|
|
28113
|
-
path: {
|
|
28137
|
+
path: {
|
|
28138
|
+
type: "string",
|
|
28139
|
+
pattern: "^/flow/codeblock/",
|
|
28140
|
+
description: "相对路径;创建时可省略,更新时必须为 /flow/codeblock/<实际脚本ID>。完整地址另行拼接调用方提供的域名。"
|
|
28141
|
+
},
|
|
28114
28142
|
description: { type: "string", minLength: 1, maxLength: 4000 }
|
|
28115
28143
|
}
|
|
28116
28144
|
},
|
|
@@ -28191,51 +28219,171 @@ function schemaFromExample(value) {
|
|
|
28191
28219
|
return { type: Number.isInteger(value) ? "integer" : "number" };
|
|
28192
28220
|
return { type: typeof value };
|
|
28193
28221
|
}
|
|
28194
|
-
function codeWriterContext(mode, requirement, inputExample, includeFullSchema) {
|
|
28222
|
+
function codeWriterContext(mode, requirement, inputExample, includeFullSchema, baseUrl) {
|
|
28195
28223
|
const common = {
|
|
28196
28224
|
contract_version: "flow-code-writer.v1",
|
|
28197
28225
|
mode,
|
|
28198
28226
|
requirement,
|
|
28199
28227
|
mutates_or_executes: false,
|
|
28200
28228
|
runtime: "Bun 1.4+ fresh worker; no state is shared between executions.",
|
|
28229
|
+
runtime_limits: {
|
|
28230
|
+
execution_timeout_ms: { minimum_default: 100, default_maximum: 15000 },
|
|
28231
|
+
code_bytes: 65535,
|
|
28232
|
+
input_bytes: 2 * 1024 * 1024,
|
|
28233
|
+
result_bytes: 10 * 1024 * 1024
|
|
28234
|
+
},
|
|
28201
28235
|
code_rules: [
|
|
28202
|
-
"
|
|
28203
|
-
"
|
|
28204
|
-
"
|
|
28205
|
-
"
|
|
28236
|
+
"所有业务数据只从全局 input 读取,不从环境变量、持久化全局变量或其他外部状态读取;返回值必须可 JSON 序列化。",
|
|
28237
|
+
"默认使用顶层 return;只有事件式/异步流程或用户明确要求时才使用 qf_output,且必须是裸的 qf_output = { ... } 对象字面量赋值,不能与顶层 return 混用,也不能遮蔽该标识符。",
|
|
28238
|
+
"优先标准 JavaScript 和服务端原生 fetch;只有原生能力确实无法满足且用户明确要求时,才使用白名单 CommonJS 字面量 require。",
|
|
28239
|
+
"禁止 import/export、动态 require、浏览器 API、定时器、阻止标识符、阻止成员和黑名单 Node 模块,不得写入真实凭据。",
|
|
28240
|
+
"业务逻辑和异步操作放在 try-catch 中;错误转换为字符串或普通对象后返回。",
|
|
28241
|
+
"不得创建无界循环、未 settle 的 Promise 或执行结束后仍运行的后台任务;所有请求必须 await 或 return。",
|
|
28242
|
+
"外部 URL、请求头、查询参数和请求体必须校验类型、长度和允许范围;HTTP 响应检查状态并按 JSON、文本、空响应分别处理。"
|
|
28243
|
+
],
|
|
28244
|
+
async_lifecycle: [
|
|
28245
|
+
"禁止 setTimeout、setInterval、setImmediate、轮询、延迟和后台重试。",
|
|
28246
|
+
"请求必须在执行超时内完成,不能遗留未等待的 fetch。"
|
|
28247
|
+
],
|
|
28248
|
+
require_policy: {
|
|
28249
|
+
allowed_call_form: "仅允许单个字符串字面量 require('模块名'),不得间接调用、动态模块名或使用 import/export。",
|
|
28250
|
+
dayjs_exception: "日期处理优先原生 Date;复杂日期解析/格式化/时区才允许使用 dayjs。",
|
|
28251
|
+
other_modules: "除 dayjs 外,只有原生能力确实无法实现且用户明确要求时才允许白名单模块。"
|
|
28252
|
+
},
|
|
28253
|
+
forbidden: {
|
|
28254
|
+
identifiers: [
|
|
28255
|
+
"eval",
|
|
28256
|
+
"Function",
|
|
28257
|
+
"Proxy",
|
|
28258
|
+
"__proto__",
|
|
28259
|
+
"child_process",
|
|
28260
|
+
"exec",
|
|
28261
|
+
"execFile",
|
|
28262
|
+
"execSync",
|
|
28263
|
+
"fork",
|
|
28264
|
+
"spawn",
|
|
28265
|
+
"module",
|
|
28266
|
+
"exports",
|
|
28267
|
+
"setImmediate",
|
|
28268
|
+
"setInterval",
|
|
28269
|
+
"setTimeout"
|
|
28270
|
+
],
|
|
28271
|
+
members: [
|
|
28272
|
+
"Object.getPrototypeOf",
|
|
28273
|
+
"Object.setPrototypeOf",
|
|
28274
|
+
"Reflect.construct",
|
|
28275
|
+
"Reflect.apply",
|
|
28276
|
+
"Reflect.get",
|
|
28277
|
+
"Reflect.set",
|
|
28278
|
+
"process.exit",
|
|
28279
|
+
"process.kill",
|
|
28280
|
+
"process.binding",
|
|
28281
|
+
"process._linkedBinding",
|
|
28282
|
+
"process.dlopen"
|
|
28283
|
+
],
|
|
28284
|
+
modules: [
|
|
28285
|
+
"child_process",
|
|
28286
|
+
"cluster",
|
|
28287
|
+
"dgram",
|
|
28288
|
+
"dns",
|
|
28289
|
+
"http",
|
|
28290
|
+
"http2",
|
|
28291
|
+
"https",
|
|
28292
|
+
"inspector",
|
|
28293
|
+
"internal",
|
|
28294
|
+
"module",
|
|
28295
|
+
"node:module",
|
|
28296
|
+
"net",
|
|
28297
|
+
"os",
|
|
28298
|
+
"perf_hooks",
|
|
28299
|
+
"async_hooks",
|
|
28300
|
+
"bun",
|
|
28301
|
+
"process",
|
|
28302
|
+
"readline",
|
|
28303
|
+
"repl",
|
|
28304
|
+
"tls",
|
|
28305
|
+
"undici",
|
|
28306
|
+
"v8",
|
|
28307
|
+
"vm",
|
|
28308
|
+
"vm2",
|
|
28309
|
+
"worker_threads",
|
|
28310
|
+
"ws"
|
|
28311
|
+
]
|
|
28312
|
+
},
|
|
28313
|
+
output_rules: [
|
|
28314
|
+
"返回值只能是普通可序列化值或 Promise;禁止循环引用、BigInt、函数、Symbol、未处理的复杂类实例及无界数组/字符串。",
|
|
28315
|
+
"平台会把即时接口返回值放入外层 HTTP 响应的 result 字段;脚本接口通常直接返回业务值。推荐 { success: true, data: value } 或 { success: false, error: message }。"
|
|
28206
28316
|
],
|
|
28207
28317
|
allowed_modules: allowedModules
|
|
28208
28318
|
};
|
|
28209
28319
|
if (mode === "non_script") {
|
|
28210
28320
|
return {
|
|
28211
28321
|
...common,
|
|
28212
|
-
input_contract:
|
|
28213
|
-
|
|
28322
|
+
input_contract: {
|
|
28323
|
+
method: "POST",
|
|
28324
|
+
path: "/flow/codeblock",
|
|
28325
|
+
rule: "请求体中的 input 原样注入全局 input,缺省为 {};平台请求体可包含 codebase64、input、qingcodeTimeout。",
|
|
28326
|
+
example: { codebase64: "<base64 JavaScript>", input: inputExample ?? {} }
|
|
28327
|
+
},
|
|
28328
|
+
generation_decisions: [
|
|
28329
|
+
"用户未说明时生成即时执行的非脚本模式。",
|
|
28330
|
+
"如果需求包含 HTTP 重定向,必须改用 script 模式 /flow/codeblock/{{script_id}}。"
|
|
28331
|
+
],
|
|
28332
|
+
deliverables: ["只含可执行 JavaScript 的 javascript 代码块", "输入和输出契约"],
|
|
28214
28333
|
test_tool: { name: "flow_execute_code", arguments: { code: "<JavaScript>", input: inputExample ?? {}, timeout_ms: 3000 } },
|
|
28215
|
-
rule: "
|
|
28334
|
+
rule: "除非用户明确要求测试,否则不要调用 flow_execute_code。",
|
|
28335
|
+
response_format: [
|
|
28336
|
+
"除非用户明确要求只返回代码,否则先说明模式和输出方式,再给 JavaScript 代码块,最后给请求/响应示例。",
|
|
28337
|
+
"非脚本接口请求地址是用户服务域名 + /flow/codeblock。"
|
|
28338
|
+
]
|
|
28216
28339
|
};
|
|
28217
28340
|
}
|
|
28218
28341
|
const bodyExample = inputExample && typeof inputExample === "object" ? inputExample : { value: "example" };
|
|
28342
|
+
const normalizedBaseUrl = baseUrl?.trim().replace(/\/+$/, "");
|
|
28343
|
+
const endpointPathTemplate = "/flow/codeblock/{{script_id}}";
|
|
28219
28344
|
return {
|
|
28220
28345
|
...common,
|
|
28221
28346
|
input_contract: {
|
|
28222
|
-
endpoint: "GET|POST /flow/codeblock/{script_id}",
|
|
28347
|
+
endpoint: "GET|POST /flow/codeblock/{{script_id}}",
|
|
28223
28348
|
shape: { query: {}, header: {}, body: {}, cookies: {} },
|
|
28224
|
-
|
|
28349
|
+
mapping: {
|
|
28350
|
+
query: "input.query;单值为字符串,重复参数为字符串数组;不含 qingcodeToken/qingcodeTimeout。",
|
|
28351
|
+
headers: "input.header;服务端过滤 x-original-cookie,需要 Cookie 时使用 cookie。",
|
|
28352
|
+
body: "input.body;POST JSON 请求体,空请求体为 {};业务数据直接发送,不包装为 input 或 input.body。",
|
|
28353
|
+
cookies: "input.cookies;Cookie 键值对象,无 Cookie 时可能不存在。"
|
|
28354
|
+
},
|
|
28355
|
+
reserved_query: ["qingcodeToken", "qingcodeTimeout"]
|
|
28225
28356
|
},
|
|
28226
|
-
|
|
28357
|
+
endpoint_url_template: normalizedBaseUrl ? `${normalizedBaseUrl}${endpointPathTemplate}` : endpointPathTemplate,
|
|
28358
|
+
endpoint_url_rule: "提供域名时,输出该域名 + /flow/codeblock/{{script_id}};URL 中不得包含凭据。",
|
|
28359
|
+
deliverables: ["只含可执行代码的 JavaScript 代码块", "独立且完整的 script-interface-doc.v1 JSON 对象"],
|
|
28227
28360
|
interface_doc_contract: {
|
|
28228
28361
|
strict_preview_gate: true,
|
|
28229
28362
|
required_fields: interfaceDocRequiredFields,
|
|
28230
28363
|
nested_rules: interfaceDocNestedRules,
|
|
28231
28364
|
body_example_schema: schemaFromExample(bodyExample),
|
|
28232
|
-
full_json_schema: includeFullSchema ? interfaceDocSchema : undefined
|
|
28365
|
+
full_json_schema: includeFullSchema ? interfaceDocSchema : undefined,
|
|
28366
|
+
separation: [
|
|
28367
|
+
"javascript 代码块只含可执行代码,不写接口文档注释。",
|
|
28368
|
+
"json 代码块只含一个合法 script-interface-doc.v1 对象,不混入 Markdown、注释或尾随逗号。"
|
|
28369
|
+
]
|
|
28370
|
+
},
|
|
28371
|
+
redirect_contract: {
|
|
28372
|
+
rule: "只有脚本接口解析 flow_redirect_url/flow_redirect_code;即时接口把它们作为普通结果。",
|
|
28373
|
+
url: "flow_redirect_url 必须是单斜杠相对路径或带主机的 http/https URL,不得含空白或控制字符。",
|
|
28374
|
+
code: "flow_redirect_code 只能是 301、302、303、307、308 或对应数字字符串。"
|
|
28233
28375
|
},
|
|
28234
28376
|
workflow: [
|
|
28235
|
-
"
|
|
28236
|
-
"
|
|
28237
|
-
"
|
|
28238
|
-
"
|
|
28377
|
+
"如果是更新,先调用 flow_get_script 读取当前版本;更新文档可先调用 flow_get_script_documentation。",
|
|
28378
|
+
"代码和完整 interface_doc 一起生成;创建或代码更新只调用一次 flow_preview_script_change。",
|
|
28379
|
+
"仅在用户明确确认后调用 flow_apply_script_change 或 flow_apply_script_documentation,并传 confirm=true。",
|
|
28380
|
+
"只有用户要求测试已发布脚本时才调用 flow_execute_script;仅测试未发布代码时调用 flow_execute_code。",
|
|
28381
|
+
"版本冲突、预览过期或校验失败时停止并重新读取、重新预览,不重试旧 preview_id。"
|
|
28382
|
+
],
|
|
28383
|
+
response_format: [
|
|
28384
|
+
"生成或修改脚本时必须同时输出独立的 javascript 和 json 代码块,除非用户明确只要代码。",
|
|
28385
|
+
"说明请求参数、主要业务行为、响应和错误处理,并提供与模式匹配的 HTTP 方法、路径、Headers/Query/Body/Cookie 和响应示例。",
|
|
28386
|
+
"脚本接口完整请求地址使用用户提供的域名 + /flow/codeblock/{{script_id}};接口文档 endpoint.path 仍使用相对路径。"
|
|
28239
28387
|
]
|
|
28240
28388
|
};
|
|
28241
28389
|
}
|
|
@@ -28302,6 +28450,20 @@ class PreviewStore {
|
|
|
28302
28450
|
var result = (value) => ({
|
|
28303
28451
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
28304
28452
|
});
|
|
28453
|
+
var serverInstructions = [
|
|
28454
|
+
"这是 Flow Codeblock Rust+Bun MCP Server。除 flow_write_code 外,工具直接调用服务端 REST API;flow_write_code 只返回生成契约。不要猜测 REST 路径,也不要把 MCP 内部令牌放入业务参数。",
|
|
28455
|
+
"代码运行于服务端 Bun 异步函数上下文,支持现代 JavaScript、async/await、Promise、箭头函数和顶层 return;默认最小超时 100ms、最大超时 15000ms,代码 65535 字节、输入 2MiB、结果 10MiB。",
|
|
28456
|
+
"工具选择:flow_write_code 只生成代码与契约,不执行、不写库;flow_execute_code 只用于用户明确要求的未发布非脚本测试;flow_execute_script 只执行已发布脚本。",
|
|
28457
|
+
"脚本读取、创建和更新流程:更新前先 flow_get_script 读取当前 version;创建或代码更新必须同时提供完整 interface_doc;先 flow_preview_script_change,再在用户明确确认后 flow_apply_script_change(confirm=true)。文档单独修改使用 flow_preview_script_documentation -> flow_apply_script_documentation。",
|
|
28458
|
+
"预览 ID 是一次性且有时效的;版本冲突、预览过期或校验失败时停止,重新读取并预览,不要重试旧 preview_id。flow_apply_* 永远需要 confirm=true。",
|
|
28459
|
+
"script-interface-doc.v1 必须包含 schema_version、title、summary、endpoint、request、responses、logic_description。endpoint 必须包含 methods 和 description;request.query、request.headers 必须存在(没有参数用 []);POST 必须有 request.body,GET-only 必须省略。",
|
|
28460
|
+
"查询参数和请求头的每一项必须有 name、type、required、description、example。请求体和每个响应必须有 content_type=application/json、schema、example;每个响应还必须有 status、description。JSON Schema 的每个节点必须声明 type,数组必须有 items,对象和 example 必须互相覆盖。",
|
|
28461
|
+
"接口文档 endpoint.path 只写相对路径:创建时省略,更新时写 /flow/codeblock/<实际脚本ID>。对外展示的完整调用地址必须使用用户提供的域名拼接 /flow/codeblock/{{脚本ID}};不要把真实 Token、密码、Cookie 或 Authorization 值写入代码、文档、示例或 URL。",
|
|
28462
|
+
"脚本模式输入来自 input.query、input.header、input.body、input.cookies;即时非脚本模式 POST /flow/codeblock 的 body.input 原样成为全局 input。代码默认使用顶层 return;只有事件式/异步流程或用户明确要求时才使用裸 qf_output 赋值,不能混用。",
|
|
28463
|
+
"优先原生 JavaScript、URL/URLSearchParams 和 fetch;禁止浏览器 API、定时器、动态模块加载、黑名单 Node 模块和危险标识符。HTTP 请求必须检查状态并处理 JSON/文本/空响应,所有异步任务必须显式 await 或 return。",
|
|
28464
|
+
"执行脚本时 method 只能是 GET 或 POST;MCP 认证、Cookie、CSRF、代理来源头和测试工具标识会被过滤。不要使用删除脚本、紧急恢复解锁或任意 HTTP 代理能力,本 MCP 不提供这些工具。"
|
|
28465
|
+
].join(`
|
|
28466
|
+
`);
|
|
28305
28467
|
function encodedScriptId(scriptId) {
|
|
28306
28468
|
return encodeURIComponent(scriptId);
|
|
28307
28469
|
}
|
|
@@ -28379,23 +28541,23 @@ function withApiErrors(handler) {
|
|
|
28379
28541
|
};
|
|
28380
28542
|
}
|
|
28381
28543
|
var documentationFields = {
|
|
28382
|
-
document: exports_external.unknown().optional(),
|
|
28383
|
-
raw_document: exports_external.string().optional(),
|
|
28384
|
-
format: exports_external.literal("json").optional()
|
|
28544
|
+
document: exports_external.unknown().optional().describe(`规范化的 script-interface-doc.v1 JSON 对象。与 raw_document 二选一;保存或代码更新时必须完整。${interfaceDocInputDescription}`),
|
|
28545
|
+
raw_document: exports_external.string().optional().describe("待服务端解析的 JSON/OpenAPI 文档文本。与 document 二选一;format=json 时按 JSON 解析。"),
|
|
28546
|
+
format: exports_external.literal("json").optional().describe("raw_document 的格式,目前只支持 json。")
|
|
28385
28547
|
};
|
|
28386
28548
|
var changeSchema = exports_external.object({
|
|
28387
|
-
operation: exports_external.enum(["create", "update"]),
|
|
28388
|
-
script_id: exports_external.string().min(1).optional(),
|
|
28389
|
-
code: exports_external.string().optional(),
|
|
28390
|
-
code_base64: exports_external.string().optional(),
|
|
28391
|
-
description: exports_external.string().optional(),
|
|
28392
|
-
ip_whitelist: exports_external.array(exports_external.string()).nullable().optional(),
|
|
28549
|
+
operation: exports_external.enum(["create", "update"]).describe("create 创建脚本;update 更新已有脚本。"),
|
|
28550
|
+
script_id: exports_external.string().min(1).optional().describe("更新目标脚本 ID。create 不得传入。"),
|
|
28551
|
+
code: exports_external.string().optional().describe("UTF-8 JavaScript 源码,与 code_base64 二选一。创建或修改代码时必填;代码只包含可执行 JavaScript。"),
|
|
28552
|
+
code_base64: exports_external.string().optional().describe("JavaScript 源码的非空 Base64,与 code 二选一。"),
|
|
28553
|
+
description: exports_external.string().optional().describe("脚本说明。可在不改代码时单独更新。"),
|
|
28554
|
+
ip_whitelist: exports_external.array(exports_external.string()).nullable().optional().describe("来源 IP/CIDR 白名单。省略表示更新时保持原值;null 或 [] 表示清除限制。"),
|
|
28393
28555
|
interface_doc: exports_external.unknown().optional().describe(interfaceDocInputDescription),
|
|
28394
|
-
rollback_to_version: exports_external.number().int().positive().optional(),
|
|
28395
|
-
expected_version: exports_external.number().int().positive().optional()
|
|
28556
|
+
rollback_to_version: exports_external.number().int().positive().optional().describe("回滚到的历史版本号。只能与单独的 update 操作使用,不能和 code 或 interface_doc 同时传入。"),
|
|
28557
|
+
expected_version: exports_external.number().int().positive().optional().describe("更新时必填的当前版本号。必须来自刚读取的 flow_get_script,用于并发冲突保护;create 不得传入。")
|
|
28396
28558
|
});
|
|
28397
28559
|
var documentationSchema = exports_external.object({
|
|
28398
|
-
script_id: exports_external.string().min(1),
|
|
28560
|
+
script_id: exports_external.string().min(1).describe("目标脚本 ID,不是完整 URL;校验或预览现有脚本的文档。"),
|
|
28399
28561
|
...documentationFields
|
|
28400
28562
|
}).superRefine((input, context) => {
|
|
28401
28563
|
if (input.document === undefined === (input.raw_document === undefined)) {
|
|
@@ -28469,26 +28631,34 @@ function assertPreview(record3, operation) {
|
|
|
28469
28631
|
return record3;
|
|
28470
28632
|
}
|
|
28471
28633
|
function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
28472
|
-
const server = new McpServer({ name: "flow-codeblock-rust", version: "0.1.
|
|
28634
|
+
const server = new McpServer({ name: "flow-codeblock-rust", version: "0.1.1" }, { instructions: serverInstructions });
|
|
28473
28635
|
server.registerTool("flow_write_code", {
|
|
28474
|
-
description: "
|
|
28636
|
+
description: "生成代码实现契约,不写数据库、不发布脚本、不执行代码。mode=non_script 生成即时 POST /flow/codeblock 代码;mode=script 生成 GET/POST /flow/codeblock/{{script_id}} 代码,并同时给出完整 script-interface-doc.v1 接口文档规则。脚本文档的 title、summary、endpoint.methods、endpoint.description、request、responses、logic_description 必须齐全;query/header 参数必须有 name/type/description/example,body 和 response 字段必须有 content_type/schema/example。需要展示完整地址时传入 base_url,结果会拼接为 base_url + /flow/codeblock/{{script_id}}。",
|
|
28475
28637
|
inputSchema: {
|
|
28476
|
-
mode: exports_external.enum(["non_script", "script"]),
|
|
28477
|
-
requirement: exports_external.string().min(1).max(20000),
|
|
28478
|
-
input_example: exports_external.unknown().optional(),
|
|
28479
|
-
include_full_schema: exports_external.boolean().optional()
|
|
28638
|
+
mode: exports_external.enum(["non_script", "script"]).describe("执行模式:non_script 为即时接口;script 为按脚本 ID 发布的 GET/POST 接口。未明确要求重定向时使用 non_script。"),
|
|
28639
|
+
requirement: exports_external.string().min(1).max(20000).describe("用户的业务需求、输入字段、同步/异步要求和错误行为。只写与本次代码有关的需求。"),
|
|
28640
|
+
input_example: exports_external.unknown().optional().describe("业务输入示例。脚本模式下用于生成 request.body/schema/example;非脚本模式下用于 flow_execute_code 测试参数。不要放真实凭据。"),
|
|
28641
|
+
include_full_schema: exports_external.boolean().optional().describe("脚本模式是否在结果中附带完整 JSON Schema;默认 false。接口文档实例仍必须按必填字段生成。"),
|
|
28642
|
+
base_url: exports_external.string().url().refine((value) => {
|
|
28643
|
+
try {
|
|
28644
|
+
const parsed = new URL(value);
|
|
28645
|
+
return (parsed.protocol === "http:" || parsed.protocol === "https:") && !parsed.username && !parsed.password && !parsed.search && !parsed.hash && !/[\u0000-\u001f\u007f]/.test(value);
|
|
28646
|
+
} catch {
|
|
28647
|
+
return false;
|
|
28648
|
+
}
|
|
28649
|
+
}, "base_url must be an http(s) URL without credentials or control characters").optional().describe("可选的用户服务域名,例如 https://flow.example.com。仅用于输出完整调用地址,会拼接 /flow/codeblock/{{script_id}};不得包含用户名、密码或 Token。")
|
|
28480
28650
|
}
|
|
28481
|
-
}, async ({ mode, requirement, input_example, include_full_schema }) => result(codeWriterContext(mode, requirement, input_example, include_full_schema ?? false)));
|
|
28651
|
+
}, async ({ mode, requirement, input_example, include_full_schema, base_url }) => result(codeWriterContext(mode, requirement, input_example, include_full_schema ?? false, base_url)));
|
|
28482
28652
|
server.registerTool("flow_list_scripts", {
|
|
28483
|
-
description: "
|
|
28653
|
+
description: "只读分页查询脚本。默认使用 page/size 偏移分页;需要连续遍历时使用 pagination=cursor,首次不传 cursor,后续使用响应中的游标。不会创建、更新或执行脚本。",
|
|
28484
28654
|
inputSchema: {
|
|
28485
|
-
page: exports_external.number().int().positive().optional(),
|
|
28486
|
-
size: exports_external.number().int().min(1).max(100).optional(),
|
|
28487
|
-
keyword: exports_external.string().optional(),
|
|
28488
|
-
sort: exports_external.enum(["updated_at", "created_at", "code_length", "version"]).optional(),
|
|
28489
|
-
order: exports_external.enum(["asc", "desc"]).optional(),
|
|
28490
|
-
pagination: exports_external.literal("cursor").optional(),
|
|
28491
|
-
cursor: exports_external.string().optional()
|
|
28655
|
+
page: exports_external.number().int().positive().optional().describe("偏移分页页码,从 1 开始;使用游标分页时省略。"),
|
|
28656
|
+
size: exports_external.number().int().min(1).max(100).optional().describe("每页数量,1-100,默认由服务端决定。"),
|
|
28657
|
+
keyword: exports_external.string().optional().describe("按脚本关键词筛选。"),
|
|
28658
|
+
sort: exports_external.enum(["updated_at", "created_at", "code_length", "version"]).optional().describe("排序字段。游标分页只支持 updated_at、created_at、code_length。"),
|
|
28659
|
+
order: exports_external.enum(["asc", "desc"]).optional().describe("排序方向,默认由服务端决定。"),
|
|
28660
|
+
pagination: exports_external.literal("cursor").optional().describe("传 cursor 启用游标分页;首次调用不要传 cursor。"),
|
|
28661
|
+
cursor: exports_external.string().optional().describe("上一页返回的游标,仅与 pagination=cursor 一起使用。")
|
|
28492
28662
|
}
|
|
28493
28663
|
}, withApiErrors(async ({ page, size, keyword, sort, order, pagination, cursor }) => {
|
|
28494
28664
|
const query = new URLSearchParams;
|
|
@@ -28509,21 +28679,27 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28509
28679
|
return result(await api2.get(`/flow/scripts${query.size ? `?${query}` : ""}`));
|
|
28510
28680
|
}));
|
|
28511
28681
|
server.registerTool("flow_get_script", {
|
|
28512
|
-
description: "
|
|
28513
|
-
inputSchema: {
|
|
28682
|
+
description: "只读读取脚本代码、元数据和版本信息。更新前必须先调用本工具并使用返回的 current_version 作为 flow_preview_script_change.expected_version。",
|
|
28683
|
+
inputSchema: {
|
|
28684
|
+
script_id: exports_external.string().min(1).describe("脚本 ID,不是完整 URL。"),
|
|
28685
|
+
version: exports_external.number().int().min(0).optional().describe("可选历史版本号;省略时读取当前版本。")
|
|
28686
|
+
}
|
|
28514
28687
|
}, withApiErrors(async ({ script_id, version: version2 }) => {
|
|
28515
28688
|
const query = version2 === undefined ? "" : `?version=${encodeURIComponent(String(version2))}`;
|
|
28516
28689
|
return result(await api2.get(`/flow/scripts/${encodedScriptId(script_id)}${query}`));
|
|
28517
28690
|
}));
|
|
28518
28691
|
server.registerTool("flow_get_script_documentation", {
|
|
28519
|
-
description: "
|
|
28520
|
-
inputSchema: {
|
|
28692
|
+
description: "只读读取当前或指定历史版本的 script-interface-doc.v1 接口文档。修改文档前先读取当前版本并保留版本号。",
|
|
28693
|
+
inputSchema: {
|
|
28694
|
+
script_id: exports_external.string().min(1).describe("脚本 ID,不是完整 URL。"),
|
|
28695
|
+
version: exports_external.number().int().min(0).optional().describe("可选历史版本号;省略时读取当前文档。")
|
|
28696
|
+
}
|
|
28521
28697
|
}, withApiErrors(async ({ script_id, version: version2 }) => {
|
|
28522
28698
|
const query = version2 === undefined ? "" : `?version=${encodeURIComponent(String(version2))}`;
|
|
28523
28699
|
return result(await api2.get(`/flow/scripts/${encodedScriptId(script_id)}/documentation${query}`));
|
|
28524
28700
|
}));
|
|
28525
28701
|
server.registerTool("flow_validate_script_documentation", {
|
|
28526
|
-
description: "
|
|
28702
|
+
description: "只读校验并规范化指定脚本的接口文档,不写入数据库。document 与 raw_document 必须二选一;传 document 时会在调用服务端前检查全部必填字段,包含 title、summary、接口说明、请求方式、请求/响应结构和示例。",
|
|
28527
28703
|
inputSchema: documentationSchema.shape
|
|
28528
28704
|
}, withApiErrors(async (input) => {
|
|
28529
28705
|
const parsed = documentationSchema.parse(input);
|
|
@@ -28538,7 +28714,7 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28538
28714
|
return result(validation);
|
|
28539
28715
|
}));
|
|
28540
28716
|
server.registerTool("flow_preview_script_change", {
|
|
28541
|
-
description: "
|
|
28717
|
+
description: "预览脚本创建或更新,不写数据库。create 必须传 code/code_base64 和完整 interface_doc,且不得传 script_id/expected_version;update 必须传 script_id 和刚读取的 expected_version,修改代码时必须同时传完整 interface_doc。预览成功后只能在用户明确确认时调用 flow_apply_script_change(confirm=true)。",
|
|
28542
28718
|
inputSchema: changeSchema.shape
|
|
28543
28719
|
}, withApiErrors(async (input) => {
|
|
28544
28720
|
const parsed = changeSchema.parse(input);
|
|
@@ -28582,8 +28758,11 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28582
28758
|
});
|
|
28583
28759
|
}));
|
|
28584
28760
|
server.registerTool("flow_apply_script_change", {
|
|
28585
|
-
description: "
|
|
28586
|
-
inputSchema: {
|
|
28761
|
+
description: "应用 flow_preview_script_change 返回的一次性 preview_id。必须传 confirm=true;工具会再次检查版本并在成功或失败后销毁预览。不支持删除脚本。版本冲突时重新读取并预览。",
|
|
28762
|
+
inputSchema: {
|
|
28763
|
+
preview_id: exports_external.string().uuid().describe("最近一次脚本变更预览返回的 UUID;不能复用过期或已应用的 ID。"),
|
|
28764
|
+
confirm: exports_external.literal(true).describe("必须为 true,表示用户已明确确认写入。")
|
|
28765
|
+
}
|
|
28587
28766
|
}, withApiErrors(async ({ preview_id }) => {
|
|
28588
28767
|
try {
|
|
28589
28768
|
const record3 = assertPreview(previews.get(preview_id), "script_change");
|
|
@@ -28607,7 +28786,7 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28607
28786
|
}
|
|
28608
28787
|
}));
|
|
28609
28788
|
server.registerTool("flow_preview_script_documentation", {
|
|
28610
|
-
description: "
|
|
28789
|
+
description: "预览接口文档保存,不写数据库。先读取脚本当前版本;document/raw_document 二选一,并确保接口文档包含所有强制字段。成功后只有 flow_apply_script_documentation(confirm=true) 才会写入。",
|
|
28611
28790
|
inputSchema: documentationSchema.shape
|
|
28612
28791
|
}, withApiErrors(async (input) => {
|
|
28613
28792
|
const parsed = documentationSchema.parse(input);
|
|
@@ -28632,8 +28811,11 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28632
28811
|
});
|
|
28633
28812
|
}));
|
|
28634
28813
|
server.registerTool("flow_apply_script_documentation", {
|
|
28635
|
-
description: "
|
|
28636
|
-
inputSchema: {
|
|
28814
|
+
description: "应用 flow_preview_script_documentation 返回的一次性预览。必须传 confirm=true;工具会再次检查脚本版本并在成功或失败后销毁预览。版本冲突时重新读取、预览。",
|
|
28815
|
+
inputSchema: {
|
|
28816
|
+
preview_id: exports_external.string().uuid().describe("最近一次接口文档预览返回的 UUID;不能复用过期或已应用的 ID。"),
|
|
28817
|
+
confirm: exports_external.literal(true).describe("必须为 true,表示用户已明确确认写入。")
|
|
28818
|
+
}
|
|
28637
28819
|
}, withApiErrors(async ({ preview_id }) => {
|
|
28638
28820
|
try {
|
|
28639
28821
|
const record3 = assertPreview(previews.get(preview_id), "documentation");
|
|
@@ -28652,23 +28834,23 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28652
28834
|
}
|
|
28653
28835
|
}));
|
|
28654
28836
|
const lockSchema = {
|
|
28655
|
-
script_id: exports_external.string().min(1),
|
|
28656
|
-
owner_name: exports_external.string().min(1),
|
|
28657
|
-
lock_password: exports_external.string().min(6),
|
|
28658
|
-
confirm: exports_external.literal(true)
|
|
28837
|
+
script_id: exports_external.string().min(1).describe("要锁定或解锁的脚本 ID。"),
|
|
28838
|
+
owner_name: exports_external.string().min(1).describe("锁定时设置、解锁时核对的所有者名称。"),
|
|
28839
|
+
lock_password: exports_external.string().min(6).describe("锁定口令;仅传给服务端,不会写入 MCP 预览或日志。"),
|
|
28840
|
+
confirm: exports_external.literal(true).describe("必须为 true,表示用户明确确认锁定或解锁。")
|
|
28659
28841
|
};
|
|
28660
|
-
server.registerTool("flow_lock_script", { description: "
|
|
28661
|
-
server.registerTool("flow_unlock_script", { description: "
|
|
28842
|
+
server.registerTool("flow_lock_script", { description: "锁定脚本以阻止并发写入。必须显式传入 confirm=true;锁定口令不会被 MCP 保存。锁定后脚本变更和文档保存可能被服务端拒绝。", inputSchema: lockSchema }, withApiErrors(async ({ script_id, owner_name, lock_password }) => result(await api2.post(`/flow/scripts/${encodedScriptId(script_id)}/lock`, { owner_name, lock_password }))));
|
|
28843
|
+
server.registerTool("flow_unlock_script", { description: "使用所有者名称和口令正常解锁脚本。必须显式传入 confirm=true;锁定口令不会被 MCP 保存。不提供紧急恢复解锁。", inputSchema: lockSchema }, withApiErrors(async ({ script_id, owner_name, lock_password }) => result(await api2.post(`/flow/scripts/${encodedScriptId(script_id)}/unlock`, { owner_name, lock_password }))));
|
|
28662
28844
|
const queryValueSchema = exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean(), exports_external.array(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()]))]);
|
|
28663
28845
|
server.registerTool("flow_execute_script", {
|
|
28664
|
-
description: "
|
|
28846
|
+
description: "执行已发布脚本,仅用于用户明确要求测试或调用时。method 只能是 GET/POST;脚本地址为当前服务域名 + /flow/codeblock/{script_id},结果包含不带业务 query 值的 request_url。query 数组会生成重复参数,POST 的 body 作为 JSON 发送。MCP 认证、Cookie、CSRF、代理来源头和测试工具标识会被过滤,qingcodeToken/qingcodeTimeout 不能作为业务参数传入。",
|
|
28665
28847
|
inputSchema: {
|
|
28666
|
-
script_id: exports_external.string().min(1),
|
|
28667
|
-
method: exports_external.enum(["GET", "POST"]).default("POST"),
|
|
28668
|
-
query: exports_external.record(exports_external.string(), queryValueSchema).optional(),
|
|
28669
|
-
headers: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
28670
|
-
body: exports_external.unknown().optional(),
|
|
28671
|
-
timeout_ms: exports_external.number().int().positive().optional()
|
|
28848
|
+
script_id: exports_external.string().min(1).describe("已发布脚本 ID;工具会调用 /flow/codeblock/{script_id}。"),
|
|
28849
|
+
method: exports_external.enum(["GET", "POST"]).default("POST").describe("脚本请求方法,只能是 GET 或 POST;默认 POST。"),
|
|
28850
|
+
query: exports_external.record(exports_external.string(), queryValueSchema).optional().describe("业务查询参数。值可为 string/number/boolean 或其数组;数组生成重复 query 参数。不要传 qingcodeToken/qingcodeTimeout。"),
|
|
28851
|
+
headers: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("业务请求头。认证、Cookie、CSRF、Forwarded、X-Real-IP 等保留头会被过滤。"),
|
|
28852
|
+
body: exports_external.unknown().optional().describe("POST JSON 请求体;GET 不发送 body。不要把脚本模式业务 body 包装为 input 或 input.body。"),
|
|
28853
|
+
timeout_ms: exports_external.number().int().positive().optional().describe("执行超时毫秒数;只能通过此字段配置,不能在 query 中传 qingcodeTimeout。")
|
|
28672
28854
|
}
|
|
28673
28855
|
}, withApiErrors(async ({ script_id, method, query, headers, body, timeout_ms }) => {
|
|
28674
28856
|
const url2 = new URL(`/flow/codeblock/${encodedScriptId(script_id)}`, api2.baseUrl);
|
|
@@ -28694,15 +28876,17 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28694
28876
|
authenticated: false,
|
|
28695
28877
|
execution: true
|
|
28696
28878
|
});
|
|
28697
|
-
|
|
28879
|
+
const requestUrl = new URL(url2);
|
|
28880
|
+
requestUrl.search = "";
|
|
28881
|
+
return result({ request_url: requestUrl.toString(), method, response });
|
|
28698
28882
|
}));
|
|
28699
28883
|
server.registerTool("flow_execute_code", {
|
|
28700
|
-
description: "执行未发布的非脚本 JavaScript
|
|
28884
|
+
description: "执行未发布的非脚本 JavaScript,仅在用户明确要求测试时使用。请求固定为 POST /flow/codeblock,body.input 原样注入全局 input;代码和 code_base64 二选一。不会把 MCP 认证信息写入用户脚本输入,也不会创建或发布脚本。",
|
|
28701
28885
|
inputSchema: {
|
|
28702
|
-
code: exports_external.string().min(1).optional(),
|
|
28703
|
-
code_base64: exports_external.string().min(1).optional(),
|
|
28704
|
-
input: exports_external.unknown().optional(),
|
|
28705
|
-
timeout_ms: exports_external.number().int().positive().optional()
|
|
28886
|
+
code: exports_external.string().min(1).optional().describe("UTF-8 JavaScript 源码,与 code_base64 二选一。"),
|
|
28887
|
+
code_base64: exports_external.string().min(1).optional().describe("JavaScript 源码的非空 Base64,与 code 二选一。"),
|
|
28888
|
+
input: exports_external.unknown().optional().describe("注入全局 input 的业务数据,默认 {}。不要放 Token、密码、Cookie 或 Authorization 值。"),
|
|
28889
|
+
timeout_ms: exports_external.number().int().positive().optional().describe("本次测试执行超时毫秒数;必须在服务端允许的最小/最大范围内。")
|
|
28706
28890
|
}
|
|
28707
28891
|
}, withApiErrors(async ({ code, code_base64, input, timeout_ms }) => {
|
|
28708
28892
|
const codeBase64 = encodeCode(code, code_base64);
|