flow-codeblock-rust-mcp 0.1.0 → 0.1.2
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 +15 -3
- package/dist/index.js +431 -102
- 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.2
|
|
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.2"],
|
|
30
30
|
"env": {
|
|
31
31
|
"FLOW_CODEBLOCK_BASE_URL": "https://flow.example.com",
|
|
32
32
|
"FLOW_CODEBLOCK_TOKEN": "<YOUR_INTERNAL_ACCESS_TOKEN>"
|
|
@@ -38,10 +38,22 @@ export FLOW_CODEBLOCK_TOKEN='<YOUR_INTERNAL_ACCESS_TOKEN>'
|
|
|
38
38
|
|
|
39
39
|
## 工具边界
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
工具覆盖代码生成、未发布代码测试、脚本列表、版本读取、接口文档校验/预览/保存、脚本创建/更新、锁定/解锁和已发布脚本执行。创建或代码更新可提交完整接口文档或 RFC 6902 `interface_doc_patch`;预览会调用 `/flow/scripts/validate`,应用时会再次透传补丁并使用事务级 `expected_version` 检测并发冲突。所有脚本写操作需要预览后显式 `confirm: true`。
|
|
42
|
+
|
|
43
|
+
最终用户交付按模式区分:`non_script` 输出完整 JavaScript、接口调用说明、请求参数及示例、执行逻辑、成功/错误输出示例和完整 `execution_url`;`script` 默认不主动回显 JavaScript 或原始 `interface_doc`,只输出接口调用说明、请求参数及示例、执行逻辑、成功/错误输出示例和发布后的完整 `script_url`。脚本代码与 `interface_doc` 仍由 MCP 内部用于预览、校验和发布,除非用户明确索要源码或原始文档。
|
|
42
44
|
|
|
43
45
|
MCP 不提供脚本删除、紧急恢复解锁、Token 查询、执行统计、所有权转移或任意 HTTP 代理工具。当前版本只提供本地 stdio 连接,不提供远程 HTTP、SSE 或 Streamable HTTP 连接。
|
|
44
46
|
|
|
47
|
+
## MCP 工具契约
|
|
48
|
+
|
|
49
|
+
服务器初始化时会通过 MCP `instructions` 下发完整的工具选择、脚本预览/确认流程、代码运行时约束和接口文档规则,因此不依赖额外 Skill 也可以直接调用工具。客户端应优先使用工具 description 和 input schema 中的字段说明,不要通过试错猜测参数。
|
|
50
|
+
|
|
51
|
+
接口文档必须包含 `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`。
|
|
52
|
+
|
|
53
|
+
接口文档的 `endpoint.path` 使用相对路径:创建时省略,更新时使用 `/flow/codeblock/<实际脚本ID>`。对外展示完整请求地址时,将用户提供的服务域名与 `/flow/codeblock/{{脚本ID}}` 拼接;不要把真实令牌、密码、Cookie 或 Authorization 值写入代码、文档、示例或 URL。
|
|
54
|
+
|
|
55
|
+
`interface_doc_patch` / `document_patch` 必须携带正整数 `expected_version`,最多 256 个 `add/remove/replace/move/copy/test` 操作。补丁预览仅返回操作数量、JSON Pointer 路径、警告和版本信息,不返回完整合并文档。
|
|
56
|
+
|
|
45
57
|
## 安全行为
|
|
46
58
|
|
|
47
59
|
- 管理请求使用 `Authorization: Bearer`,执行已发布脚本时不会把 MCP Token 转发到脚本输入。
|
package/dist/index.js
CHANGED
|
@@ -27877,23 +27877,68 @@ 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(" ");
|
|
27904
|
+
var patchPathSchema = exports_external.string().describe("RFC 6901 JSON Pointer 路径;数组路径使用当前 canonical 文档的索引。");
|
|
27905
|
+
var interfaceDocPatchOperationSchema = exports_external.union([
|
|
27906
|
+
exports_external.object({ op: exports_external.literal("add"), path: patchPathSchema, value: exports_external.unknown() }).strict(),
|
|
27907
|
+
exports_external.object({ op: exports_external.literal("remove"), path: patchPathSchema }).strict(),
|
|
27908
|
+
exports_external.object({ op: exports_external.literal("replace"), path: patchPathSchema, value: exports_external.unknown() }).strict(),
|
|
27909
|
+
exports_external.object({ op: exports_external.literal("move"), from: patchPathSchema, path: patchPathSchema }).strict(),
|
|
27910
|
+
exports_external.object({ op: exports_external.literal("copy"), from: patchPathSchema, path: patchPathSchema }).strict(),
|
|
27911
|
+
exports_external.object({ op: exports_external.literal("test"), path: patchPathSchema, value: exports_external.unknown() }).strict()
|
|
27912
|
+
]).superRefine((operation, context) => {
|
|
27913
|
+
if (["add", "replace", "test"].includes(operation.op) && !Object.prototype.hasOwnProperty.call(operation, "value")) {
|
|
27914
|
+
context.addIssue({ code: "custom", path: ["value"], message: "value is required for this operation" });
|
|
27915
|
+
}
|
|
27916
|
+
});
|
|
27917
|
+
var interfaceDocPatchSchema = exports_external.array(interfaceDocPatchOperationSchema).min(1).max(256).describe("RFC 6902 JSON Patch 操作数组;按顺序应用,不能与完整 interface_doc 同时提供。");
|
|
27918
|
+
var interfaceDocPatchJsonSchema = {
|
|
27919
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
27920
|
+
$id: "https://flow-codeblock.local/schemas/script-interface-doc.patch.v1.json",
|
|
27921
|
+
title: "script-interface-doc.v1 JSON Patch",
|
|
27922
|
+
description: "RFC 6902 operations applied in order to an existing canonical script interface document.",
|
|
27923
|
+
type: "array",
|
|
27924
|
+
minItems: 1,
|
|
27925
|
+
maxItems: 256,
|
|
27926
|
+
items: {
|
|
27927
|
+
oneOf: [
|
|
27928
|
+
{ type: "object", additionalProperties: false, required: ["op", "path", "value"], properties: { op: { const: "add" }, path: { type: "string" }, value: {} } },
|
|
27929
|
+
{ type: "object", additionalProperties: false, required: ["op", "path"], properties: { op: { const: "remove" }, path: { type: "string" } } },
|
|
27930
|
+
{ type: "object", additionalProperties: false, required: ["op", "path", "value"], properties: { op: { const: "replace" }, path: { type: "string" }, value: {} } },
|
|
27931
|
+
{ type: "object", additionalProperties: false, required: ["op", "from", "path"], properties: { op: { const: "move" }, from: { type: "string" }, path: { type: "string" } } },
|
|
27932
|
+
{ type: "object", additionalProperties: false, required: ["op", "from", "path"], properties: { op: { const: "copy" }, from: { type: "string" }, path: { type: "string" } } },
|
|
27933
|
+
{ type: "object", additionalProperties: false, required: ["op", "path", "value"], properties: { op: { const: "test" }, path: { type: "string" }, value: {} } }
|
|
27934
|
+
]
|
|
27935
|
+
}
|
|
27936
|
+
};
|
|
27937
|
+
function assertInterfaceDocPatch(patch) {
|
|
27938
|
+
const parsed = interfaceDocPatchSchema.safeParse(patch);
|
|
27939
|
+
if (!parsed.success)
|
|
27940
|
+
throw new Error(`interface_doc_patch 格式无效: ${parsed.error.message}`);
|
|
27941
|
+
}
|
|
27897
27942
|
function isObject2(value) {
|
|
27898
27943
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27899
27944
|
}
|
|
@@ -27941,6 +27986,8 @@ function validateSchemaExampleCoverage(schema, example, schemaPath, examplePath,
|
|
|
27941
27986
|
issues.push(`${examplePath} is missing ${key}`);
|
|
27942
27987
|
} else if (isObject2(propertySchema)) {
|
|
27943
27988
|
validateSchemaExampleCoverage(propertySchema, example[key], `${schemaPath}.properties.${key}`, `${examplePath}.${key}`, issues);
|
|
27989
|
+
} else {
|
|
27990
|
+
issues.push(`${schemaPath}.properties.${key} must be a JSON Schema object`);
|
|
27944
27991
|
}
|
|
27945
27992
|
}
|
|
27946
27993
|
}
|
|
@@ -27973,6 +28020,9 @@ function validateParameters(value, path, issues) {
|
|
|
27973
28020
|
issues.push(`${path} must be present; use [] when there are no parameters`);
|
|
27974
28021
|
return;
|
|
27975
28022
|
}
|
|
28023
|
+
if (value.length > 100)
|
|
28024
|
+
issues.push(`${path} must contain at most 100 parameters`);
|
|
28025
|
+
const names = new Set;
|
|
27976
28026
|
value.forEach((parameter, index) => {
|
|
27977
28027
|
const itemPath = `${path}[${index}]`;
|
|
27978
28028
|
if (!isObject2(parameter)) {
|
|
@@ -27981,6 +28031,13 @@ function validateParameters(value, path, issues) {
|
|
|
27981
28031
|
}
|
|
27982
28032
|
requireText(parameter, "name", itemPath, issues);
|
|
27983
28033
|
requireText(parameter, "description", itemPath, issues);
|
|
28034
|
+
if (typeof parameter.name === "string") {
|
|
28035
|
+
const name = parameter.name.trim();
|
|
28036
|
+
if (name && names.has(name))
|
|
28037
|
+
issues.push(`${itemPath}.name must be unique within ${path}`);
|
|
28038
|
+
if (name)
|
|
28039
|
+
names.add(name);
|
|
28040
|
+
}
|
|
27984
28041
|
if (!["string", "integer", "number", "boolean", "array", "object"].includes(String(parameter.type))) {
|
|
27985
28042
|
issues.push(`${itemPath}.type is unsupported`);
|
|
27986
28043
|
}
|
|
@@ -28027,6 +28084,9 @@ function interfaceDocCompletenessIssues(document, operation) {
|
|
|
28027
28084
|
if (methods.some((method) => method !== "GET" && method !== "POST")) {
|
|
28028
28085
|
issues.push("interface_doc.endpoint.methods may contain only GET or POST");
|
|
28029
28086
|
}
|
|
28087
|
+
if (new Set(methods).size !== methods.length) {
|
|
28088
|
+
issues.push("interface_doc.endpoint.methods must not contain duplicates");
|
|
28089
|
+
}
|
|
28030
28090
|
}
|
|
28031
28091
|
if (operation === "update") {
|
|
28032
28092
|
if (typeof document.endpoint.path !== "string" || !document.endpoint.path.startsWith("/flow/codeblock/") || document.endpoint.path.includes("{script_id}")) {
|
|
@@ -28053,6 +28113,8 @@ function interfaceDocCompletenessIssues(document, operation) {
|
|
|
28053
28113
|
if (!Array.isArray(document.responses) || document.responses.length === 0) {
|
|
28054
28114
|
issues.push("interface_doc.responses must contain at least one response");
|
|
28055
28115
|
} else {
|
|
28116
|
+
if (document.responses.length > 50)
|
|
28117
|
+
issues.push("interface_doc.responses must contain at most 50 responses");
|
|
28056
28118
|
document.responses.forEach((response, index) => {
|
|
28057
28119
|
const path = `interface_doc.responses[${index}]`;
|
|
28058
28120
|
if (!isObject2(response)) {
|
|
@@ -28110,7 +28172,11 @@ var interfaceDocSchema = {
|
|
|
28110
28172
|
additionalProperties: false,
|
|
28111
28173
|
properties: {
|
|
28112
28174
|
methods: { type: "array", minItems: 1, maxItems: 2, uniqueItems: true, items: { enum: ["GET", "POST"] } },
|
|
28113
|
-
path: {
|
|
28175
|
+
path: {
|
|
28176
|
+
type: "string",
|
|
28177
|
+
pattern: "^/flow/codeblock/",
|
|
28178
|
+
description: "相对路径;创建时可省略,更新时必须为 /flow/codeblock/<实际脚本ID>。完整地址另行拼接调用方提供的域名。"
|
|
28179
|
+
},
|
|
28114
28180
|
description: { type: "string", minLength: 1, maxLength: 4000 }
|
|
28115
28181
|
}
|
|
28116
28182
|
},
|
|
@@ -28191,51 +28257,182 @@ function schemaFromExample(value) {
|
|
|
28191
28257
|
return { type: Number.isInteger(value) ? "integer" : "number" };
|
|
28192
28258
|
return { type: typeof value };
|
|
28193
28259
|
}
|
|
28194
|
-
function codeWriterContext(mode, requirement, inputExample, includeFullSchema) {
|
|
28260
|
+
function codeWriterContext(mode, requirement, inputExample, includeFullSchema, baseUrl) {
|
|
28195
28261
|
const common = {
|
|
28196
28262
|
contract_version: "flow-code-writer.v1",
|
|
28197
28263
|
mode,
|
|
28198
28264
|
requirement,
|
|
28199
28265
|
mutates_or_executes: false,
|
|
28200
28266
|
runtime: "Bun 1.4+ fresh worker; no state is shared between executions.",
|
|
28267
|
+
runtime_limits: {
|
|
28268
|
+
execution_timeout_ms: { minimum_default: 100, default_maximum: 15000 },
|
|
28269
|
+
code_bytes: 65535,
|
|
28270
|
+
input_bytes: 2 * 1024 * 1024,
|
|
28271
|
+
result_bytes: 10 * 1024 * 1024
|
|
28272
|
+
},
|
|
28201
28273
|
code_rules: [
|
|
28202
|
-
"
|
|
28203
|
-
"
|
|
28204
|
-
"
|
|
28205
|
-
"
|
|
28274
|
+
"所有业务数据只从全局 input 读取,不从环境变量、持久化全局变量或其他外部状态读取;返回值必须可 JSON 序列化。",
|
|
28275
|
+
"默认使用顶层 return;只有事件式/异步流程或用户明确要求时才使用 qf_output,且必须是裸的 qf_output = { ... } 对象字面量赋值,不能与顶层 return 混用,也不能遮蔽该标识符。",
|
|
28276
|
+
"优先标准 JavaScript 和服务端原生 fetch;只有原生能力确实无法满足且用户明确要求时,才使用白名单 CommonJS 字面量 require。",
|
|
28277
|
+
"禁止 import/export、动态 require、浏览器 API、定时器、阻止标识符、阻止成员和黑名单 Node 模块,不得写入真实凭据。",
|
|
28278
|
+
"业务逻辑和异步操作放在 try-catch 中;错误转换为字符串或普通对象后返回。",
|
|
28279
|
+
"不得创建无界循环、未 settle 的 Promise 或执行结束后仍运行的后台任务;所有请求必须 await 或 return。",
|
|
28280
|
+
"外部 URL、请求头、查询参数和请求体必须校验类型、长度和允许范围;HTTP 响应检查状态并按 JSON、文本、空响应分别处理。"
|
|
28281
|
+
],
|
|
28282
|
+
async_lifecycle: [
|
|
28283
|
+
"禁止 setTimeout、setInterval、setImmediate、轮询、延迟和后台重试。",
|
|
28284
|
+
"请求必须在执行超时内完成,不能遗留未等待的 fetch。"
|
|
28285
|
+
],
|
|
28286
|
+
require_policy: {
|
|
28287
|
+
allowed_call_form: "仅允许单个字符串字面量 require('模块名'),不得间接调用、动态模块名或使用 import/export。",
|
|
28288
|
+
dayjs_exception: "日期处理优先原生 Date;复杂日期解析/格式化/时区才允许使用 dayjs。",
|
|
28289
|
+
other_modules: "除 dayjs 外,只有原生能力确实无法实现且用户明确要求时才允许白名单模块。"
|
|
28290
|
+
},
|
|
28291
|
+
forbidden: {
|
|
28292
|
+
identifiers: [
|
|
28293
|
+
"eval",
|
|
28294
|
+
"Function",
|
|
28295
|
+
"Proxy",
|
|
28296
|
+
"__proto__",
|
|
28297
|
+
"child_process",
|
|
28298
|
+
"exec",
|
|
28299
|
+
"execFile",
|
|
28300
|
+
"execSync",
|
|
28301
|
+
"fork",
|
|
28302
|
+
"spawn",
|
|
28303
|
+
"module",
|
|
28304
|
+
"exports",
|
|
28305
|
+
"setImmediate",
|
|
28306
|
+
"setInterval",
|
|
28307
|
+
"setTimeout"
|
|
28308
|
+
],
|
|
28309
|
+
members: [
|
|
28310
|
+
"Object.getPrototypeOf",
|
|
28311
|
+
"Object.setPrototypeOf",
|
|
28312
|
+
"Reflect.construct",
|
|
28313
|
+
"Reflect.apply",
|
|
28314
|
+
"Reflect.get",
|
|
28315
|
+
"Reflect.set",
|
|
28316
|
+
"process.exit",
|
|
28317
|
+
"process.kill",
|
|
28318
|
+
"process.binding",
|
|
28319
|
+
"process._linkedBinding",
|
|
28320
|
+
"process.dlopen"
|
|
28321
|
+
],
|
|
28322
|
+
modules: [
|
|
28323
|
+
"child_process",
|
|
28324
|
+
"cluster",
|
|
28325
|
+
"dgram",
|
|
28326
|
+
"dns",
|
|
28327
|
+
"http",
|
|
28328
|
+
"http2",
|
|
28329
|
+
"https",
|
|
28330
|
+
"inspector",
|
|
28331
|
+
"internal",
|
|
28332
|
+
"module",
|
|
28333
|
+
"node:module",
|
|
28334
|
+
"net",
|
|
28335
|
+
"os",
|
|
28336
|
+
"perf_hooks",
|
|
28337
|
+
"async_hooks",
|
|
28338
|
+
"bun",
|
|
28339
|
+
"process",
|
|
28340
|
+
"readline",
|
|
28341
|
+
"repl",
|
|
28342
|
+
"tls",
|
|
28343
|
+
"undici",
|
|
28344
|
+
"v8",
|
|
28345
|
+
"vm",
|
|
28346
|
+
"vm2",
|
|
28347
|
+
"worker_threads",
|
|
28348
|
+
"ws"
|
|
28349
|
+
]
|
|
28350
|
+
},
|
|
28351
|
+
output_rules: [
|
|
28352
|
+
"返回值只能是普通可序列化值或 Promise;禁止循环引用、BigInt、函数、Symbol、未处理的复杂类实例及无界数组/字符串。",
|
|
28353
|
+
"平台会把即时接口返回值放入外层 HTTP 响应的 result 字段;脚本接口通常直接返回业务值。推荐 { success: true, data: value } 或 { success: false, error: message }。"
|
|
28206
28354
|
],
|
|
28207
28355
|
allowed_modules: allowedModules
|
|
28208
28356
|
};
|
|
28209
28357
|
if (mode === "non_script") {
|
|
28210
28358
|
return {
|
|
28211
28359
|
...common,
|
|
28212
|
-
input_contract:
|
|
28213
|
-
|
|
28360
|
+
input_contract: {
|
|
28361
|
+
method: "POST",
|
|
28362
|
+
path: "/flow/codeblock",
|
|
28363
|
+
rule: "请求体中的 input 原样注入全局 input,缺省为 {};平台请求体可包含 codebase64、input、qingcodeTimeout。",
|
|
28364
|
+
example: { codebase64: "<base64 JavaScript>", input: inputExample ?? {} }
|
|
28365
|
+
},
|
|
28366
|
+
generation_decisions: [
|
|
28367
|
+
"用户未说明时生成即时执行的非脚本模式。",
|
|
28368
|
+
"如果需求包含 HTTP 重定向,必须改用 script 模式 /flow/codeblock/{{script_id}}。"
|
|
28369
|
+
],
|
|
28370
|
+
deliverables: ["只含可执行 JavaScript 的 javascript 代码块", "输入和输出契约"],
|
|
28214
28371
|
test_tool: { name: "flow_execute_code", arguments: { code: "<JavaScript>", input: inputExample ?? {}, timeout_ms: 3000 } },
|
|
28215
|
-
rule: "
|
|
28372
|
+
rule: "除非用户明确要求测试,否则不要调用 flow_execute_code。",
|
|
28373
|
+
response_format: [
|
|
28374
|
+
"除非用户明确要求只返回代码,否则先说明模式和输出方式,再给 JavaScript 代码块,最后给请求/响应示例。",
|
|
28375
|
+
"非脚本接口交付完整 JavaScript、接口调用说明、请求参数及示例、执行逻辑、成功/错误输出示例和 execution_url。"
|
|
28376
|
+
]
|
|
28216
28377
|
};
|
|
28217
28378
|
}
|
|
28218
28379
|
const bodyExample = inputExample && typeof inputExample === "object" ? inputExample : { value: "example" };
|
|
28380
|
+
const normalizedBaseUrl = baseUrl?.trim().replace(/\/+$/, "");
|
|
28381
|
+
const endpointPathTemplate = "/flow/codeblock/{{script_id}}";
|
|
28219
28382
|
return {
|
|
28220
28383
|
...common,
|
|
28221
28384
|
input_contract: {
|
|
28222
|
-
endpoint: "GET|POST /flow/codeblock/{script_id}",
|
|
28385
|
+
endpoint: "GET|POST /flow/codeblock/{{script_id}}",
|
|
28223
28386
|
shape: { query: {}, header: {}, body: {}, cookies: {} },
|
|
28224
|
-
|
|
28387
|
+
mapping: {
|
|
28388
|
+
query: "input.query;单值为字符串,重复参数为字符串数组;不含 qingcodeToken/qingcodeTimeout。",
|
|
28389
|
+
headers: "input.header;服务端过滤 x-original-cookie,需要 Cookie 时使用 cookie。",
|
|
28390
|
+
body: "input.body;POST JSON 请求体,空请求体为 {};业务数据直接发送,不包装为 input 或 input.body。",
|
|
28391
|
+
cookies: "input.cookies;Cookie 键值对象,无 Cookie 时可能不存在。"
|
|
28392
|
+
},
|
|
28393
|
+
reserved_query: ["qingcodeToken", "qingcodeTimeout"]
|
|
28225
28394
|
},
|
|
28226
|
-
|
|
28395
|
+
endpoint_url_template: normalizedBaseUrl ? `${normalizedBaseUrl}${endpointPathTemplate}` : endpointPathTemplate,
|
|
28396
|
+
endpoint_url_rule: "提供域名时,输出该域名 + /flow/codeblock/{{script_id}};URL 中不得包含凭据。",
|
|
28397
|
+
internal_artifacts: [
|
|
28398
|
+
"只含可执行代码的 JavaScript 代码块(提交预览、校验和发布;默认不回显)",
|
|
28399
|
+
"独立且完整的 script-interface-doc.v1 JSON 对象(提交预览、校验和发布;默认不回显)"
|
|
28400
|
+
],
|
|
28401
|
+
final_deliverables: [
|
|
28402
|
+
"接口调用说明",
|
|
28403
|
+
"请求参数及示例",
|
|
28404
|
+
"执行逻辑",
|
|
28405
|
+
"成功/错误输出示例",
|
|
28406
|
+
"发布后的完整 script_url"
|
|
28407
|
+
],
|
|
28227
28408
|
interface_doc_contract: {
|
|
28228
28409
|
strict_preview_gate: true,
|
|
28229
28410
|
required_fields: interfaceDocRequiredFields,
|
|
28230
28411
|
nested_rules: interfaceDocNestedRules,
|
|
28231
28412
|
body_example_schema: schemaFromExample(bodyExample),
|
|
28232
|
-
full_json_schema: includeFullSchema ? interfaceDocSchema : undefined
|
|
28413
|
+
full_json_schema: includeFullSchema ? interfaceDocSchema : undefined,
|
|
28414
|
+
patch_json_schema: includeFullSchema ? interfaceDocPatchJsonSchema : undefined,
|
|
28415
|
+
separation: [
|
|
28416
|
+
"javascript 代码块只含可执行代码,不写接口文档注释。",
|
|
28417
|
+
"json 代码块只含一个合法 script-interface-doc.v1 对象,不混入 Markdown、注释或尾随逗号。"
|
|
28418
|
+
]
|
|
28419
|
+
},
|
|
28420
|
+
redirect_contract: {
|
|
28421
|
+
rule: "只有脚本接口解析 flow_redirect_url/flow_redirect_code;即时接口把它们作为普通结果。",
|
|
28422
|
+
url: "flow_redirect_url 必须是单斜杠相对路径或带主机的 http/https URL,不得含空白或控制字符。",
|
|
28423
|
+
code: "flow_redirect_code 只能是 301、302、303、307、308 或对应数字字符串。"
|
|
28233
28424
|
},
|
|
28234
28425
|
workflow: [
|
|
28235
|
-
"
|
|
28236
|
-
"
|
|
28237
|
-
"
|
|
28238
|
-
"
|
|
28426
|
+
"如果是更新,先调用 flow_get_script 读取当前版本;更新文档可先调用 flow_get_script_documentation。",
|
|
28427
|
+
"代码和完整 interface_doc 一起生成;创建或代码更新只调用一次 flow_preview_script_change。",
|
|
28428
|
+
"仅在用户明确确认后调用 flow_apply_script_change 或 flow_apply_script_documentation,并传 confirm=true。",
|
|
28429
|
+
"只有用户要求测试已发布脚本时才调用 flow_execute_script;仅测试未发布代码时调用 flow_execute_code。",
|
|
28430
|
+
"版本冲突、预览过期或校验失败时停止并重新读取、重新预览,不重试旧 preview_id。"
|
|
28431
|
+
],
|
|
28432
|
+
response_format: [
|
|
28433
|
+
"脚本模式默认不回显 JavaScript 或原始 interface_doc;只展示接口调用说明、请求参数及示例、执行逻辑、成功/错误输出示例和发布后的 script_url,除非用户明确索要源码或原始文档。",
|
|
28434
|
+
"脚本代码与 interface_doc 仍必须在内部提交给预览、校验和发布工具。",
|
|
28435
|
+
"说明请求参数、主要业务行为、响应和错误处理,并提供与模式匹配的 HTTP 方法、路径、Headers/Query/Body/Cookie 和响应示例。"
|
|
28239
28436
|
]
|
|
28240
28437
|
};
|
|
28241
28438
|
}
|
|
@@ -28302,9 +28499,45 @@ class PreviewStore {
|
|
|
28302
28499
|
var result = (value) => ({
|
|
28303
28500
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
28304
28501
|
});
|
|
28502
|
+
var serverInstructions = [
|
|
28503
|
+
"这是 Flow Codeblock Rust+Bun MCP Server。除 flow_write_code 外,工具直接调用服务端 REST API;flow_write_code 只返回生成契约。不要猜测 REST 路径,也不要把 MCP 内部令牌放入业务参数。",
|
|
28504
|
+
"代码运行于服务端 Bun 异步函数上下文,支持现代 JavaScript、async/await、Promise、箭头函数和顶层 return;默认最小超时 100ms、最大超时 15000ms,代码 65535 字节、输入 2MiB、结果 10MiB。",
|
|
28505
|
+
"工具选择:flow_write_code 只生成代码与契约,不执行、不写库;flow_execute_code 只用于用户明确要求的未发布非脚本测试;flow_execute_script 只执行已发布脚本。",
|
|
28506
|
+
"脚本读取、创建和更新流程:更新前先 flow_get_script 读取当前 version;创建必须提供完整 interface_doc,更新代码或文档可提供完整 interface_doc 或 RFC 6902 interface_doc_patch(二选一,补丁必须带 expected_version);先 flow_preview_script_change,再在用户明确确认后 flow_apply_script_change(confirm=true)。文档单独修改使用 flow_preview_script_documentation -> flow_apply_script_documentation。",
|
|
28507
|
+
"预览 ID 是一次性且有时效的;版本冲突、预览过期或校验失败时停止,重新读取并预览,不要重试旧 preview_id。flow_apply_* 永远需要 confirm=true。",
|
|
28508
|
+
"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 必须省略。增量文档使用最多 256 项的 add/remove/replace/move/copy/test JSON Patch,预览只回显操作数量和路径,不回显合并文档。",
|
|
28509
|
+
"查询参数和请求头的每一项必须有 name、type、required、description、example。请求体和每个响应必须有 content_type=application/json、schema、example;每个响应还必须有 status、description。JSON Schema 的每个节点必须声明 type,数组必须有 items,对象和 example 必须互相覆盖。",
|
|
28510
|
+
"接口文档 endpoint.path 只写相对路径:创建时省略,更新时写 /flow/codeblock/<实际脚本ID>。对外展示的完整调用地址必须使用用户提供的域名拼接 /flow/codeblock/{{脚本ID}};不要把真实 Token、密码、Cookie 或 Authorization 值写入代码、文档、示例或 URL。",
|
|
28511
|
+
"脚本模式输入来自 input.query、input.header、input.body、input.cookies;即时非脚本模式 POST /flow/codeblock 的 body.input 原样成为全局 input。代码默认使用顶层 return;只有事件式/异步流程或用户明确要求时才使用裸 qf_output 赋值,不能混用。",
|
|
28512
|
+
"最终用户交付按模式区分:non_script 输出完整 JavaScript、接口调用说明、请求参数及示例、执行逻辑、成功/错误输出示例和完整 execution_url;script 默认不主动回显 JavaScript 或原始 interface_doc,只输出接口调用说明、请求参数及示例、执行逻辑、成功/错误输出示例和发布后的完整 script_url,除非用户明确索要源码或原始文档。script 的代码与 interface_doc 仍必须内部提交给预览、校验和发布工具。",
|
|
28513
|
+
"优先原生 JavaScript、URL/URLSearchParams 和 fetch;禁止浏览器 API、定时器、动态模块加载、黑名单 Node 模块和危险标识符。HTTP 请求必须检查状态并处理 JSON/文本/空响应,所有异步任务必须显式 await 或 return。",
|
|
28514
|
+
"执行脚本时 method 只能是 GET 或 POST;MCP 认证、Cookie、CSRF、代理来源头和测试工具标识会被过滤。不要使用删除脚本、紧急恢复解锁或任意 HTTP 代理能力,本 MCP 不提供这些工具。"
|
|
28515
|
+
].join(`
|
|
28516
|
+
`);
|
|
28305
28517
|
function encodedScriptId(scriptId) {
|
|
28306
28518
|
return encodeURIComponent(scriptId);
|
|
28307
28519
|
}
|
|
28520
|
+
function scriptUrl(api2, scriptId) {
|
|
28521
|
+
return new URL(`/flow/codeblock/${encodedScriptId(scriptId)}`, api2.baseUrl).toString();
|
|
28522
|
+
}
|
|
28523
|
+
function executionUrl(api2) {
|
|
28524
|
+
return new URL("/flow/codeblock", api2.baseUrl).toString();
|
|
28525
|
+
}
|
|
28526
|
+
function withScriptUrl(api2, payload, fallbackScriptId) {
|
|
28527
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
28528
|
+
return payload;
|
|
28529
|
+
const data = responseData(payload);
|
|
28530
|
+
const scriptId = typeof data.script_id === "string" && data.script_id.length > 0 ? data.script_id : fallbackScriptId;
|
|
28531
|
+
if (!scriptId)
|
|
28532
|
+
return payload;
|
|
28533
|
+
return {
|
|
28534
|
+
...payload,
|
|
28535
|
+
data: {
|
|
28536
|
+
...data,
|
|
28537
|
+
script_url: scriptUrl(api2, scriptId)
|
|
28538
|
+
}
|
|
28539
|
+
};
|
|
28540
|
+
}
|
|
28308
28541
|
function encodeCode(code, codeBase64) {
|
|
28309
28542
|
if (code === undefined && codeBase64 === undefined)
|
|
28310
28543
|
return;
|
|
@@ -28379,30 +28612,40 @@ function withApiErrors(handler) {
|
|
|
28379
28612
|
};
|
|
28380
28613
|
}
|
|
28381
28614
|
var documentationFields = {
|
|
28382
|
-
document: exports_external.unknown().optional(),
|
|
28383
|
-
raw_document: exports_external.string().optional(),
|
|
28384
|
-
format: exports_external.literal("json").optional()
|
|
28615
|
+
document: exports_external.unknown().optional().describe(`规范化的 script-interface-doc.v1 JSON 对象。与 raw_document、document_patch 三选一;保存或代码更新时必须完整。${interfaceDocInputDescription}`),
|
|
28616
|
+
raw_document: exports_external.string().optional().describe("待服务端解析的 JSON/OpenAPI 文档文本。与 document、document_patch 三选一;format=json 时按 JSON 解析。"),
|
|
28617
|
+
format: exports_external.literal("json").optional().describe("raw_document 的格式,目前只支持 json。"),
|
|
28618
|
+
document_patch: interfaceDocPatchSchema.optional().describe("仅已有脚本使用的 RFC 6902 增量补丁;与 document、raw_document 三选一。"),
|
|
28619
|
+
expected_version: exports_external.number().int().positive().optional().describe("补丁的当前脚本版本;提交补丁时必填,必须来自刚读取的当前版本。")
|
|
28385
28620
|
};
|
|
28386
28621
|
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(),
|
|
28622
|
+
operation: exports_external.enum(["create", "update"]).describe("create 创建脚本;update 更新已有脚本。"),
|
|
28623
|
+
script_id: exports_external.string().min(1).optional().describe("更新目标脚本 ID。create 不得传入。"),
|
|
28624
|
+
code: exports_external.string().optional().describe("UTF-8 JavaScript 源码,与 code_base64 二选一。创建或修改代码时必填;代码只包含可执行 JavaScript。"),
|
|
28625
|
+
code_base64: exports_external.string().optional().describe("JavaScript 源码的非空 Base64,与 code 二选一。"),
|
|
28626
|
+
description: exports_external.string().optional().describe("脚本说明。可在不改代码时单独更新。"),
|
|
28627
|
+
ip_whitelist: exports_external.array(exports_external.string()).nullable().optional().describe("来源 IP/CIDR 白名单。省略表示更新时保持原值;null 或 [] 表示清除限制。"),
|
|
28393
28628
|
interface_doc: exports_external.unknown().optional().describe(interfaceDocInputDescription),
|
|
28394
|
-
|
|
28395
|
-
|
|
28629
|
+
interface_doc_patch: interfaceDocPatchSchema.optional().describe("仅 update 使用的 RFC 6902 JSON Patch;与完整 interface_doc 互斥,create 禁止使用。"),
|
|
28630
|
+
rollback_to_version: exports_external.number().int().positive().optional().describe("回滚到的历史版本号。只能与单独的 update 操作使用,不能和 code、interface_doc 或 interface_doc_patch 同时传入。"),
|
|
28631
|
+
expected_version: exports_external.number().int().positive().optional().describe("更新时必填的当前版本号。必须来自刚读取的 flow_get_script,用于并发冲突保护;create 不得传入。")
|
|
28396
28632
|
});
|
|
28397
28633
|
var documentationSchema = exports_external.object({
|
|
28398
|
-
script_id: exports_external.string().min(1),
|
|
28634
|
+
script_id: exports_external.string().min(1).describe("目标脚本 ID,不是完整 URL;校验或预览现有脚本的文档。"),
|
|
28399
28635
|
...documentationFields
|
|
28400
28636
|
}).superRefine((input, context) => {
|
|
28401
|
-
|
|
28402
|
-
|
|
28637
|
+
const supplied = [input.document, input.raw_document, input.document_patch].filter((value) => value !== undefined).length;
|
|
28638
|
+
if (supplied !== 1) {
|
|
28639
|
+
context.addIssue({ code: "custom", message: "Provide exactly one of document, raw_document, or document_patch" });
|
|
28640
|
+
}
|
|
28641
|
+
if (input.document_patch !== undefined && input.expected_version === undefined) {
|
|
28642
|
+
context.addIssue({ code: "custom", message: "document_patch requires expected_version" });
|
|
28403
28643
|
}
|
|
28404
28644
|
});
|
|
28405
28645
|
function documentationBody(input) {
|
|
28646
|
+
if (input.document_patch !== undefined) {
|
|
28647
|
+
return { document_patch: input.document_patch, expected_version: input.expected_version };
|
|
28648
|
+
}
|
|
28406
28649
|
if (input.raw_document !== undefined) {
|
|
28407
28650
|
return { raw_document: input.raw_document, ...input.format ? { format: input.format } : {} };
|
|
28408
28651
|
}
|
|
@@ -28422,6 +28665,9 @@ function assertScriptChangeInput(input) {
|
|
|
28422
28665
|
}
|
|
28423
28666
|
if (!hasCode)
|
|
28424
28667
|
throw new Error("Create preview requires code or code_base64");
|
|
28668
|
+
if (input.interface_doc_patch !== undefined) {
|
|
28669
|
+
throw new Error("Create preview cannot use interface_doc_patch; submit a complete interface_doc");
|
|
28670
|
+
}
|
|
28425
28671
|
if (input.interface_doc === undefined) {
|
|
28426
28672
|
throw new Error("Create preview requires a complete interface_doc");
|
|
28427
28673
|
}
|
|
@@ -28432,34 +28678,64 @@ function assertScriptChangeInput(input) {
|
|
|
28432
28678
|
if (!input.script_id || input.expected_version === undefined) {
|
|
28433
28679
|
throw new Error("Update preview requires script_id and expected_version");
|
|
28434
28680
|
}
|
|
28435
|
-
if (!hasCode && input.description === undefined && input.ip_whitelist === undefined && input.interface_doc === undefined && input.rollback_to_version === undefined) {
|
|
28436
|
-
throw new Error("Update preview requires code, description, ip_whitelist, interface_doc, or rollback_to_version");
|
|
28681
|
+
if (!hasCode && input.description === undefined && input.ip_whitelist === undefined && input.interface_doc === undefined && input.interface_doc_patch === undefined && input.rollback_to_version === undefined) {
|
|
28682
|
+
throw new Error("Update preview requires code, description, ip_whitelist, interface_doc, interface_doc_patch, or rollback_to_version");
|
|
28437
28683
|
}
|
|
28438
|
-
if (hasCode && input.interface_doc === undefined) {
|
|
28439
|
-
throw new Error("Updating code requires a complete interface_doc");
|
|
28684
|
+
if (hasCode && input.interface_doc === undefined && input.interface_doc_patch === undefined) {
|
|
28685
|
+
throw new Error("Updating code requires a complete interface_doc or interface_doc_patch");
|
|
28440
28686
|
}
|
|
28441
28687
|
}
|
|
28442
|
-
if (input.
|
|
28443
|
-
throw new Error("
|
|
28688
|
+
if (input.interface_doc !== undefined && input.interface_doc_patch !== undefined) {
|
|
28689
|
+
throw new Error("interface_doc and interface_doc_patch are mutually exclusive");
|
|
28690
|
+
}
|
|
28691
|
+
if (input.rollback_to_version !== undefined && (hasCode || input.interface_doc !== undefined || input.interface_doc_patch !== undefined)) {
|
|
28692
|
+
throw new Error("rollback_to_version cannot be combined with code, interface_doc, or interface_doc_patch");
|
|
28444
28693
|
}
|
|
28445
28694
|
if (input.interface_doc !== undefined) {
|
|
28446
28695
|
assertCompleteInterfaceDoc(input.interface_doc, input.operation);
|
|
28447
28696
|
}
|
|
28697
|
+
if (input.interface_doc_patch !== undefined)
|
|
28698
|
+
assertInterfaceDocPatch(input.interface_doc_patch);
|
|
28448
28699
|
}
|
|
28449
28700
|
async function validateScriptChange(api2, operation, payload) {
|
|
28450
28701
|
const hasCode = typeof payload.code_base64 === "string";
|
|
28451
28702
|
const hasDocument = payload.interface_doc !== undefined && payload.interface_doc !== null;
|
|
28703
|
+
const hasPatch = payload.interface_doc_patch !== undefined;
|
|
28452
28704
|
const hasWhitelist = payload.ip_whitelist !== undefined;
|
|
28453
|
-
if (!hasCode && !hasDocument && !hasWhitelist) {
|
|
28705
|
+
if (!hasCode && !hasDocument && !hasPatch && !hasWhitelist) {
|
|
28454
28706
|
return { success: true, data: { valid: true, warnings: [] } };
|
|
28455
28707
|
}
|
|
28456
28708
|
return api2.post("/flow/scripts/validate", {
|
|
28457
28709
|
...hasCode ? { code_base64: payload.code_base64 } : {},
|
|
28458
28710
|
...operation === "update" ? { script_id: payload.script_id } : {},
|
|
28711
|
+
...hasPatch ? { expected_version: payload.expected_version } : {},
|
|
28459
28712
|
...hasWhitelist ? { ip_whitelist: payload.ip_whitelist } : {},
|
|
28460
|
-
...hasDocument ? { interface_doc: payload.interface_doc } : {}
|
|
28713
|
+
...hasDocument ? { interface_doc: payload.interface_doc } : {},
|
|
28714
|
+
...hasPatch ? { interface_doc_patch: payload.interface_doc_patch } : {}
|
|
28461
28715
|
});
|
|
28462
28716
|
}
|
|
28717
|
+
function compactPatchValidation(validation, patch, currentVersion2) {
|
|
28718
|
+
const data = responseData(validation);
|
|
28719
|
+
const operations = Array.isArray(patch) ? patch : [];
|
|
28720
|
+
return {
|
|
28721
|
+
valid: data.valid ?? true,
|
|
28722
|
+
warnings: Array.isArray(data.warnings) ? data.warnings : [],
|
|
28723
|
+
current_version: currentVersion2,
|
|
28724
|
+
interface_doc_patch: {
|
|
28725
|
+
operation_count: operations.length,
|
|
28726
|
+
paths: operations.map((operation) => {
|
|
28727
|
+
if (!operation || typeof operation !== "object")
|
|
28728
|
+
return null;
|
|
28729
|
+
const value = operation;
|
|
28730
|
+
return {
|
|
28731
|
+
op: typeof value.op === "string" ? value.op : "unknown",
|
|
28732
|
+
path: typeof value.path === "string" ? value.path : "",
|
|
28733
|
+
...typeof value.from === "string" ? { from: value.from } : {}
|
|
28734
|
+
};
|
|
28735
|
+
})
|
|
28736
|
+
}
|
|
28737
|
+
};
|
|
28738
|
+
}
|
|
28463
28739
|
function assertPreview(record3, operation) {
|
|
28464
28740
|
if (!record3 || record3.operation !== operation)
|
|
28465
28741
|
throw new Error("Preview is missing, expired, or has the wrong operation");
|
|
@@ -28469,26 +28745,37 @@ function assertPreview(record3, operation) {
|
|
|
28469
28745
|
return record3;
|
|
28470
28746
|
}
|
|
28471
28747
|
function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
28472
|
-
const server = new McpServer({ name: "flow-codeblock-rust", version: "0.1.
|
|
28748
|
+
const server = new McpServer({ name: "flow-codeblock-rust", version: "0.1.2" }, { instructions: serverInstructions });
|
|
28473
28749
|
server.registerTool("flow_write_code", {
|
|
28474
|
-
description: "
|
|
28750
|
+
description: "生成代码实现契约,不写数据库、不发布脚本、不执行代码。mode=non_script 生成即时 POST /flow/codeblock 代码,最终交付包含完整 execution_url;mode=script 生成 GET/POST /flow/codeblock/{{script_id}} 代码,内部生成完整 script-interface-doc.v1 供预览、校验和发布,最终交付默认只展示接口调用说明、请求参数及示例、执行逻辑、成功/错误输出示例和 script_url,除非用户明确索要源码或原始 interface_doc。脚本文档的 title、summary、endpoint.methods、endpoint.description、request、responses、logic_description 必须齐全;query/header 参数必须有 name/type/description/example,body 和 response 字段必须有 content_type/schema/example。需要展示用户指定的完整地址时可传入 base_url。",
|
|
28475
28751
|
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()
|
|
28752
|
+
mode: exports_external.enum(["non_script", "script"]).describe("执行模式:non_script 为即时接口;script 为按脚本 ID 发布的 GET/POST 接口。未明确要求重定向时使用 non_script。"),
|
|
28753
|
+
requirement: exports_external.string().min(1).max(20000).describe("用户的业务需求、输入字段、同步/异步要求和错误行为。只写与本次代码有关的需求。"),
|
|
28754
|
+
input_example: exports_external.unknown().optional().describe("业务输入示例。脚本模式下用于生成 request.body/schema/example;非脚本模式下用于 flow_execute_code 测试参数。不要放真实凭据。"),
|
|
28755
|
+
include_full_schema: exports_external.boolean().optional().describe("脚本模式是否在结果中附带完整 JSON Schema;默认 false。接口文档实例仍必须按必填字段生成。"),
|
|
28756
|
+
base_url: exports_external.string().url().refine((value) => {
|
|
28757
|
+
try {
|
|
28758
|
+
const parsed = new URL(value);
|
|
28759
|
+
return (parsed.protocol === "http:" || parsed.protocol === "https:") && !parsed.username && !parsed.password && !parsed.search && !parsed.hash && !/[\u0000-\u001f\u007f]/.test(value);
|
|
28760
|
+
} catch {
|
|
28761
|
+
return false;
|
|
28762
|
+
}
|
|
28763
|
+
}, "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
28764
|
}
|
|
28481
|
-
}, async ({ mode, requirement, input_example, include_full_schema }) =>
|
|
28765
|
+
}, async ({ mode, requirement, input_example, include_full_schema, base_url }) => {
|
|
28766
|
+
const context = codeWriterContext(mode, requirement, input_example, include_full_schema ?? false, base_url);
|
|
28767
|
+
return result(mode === "non_script" ? { ...context, execution_url: executionUrl(api2) } : context);
|
|
28768
|
+
});
|
|
28482
28769
|
server.registerTool("flow_list_scripts", {
|
|
28483
|
-
description: "
|
|
28770
|
+
description: "只读分页查询脚本。默认使用 page/size 偏移分页;需要连续遍历时使用 pagination=cursor,首次不传 cursor,后续使用响应中的游标。不会创建、更新或执行脚本。",
|
|
28484
28771
|
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()
|
|
28772
|
+
page: exports_external.number().int().positive().optional().describe("偏移分页页码,从 1 开始;使用游标分页时省略。"),
|
|
28773
|
+
size: exports_external.number().int().min(1).max(100).optional().describe("每页数量,1-100,默认由服务端决定。"),
|
|
28774
|
+
keyword: exports_external.string().optional().describe("按脚本关键词筛选。"),
|
|
28775
|
+
sort: exports_external.enum(["updated_at", "created_at", "code_length", "version"]).optional().describe("排序字段。游标分页只支持 updated_at、created_at、code_length。"),
|
|
28776
|
+
order: exports_external.enum(["asc", "desc"]).optional().describe("排序方向,默认由服务端决定。"),
|
|
28777
|
+
pagination: exports_external.literal("cursor").optional().describe("传 cursor 启用游标分页;首次调用不要传 cursor。"),
|
|
28778
|
+
cursor: exports_external.string().optional().describe("上一页返回的游标,仅与 pagination=cursor 一起使用。")
|
|
28492
28779
|
}
|
|
28493
28780
|
}, withApiErrors(async ({ page, size, keyword, sort, order, pagination, cursor }) => {
|
|
28494
28781
|
const query = new URLSearchParams;
|
|
@@ -28509,36 +28796,48 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28509
28796
|
return result(await api2.get(`/flow/scripts${query.size ? `?${query}` : ""}`));
|
|
28510
28797
|
}));
|
|
28511
28798
|
server.registerTool("flow_get_script", {
|
|
28512
|
-
description: "
|
|
28513
|
-
inputSchema: {
|
|
28799
|
+
description: "只读读取脚本代码、元数据和版本信息。更新前必须先调用本工具并使用返回的 current_version 作为 flow_preview_script_change.expected_version。",
|
|
28800
|
+
inputSchema: {
|
|
28801
|
+
script_id: exports_external.string().min(1).describe("脚本 ID,不是完整 URL。"),
|
|
28802
|
+
version: exports_external.number().int().min(0).optional().describe("可选历史版本号;省略时读取当前版本。")
|
|
28803
|
+
}
|
|
28514
28804
|
}, withApiErrors(async ({ script_id, version: version2 }) => {
|
|
28515
28805
|
const query = version2 === undefined ? "" : `?version=${encodeURIComponent(String(version2))}`;
|
|
28516
28806
|
return result(await api2.get(`/flow/scripts/${encodedScriptId(script_id)}${query}`));
|
|
28517
28807
|
}));
|
|
28518
28808
|
server.registerTool("flow_get_script_documentation", {
|
|
28519
|
-
description: "
|
|
28520
|
-
inputSchema: {
|
|
28809
|
+
description: "只读读取当前或指定历史版本的 script-interface-doc.v1 接口文档。修改文档前先读取当前版本并保留版本号。",
|
|
28810
|
+
inputSchema: {
|
|
28811
|
+
script_id: exports_external.string().min(1).describe("脚本 ID,不是完整 URL。"),
|
|
28812
|
+
version: exports_external.number().int().min(0).optional().describe("可选历史版本号;省略时读取当前文档。")
|
|
28813
|
+
}
|
|
28521
28814
|
}, withApiErrors(async ({ script_id, version: version2 }) => {
|
|
28522
28815
|
const query = version2 === undefined ? "" : `?version=${encodeURIComponent(String(version2))}`;
|
|
28523
28816
|
return result(await api2.get(`/flow/scripts/${encodedScriptId(script_id)}/documentation${query}`));
|
|
28524
28817
|
}));
|
|
28525
28818
|
server.registerTool("flow_validate_script_documentation", {
|
|
28526
|
-
description: "
|
|
28819
|
+
description: "只读校验并规范化指定脚本的接口文档,不写入数据库。完整 document、raw_document 和 RFC 6902 document_patch 三选一;补丁必须带 expected_version,响应不会用于发布确认。",
|
|
28527
28820
|
inputSchema: documentationSchema.shape
|
|
28528
28821
|
}, withApiErrors(async (input) => {
|
|
28529
28822
|
const parsed = documentationSchema.parse(input);
|
|
28530
28823
|
if (parsed.document !== undefined) {
|
|
28531
28824
|
assertCompleteInterfaceDoc(parsed.document, "update");
|
|
28532
28825
|
}
|
|
28533
|
-
|
|
28534
|
-
|
|
28826
|
+
if (parsed.document_patch !== undefined)
|
|
28827
|
+
assertInterfaceDocPatch(parsed.document_patch);
|
|
28828
|
+
const validation = parsed.document_patch !== undefined ? await api2.post("/flow/scripts/validate", {
|
|
28829
|
+
script_id: parsed.script_id,
|
|
28830
|
+
expected_version: parsed.expected_version,
|
|
28831
|
+
interface_doc_patch: parsed.document_patch
|
|
28832
|
+
}) : await api2.post(`/flow/scripts/${encodedScriptId(parsed.script_id)}/documentation`, documentationBody(parsed));
|
|
28833
|
+
const document = responseData(validation).document ?? responseData(validation).interface_doc;
|
|
28535
28834
|
if (document === undefined)
|
|
28536
28835
|
throw new Error("Documentation validation response did not include document");
|
|
28537
28836
|
assertCompleteInterfaceDoc(document, "update");
|
|
28538
28837
|
return result(validation);
|
|
28539
28838
|
}));
|
|
28540
28839
|
server.registerTool("flow_preview_script_change", {
|
|
28541
|
-
description: "
|
|
28840
|
+
description: "预览脚本创建或更新,不写数据库。create 必须传 code/code_base64 和完整 interface_doc,且不得传 script_id/expected_version;update 必须传 script_id 和刚读取的 expected_version,修改代码时必须同时传完整 interface_doc 或 interface_doc_patch。预览成功后只能在用户明确确认时调用 flow_apply_script_change(confirm=true)。",
|
|
28542
28841
|
inputSchema: changeSchema.shape
|
|
28543
28842
|
}, withApiErrors(async (input) => {
|
|
28544
28843
|
const parsed = changeSchema.parse(input);
|
|
@@ -28551,6 +28850,7 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28551
28850
|
...parsed.description !== undefined ? { description: parsed.description } : {},
|
|
28552
28851
|
...parsed.ip_whitelist !== undefined ? { ip_whitelist: parsed.ip_whitelist } : {},
|
|
28553
28852
|
...parsed.interface_doc !== undefined ? { interface_doc: parsed.operation === "create" ? removeCreatePath(parsed.interface_doc) : parsed.interface_doc } : {},
|
|
28853
|
+
...parsed.interface_doc_patch !== undefined ? { interface_doc_patch: parsed.interface_doc_patch } : {},
|
|
28554
28854
|
...parsed.rollback_to_version !== undefined ? { rollback_to_version: parsed.rollback_to_version } : {}
|
|
28555
28855
|
};
|
|
28556
28856
|
const expectedVersion = parsed.operation === "update" ? await fetchCurrentVersion(api2, parsed.script_id) : undefined;
|
|
@@ -28559,8 +28859,9 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28559
28859
|
}
|
|
28560
28860
|
const validation = await validateScriptChange(api2, parsed.operation, payload);
|
|
28561
28861
|
const normalized = responseData(validation).interface_doc;
|
|
28562
|
-
if (normalized !== undefined)
|
|
28862
|
+
if (parsed.interface_doc !== undefined && normalized !== undefined)
|
|
28563
28863
|
payload.interface_doc = normalized;
|
|
28864
|
+
const previewValidation = parsed.interface_doc_patch !== undefined ? compactPatchValidation(validation, parsed.interface_doc_patch, expectedVersion) : { valid: true, server_validation: validation };
|
|
28564
28865
|
const previewId = randomUUID();
|
|
28565
28866
|
const storedPreview = previews.put(previewId, "script_change", payload, expectedVersion);
|
|
28566
28867
|
return result({
|
|
@@ -28569,21 +28870,24 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28569
28870
|
expected_version: expectedVersion,
|
|
28570
28871
|
expires_at: new Date(storedPreview.expiresAt).toISOString(),
|
|
28571
28872
|
validation: {
|
|
28572
|
-
|
|
28573
|
-
server_validation: validation
|
|
28873
|
+
...previewValidation
|
|
28574
28874
|
},
|
|
28575
28875
|
changes: {
|
|
28576
28876
|
code: codeBase64 !== undefined,
|
|
28577
28877
|
description: parsed.description !== undefined,
|
|
28578
28878
|
ip_whitelist: parsed.ip_whitelist !== undefined,
|
|
28579
28879
|
interface_doc: parsed.interface_doc !== undefined,
|
|
28880
|
+
interface_doc_patch: parsed.interface_doc_patch !== undefined,
|
|
28580
28881
|
rollback_to_version: parsed.rollback_to_version !== undefined
|
|
28581
28882
|
}
|
|
28582
28883
|
});
|
|
28583
28884
|
}));
|
|
28584
28885
|
server.registerTool("flow_apply_script_change", {
|
|
28585
|
-
description: "
|
|
28586
|
-
inputSchema: {
|
|
28886
|
+
description: "应用 flow_preview_script_change 返回的一次性 preview_id。必须传 confirm=true;工具会再次检查版本并在成功或失败后销毁预览。发布成功时返回由 FLOW_CODEBLOCK_BASE_URL 生成的完整 script_url。不支持删除脚本。版本冲突时重新读取并预览。",
|
|
28887
|
+
inputSchema: {
|
|
28888
|
+
preview_id: exports_external.string().uuid().describe("最近一次脚本变更预览返回的 UUID;不能复用过期或已应用的 ID。"),
|
|
28889
|
+
confirm: exports_external.literal(true).describe("必须为 true,表示用户已明确确认写入。")
|
|
28890
|
+
}
|
|
28587
28891
|
}, withApiErrors(async ({ preview_id }) => {
|
|
28588
28892
|
try {
|
|
28589
28893
|
const record3 = assertPreview(previews.get(preview_id), "script_change");
|
|
@@ -28595,45 +28899,60 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28595
28899
|
if (actualVersion !== record3.expectedVersion) {
|
|
28596
28900
|
throw new Error(`Script version changed from ${record3.expectedVersion} to ${actualVersion}; read and preview again`);
|
|
28597
28901
|
}
|
|
28902
|
+
if (payload.interface_doc_patch !== undefined) {
|
|
28903
|
+
await validateScriptChange(api2, "update", payload);
|
|
28904
|
+
}
|
|
28598
28905
|
const body = { ...payload };
|
|
28599
28906
|
delete body.script_id;
|
|
28600
28907
|
response = await api2.put(`/flow/scripts/${encodedScriptId(scriptId)}`, body);
|
|
28601
28908
|
} else {
|
|
28602
28909
|
response = await api2.post("/flow/scripts", payload);
|
|
28603
28910
|
}
|
|
28604
|
-
return result(response);
|
|
28911
|
+
return result(withScriptUrl(api2, response, scriptId));
|
|
28605
28912
|
} finally {
|
|
28606
28913
|
previews.delete(preview_id);
|
|
28607
28914
|
}
|
|
28608
28915
|
}));
|
|
28609
28916
|
server.registerTool("flow_preview_script_documentation", {
|
|
28610
|
-
description: "
|
|
28917
|
+
description: "预览接口文档保存,不写数据库。先读取脚本当前版本;document、raw_document 和 document_patch 三选一,补丁必须带 expected_version,完整文档必须包含所有强制字段。成功后只有 flow_apply_script_documentation(confirm=true) 才会写入。",
|
|
28611
28918
|
inputSchema: documentationSchema.shape
|
|
28612
28919
|
}, withApiErrors(async (input) => {
|
|
28613
28920
|
const parsed = documentationSchema.parse(input);
|
|
28614
28921
|
const expectedVersion = await fetchCurrentVersion(api2, parsed.script_id);
|
|
28922
|
+
if (parsed.document_patch !== undefined && parsed.expected_version !== expectedVersion) {
|
|
28923
|
+
throw new Error(`Script version changed from ${parsed.expected_version} to ${expectedVersion}; read and preview again`);
|
|
28924
|
+
}
|
|
28615
28925
|
const rawBody = documentationBody(parsed);
|
|
28616
|
-
const validation = parsed.
|
|
28926
|
+
const validation = parsed.document_patch !== undefined ? await api2.post("/flow/scripts/validate", {
|
|
28927
|
+
script_id: parsed.script_id,
|
|
28928
|
+
expected_version: parsed.expected_version,
|
|
28929
|
+
interface_doc_patch: parsed.document_patch
|
|
28930
|
+
}) : parsed.raw_document !== undefined ? await api2.post(`/flow/scripts/${encodedScriptId(parsed.script_id)}/documentation`, rawBody) : await api2.post("/flow/scripts/validate", {
|
|
28617
28931
|
script_id: parsed.script_id,
|
|
28618
28932
|
interface_doc: rawBody.document
|
|
28619
28933
|
});
|
|
28620
28934
|
const document = responseData(validation).interface_doc ?? responseData(validation).document;
|
|
28621
|
-
if (
|
|
28622
|
-
|
|
28623
|
-
|
|
28935
|
+
if (parsed.document_patch === undefined) {
|
|
28936
|
+
if (document === undefined)
|
|
28937
|
+
throw new Error("Documentation validation response did not include document");
|
|
28938
|
+
assertCompleteInterfaceDoc(document, "update");
|
|
28939
|
+
}
|
|
28624
28940
|
const previewId = randomUUID();
|
|
28625
|
-
const storedPreview = previews.put(previewId, "documentation", { script_id: parsed.script_id, document }, expectedVersion);
|
|
28941
|
+
const storedPreview = previews.put(previewId, "documentation", parsed.document_patch !== undefined ? { script_id: parsed.script_id, expected_version: expectedVersion, document_patch: parsed.document_patch } : { script_id: parsed.script_id, document }, expectedVersion);
|
|
28626
28942
|
return result({
|
|
28627
28943
|
preview_id: previewId,
|
|
28628
28944
|
script_id: parsed.script_id,
|
|
28629
28945
|
expected_version: expectedVersion,
|
|
28630
28946
|
expires_at: new Date(storedPreview.expiresAt).toISOString(),
|
|
28631
|
-
validation: responseData(validation)
|
|
28947
|
+
validation: parsed.document_patch !== undefined ? compactPatchValidation(validation, parsed.document_patch, expectedVersion) : responseData(validation)
|
|
28632
28948
|
});
|
|
28633
28949
|
}));
|
|
28634
28950
|
server.registerTool("flow_apply_script_documentation", {
|
|
28635
|
-
description: "
|
|
28636
|
-
inputSchema: {
|
|
28951
|
+
description: "应用 flow_preview_script_documentation 返回的一次性预览。必须传 confirm=true;工具会再次检查脚本版本并在成功或失败后销毁预览。发布成功时返回完整 script_url。版本冲突时重新读取、预览。",
|
|
28952
|
+
inputSchema: {
|
|
28953
|
+
preview_id: exports_external.string().uuid().describe("最近一次接口文档预览返回的 UUID;不能复用过期或已应用的 ID。"),
|
|
28954
|
+
confirm: exports_external.literal(true).describe("必须为 true,表示用户已明确确认写入。")
|
|
28955
|
+
}
|
|
28637
28956
|
}, withApiErrors(async ({ preview_id }) => {
|
|
28638
28957
|
try {
|
|
28639
28958
|
const record3 = assertPreview(previews.get(preview_id), "documentation");
|
|
@@ -28642,33 +28961,42 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28642
28961
|
if (actualVersion !== record3.expectedVersion) {
|
|
28643
28962
|
throw new Error(`Script version changed from ${record3.expectedVersion} to ${actualVersion}; read and preview again`);
|
|
28644
28963
|
}
|
|
28964
|
+
const patch = record3.payload.document_patch;
|
|
28965
|
+
if (patch !== undefined) {
|
|
28966
|
+
assertInterfaceDocPatch(patch);
|
|
28967
|
+
await api2.post("/flow/scripts/validate", {
|
|
28968
|
+
script_id: scriptId,
|
|
28969
|
+
expected_version: record3.expectedVersion,
|
|
28970
|
+
interface_doc_patch: patch
|
|
28971
|
+
});
|
|
28972
|
+
}
|
|
28645
28973
|
const response = await api2.put(`/flow/scripts/${encodedScriptId(scriptId)}/documentation`, {
|
|
28646
|
-
document: record3.payload.document,
|
|
28974
|
+
...patch !== undefined ? { document_patch: patch } : { document: record3.payload.document },
|
|
28647
28975
|
expected_version: record3.expectedVersion
|
|
28648
28976
|
});
|
|
28649
|
-
return result(response);
|
|
28977
|
+
return result(withScriptUrl(api2, response, scriptId));
|
|
28650
28978
|
} finally {
|
|
28651
28979
|
previews.delete(preview_id);
|
|
28652
28980
|
}
|
|
28653
28981
|
}));
|
|
28654
28982
|
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)
|
|
28983
|
+
script_id: exports_external.string().min(1).describe("要锁定或解锁的脚本 ID。"),
|
|
28984
|
+
owner_name: exports_external.string().min(1).describe("锁定时设置、解锁时核对的所有者名称。"),
|
|
28985
|
+
lock_password: exports_external.string().min(6).describe("锁定口令;仅传给服务端,不会写入 MCP 预览或日志。"),
|
|
28986
|
+
confirm: exports_external.literal(true).describe("必须为 true,表示用户明确确认锁定或解锁。")
|
|
28659
28987
|
};
|
|
28660
|
-
server.registerTool("flow_lock_script", { description: "
|
|
28661
|
-
server.registerTool("flow_unlock_script", { description: "
|
|
28988
|
+
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 }))));
|
|
28989
|
+
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
28990
|
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
28991
|
server.registerTool("flow_execute_script", {
|
|
28664
|
-
description: "
|
|
28992
|
+
description: "执行已发布脚本,仅用于用户明确要求测试或调用时。method 只能是 GET/POST;结果包含由 FLOW_CODEBLOCK_BASE_URL 和 script_id 生成的完整 script_url。query 数组会生成重复参数,POST 的 body 作为 JSON 发送。MCP 认证、Cookie、CSRF、代理来源头和测试工具标识会被过滤,qingcodeToken/qingcodeTimeout 不能作为业务参数传入。",
|
|
28665
28993
|
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()
|
|
28994
|
+
script_id: exports_external.string().min(1).describe("已发布脚本 ID;工具会调用 /flow/codeblock/{script_id}。"),
|
|
28995
|
+
method: exports_external.enum(["GET", "POST"]).default("POST").describe("脚本请求方法,只能是 GET 或 POST;默认 POST。"),
|
|
28996
|
+
query: exports_external.record(exports_external.string(), queryValueSchema).optional().describe("业务查询参数。值可为 string/number/boolean 或其数组;数组生成重复 query 参数。不要传 qingcodeToken/qingcodeTimeout。"),
|
|
28997
|
+
headers: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("业务请求头。认证、Cookie、CSRF、Forwarded、X-Real-IP 等保留头会被过滤。"),
|
|
28998
|
+
body: exports_external.unknown().optional().describe("POST JSON 请求体;GET 不发送 body。不要把脚本模式业务 body 包装为 input 或 input.body。"),
|
|
28999
|
+
timeout_ms: exports_external.number().int().positive().optional().describe("执行超时毫秒数;只能通过此字段配置,不能在 query 中传 qingcodeTimeout。")
|
|
28672
29000
|
}
|
|
28673
29001
|
}, withApiErrors(async ({ script_id, method, query, headers, body, timeout_ms }) => {
|
|
28674
29002
|
const url2 = new URL(`/flow/codeblock/${encodedScriptId(script_id)}`, api2.baseUrl);
|
|
@@ -28694,15 +29022,15 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28694
29022
|
authenticated: false,
|
|
28695
29023
|
execution: true
|
|
28696
29024
|
});
|
|
28697
|
-
return result({ response });
|
|
29025
|
+
return result({ script_url: scriptUrl(api2, script_id), method, response });
|
|
28698
29026
|
}));
|
|
28699
29027
|
server.registerTool("flow_execute_code", {
|
|
28700
|
-
description: "执行未发布的非脚本 JavaScript
|
|
29028
|
+
description: "执行未发布的非脚本 JavaScript,仅在用户明确要求测试时使用。结果包含完整 execution_url。请求固定为 POST /flow/codeblock,body.input 原样注入全局 input;代码和 code_base64 二选一。不会把 MCP 认证信息写入用户脚本输入,也不会创建或发布脚本。",
|
|
28701
29029
|
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()
|
|
29030
|
+
code: exports_external.string().min(1).optional().describe("UTF-8 JavaScript 源码,与 code_base64 二选一。"),
|
|
29031
|
+
code_base64: exports_external.string().min(1).optional().describe("JavaScript 源码的非空 Base64,与 code 二选一。"),
|
|
29032
|
+
input: exports_external.unknown().optional().describe("注入全局 input 的业务数据,默认 {}。不要放 Token、密码、Cookie 或 Authorization 值。"),
|
|
29033
|
+
timeout_ms: exports_external.number().int().positive().optional().describe("本次测试执行超时毫秒数;必须在服务端允许的最小/最大范围内。")
|
|
28706
29034
|
}
|
|
28707
29035
|
}, withApiErrors(async ({ code, code_base64, input, timeout_ms }) => {
|
|
28708
29036
|
const codeBase64 = encodeCode(code, code_base64);
|
|
@@ -28713,6 +29041,7 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28713
29041
|
};
|
|
28714
29042
|
return result({
|
|
28715
29043
|
mode: "non_script",
|
|
29044
|
+
execution_url: executionUrl(api2),
|
|
28716
29045
|
response: await api2.request("/flow/codeblock", {
|
|
28717
29046
|
method: "POST",
|
|
28718
29047
|
body: JSON.stringify(body),
|