comfyui-mcp 0.52.88 → 0.52.89
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.
|
@@ -4950,6 +4950,7 @@ const QUERY_GRAPH_MAX_CHARS_DEFAULT = 12000;
|
|
|
4950
4950
|
const QUERY_GRAPH_MAX_CHARS_CEILING = 60000;
|
|
4951
4951
|
const DETAIL_WIDGET_MAX_CHARS_DEFAULT = 2048;
|
|
4952
4952
|
const DETAIL_WIDGET_MAX_CHARS_CEILING = 32768;
|
|
4953
|
+
const QUERY_GRAPH_WIDGET_MAX_CHARS_NOTE = "widget_max_chars_note";
|
|
4953
4954
|
/** The panel's own clamp for graph_query's budget, mirrored so the figure this module
|
|
4954
4955
|
* reports and enforces is the one the panel was actually working to. */
|
|
4955
4956
|
function clampQueryGraphMaxChars(v) {
|
|
@@ -4958,6 +4959,49 @@ function clampQueryGraphMaxChars(v) {
|
|
|
4958
4959
|
return QUERY_GRAPH_MAX_CHARS_DEFAULT;
|
|
4959
4960
|
return Math.min(Math.max(Math.floor(n), QUERY_GRAPH_MAX_CHARS_FLOOR), QUERY_GRAPH_MAX_CHARS_CEILING);
|
|
4960
4961
|
}
|
|
4962
|
+
/**
|
|
4963
|
+
* `widget_max_chars` is deliberately a pinpoint-only escape hatch. Keep the
|
|
4964
|
+
* precondition at the MCP seam as well as in the Panel serializer: older Panel
|
|
4965
|
+
* builds silently ignored the argument, and broad reads must never accidentally
|
|
4966
|
+
* trade away the query's token bound for one large widget.
|
|
4967
|
+
*/
|
|
4968
|
+
function widgetMaxCharsRequest(args) {
|
|
4969
|
+
const requested = args.widget_max_chars;
|
|
4970
|
+
if (typeof requested !== "number")
|
|
4971
|
+
return {};
|
|
4972
|
+
if (args.fields !== "detail") {
|
|
4973
|
+
return {
|
|
4974
|
+
note: `widget_max_chars=${requested} was not applied: it is accepted only with ` +
|
|
4975
|
+
`fields:"detail" and exactly one explicit ids entry; this query used ` +
|
|
4976
|
+
`${args.fields === undefined ? "an omitted fields projection" : `fields:"${String(args.fields)}"`}. ` +
|
|
4977
|
+
`The default ${DETAIL_WIDGET_MAX_CHARS_DEFAULT}-char per-widget cap remains in force.`,
|
|
4978
|
+
};
|
|
4979
|
+
}
|
|
4980
|
+
if (!Array.isArray(args.ids) || args.ids.length !== 1) {
|
|
4981
|
+
return {
|
|
4982
|
+
note: `widget_max_chars=${requested} was not applied: it is accepted only with ` +
|
|
4983
|
+
`fields:"detail" and exactly one explicit ids entry; this query supplied ` +
|
|
4984
|
+
`${Array.isArray(args.ids) ? `${args.ids.length} ids` : "no explicit ids"}. ` +
|
|
4985
|
+
`The default ${DETAIL_WIDGET_MAX_CHARS_DEFAULT}-char per-widget cap remains in force.`,
|
|
4986
|
+
};
|
|
4987
|
+
}
|
|
4988
|
+
return { value: requested };
|
|
4989
|
+
}
|
|
4990
|
+
/** A legacy Panel can accept the command but still serialize at its old cap. */
|
|
4991
|
+
function legacyWidgetMaxCharsNote(res, requested) {
|
|
4992
|
+
if (typeof requested !== "number" || requested <= DETAIL_WIDGET_MAX_CHARS_DEFAULT)
|
|
4993
|
+
return undefined;
|
|
4994
|
+
const text = res.content
|
|
4995
|
+
.filter((block) => block.type === "text")
|
|
4996
|
+
.map((block) => (block.type === "text" ? block.text : ""))
|
|
4997
|
+
.join("\n");
|
|
4998
|
+
if (!/2048-char per-widget cap/.test(text))
|
|
4999
|
+
return undefined;
|
|
5000
|
+
return (`widget_max_chars=${requested} was not applied: the connected Panel returned its ` +
|
|
5001
|
+
`default ${DETAIL_WIDGET_MAX_CHARS_DEFAULT}-char per-widget cap. Update the Panel ` +
|
|
5002
|
+
`to a build that supports graph_query.widget_max_chars, then retry this exact ` +
|
|
5003
|
+
`detail read; the query result below is otherwise unchanged.`);
|
|
5004
|
+
}
|
|
4961
5005
|
/** The reply fields that are CONTEXT rather than answer. `viewing` is deliberately not
|
|
4962
5006
|
* one of them: it identifies which graph the answer describes, it is a couple of small
|
|
4963
5007
|
* fields, and a reply that cannot say what it is about is worse than a bigger one. */
|
|
@@ -4972,6 +5016,7 @@ const QUERY_GRAPH_RIDER_KEYS = [
|
|
|
4972
5016
|
* (independent gate P0). `max_chars` is re-set unconditionally just below. */
|
|
4973
5017
|
const QUERY_GRAPH_OWNED_KEYS = new Set([
|
|
4974
5018
|
"max_chars",
|
|
5019
|
+
QUERY_GRAPH_WIDGET_MAX_CHARS_NOTE,
|
|
4975
5020
|
"groups_membership_omitted",
|
|
4976
5021
|
"groups_omitted",
|
|
4977
5022
|
"rails_omitted",
|
|
@@ -5018,7 +5063,7 @@ function groupIndexEntry(g) {
|
|
|
5018
5063
|
* than silently ignored — `max_chars` bounds characters, and calling image bytes
|
|
5019
5064
|
* characters would be its own false figure.
|
|
5020
5065
|
*/
|
|
5021
|
-
function fitQueryGraphReply(res, requested) {
|
|
5066
|
+
function fitQueryGraphReply(res, requested, widgetMaxCharsNote) {
|
|
5022
5067
|
const payload = parseToolResultJson(res);
|
|
5023
5068
|
if (!payload)
|
|
5024
5069
|
return res;
|
|
@@ -5065,6 +5110,8 @@ function fitQueryGraphReply(res, requested) {
|
|
|
5065
5110
|
// trustworthy, and a caller who learns to distrust it will ignore the real ones. This
|
|
5066
5111
|
// accounting is the sole author of these fields, so it is also their sole source.
|
|
5067
5112
|
answer.max_chars = budget;
|
|
5113
|
+
if (widgetMaxCharsNote)
|
|
5114
|
+
answer[QUERY_GRAPH_WIDGET_MAX_CHARS_NOTE] = widgetMaxCharsNote;
|
|
5068
5115
|
const groups = Array.isArray(riders.groups) ? riders.groups : null;
|
|
5069
5116
|
const hasGroups = !!groups && groups.length > 0;
|
|
5070
5117
|
const hasRails = riders.rails !== undefined;
|
|
@@ -11980,7 +12027,7 @@ export function buildPanelToolDefs() {
|
|
|
11980
12027
|
// Local helper so each def reads like the original `tool(...)` call.
|
|
11981
12028
|
const def = (name, description, schema, handler) => ({ name, description, schema, handler });
|
|
11982
12029
|
const defs = [
|
|
11983
|
-
def("panel_query_graph", "FILTER or TRAVERSE a SUBSET of the live canvas, for when you ALREADY KNOW what you're looking for. NOT for 'show me the canvas' or any whole-graph overview — call panel_graph_outline FIRST for that. NOT get_workflow's query action (that queries a saved file or JSON you provide, not the live canvas). Filters, traverses, projects and aggregates over the workflow the user is CURRENTLY VIEWING without dumping the whole graph (replaces the old panel_get_graph full-JSON dump; output is TOKEN-BOUNDED with an explicit truncation marker, so a big graph can never flood your context). Combine: `types` (node type contains any), `title` (contains), `where` widget predicates ANDed ('cfg>7', 'steps<=20', 'sampler_name=euler', 'text~sunset' — ops = != >= <= > < ~contains), `ids` (exact nodes — THE way to read ONE node's exact slot/widget detail: {ids:[42], fields:'detail'}), `upstream_of`/`downstream_of` + `depth` (dependency traversal: upstream = what FEEDS that node, downstream = what CONSUMES it; seed at depth 0), `fields` ('compact' one line per node [default], 'ids', 'detail' = the full node summary with slots + connections + mode), `group_by:'type'` (counts only), `limit` (default 40). detail rows include each node's MODE — a 'bypass' node is skipped and a 'mute' node kills everything downstream, so check modes on the path you care about before running (fix with panel_set_node_mode). Every result also carries `groups` (id, title, member node_ids — groups are geometric, trust this list) and, when viewing a SUBGRAPH (after panel_enter_subgraph), `rails` (boundary rail ids/slots). `max_chars` bounds the WHOLE result, those riders included, and the rows you asked for are spent first: on a big graph the riders lose their member ids, then drop out entirely, rather than starving your query — and each says in-band when it did, with the true counts. `widget_max_chars` raises the per-widget cap only for `fields:'detail'`; use it only with exactly one explicit `ids` entry (for example `{ids:[42], fields:'detail', widget_max_chars:8192}`), with a default of 2048 and a maximum of 32768. It does not change compact, ids, or broad reads. Typical flow: panel_graph_outline to orient → panel_query_graph to pinpoint/inspect → edit. Read-only.", {
|
|
12030
|
+
def("panel_query_graph", "FILTER or TRAVERSE a SUBSET of the live canvas, for when you ALREADY KNOW what you're looking for. NOT for 'show me the canvas' or any whole-graph overview — call panel_graph_outline FIRST for that. NOT get_workflow's query action (that queries a saved file or JSON you provide, not the live canvas). Filters, traverses, projects and aggregates over the workflow the user is CURRENTLY VIEWING without dumping the whole graph (replaces the old panel_get_graph full-JSON dump; output is TOKEN-BOUNDED with an explicit truncation marker, so a big graph can never flood your context). Combine: `types` (node type contains any), `title` (contains), `where` widget predicates ANDed ('cfg>7', 'steps<=20', 'sampler_name=euler', 'text~sunset' — ops = != >= <= > < ~contains), `ids` (exact nodes — THE way to read ONE node's exact slot/widget detail: {ids:[42], fields:'detail'}), `upstream_of`/`downstream_of` + `depth` (dependency traversal: upstream = what FEEDS that node, downstream = what CONSUMES it; seed at depth 0), `fields` ('compact' one line per node [default], 'ids', 'detail' = the full node summary with slots + connections + mode), `group_by:'type'` (counts only), `limit` (default 40). detail rows include each node's MODE — a 'bypass' node is skipped and a 'mute' node kills everything downstream, so check modes on the path you care about before running (fix with panel_set_node_mode). Every result also carries `groups` (id, title, member node_ids — groups are geometric, trust this list) and, when viewing a SUBGRAPH (after panel_enter_subgraph), `rails` (boundary rail ids/slots). `max_chars` bounds the WHOLE result, those riders included, and the rows you asked for are spent first: on a big graph the riders lose their member ids, then drop out entirely, rather than starving your query — and each says in-band when it did, with the true counts. `widget_max_chars` raises the per-widget cap only for `fields:'detail'`; use it only with exactly one explicit `ids` entry (for example `{ids:[42], fields:'detail', widget_max_chars:8192}`), with a default of 2048 and a maximum of 32768. If supplied without those preconditions, it is not sent and the result says why; a legacy Panel that still returns the default cap is named in the result. It does not change compact, ids, or broad reads. Typical flow: panel_graph_outline to orient → panel_query_graph to pinpoint/inspect → edit. Read-only.", {
|
|
11984
12031
|
types: z.array(z.string()).optional().describe("Node type contains ANY of these (case-insensitive)."),
|
|
11985
12032
|
title: z.string().optional().describe("Node title contains this."),
|
|
11986
12033
|
where: z
|
|
@@ -12031,21 +12078,27 @@ export function buildPanelToolDefs() {
|
|
|
12031
12078
|
// The panel bounds `text`; the `groups`/`rails` riders were never in that
|
|
12032
12079
|
// accounting, so on a large graph the reply could be many times the budget it
|
|
12033
12080
|
// announced. Nothing is shed while the whole reply fits.
|
|
12034
|
-
async (args, ctx) =>
|
|
12035
|
-
|
|
12036
|
-
|
|
12037
|
-
|
|
12038
|
-
|
|
12039
|
-
|
|
12040
|
-
|
|
12041
|
-
|
|
12042
|
-
|
|
12043
|
-
|
|
12044
|
-
|
|
12045
|
-
|
|
12046
|
-
|
|
12047
|
-
|
|
12048
|
-
|
|
12081
|
+
async (args, ctx) => {
|
|
12082
|
+
const widgetMaxChars = widgetMaxCharsRequest(args);
|
|
12083
|
+
const panelReply = await ctx.call({
|
|
12084
|
+
cmd: "graph_query",
|
|
12085
|
+
types: args.types,
|
|
12086
|
+
title: args.title,
|
|
12087
|
+
where: args.where,
|
|
12088
|
+
ids: args.ids,
|
|
12089
|
+
upstream_of: args.upstream_of,
|
|
12090
|
+
downstream_of: args.downstream_of,
|
|
12091
|
+
depth: args.depth,
|
|
12092
|
+
fields: args.fields,
|
|
12093
|
+
group_by: args.group_by,
|
|
12094
|
+
limit: args.limit,
|
|
12095
|
+
max_chars: args.max_chars,
|
|
12096
|
+
...(widgetMaxChars.value === undefined
|
|
12097
|
+
? {}
|
|
12098
|
+
: { widget_max_chars: widgetMaxChars.value }),
|
|
12099
|
+
});
|
|
12100
|
+
return fitQueryGraphReply(panelReply, args.max_chars, widgetMaxChars.note ?? legacyWidgetMaxCharsNote(panelReply, widgetMaxChars.value));
|
|
12101
|
+
}),
|
|
12049
12102
|
def("panel_graph_outline", "READ THE LIVE CANVAS the user is looking at, as text. 'Show me what's on the canvas' / 'what's on the graph right now' / 'read the current workflow' / 'describe the open graph' -> THIS TOOL, with no arguments. NOT visualize_workflow (it DRAWS A DIAGRAM of a workflow you PASS IN — a saved file or JSON — and never sees the live canvas). NOT panel_query_graph (that FILTERS a SUBSET, for when you already know what you're looking for). Returns one `outline` string covering the WHOLE open graph, topologically sorted (sources first, sinks last): each node as `id Type \"title\" [bypass/mute] [OUTPUT] · group:X widget=value …` with `← inputs` (source_node.output_name) and `→ outputs` (target_node.input_name), after a GROUPS index (title → member node ids). It gives you the WIRING you would otherwise reconstruct by hand — read it FIRST to get oriented, then panel_query_graph to inspect one node ({ids:[42], fields:'detail'}) or panel_find_nodes for free-text search. Over `max_chars` it never cuts the graph short: it sheds per-node detail, or refuses with a reason — never a partial outline. Read-only.", {
|
|
12050
12103
|
max_chars: z
|
|
12051
12104
|
.number()
|