flow-codeblock-rust-mcp 0.1.8 → 0.1.10
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 +6 -2
- package/dist/index.js +203 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Local stdio MCP server for Flow Codeblock Rust+Bun. It calls the server-side Rus
|
|
|
7
7
|
Bun 1.4.0 or newer is required:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
bunx --bun flow-codeblock-rust-mcp@0.1.
|
|
10
|
+
bunx --bun flow-codeblock-rust-mcp@0.1.10
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Configure the environment:
|
|
@@ -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.10"],
|
|
30
30
|
"env": {
|
|
31
31
|
"FLOW_CODEBLOCK_BASE_URL": "https://flow.example.com",
|
|
32
32
|
"FLOW_CODEBLOCK_TOKEN": "<YOUR_INTERNAL_ACCESS_TOKEN>"
|
|
@@ -38,6 +38,8 @@ export FLOW_CODEBLOCK_TOKEN='<YOUR_INTERNAL_ACCESS_TOKEN>'
|
|
|
38
38
|
|
|
39
39
|
## Tool boundaries
|
|
40
40
|
|
|
41
|
+
Generated code treats `input` as a reserved, read-only runtime binding: never declare, rebind, or shadow it in any scope; use an alias such as `const payload = input` when a local name is needed. Review the complete source for input shadowing before every execution and retry.
|
|
42
|
+
|
|
41
43
|
The code contract follows the current Rust+Bun module allowlist. `crypto-js` has been removed; use `node:crypto` for cryptography. Excel imports are limited to `read-excel-file/node`, `read-excel-file/universal`, `write-excel-file/node`, `write-excel-file/universal`, and `write-excel-file/utility`; these modules run in the server's shared heavy execution pool.
|
|
42
44
|
|
|
43
45
|
The tools cover code-contract generation, unpublished-code tests, script listing, version reads, documentation validation/preview/save, script creation/update, locking/unlocking, and published-script execution. Creates and code updates can submit a complete interface document or RFC 6902 `interface_doc_patch`; preview calls `/flow/scripts/validate`, and apply re-submits patches with transactional `expected_version` conflict detection. Every script write requires a preview and explicit `confirm: true`.
|
|
@@ -54,6 +56,8 @@ At initialization the server sends complete tool selection, preview/confirmation
|
|
|
54
56
|
|
|
55
57
|
Interface documents must include `schema_version`, `title`, `summary`, `endpoint`, `request`, `responses`, and `logic_description`. `endpoint` requires `methods` and `description`; `request.query` and `request.headers` must exist (use `[]` when empty). POST documents also require `request.body` with `content_type`, `schema`, and `example`. Every response requires `status`, `description`, `content_type`, `schema`, and `example`; every query/header parameter requires `name`, `type`, `required`, `description`, and `example`.
|
|
56
58
|
|
|
59
|
+
Every nested schema property, array item, and object-form `additionalProperties` node requires `type`, `description`, and `example`, with examples covering declared fields. MCP accepts one legacy JSON-text parse, fills examples only from matching parent examples, and uses a neutral description when one is omitted.
|
|
60
|
+
|
|
57
61
|
`endpoint.path` is relative: omit it when creating and use `/flow/codeblock/<actual-script-id>` when updating. Public request URLs combine the caller-provided service origin with `/flow/codeblock/{{script_id}}`. Never put real tokens, passwords, cookies, or Authorization values in code, documents, examples, or URLs.
|
|
58
62
|
|
|
59
63
|
`interface_doc_patch` and `document_patch` require a positive integer `expected_version` and support at most 256 `add/remove/replace/move/copy/test` operations. Patch previews return operation counts, JSON Pointer paths, warnings, and version information, never the complete merged document.
|
package/dist/index.js
CHANGED
|
@@ -27896,6 +27896,7 @@ var interfaceDocNestedRules = [
|
|
|
27896
27896
|
];
|
|
27897
27897
|
var interfaceDocInputDescription = [
|
|
27898
27898
|
"A complete script-interface-doc.v1 document is required when creating a script, changing code, or saving documentation.",
|
|
27899
|
+
"Submit interface_doc/document as a JSON object. Legacy JSON text is accepted and parsed once by MCP for compatibility. MCP fills nested examples only from matching parent examples, uses a neutral description when one is omitted, and never guesses a missing type.",
|
|
27899
27900
|
`Required structure: ${Object.entries(interfaceDocRequiredFields).map(([key, fields]) => `${key}=[${fields.join(", ")}]`).join("; ")}.`,
|
|
27900
27901
|
...interfaceDocNestedRules,
|
|
27901
27902
|
"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>.",
|
|
@@ -27934,6 +27935,73 @@ var interfaceDocPatchJsonSchema = {
|
|
|
27934
27935
|
]
|
|
27935
27936
|
}
|
|
27936
27937
|
};
|
|
27938
|
+
var schemaNodeInputSchema;
|
|
27939
|
+
schemaNodeInputSchema = exports_external.lazy(() => exports_external.looseObject({
|
|
27940
|
+
type: exports_external.string().min(1).describe("JSON Schema type such as object, array, string, integer, number, or boolean."),
|
|
27941
|
+
description: exports_external.string().min(1).describe("Description of this nested field or data structure."),
|
|
27942
|
+
example: exports_external.unknown().describe("A concrete value matching this nested schema node."),
|
|
27943
|
+
properties: exports_external.record(exports_external.string(), schemaNodeInputSchema).optional().describe("Map fixed object field names to child schema nodes."),
|
|
27944
|
+
items: schemaNodeInputSchema.optional().describe("Child schema node for each array item."),
|
|
27945
|
+
additionalProperties: exports_external.union([exports_external.boolean(), schemaNodeInputSchema]).optional().describe("Boolean or child schema for dynamic object keys."),
|
|
27946
|
+
required: exports_external.array(exports_external.string()).optional().describe("Runtime-required object field names.")
|
|
27947
|
+
}));
|
|
27948
|
+
var schemaRootInputSchema = exports_external.looseObject({
|
|
27949
|
+
type: exports_external.string().min(1).describe("JSON Schema type such as object, array, string, integer, number, or boolean."),
|
|
27950
|
+
description: exports_external.string().optional().describe("Description of the root data structure."),
|
|
27951
|
+
example: exports_external.unknown().optional().describe("Complete example matching this root schema."),
|
|
27952
|
+
properties: exports_external.record(exports_external.string(), schemaNodeInputSchema).optional().describe("Map fixed object field names to child schema nodes."),
|
|
27953
|
+
items: schemaNodeInputSchema.optional().describe("Child schema node for each array item."),
|
|
27954
|
+
additionalProperties: exports_external.union([exports_external.boolean(), schemaNodeInputSchema]).optional().describe("Boolean or child schema for dynamic object keys."),
|
|
27955
|
+
required: exports_external.array(exports_external.string()).optional().describe("Runtime-required object field names.")
|
|
27956
|
+
});
|
|
27957
|
+
var parameterInputSchema = exports_external.looseObject({
|
|
27958
|
+
name: exports_external.string().min(1).describe("Caller-facing query parameter or HTTP header name."),
|
|
27959
|
+
type: exports_external.enum(["string", "integer", "number", "boolean", "array", "object"]).describe("Parameter JSON type."),
|
|
27960
|
+
required: exports_external.boolean().describe("Whether the parameter is required at runtime."),
|
|
27961
|
+
description: exports_external.string().min(1).describe("Purpose and constraints of the parameter."),
|
|
27962
|
+
example: exports_external.unknown().describe("Concrete example value for the parameter."),
|
|
27963
|
+
default: exports_external.unknown().optional().describe("Optional default value."),
|
|
27964
|
+
format: exports_external.string().optional().describe("Optional format hint."),
|
|
27965
|
+
enum_values: exports_external.array(exports_external.unknown()).optional().describe("Optional allowed values.")
|
|
27966
|
+
});
|
|
27967
|
+
var interfaceDocToolInputObjectSchema = exports_external.looseObject({
|
|
27968
|
+
schema_version: exports_external.literal("script-interface-doc.v1").describe("Fixed document contract version."),
|
|
27969
|
+
title: exports_external.string().min(1).describe("Interface document title."),
|
|
27970
|
+
summary: exports_external.string().min(1).describe("One-sentence caller-facing summary."),
|
|
27971
|
+
endpoint: exports_external.looseObject({
|
|
27972
|
+
methods: exports_external.array(exports_external.enum(["GET", "POST"])).min(1).describe("HTTP methods supported by the endpoint."),
|
|
27973
|
+
path: exports_external.string().optional().describe("Actual /flow/codeblock/{script_id} path on update."),
|
|
27974
|
+
description: exports_external.string().min(1).describe("What the callable endpoint does.")
|
|
27975
|
+
}).describe("HTTP endpoint contract."),
|
|
27976
|
+
request: exports_external.looseObject({
|
|
27977
|
+
query: exports_external.array(parameterInputSchema).describe("Caller-facing URL query parameters; use [] when empty."),
|
|
27978
|
+
headers: exports_external.array(parameterInputSchema).describe("Caller-facing HTTP headers; use [] when empty."),
|
|
27979
|
+
body: exports_external.looseObject({
|
|
27980
|
+
content_type: exports_external.literal("application/json").describe("Must be application/json."),
|
|
27981
|
+
schema: schemaRootInputSchema.describe("Request body JSON Schema."),
|
|
27982
|
+
example: exports_external.unknown().describe("Complete request body example matching schema.")
|
|
27983
|
+
}).optional().describe("POST request body.")
|
|
27984
|
+
}).describe("Caller request contract."),
|
|
27985
|
+
responses: exports_external.array(exports_external.looseObject({
|
|
27986
|
+
status: exports_external.number().int().min(100).max(599).describe("HTTP status code from 100 through 599."),
|
|
27987
|
+
description: exports_external.string().min(1).describe("Business meaning of this response branch."),
|
|
27988
|
+
content_type: exports_external.literal("application/json").describe("Must be application/json."),
|
|
27989
|
+
schema: schemaRootInputSchema.describe("Response body JSON Schema."),
|
|
27990
|
+
example: exports_external.unknown().describe("Complete response example matching schema.")
|
|
27991
|
+
})).min(1).describe("Complete response branches at the document root."),
|
|
27992
|
+
logic_description: exports_external.string().min(20).describe("Endpoint processing logic; at least 20 characters."),
|
|
27993
|
+
usage_refs: exports_external.array(exports_external.unknown()).optional().describe("Real application references only.")
|
|
27994
|
+
});
|
|
27995
|
+
function parseLegacyJsonDocument(value) {
|
|
27996
|
+
if (typeof value !== "string")
|
|
27997
|
+
return value;
|
|
27998
|
+
try {
|
|
27999
|
+
return JSON.parse(value);
|
|
28000
|
+
} catch {
|
|
28001
|
+
return value;
|
|
28002
|
+
}
|
|
28003
|
+
}
|
|
28004
|
+
var interfaceDocToolInputSchema = exports_external.preprocess(parseLegacyJsonDocument, interfaceDocToolInputObjectSchema).describe(interfaceDocInputDescription);
|
|
27937
28005
|
function assertInterfaceDocPatch(patch) {
|
|
27938
28006
|
const parsed = interfaceDocPatchSchema.safeParse(patch);
|
|
27939
28007
|
if (!parsed.success)
|
|
@@ -27945,18 +28013,82 @@ function isObject2(value) {
|
|
|
27945
28013
|
function hasOwn(object4, key) {
|
|
27946
28014
|
return Object.prototype.hasOwnProperty.call(object4, key);
|
|
27947
28015
|
}
|
|
28016
|
+
function normalizeSchemaNode(schema, parentExample, fieldName, root = false) {
|
|
28017
|
+
if (!root) {
|
|
28018
|
+
if (typeof schema.description !== "string" || !schema.description.trim()) {
|
|
28019
|
+
schema.description = `Value for field "${fieldName}".`;
|
|
28020
|
+
}
|
|
28021
|
+
if (!hasOwn(schema, "example") && parentExample !== undefined) {
|
|
28022
|
+
schema.example = structuredClone(parentExample);
|
|
28023
|
+
}
|
|
28024
|
+
}
|
|
28025
|
+
const example = hasOwn(schema, "example") ? schema.example : parentExample;
|
|
28026
|
+
if (schema.type === "array") {
|
|
28027
|
+
if (isObject2(schema.items)) {
|
|
28028
|
+
const itemExample = Array.isArray(example) && example.length > 0 ? example[0] : undefined;
|
|
28029
|
+
normalizeSchemaNode(schema.items, itemExample, `${fieldName} item`);
|
|
28030
|
+
}
|
|
28031
|
+
return;
|
|
28032
|
+
}
|
|
28033
|
+
if (schema.type !== "object")
|
|
28034
|
+
return;
|
|
28035
|
+
const properties = isObject2(schema.properties) ? schema.properties : undefined;
|
|
28036
|
+
if (properties) {
|
|
28037
|
+
for (const [key, propertySchema] of Object.entries(properties)) {
|
|
28038
|
+
if (!isObject2(propertySchema))
|
|
28039
|
+
continue;
|
|
28040
|
+
const childExample = isObject2(example) && hasOwn(example, key) ? example[key] : undefined;
|
|
28041
|
+
normalizeSchemaNode(propertySchema, childExample, key);
|
|
28042
|
+
}
|
|
28043
|
+
}
|
|
28044
|
+
if (isObject2(schema.additionalProperties)) {
|
|
28045
|
+
const knownKeys = new Set(properties ? Object.keys(properties) : []);
|
|
28046
|
+
const dynamicExample = isObject2(example) ? Object.entries(example).find(([key]) => !knownKeys.has(key))?.[1] : undefined;
|
|
28047
|
+
normalizeSchemaNode(schema.additionalProperties, dynamicExample, `${fieldName} value`);
|
|
28048
|
+
}
|
|
28049
|
+
}
|
|
28050
|
+
function normalizeInterfaceDocument(document) {
|
|
28051
|
+
if (!isObject2(document))
|
|
28052
|
+
return document;
|
|
28053
|
+
const normalized = structuredClone(document);
|
|
28054
|
+
const containers = [];
|
|
28055
|
+
if (isObject2(normalized.request) && isObject2(normalized.request.body)) {
|
|
28056
|
+
containers.push(normalized.request.body);
|
|
28057
|
+
}
|
|
28058
|
+
if (Array.isArray(normalized.responses)) {
|
|
28059
|
+
containers.push(...normalized.responses.filter(isObject2));
|
|
28060
|
+
}
|
|
28061
|
+
for (const container of containers) {
|
|
28062
|
+
const schema = isObject2(container.schema) ? container.schema : undefined;
|
|
28063
|
+
if (!schema)
|
|
28064
|
+
continue;
|
|
28065
|
+
if (!hasOwn(container, "example") && hasOwn(schema, "example")) {
|
|
28066
|
+
container.example = structuredClone(schema.example);
|
|
28067
|
+
}
|
|
28068
|
+
normalizeSchemaNode(schema, container.example, "root", true);
|
|
28069
|
+
}
|
|
28070
|
+
return normalized;
|
|
28071
|
+
}
|
|
27948
28072
|
function requireText(object4, key, path, issues, minLength = 1) {
|
|
27949
28073
|
const value = object4[key];
|
|
27950
28074
|
if (typeof value !== "string" || value.trim().length < minLength) {
|
|
27951
28075
|
issues.push(`${path}.${key} must be a non-empty string with at least ${minLength} characters`);
|
|
27952
28076
|
}
|
|
27953
28077
|
}
|
|
27954
|
-
function validateSchemaExampleCoverage(schema, example, schemaPath, examplePath, issues) {
|
|
28078
|
+
function validateSchemaExampleCoverage(schema, example, schemaPath, examplePath, issues, requireMetadata = false) {
|
|
27955
28079
|
if (typeof schema.type !== "string") {
|
|
27956
28080
|
issues.push(`${schemaPath}.type is required`);
|
|
27957
|
-
|
|
28081
|
+
}
|
|
28082
|
+
if (requireMetadata) {
|
|
28083
|
+
requireText(schema, "description", schemaPath, issues);
|
|
28084
|
+
if (!hasOwn(schema, "example")) {
|
|
28085
|
+
issues.push(`${schemaPath}.example is required`);
|
|
28086
|
+
}
|
|
27958
28087
|
}
|
|
27959
28088
|
if (schema.type === "array") {
|
|
28089
|
+
if (!isObject2(schema.items)) {
|
|
28090
|
+
issues.push(`${schemaPath}.items is required for array schemas`);
|
|
28091
|
+
}
|
|
27960
28092
|
if (!Array.isArray(example)) {
|
|
27961
28093
|
issues.push(`${examplePath} must be an array`);
|
|
27962
28094
|
return;
|
|
@@ -27965,14 +28097,15 @@ function validateSchemaExampleCoverage(schema, example, schemaPath, examplePath,
|
|
|
27965
28097
|
issues.push(`${schemaPath}.items must fully describe array elements`);
|
|
27966
28098
|
return;
|
|
27967
28099
|
}
|
|
27968
|
-
example.forEach((item, index) => validateSchemaExampleCoverage(schema.items, item, `${schemaPath}.items`, `${examplePath}[${index}]`, issues));
|
|
28100
|
+
example.forEach((item, index) => validateSchemaExampleCoverage(schema.items, item, `${schemaPath}.items`, `${examplePath}[${index}]`, issues, true));
|
|
27969
28101
|
return;
|
|
27970
28102
|
}
|
|
27971
28103
|
if (schema.type !== "object")
|
|
27972
28104
|
return;
|
|
27973
28105
|
const properties = isObject2(schema.properties) ? schema.properties : undefined;
|
|
27974
28106
|
const additionalProperties = isObject2(schema.additionalProperties) ? schema.additionalProperties : undefined;
|
|
27975
|
-
|
|
28107
|
+
const allowsAdditionalProperties = additionalProperties !== undefined || schema.additionalProperties === true;
|
|
28108
|
+
if (!properties && !allowsAdditionalProperties) {
|
|
27976
28109
|
issues.push(`${schemaPath} must define properties or an additionalProperties schema`);
|
|
27977
28110
|
return;
|
|
27978
28111
|
}
|
|
@@ -27985,7 +28118,7 @@ function validateSchemaExampleCoverage(schema, example, schemaPath, examplePath,
|
|
|
27985
28118
|
if (!hasOwn(example, key)) {
|
|
27986
28119
|
issues.push(`${examplePath} is missing ${key}`);
|
|
27987
28120
|
} else if (isObject2(propertySchema)) {
|
|
27988
|
-
validateSchemaExampleCoverage(propertySchema, example[key], `${schemaPath}.properties.${key}`, `${examplePath}.${key}`, issues);
|
|
28121
|
+
validateSchemaExampleCoverage(propertySchema, example[key], `${schemaPath}.properties.${key}`, `${examplePath}.${key}`, issues, true);
|
|
27989
28122
|
} else {
|
|
27990
28123
|
issues.push(`${schemaPath}.properties.${key} must be a JSON Schema object`);
|
|
27991
28124
|
}
|
|
@@ -27995,8 +28128,8 @@ function validateSchemaExampleCoverage(schema, example, schemaPath, examplePath,
|
|
|
27995
28128
|
if (properties && hasOwn(properties, key))
|
|
27996
28129
|
continue;
|
|
27997
28130
|
if (additionalProperties) {
|
|
27998
|
-
validateSchemaExampleCoverage(additionalProperties, value, `${schemaPath}.additionalProperties`, `${examplePath}.${key}`, issues);
|
|
27999
|
-
} else {
|
|
28131
|
+
validateSchemaExampleCoverage(additionalProperties, value, `${schemaPath}.additionalProperties`, `${examplePath}.${key}`, issues, true);
|
|
28132
|
+
} else if (schema.additionalProperties !== true) {
|
|
28000
28133
|
issues.push(`${schemaPath}.properties does not define example field ${key}`);
|
|
28001
28134
|
}
|
|
28002
28135
|
}
|
|
@@ -28128,7 +28261,7 @@ function interfaceDocCompletenessIssues(document, operation) {
|
|
|
28128
28261
|
validateSchemaAndExample(response, path, issues);
|
|
28129
28262
|
});
|
|
28130
28263
|
}
|
|
28131
|
-
return issues;
|
|
28264
|
+
return [...new Set(issues)];
|
|
28132
28265
|
}
|
|
28133
28266
|
function assertCompleteInterfaceDoc(document, operation) {
|
|
28134
28267
|
const issues = interfaceDocCompletenessIssues(document, operation);
|
|
@@ -28222,7 +28355,7 @@ var interfaceDocSchema = {
|
|
|
28222
28355
|
additionalProperties: false,
|
|
28223
28356
|
properties: {
|
|
28224
28357
|
content_type: { const: "application/json" },
|
|
28225
|
-
schema: {},
|
|
28358
|
+
schema: { $ref: "#/$defs/schema_root" },
|
|
28226
28359
|
example: {}
|
|
28227
28360
|
}
|
|
28228
28361
|
},
|
|
@@ -28234,30 +28367,68 @@ var interfaceDocSchema = {
|
|
|
28234
28367
|
status: { type: "integer", minimum: 100, maximum: 599 },
|
|
28235
28368
|
description: { type: "string", minLength: 1, maxLength: 4000 },
|
|
28236
28369
|
content_type: { const: "application/json" },
|
|
28237
|
-
schema: {},
|
|
28370
|
+
schema: { $ref: "#/$defs/schema_root" },
|
|
28238
28371
|
example: {}
|
|
28239
28372
|
}
|
|
28373
|
+
},
|
|
28374
|
+
schema_root: {
|
|
28375
|
+
type: "object",
|
|
28376
|
+
required: ["type"],
|
|
28377
|
+
additionalProperties: true,
|
|
28378
|
+
properties: {
|
|
28379
|
+
type: { type: "string", minLength: 1 },
|
|
28380
|
+
description: { type: "string", minLength: 1 },
|
|
28381
|
+
example: {},
|
|
28382
|
+
properties: { type: "object", additionalProperties: { $ref: "#/$defs/schema_node" } },
|
|
28383
|
+
items: { $ref: "#/$defs/schema_node" },
|
|
28384
|
+
additionalProperties: { anyOf: [{ $ref: "#/$defs/schema_node" }, { type: "boolean" }] },
|
|
28385
|
+
required: { type: "array", items: { type: "string" } }
|
|
28386
|
+
}
|
|
28387
|
+
},
|
|
28388
|
+
schema_node: {
|
|
28389
|
+
type: "object",
|
|
28390
|
+
required: ["type", "description", "example"],
|
|
28391
|
+
additionalProperties: true,
|
|
28392
|
+
properties: {
|
|
28393
|
+
type: { type: "string", minLength: 1 },
|
|
28394
|
+
description: { type: "string", minLength: 1 },
|
|
28395
|
+
example: {},
|
|
28396
|
+
properties: { type: "object", additionalProperties: { $ref: "#/$defs/schema_node" } },
|
|
28397
|
+
items: { $ref: "#/$defs/schema_node" },
|
|
28398
|
+
additionalProperties: { anyOf: [{ $ref: "#/$defs/schema_node" }, { type: "boolean" }] },
|
|
28399
|
+
required: { type: "array", items: { type: "string" } }
|
|
28400
|
+
}
|
|
28240
28401
|
}
|
|
28241
28402
|
}
|
|
28242
28403
|
};
|
|
28243
|
-
function schemaFromExample(value) {
|
|
28404
|
+
function schemaFromExample(value, fieldName = "value") {
|
|
28405
|
+
const metadata = { description: `Value for ${fieldName}.`, example: structuredClone(value) };
|
|
28244
28406
|
if (Array.isArray(value)) {
|
|
28245
|
-
return {
|
|
28407
|
+
return {
|
|
28408
|
+
type: "array",
|
|
28409
|
+
...metadata,
|
|
28410
|
+
items: value.length > 0 ? schemaFromExample(value[0], `${fieldName} item`) : {
|
|
28411
|
+
type: "string",
|
|
28412
|
+
description: `Value for ${fieldName} item.`,
|
|
28413
|
+
example: ""
|
|
28414
|
+
}
|
|
28415
|
+
};
|
|
28246
28416
|
}
|
|
28247
28417
|
if (value !== null && typeof value === "object") {
|
|
28248
28418
|
const entries = Object.entries(value);
|
|
28249
28419
|
return {
|
|
28250
28420
|
type: "object",
|
|
28251
|
-
|
|
28421
|
+
...metadata,
|
|
28422
|
+
properties: Object.fromEntries(entries.map(([key, item]) => [key, schemaFromExample(item, key)])),
|
|
28252
28423
|
required: entries.map(([key]) => key),
|
|
28253
28424
|
additionalProperties: false
|
|
28254
28425
|
};
|
|
28255
28426
|
}
|
|
28256
28427
|
if (value === null)
|
|
28257
|
-
return { type: "null" };
|
|
28428
|
+
return { type: "null", ...metadata };
|
|
28258
28429
|
if (typeof value === "number")
|
|
28259
|
-
return { type: Number.isInteger(value) ? "integer" : "number" };
|
|
28260
|
-
return { type: typeof value };
|
|
28430
|
+
return { type: Number.isInteger(value) ? "integer" : "number", ...metadata };
|
|
28431
|
+
return { type: typeof value, ...metadata };
|
|
28261
28432
|
}
|
|
28262
28433
|
function codeWriterContext(mode, requirement, inputExample, includeFullSchema, baseUrl) {
|
|
28263
28434
|
const common = {
|
|
@@ -28294,6 +28465,7 @@ function codeWriterContext(mode, requirement, inputExample, includeFullSchema, b
|
|
|
28294
28465
|
},
|
|
28295
28466
|
code_rules: [
|
|
28296
28467
|
"Read all business data from the global input only; do not read environment variables, persistent globals, or other external state. Return values must be JSON-serializable.",
|
|
28468
|
+
"Treat input as a reserved, read-only runtime binding. Never declare, redeclare, rebind, or destructure a local binding named input in any scope, including const/let/var declarations, function parameters, catch bindings, and nested callbacks. If a local name is needed, alias it to payload or another name, for example const payload = input. Review the complete source for input shadowing before every execution and retry.",
|
|
28297
28469
|
"Use top-level return by default. Use qf_output only for event-style/asynchronous flows or when explicitly requested, and assign it as a bare qf_output = { ... } object literal. Never mix it with top-level return or shadow the identifier.",
|
|
28298
28470
|
"Prefer standard JavaScript, Bun-native fetch, real axios, and node:crypto. Network requests use Bun's native network stack. Use a whitelisted CommonJS literal require only when native capabilities cannot meet the requirement and the user explicitly requests it. crypto-js has been removed and must not be generated.",
|
|
28299
28471
|
"Do not use import/export, dynamic require, browser APIs, timers, forbidden identifiers or members, or blacklisted Node modules. Never write real credentials.",
|
|
@@ -28534,10 +28706,11 @@ var serverInstructions = [
|
|
|
28534
28706
|
"Tool routing: flow_write_code only generates code and its contract; flow_execute_code tests unpublished generated code; flow_execute_script runs published scripts. When the generated code and available safe input are sufficient for a meaningful runtime test, execute it immediately without waiting for user confirmation. If required input or credentials are missing, report that runtime verification was not performed instead of inventing them.",
|
|
28535
28707
|
"Script workflow: read the current version with flow_get_script before updates; creates require a complete interface_doc, while code or document updates may use a complete interface_doc or an RFC 6902 interface_doc_patch (never both, and patches require expected_version). Preview with flow_preview_script_change, then call flow_apply_script_change(confirm=true) only after explicit user confirmation. Documentation-only changes use flow_preview_script_documentation -> flow_apply_script_documentation.",
|
|
28536
28708
|
"Preview IDs are single-use and time-limited. On a version conflict, expired preview, or validation failure, stop, read again, and preview again; never retry an old preview_id. Every flow_apply_* call requires confirm=true.",
|
|
28709
|
+
"Interface-document validation reports every discovered nested schema issue in one response; fix the complete list before making another preview call.",
|
|
28537
28710
|
"script-interface-doc.v1 requires schema_version, title, summary, endpoint, request, responses, and logic_description. endpoint requires methods and description; request.query and request.headers are required arrays (use [] when empty); POST requires request.body and GET-only documents must omit it. JSON Patch supports at most 256 add/remove/replace/move/copy/test operations; preview responses show operation counts and paths, not merged documents.",
|
|
28538
|
-
"Every query parameter and request header requires name, type, required, description, and example. Request bodies and responses require content_type=application/json, schema, and example; every response also requires status and description. Every JSON Schema node declares type, and object
|
|
28711
|
+
"Every query parameter and request header requires name, type, required, description, and example. Request bodies and responses require content_type=application/json, schema, and example; every response also requires status and description. Every root JSON Schema node declares type; every nested property, array item, and object-form additionalProperties node declares type, description, and example. Arrays must define items, fixed object properties must be covered by the complete example, and additionalProperties=true is reserved for opaque upstream JSON.",
|
|
28539
28712
|
"Keep endpoint.path relative: omit it on create and use /flow/codeblock/<actual-script-id> on update. Public call URLs use the caller-provided domain plus /flow/codeblock/{{script_id}}; never put real tokens, passwords, cookies, or Authorization values in code, documents, examples, or URLs.",
|
|
28540
|
-
"Script input comes from input.query, input.header, input.body, and input.cookies; for immediate non-script POST /flow/codeblock, body.input becomes global input unchanged. Use top-level return by default; use a bare qf_output assignment only for event-style/asynchronous flows or when explicitly requested, never both.",
|
|
28713
|
+
"Script input comes from input.query, input.header, input.body, and input.cookies; for immediate non-script POST /flow/codeblock, body.input becomes global input unchanged. Treat input as a reserved, read-only runtime binding: never declare, redeclare, rebind, or destructure a local binding named input in any scope, including function parameters and nested callbacks. Use an alias such as const payload = input when a local name is needed. Use top-level return by default; use a bare qf_output assignment only for event-style/asynchronous flows or when explicitly requested, never both.",
|
|
28541
28714
|
"For every initial generation and every later revision in non-script mode, final delivery always includes the complete latest generated JavaScript, even after runtime verification; never return only a patch, diff, or partial snippet. Also include caller-facing invocation instructions, parameters/examples, logic, success/error examples, and execution_url. Script delivery omits JavaScript and raw interface_doc by default and includes invocation instructions, parameters/examples, logic, success/error examples, and the published script_url unless the user asks for source or raw documentation. Code and interface_doc remain internal preview/validation/publication inputs.",
|
|
28542
28715
|
"Prefer native JavaScript, URL/URLSearchParams, Bun-native fetch, and node:crypto; crypto-js has been removed. Treat every forbidden identifier as forbidden in every syntactic position, including property names and method calls. Never generate RegExp.exec or .exec(...); use text.match(regex) for capture groups or regex.test(text) for boolean checks. Before execution, review the complete source and rewrite every forbidden identifier, member, or module. Do not generate browser APIs, timers, dynamic module loading, constructor-based code generation, or blacklisted Node modules (including fs/node:fs). Excel imports are limited to read-excel-file/node, read-excel-file/universal, write-excel-file/node, write-excel-file/universal, and write-excel-file/utility. Check HTTP status and handle JSON, text, and empty responses; await or return every async task.",
|
|
28543
28716
|
"Script execution accepts only GET or POST. MCP authentication, cookies, CSRF, proxy-source headers, and test-tool markers are filtered. There is no script deletion, emergency unlock, or arbitrary HTTP proxy tool; direct those requests to the web UI or controlled REST/operations flow."
|
|
@@ -28641,7 +28814,7 @@ function withApiErrors(handler) {
|
|
|
28641
28814
|
};
|
|
28642
28815
|
}
|
|
28643
28816
|
var documentationFields = {
|
|
28644
|
-
document:
|
|
28817
|
+
document: interfaceDocToolInputSchema.optional().describe(`Normalized script-interface-doc.v1 JSON object. Choose exactly one of document, raw_document, or document_patch; complete documents are required for saves and code updates. ${interfaceDocInputDescription}`),
|
|
28645
28818
|
raw_document: exports_external.string().optional().describe("JSON/OpenAPI document text for server parsing. Choose exactly one of document, raw_document, or document_patch; format=json parses JSON."),
|
|
28646
28819
|
format: exports_external.literal("json").optional().describe("Format of raw_document; only json is supported."),
|
|
28647
28820
|
document_patch: interfaceDocPatchSchema.optional().describe("RFC 6902 patch for an existing script only. Choose exactly one of document, raw_document, or document_patch."),
|
|
@@ -28654,7 +28827,7 @@ var changeSchema = exports_external.object({
|
|
|
28654
28827
|
code_base64: exports_external.string().optional().describe("Non-empty Base64-encoded JavaScript, mutually exclusive with code."),
|
|
28655
28828
|
description: exports_external.string().optional().describe("Script description. Can be updated without changing code."),
|
|
28656
28829
|
ip_whitelist: exports_external.array(exports_external.string()).nullable().optional().describe("Source IP/CIDR allowlist. Omit on update to keep the current value; null or [] clears the restriction."),
|
|
28657
|
-
interface_doc:
|
|
28830
|
+
interface_doc: interfaceDocToolInputSchema.optional().describe(interfaceDocInputDescription),
|
|
28658
28831
|
interface_doc_patch: interfaceDocPatchSchema.optional().describe("RFC 6902 patch for update only; mutually exclusive with interface_doc and forbidden for create."),
|
|
28659
28832
|
rollback_to_version: exports_external.number().int().positive().optional().describe("Historical version to restore. Use only as a standalone update and never with code, interface_doc, or interface_doc_patch."),
|
|
28660
28833
|
expected_version: exports_external.number().int().positive().optional().describe("Required for update and must be the current_version from flow_get_script for concurrency protection; forbidden for create.")
|
|
@@ -28721,6 +28894,7 @@ function assertScriptChangeInput(input) {
|
|
|
28721
28894
|
throw new Error("rollback_to_version cannot be combined with code, interface_doc, or interface_doc_patch");
|
|
28722
28895
|
}
|
|
28723
28896
|
if (input.interface_doc !== undefined) {
|
|
28897
|
+
input.interface_doc = normalizeInterfaceDocument(input.interface_doc);
|
|
28724
28898
|
assertCompleteInterfaceDoc(input.interface_doc, input.operation);
|
|
28725
28899
|
}
|
|
28726
28900
|
if (input.interface_doc_patch !== undefined)
|
|
@@ -28774,15 +28948,15 @@ function assertPreview(record3, operation) {
|
|
|
28774
28948
|
return record3;
|
|
28775
28949
|
}
|
|
28776
28950
|
function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
28777
|
-
const server = new McpServer({ name: "flow-codeblock-rust", version: "0.1.
|
|
28951
|
+
const server = new McpServer({ name: "flow-codeblock-rust", version: "0.1.10" }, { instructions: serverInstructions });
|
|
28778
28952
|
server.registerTool("flow_write_code", {
|
|
28779
28953
|
title: "Get the Flow JavaScript authoring contract",
|
|
28780
|
-
description: "Call this before writing or revising Flow Codeblock JavaScript. It returns the mode-specific authoring contract, including forbidden-identifier replacement rules, and never writes the database, publishes a script, or executes code. For every non_script generation or revision, always deliver the complete latest generated JavaScript plus execution_url, never only a patch or partial snippet; use script for persistent GET/POST /flow/codeblock/{{script_id}} code with a complete script-interface-doc.v1 for preview, validation, and publication. Script delivery includes invocation instructions, parameters/examples, logic, success/error examples, and script_url rather than source or raw interface_doc unless requested. Set base_url only when a caller-facing URL template is needed.",
|
|
28954
|
+
description: "Call this before writing or revising Flow Codeblock JavaScript. It returns the mode-specific authoring contract, including forbidden-identifier and reserved-input replacement rules, and never writes the database, publishes a script, or executes code. For every non_script generation or revision, always deliver the complete latest generated JavaScript plus execution_url, never only a patch or partial snippet; use script for persistent GET/POST /flow/codeblock/{{script_id}} code with a complete script-interface-doc.v1 for preview, validation, and publication. Script delivery includes invocation instructions, parameters/examples, logic, success/error examples, and script_url rather than source or raw interface_doc unless requested. Set base_url only when a caller-facing URL template is needed.",
|
|
28781
28955
|
inputSchema: {
|
|
28782
28956
|
mode: exports_external.enum(["non_script", "script"]).describe("Generation mode. Use non_script for immediate, non-persistent execution; use script for a persistent GET/POST endpoint or HTTP redirects."),
|
|
28783
28957
|
requirement: exports_external.string().min(1).max(20000).describe("Complete business requirements, input fields, expected output, external APIs, synchronization/async needs, and error behavior. Include only requirements relevant to this code."),
|
|
28784
28958
|
input_example: exports_external.unknown().optional().describe("Business input example. In script mode it helps generate request.body/schema/example; in non_script mode it supplies flow_execute_code test input. Never include real credentials."),
|
|
28785
|
-
include_full_schema: exports_external.boolean().optional().describe("Whether to include the complete JSON Schema in the response; defaults to
|
|
28959
|
+
include_full_schema: exports_external.boolean().optional().describe("Whether to include the complete recursive JSON Schema in the response; defaults to true for script mode to avoid a follow-up schema call. Set false only when the caller already has the schema."),
|
|
28786
28960
|
base_url: exports_external.string().url().refine((value) => {
|
|
28787
28961
|
try {
|
|
28788
28962
|
const parsed = new URL(value);
|
|
@@ -28793,7 +28967,7 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28793
28967
|
}, "base_url must be an http(s) URL without credentials or control characters").optional().describe("Optional caller service origin such as https://flow.example.com. Used only to render /flow/codeblock/{{script_id}}; credentials and control characters are forbidden.")
|
|
28794
28968
|
}
|
|
28795
28969
|
}, async ({ mode, requirement, input_example, include_full_schema, base_url }) => {
|
|
28796
|
-
const context = codeWriterContext(mode, requirement, input_example, include_full_schema ??
|
|
28970
|
+
const context = codeWriterContext(mode, requirement, input_example, include_full_schema ?? mode === "script", base_url);
|
|
28797
28971
|
return result(mode === "non_script" ? { ...context, execution_url: executionUrl(api2) } : context);
|
|
28798
28972
|
});
|
|
28799
28973
|
server.registerTool("flow_list_scripts", {
|
|
@@ -28855,6 +29029,7 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28855
29029
|
}, withApiErrors(async (input) => {
|
|
28856
29030
|
const parsed = documentationSchema.parse(input);
|
|
28857
29031
|
if (parsed.document !== undefined) {
|
|
29032
|
+
parsed.document = normalizeInterfaceDocument(parsed.document);
|
|
28858
29033
|
assertCompleteInterfaceDoc(parsed.document, "update");
|
|
28859
29034
|
}
|
|
28860
29035
|
if (parsed.document_patch !== undefined)
|
|
@@ -28955,6 +29130,8 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
28955
29130
|
inputSchema: documentationSchema.shape
|
|
28956
29131
|
}, withApiErrors(async (input) => {
|
|
28957
29132
|
const parsed = documentationSchema.parse(input);
|
|
29133
|
+
if (parsed.document !== undefined)
|
|
29134
|
+
parsed.document = normalizeInterfaceDocument(parsed.document);
|
|
28958
29135
|
const expectedVersion = await fetchCurrentVersion(api2, parsed.script_id);
|
|
28959
29136
|
if (parsed.document_patch !== undefined && parsed.expected_version !== expectedVersion) {
|
|
28960
29137
|
throw new Error(`Script version changed from ${parsed.expected_version} to ${expectedVersion}; read and preview again`);
|
|
@@ -29065,11 +29242,11 @@ function createMcpServer({ api: api2, previews = new PreviewStore }) {
|
|
|
29065
29242
|
}));
|
|
29066
29243
|
server.registerTool("flow_execute_code", {
|
|
29067
29244
|
title: "Execute unpublished code",
|
|
29068
|
-
description: "Execute unpublished generated JavaScript when requested or when the available safe input is sufficient for a meaningful runtime test; execution-only verification does not require user confirmation. The result includes a complete execution_url. The request is always POST /flow/codeblock, and body.input is injected unchanged as global input;
|
|
29245
|
+
description: "Execute unpublished generated JavaScript when requested or when the available safe input is sufficient for a meaningful runtime test; execution-only verification does not require user confirmation. The result includes a complete execution_url. The request is always POST /flow/codeblock, and body.input is injected unchanged as global input; input is a reserved runtime binding and generated code must never declare, rebind, or shadow it; use an alias such as const payload = input. Provide exactly one of code or code_base64. User-code failures preserve verified error.details source locations when available. MCP authentication is never written into user input, and this tool does not create or publish scripts.",
|
|
29069
29246
|
inputSchema: {
|
|
29070
29247
|
code: exports_external.string().min(1).optional().describe("UTF-8 JavaScript source, mutually exclusive with code_base64."),
|
|
29071
29248
|
code_base64: exports_external.string().min(1).optional().describe("Non-empty Base64-encoded JavaScript source, mutually exclusive with code."),
|
|
29072
|
-
input: exports_external.unknown().optional().describe("Business data injected into global input; defaults to {}. Do not include tokens, passwords, cookies, or Authorization values."),
|
|
29249
|
+
input: exports_external.unknown().optional().describe("Business data injected into global input; defaults to {}. Do not include tokens, passwords, cookies, or Authorization values. In generated code, input is a reserved binding; never declare, rebind, or shadow it."),
|
|
29073
29250
|
timeout_ms: exports_external.number().int().positive().optional().describe("Test execution timeout in milliseconds; it must be within the server's allowed range.")
|
|
29074
29251
|
}
|
|
29075
29252
|
}, withApiErrors(async ({ code, code_base64, input, timeout_ms }) => {
|