From 3adb3a5fd8767e42ceff7c90e4d23359b4f34d63 Mon Sep 17 00:00:00 2001 From: Andreas Hell Date: Fri, 6 Mar 2026 15:28:14 +0100 Subject: [PATCH 1/6] Added MCP image export --- .../src/features/export/export-modules.ts | 6 + .../src/features/export/mcp-png-export.ts | 176 ++++++++++++++++++ .../src/action-protocol/model-saving.ts | 61 +++++- 3 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 packages/client/src/features/export/mcp-png-export.ts diff --git a/packages/client/src/features/export/export-modules.ts b/packages/client/src/features/export/export-modules.ts index 820dfd5e..53c7a47a 100644 --- a/packages/client/src/features/export/export-modules.ts +++ b/packages/client/src/features/export/export-modules.ts @@ -26,6 +26,7 @@ import { } from '@eclipse-glsp/sprotty'; import { ExportSvgActionHandler } from './export-svg-action-handler'; import { GLSPSvgExporter } from './glsp-svg-exporter'; +import { ExportMcpPngCommand, ExportMcpPngPostprocessor, GLSPMcpPngExporter } from './mcp-png-export'; export const exportModule = new FeatureModule( (bind, _unbind, isBound) => { @@ -33,6 +34,11 @@ export const exportModule = new FeatureModule( bindAsService(context, TYPES.HiddenVNodePostprocessor, ExportSvgPostprocessor); configureCommand(context, ExportSvgCommand); bind(TYPES.SvgExporter).to(GLSPSvgExporter).inSingletonScope(); + + bind(ExportMcpPngPostprocessor).toSelf().inSingletonScope(); + bind(TYPES.HiddenVNodePostprocessor).toService(ExportMcpPngPostprocessor); + configureCommand({ bind, isBound }, ExportMcpPngCommand); + bind(GLSPMcpPngExporter).toSelf().inSingletonScope(); }, { featureId: Symbol('export') } ); diff --git a/packages/client/src/features/export/mcp-png-export.ts b/packages/client/src/features/export/mcp-png-export.ts new file mode 100644 index 00000000..964b97c5 --- /dev/null +++ b/packages/client/src/features/export/mcp-png-export.ts @@ -0,0 +1,176 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { + Action, + CommandExecutionContext, + CommandResult, + ExportMcpPngAction, + GModelElement, + GModelRoot, + HiddenCommand, + isExportable, + isHoverable, + isSelectable, + isViewport, + IVNodePostprocessor, + RequestExportMcpPngAction, + TYPES +} from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; +import { VNode } from 'snabbdom'; +import { GLSPSvgExporter } from './glsp-svg-exporter'; + +/** + * This class extends {@link GLSPSvgExporter} in order to make use of the SVG creation logic. + * It then uses the SVG string to generate an equivalent PNG instead. + * + * This class should not be used for standard SVG generation, but only for generating PNG for MCP purposes. + */ +@injectable() +export class GLSPMcpPngExporter extends GLSPSvgExporter { + async exportPng(root: GModelRoot, request?: RequestExportMcpPngAction): Promise { + return new Promise((resolve, reject) => { + if (typeof document === 'undefined') { + reject(new Error('Failed to find document.')); + return; + } + + let svgElement = this.findSvgElement(); + if (!svgElement) { + reject(new Error('Failed to find SVG element.')); + return; + } + + svgElement = this.prepareSvgElement(svgElement, root); + const serializedSvg = this.createSvg(svgElement, root, request?.options ?? {}, request); + const svgExport = this.getSvgExport(serializedSvg, svgElement, root); + + const svgBlob = new Blob([svgExport], { type: 'image/svg+xml;charset=utf-8' }); + const url = URL.createObjectURL(svgBlob); + + const img = new Image(); + + img.onload = async () => { + try { + const bitmap = await createImageBitmap(img); + + const aspect = bitmap.width / bitmap.height; + const width = request?.options?.width ?? 1024; + const height = width / aspect; + const offscreen = new OffscreenCanvas(width, height); + const ctx = offscreen.getContext('2d'); + + if (!ctx) { + reject(new Error('Failed to get offscreen context.')); + return; + } + + ctx.fillStyle = 'white'; + ctx.fillRect(0, 0, width, height); + ctx.drawImage(bitmap, 0, 0, width, height); + + const outBlob = await offscreen.convertToBlob({ type: 'image/png' }); + const reader = new FileReader(); + reader.onloadend = () => { + URL.revokeObjectURL(url); + const result = reader.result as string; + this.actionDispatcher.dispatch( + ExportMcpPngAction.create(result.replace('data:image/png;base64,', ''), { + options: request?.options ?? { sessionId: '' } + }) + ); + resolve(); + }; + reader.readAsDataURL(outBlob); + } catch (err) { + reject(err); + } + }; + + img.onerror = () => { + URL.revokeObjectURL(url); + reject(new Error('Failed to load SVG into image element.')); + }; + + img.src = url; + }); + } +} + +/** + * See sprotty's `ExportSvgCommand` + */ +export class ExportMcpPngCommand extends HiddenCommand { + static readonly KIND = RequestExportMcpPngAction.KIND; + + constructor(@inject(TYPES.Action) protected action: RequestExportMcpPngAction) { + super(); + } + + execute(context: CommandExecutionContext): CommandResult { + if (isExportable(context.root)) { + const root = context.modelFactory.createRoot(context.root); + if (isExportable(root)) { + if (isViewport(root)) { + root.zoom = 1; + root.scroll = { x: 0, y: 0 }; + } + root.index.all().forEach(element => { + if (isSelectable(element) && element.selected) { + element.selected = false; + } + if (isHoverable(element) && element.hoverFeedback) { + element.hoverFeedback = false; + } + }); + return { + model: root, + modelChanged: true, + cause: this.action + }; + } + } + return { + model: context.root, + modelChanged: false + }; + } +} + +/** + * See sprotty's `ExportSvgPostprocessor` + */ +@injectable() +export class ExportMcpPngPostprocessor implements IVNodePostprocessor { + protected root: GModelRoot; + + @inject(GLSPMcpPngExporter) + protected pngExporter: GLSPMcpPngExporter; + + decorate(vnode: VNode, element: GModelElement): VNode { + if (element instanceof GModelRoot) { + this.root = element; + } + return vnode; + } + + postUpdate(cause?: Action): void { + if (this.root && cause !== undefined && cause.kind === RequestExportMcpPngAction.KIND) { + this.pngExporter.exportPng(this.root, cause as RequestExportMcpPngAction); + } + } +} diff --git a/packages/protocol/src/action-protocol/model-saving.ts b/packages/protocol/src/action-protocol/model-saving.ts index f09a05b9..9dc678ec 100644 --- a/packages/protocol/src/action-protocol/model-saving.ts +++ b/packages/protocol/src/action-protocol/model-saving.ts @@ -108,7 +108,7 @@ export namespace RequestExportSvgAction { } } -/** Configuration options for the {@link RequestExportSvgAction */ +/** Configuration options for the {@link RequestExportSvgAction} */ export interface ExportSvgOptions extends sprotty.ExportSvgOptions { // If set to false applied diagram styles will not be copied to the exported SVG skipCopyStyles?: boolean; @@ -143,3 +143,62 @@ export namespace ExportSvgAction { }; } } + +/** + * A `RequestExportMcpPngAction` is sent by the server to initiate the PNG export of the current diagram to provide visual information + * to an AI agent. + * The handler of this action is expected to retrieve the diagram PNG and should send a {@link ExportMcpPngAction} as response. + * Typically the {@link ExportMcpPngAction} is handled on the server side. + */ +export interface RequestExportMcpPngAction extends RequestAction { + kind: typeof RequestExportMcpPngAction.KIND; + options?: ExportMcpPngOptions; +} +export namespace RequestExportMcpPngAction { + export const KIND = 'requestExportMcpPng'; + + export function is(object: unknown): object is RequestExportMcpPngAction { + return RequestAction.hasKind(object, KIND); + } + + export function create(options: { options?: ExportMcpPngOptions; requestId?: string } = {}): RequestExportMcpPngAction { + return { + kind: KIND, + requestId: '', + ...options + }; + } +} + +/** Configuration options for the {@link RequestExportMcpPngAction} */ +export interface ExportMcpPngOptions extends ExportSvgOptions { + sessionId: string; + width?: number; +} + +/** + * The client sends an `ExportMcpPngAction` to communicate the diagram, which represents the current model state, in PNG format. + * The action only provides the diagram PNG as base64 string. + */ +export interface ExportMcpPngAction extends ResponseAction { + kind: typeof ExportMcpPngAction.KIND; + png: string; + options?: ExportMcpPngOptions; +} + +export namespace ExportMcpPngAction { + export const KIND = 'exportMcpPng'; + + export function is(object: unknown): object is ExportMcpPngAction { + return Action.hasKind(object, KIND) && hasStringProp(object, 'png'); + } + + export function create(png: string, options: { options?: ExportMcpPngOptions; responseId?: string } = {}): ExportMcpPngAction { + return { + kind: KIND, + png, + responseId: '', + ...options + }; + } +} From 0499a49a29a971cccbde4f6444f9db814afbd3b8 Mon Sep 17 00:00:00 2001 From: Andreas Hell Date: Wed, 11 Mar 2026 16:52:40 +0100 Subject: [PATCH 2/6] Added MCP selection --- packages/client/src/default-modules.ts | 4 +- packages/client/src/index.ts | 2 + packages/client/src/mcp/mcp-get-selection.ts | 58 ++++++++++++++++++++ packages/client/src/mcp/mcp-module.ts | 26 +++++++++ packages/protocol/src/action-protocol/mcp.ts | 55 +++++++++++++++++++ packages/protocol/src/index.ts | 1 + 6 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 packages/client/src/mcp/mcp-get-selection.ts create mode 100644 packages/client/src/mcp/mcp-module.ts create mode 100644 packages/protocol/src/action-protocol/mcp.ts diff --git a/packages/client/src/default-modules.ts b/packages/client/src/default-modules.ts index 1e630e59..d8b0f46d 100644 --- a/packages/client/src/default-modules.ts +++ b/packages/client/src/default-modules.ts @@ -63,6 +63,7 @@ import { toolFocusLossModule } from './features/tools/tool-focus-loss-module'; import { markerNavigatorModule, validationModule } from './features/validation/validation-modules'; import { viewportModule } from './features/viewport/viewport-modules'; import { zorderModule } from './features/zorder/zorder-module'; +import { mcpModule } from './mcp/mcp-module'; export const DEFAULT_MODULES = [ defaultModule, @@ -104,7 +105,8 @@ export const DEFAULT_MODULES = [ statusModule, resizeModule, searchPaletteModule, - searchPaletteDefaultSuggestionsModule + searchPaletteDefaultSuggestionsModule, + mcpModule ] as const; /** diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 8d197159..2bc29166 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -230,6 +230,8 @@ export * from './features/viewport/viewport-tool'; export * from './features/viewport/zoom-viewport-action'; export * from './features/zorder/bring-to-front-command'; export * from './features/zorder/zorder-module'; +export * from './mcp/mcp-get-selection'; +export * from './mcp/mcp-module'; export * from './model'; export * from './re-exports'; export * from './standalone-modules'; diff --git a/packages/client/src/mcp/mcp-get-selection.ts b/packages/client/src/mcp/mcp-get-selection.ts new file mode 100644 index 00000000..3dcc975a --- /dev/null +++ b/packages/client/src/mcp/mcp-get-selection.ts @@ -0,0 +1,58 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { + Command, + CommandExecutionContext, + CommandReturn, + GetSelectionMcpAction, + IActionDispatcher, + isSelectable, + SelectionMcpResult, + toArray, + TYPES +} from '@eclipse-glsp/sprotty'; +import { inject, injectable } from 'inversify'; + +@injectable() +export class GetSelectionMcpCommand extends Command { + static readonly KIND = GetSelectionMcpAction.KIND; + + @inject(TYPES.IActionDispatcher) + protected actionDispatcher: IActionDispatcher; + + constructor(@inject(TYPES.Action) protected readonly action: GetSelectionMcpAction) { + super(); + } + + execute(context: CommandExecutionContext): CommandReturn { + const selection = context.root.index + .all() + .filter(e => isSelectable(e) && e.selected) + .map(e => e.id); + const result = SelectionMcpResult.create(toArray(selection), this.action.mcpRequestId); + this.actionDispatcher.dispatch(result); + return { model: context.root, modelChanged: false }; + } + + undo(context: CommandExecutionContext): CommandReturn { + return { model: context.root, modelChanged: false }; + } + + redo(context: CommandExecutionContext): CommandReturn { + return { model: context.root, modelChanged: false }; + } +} diff --git a/packages/client/src/mcp/mcp-module.ts b/packages/client/src/mcp/mcp-module.ts new file mode 100644 index 00000000..8c1db3fa --- /dev/null +++ b/packages/client/src/mcp/mcp-module.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { configureCommand, FeatureModule } from '@eclipse-glsp/sprotty'; +import { GetSelectionMcpCommand } from './mcp-get-selection'; + +export const mcpModule = new FeatureModule( + (bind, _unbind, isBound) => { + configureCommand({ bind, isBound }, GetSelectionMcpCommand); + // TODO move export png here + }, + { featureId: Symbol('mcp') } +); diff --git a/packages/protocol/src/action-protocol/mcp.ts b/packages/protocol/src/action-protocol/mcp.ts new file mode 100644 index 00000000..e0709f66 --- /dev/null +++ b/packages/protocol/src/action-protocol/mcp.ts @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2026 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action } from './base-protocol'; + +/** + * Request action for retrieving the current selection. + */ +export interface GetSelectionMcpAction extends Action { + kind: typeof GetSelectionMcpAction.KIND; + mcpRequestId: string; +} +export namespace GetSelectionMcpAction { + export const KIND = 'GetSelectionMcpAction'; + + export function create(mcpRequestId: string): GetSelectionMcpAction { + return { + kind: KIND, + mcpRequestId + }; + } +} + +/** + * Result for a `GetSelectionMcpAction`. + */ +export interface SelectionMcpResult extends Action { + kind: typeof SelectionMcpResult.KIND; + mcpRequestId: string; + selectedElementsIDs: string[]; +} +export namespace SelectionMcpResult { + export const KIND = 'SelectionMcpResult'; + + export function create(selectedElementsIDs: string[], mcpRequestId: string): SelectionMcpResult { + return { + kind: KIND, + selectedElementsIDs, + mcpRequestId + }; + } +} diff --git a/packages/protocol/src/index.ts b/packages/protocol/src/index.ts index d2504d73..2496a945 100644 --- a/packages/protocol/src/index.ts +++ b/packages/protocol/src/index.ts @@ -25,6 +25,7 @@ export * from './action-protocol/element-selection'; export * from './action-protocol/element-text-editing'; export * from './action-protocol/element-type-hints'; export * from './action-protocol/element-validation'; +export * from './action-protocol/mcp'; export * from './action-protocol/model-data'; export * from './action-protocol/model-edit-mode'; export * from './action-protocol/model-layout'; From 68e00653caf6555036f9aff9cec842da0aedbad6 Mon Sep 17 00:00:00 2001 From: Andreas Hell Date: Wed, 11 Mar 2026 17:17:36 +0100 Subject: [PATCH 3/6] Restructured MCP png components --- .../src/features/export/export-modules.ts | 6 -- packages/client/src/index.ts | 1 + .../mcp-export-png.ts} | 32 +++++--- packages/client/src/mcp/mcp-get-selection.ts | 4 +- packages/client/src/mcp/mcp-module.ts | 9 ++- packages/protocol/src/action-protocol/mcp.ts | 80 +++++++++++++++++-- .../src/action-protocol/model-saving.ts | 59 -------------- 7 files changed, 104 insertions(+), 87 deletions(-) rename packages/client/src/{features/export/mcp-png-export.ts => mcp/mcp-export-png.ts} (85%) diff --git a/packages/client/src/features/export/export-modules.ts b/packages/client/src/features/export/export-modules.ts index 53c7a47a..820dfd5e 100644 --- a/packages/client/src/features/export/export-modules.ts +++ b/packages/client/src/features/export/export-modules.ts @@ -26,7 +26,6 @@ import { } from '@eclipse-glsp/sprotty'; import { ExportSvgActionHandler } from './export-svg-action-handler'; import { GLSPSvgExporter } from './glsp-svg-exporter'; -import { ExportMcpPngCommand, ExportMcpPngPostprocessor, GLSPMcpPngExporter } from './mcp-png-export'; export const exportModule = new FeatureModule( (bind, _unbind, isBound) => { @@ -34,11 +33,6 @@ export const exportModule = new FeatureModule( bindAsService(context, TYPES.HiddenVNodePostprocessor, ExportSvgPostprocessor); configureCommand(context, ExportSvgCommand); bind(TYPES.SvgExporter).to(GLSPSvgExporter).inSingletonScope(); - - bind(ExportMcpPngPostprocessor).toSelf().inSingletonScope(); - bind(TYPES.HiddenVNodePostprocessor).toService(ExportMcpPngPostprocessor); - configureCommand({ bind, isBound }, ExportMcpPngCommand); - bind(GLSPMcpPngExporter).toSelf().inSingletonScope(); }, { featureId: Symbol('export') } ); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 2bc29166..8be93877 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -230,6 +230,7 @@ export * from './features/viewport/viewport-tool'; export * from './features/viewport/zoom-viewport-action'; export * from './features/zorder/bring-to-front-command'; export * from './features/zorder/zorder-module'; +export * from './mcp/mcp-export-png'; export * from './mcp/mcp-get-selection'; export * from './mcp/mcp-module'; export * from './model'; diff --git a/packages/client/src/features/export/mcp-png-export.ts b/packages/client/src/mcp/mcp-export-png.ts similarity index 85% rename from packages/client/src/features/export/mcp-png-export.ts rename to packages/client/src/mcp/mcp-export-png.ts index 964b97c5..f7eb5083 100644 --- a/packages/client/src/features/export/mcp-png-export.ts +++ b/packages/client/src/mcp/mcp-export-png.ts @@ -18,7 +18,8 @@ import { Action, CommandExecutionContext, CommandResult, - ExportMcpPngAction, + ExportPngMcpAction, + ExportPngMcpActionResult, GModelElement, GModelRoot, HiddenCommand, @@ -27,12 +28,11 @@ import { isSelectable, isViewport, IVNodePostprocessor, - RequestExportMcpPngAction, TYPES } from '@eclipse-glsp/sprotty'; import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { GLSPSvgExporter } from './glsp-svg-exporter'; +import { GLSPSvgExporter } from '../features/export/glsp-svg-exporter'; /** * This class extends {@link GLSPSvgExporter} in order to make use of the SVG creation logic. @@ -42,7 +42,11 @@ import { GLSPSvgExporter } from './glsp-svg-exporter'; */ @injectable() export class GLSPMcpPngExporter extends GLSPSvgExporter { - async exportPng(root: GModelRoot, request?: RequestExportMcpPngAction): Promise { + async exportPng(root: GModelRoot, request?: ExportPngMcpAction): Promise { + if (!request) { + return; + } + return new Promise((resolve, reject) => { if (typeof document === 'undefined') { reject(new Error('Failed to find document.')); @@ -89,9 +93,11 @@ export class GLSPMcpPngExporter extends GLSPSvgExporter { URL.revokeObjectURL(url); const result = reader.result as string; this.actionDispatcher.dispatch( - ExportMcpPngAction.create(result.replace('data:image/png;base64,', ''), { - options: request?.options ?? { sessionId: '' } - }) + ExportPngMcpActionResult.create( + result.replace('data:image/png;base64,', ''), + request.mcpRequestId, + request.options + ) ); resolve(); }; @@ -114,10 +120,10 @@ export class GLSPMcpPngExporter extends GLSPSvgExporter { /** * See sprotty's `ExportSvgCommand` */ -export class ExportMcpPngCommand extends HiddenCommand { - static readonly KIND = RequestExportMcpPngAction.KIND; +export class ExportPngMcpCommand extends HiddenCommand { + static readonly KIND = ExportPngMcpAction.KIND; - constructor(@inject(TYPES.Action) protected action: RequestExportMcpPngAction) { + constructor(@inject(TYPES.Action) protected action: ExportPngMcpAction) { super(); } @@ -155,7 +161,7 @@ export class ExportMcpPngCommand extends HiddenCommand { * See sprotty's `ExportSvgPostprocessor` */ @injectable() -export class ExportMcpPngPostprocessor implements IVNodePostprocessor { +export class ExportPngMcpPostprocessor implements IVNodePostprocessor { protected root: GModelRoot; @inject(GLSPMcpPngExporter) @@ -169,8 +175,8 @@ export class ExportMcpPngPostprocessor implements IVNodePostprocessor { } postUpdate(cause?: Action): void { - if (this.root && cause !== undefined && cause.kind === RequestExportMcpPngAction.KIND) { - this.pngExporter.exportPng(this.root, cause as RequestExportMcpPngAction); + if (this.root && cause !== undefined && cause.kind === ExportPngMcpAction.KIND) { + this.pngExporter.exportPng(this.root, cause as ExportPngMcpAction); } } } diff --git a/packages/client/src/mcp/mcp-get-selection.ts b/packages/client/src/mcp/mcp-get-selection.ts index 3dcc975a..3e247aa3 100644 --- a/packages/client/src/mcp/mcp-get-selection.ts +++ b/packages/client/src/mcp/mcp-get-selection.ts @@ -19,9 +19,9 @@ import { CommandExecutionContext, CommandReturn, GetSelectionMcpAction, + GetSelectionMcpResultAction, IActionDispatcher, isSelectable, - SelectionMcpResult, toArray, TYPES } from '@eclipse-glsp/sprotty'; @@ -43,7 +43,7 @@ export class GetSelectionMcpCommand extends Command { .all() .filter(e => isSelectable(e) && e.selected) .map(e => e.id); - const result = SelectionMcpResult.create(toArray(selection), this.action.mcpRequestId); + const result = GetSelectionMcpResultAction.create(toArray(selection), this.action.mcpRequestId); this.actionDispatcher.dispatch(result); return { model: context.root, modelChanged: false }; } diff --git a/packages/client/src/mcp/mcp-module.ts b/packages/client/src/mcp/mcp-module.ts index 8c1db3fa..216b1deb 100644 --- a/packages/client/src/mcp/mcp-module.ts +++ b/packages/client/src/mcp/mcp-module.ts @@ -14,13 +14,18 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { configureCommand, FeatureModule } from '@eclipse-glsp/sprotty'; +import { configureCommand, FeatureModule, TYPES } from '@eclipse-glsp/sprotty'; +import { ExportPngMcpCommand, ExportPngMcpPostprocessor, GLSPMcpPngExporter } from './mcp-export-png'; import { GetSelectionMcpCommand } from './mcp-get-selection'; export const mcpModule = new FeatureModule( (bind, _unbind, isBound) => { configureCommand({ bind, isBound }, GetSelectionMcpCommand); - // TODO move export png here + + bind(ExportPngMcpPostprocessor).toSelf().inSingletonScope(); + bind(TYPES.HiddenVNodePostprocessor).toService(ExportPngMcpPostprocessor); + configureCommand({ bind, isBound }, ExportPngMcpCommand); + bind(GLSPMcpPngExporter).toSelf().inSingletonScope(); }, { featureId: Symbol('mcp') } ); diff --git a/packages/protocol/src/action-protocol/mcp.ts b/packages/protocol/src/action-protocol/mcp.ts index e0709f66..8dd5ae23 100644 --- a/packages/protocol/src/action-protocol/mcp.ts +++ b/packages/protocol/src/action-protocol/mcp.ts @@ -14,7 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { hasStringProp } from '../utils/type-util'; import { Action } from './base-protocol'; +import { ExportSvgOptions } from './model-saving'; /** * Request action for retrieving the current selection. @@ -26,6 +28,10 @@ export interface GetSelectionMcpAction extends Action { export namespace GetSelectionMcpAction { export const KIND = 'GetSelectionMcpAction'; + export function is(object: unknown): object is GetSelectionMcpAction { + return Action.hasKind(object, KIND); + } + export function create(mcpRequestId: string): GetSelectionMcpAction { return { kind: KIND, @@ -37,15 +43,19 @@ export namespace GetSelectionMcpAction { /** * Result for a `GetSelectionMcpAction`. */ -export interface SelectionMcpResult extends Action { - kind: typeof SelectionMcpResult.KIND; +export interface GetSelectionMcpResultAction extends Action { + kind: typeof GetSelectionMcpResultAction.KIND; mcpRequestId: string; selectedElementsIDs: string[]; } -export namespace SelectionMcpResult { - export const KIND = 'SelectionMcpResult'; +export namespace GetSelectionMcpResultAction { + export const KIND = 'GetSelectionMcpResultAction'; - export function create(selectedElementsIDs: string[], mcpRequestId: string): SelectionMcpResult { + export function is(object: unknown): object is GetSelectionMcpResultAction { + return Action.hasKind(object, KIND); + } + + export function create(selectedElementsIDs: string[], mcpRequestId: string): GetSelectionMcpResultAction { return { kind: KIND, selectedElementsIDs, @@ -53,3 +63,63 @@ export namespace SelectionMcpResult { }; } } + +/** + * A `ExportPngMcpAction` is sent by the server to initiate the PNG export of the current diagram to provide visual information + * to an AI agent. + * The handler of this action is expected to retrieve the diagram PNG and should send a {@link ExportPngMcpActionResult} as response. + * Typically the {@link ExportPngMcpActionResult} is handled on the server side. + */ +export interface ExportPngMcpAction extends Action { + kind: typeof ExportPngMcpAction.KIND; + mcpRequestId: string; + options?: ExportPngOptions; +} +export namespace ExportPngMcpAction { + export const KIND = 'ExportPngMcpAction'; + + export function is(object: unknown): object is ExportPngMcpAction { + return Action.hasKind(object, KIND); + } + + export function create(mcpRequestId: string, options: ExportPngOptions = {}): ExportPngMcpAction { + return { + kind: KIND, + mcpRequestId, + ...options + }; + } +} + +/** Configuration options for the {@link ExportPngMcpAction} */ +export interface ExportPngOptions extends ExportSvgOptions { + width?: number; +} + +/** + * The client sends an `ExportPngMcpActionResult` to communicate the diagram, which represents the current model state, in PNG format. + * The action only provides the diagram PNG as base64 string. + */ +export interface ExportPngMcpActionResult extends Action { + kind: typeof ExportPngMcpActionResult.KIND; + mcpRequestId: string; + png: string; + options?: ExportPngOptions; +} + +export namespace ExportPngMcpActionResult { + export const KIND = 'ExportPngMcpActionResult'; + + export function is(object: unknown): object is ExportPngMcpActionResult { + return Action.hasKind(object, KIND) && hasStringProp(object, 'png'); + } + + export function create(png: string, mcpRequestId: string, options: ExportPngOptions = {}): ExportPngMcpActionResult { + return { + kind: KIND, + mcpRequestId, + png, + ...options + }; + } +} diff --git a/packages/protocol/src/action-protocol/model-saving.ts b/packages/protocol/src/action-protocol/model-saving.ts index 9dc678ec..c038d10a 100644 --- a/packages/protocol/src/action-protocol/model-saving.ts +++ b/packages/protocol/src/action-protocol/model-saving.ts @@ -143,62 +143,3 @@ export namespace ExportSvgAction { }; } } - -/** - * A `RequestExportMcpPngAction` is sent by the server to initiate the PNG export of the current diagram to provide visual information - * to an AI agent. - * The handler of this action is expected to retrieve the diagram PNG and should send a {@link ExportMcpPngAction} as response. - * Typically the {@link ExportMcpPngAction} is handled on the server side. - */ -export interface RequestExportMcpPngAction extends RequestAction { - kind: typeof RequestExportMcpPngAction.KIND; - options?: ExportMcpPngOptions; -} -export namespace RequestExportMcpPngAction { - export const KIND = 'requestExportMcpPng'; - - export function is(object: unknown): object is RequestExportMcpPngAction { - return RequestAction.hasKind(object, KIND); - } - - export function create(options: { options?: ExportMcpPngOptions; requestId?: string } = {}): RequestExportMcpPngAction { - return { - kind: KIND, - requestId: '', - ...options - }; - } -} - -/** Configuration options for the {@link RequestExportMcpPngAction} */ -export interface ExportMcpPngOptions extends ExportSvgOptions { - sessionId: string; - width?: number; -} - -/** - * The client sends an `ExportMcpPngAction` to communicate the diagram, which represents the current model state, in PNG format. - * The action only provides the diagram PNG as base64 string. - */ -export interface ExportMcpPngAction extends ResponseAction { - kind: typeof ExportMcpPngAction.KIND; - png: string; - options?: ExportMcpPngOptions; -} - -export namespace ExportMcpPngAction { - export const KIND = 'exportMcpPng'; - - export function is(object: unknown): object is ExportMcpPngAction { - return Action.hasKind(object, KIND) && hasStringProp(object, 'png'); - } - - export function create(png: string, options: { options?: ExportMcpPngOptions; responseId?: string } = {}): ExportMcpPngAction { - return { - kind: KIND, - png, - responseId: '', - ...options - }; - } -} From 50d1abfaacffb29d986b5918a83d2d45b4d78c1d Mon Sep 17 00:00:00 2001 From: Andreas Hell Date: Wed, 11 Mar 2026 17:58:10 +0100 Subject: [PATCH 4/6] Adjusted workflow JSON --- examples/workflow-standalone/app/example1.wf | 300 ++++++++++++------- 1 file changed, 198 insertions(+), 102 deletions(-) diff --git a/examples/workflow-standalone/app/example1.wf b/examples/workflow-standalone/app/example1.wf index 5500e816..0aa67475 100644 --- a/examples/workflow-standalone/app/example1.wf +++ b/examples/workflow-standalone/app/example1.wf @@ -6,7 +6,7 @@ "y": 0 }, "id": "sprotty", - "revision": 1, + "revision": 2, "children": [ { "cssClasses": [ @@ -14,7 +14,7 @@ "automated" ], "type": "task:automated", - "id": "task_ChkTp", + "id": "task:automated_23lk", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -41,7 +41,7 @@ { "cssClasses": [], "type": "icon", - "id": "task_ChkTp_icon", + "id": "task:automated_23lk_icon", "position": { "x": 5, "y": 5 @@ -56,17 +56,17 @@ "cssClasses": [], "type": "label:heading", "alignment": { - "x": 20.6171875, - "y": 13 + "x": 20.616666793823242, + "y": 12.800000190734863 }, - "id": "task_ChkTp_label", + "id": "task:automated_23lk_label", "text": "ChkTp", "position": { "x": 31, "y": 7 }, "size": { - "width": 41.4482421875, + "width": 41.233333587646484, "height": 16 }, "children": [] @@ -79,7 +79,7 @@ "manual" ], "type": "task:manual", - "id": "task_Push", + "id": "task:manual_j3l4", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -106,7 +106,7 @@ { "cssClasses": [], "type": "icon", - "id": "task_Push_icon", + "id": "task:manual_j3l4_icon", "position": { "x": 5, "y": 5 @@ -121,17 +121,17 @@ "cssClasses": [], "type": "label:heading", "alignment": { - "x": 15.9609375, - "y": 13 + "x": 15.949999809265137, + "y": 12.800000190734863 }, - "id": "task_Push_label", + "id": "task:manual_j3l4_label", "text": "Push", "position": { "x": 31, "y": 7 }, "size": { - "width": 31.921875, + "width": 31.899999618530273, "height": 16 }, "children": [] @@ -143,7 +143,7 @@ "forkOrJoin" ], "type": "activityNode:fork", - "id": "fork_1", + "id": "activityNode:fork_4c22", "position": { "x": 170, "y": 90 @@ -159,18 +159,30 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_task_Push_fork_1", - "sourceId": "task_Push", - "targetId": "fork_1", + "id": "edge_123e", + "sourceId": "task:manual_j3l4", + "targetId": "activityNode:fork_4c22", + "args": { + "edgeSourcePointX": 142.921875, + "edgeSourcePointY": 115, + "edgeTargetPointX": 169.5, + "edgeTargetPointY": 115 + }, "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_fork1_task_ChkTp", - "sourceId": "fork_1", - "targetId": "task_ChkTp", + "id": "edge_c344", + "sourceId": "activityNode:fork_4c22", + "targetId": "task:automated_23lk", + "args": { + "edgeSourcePointX": 179.9129171124612, + "edgeSourcePointY": 117.84892269711828, + "edgeTargetPointX": 235.356884765625, + "edgeTargetPointY": 150 + }, "children": [] }, { @@ -179,7 +191,7 @@ "automated" ], "type": "task:automated", - "id": "task_ChkWt", + "id": "task:automated_223c", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -206,7 +218,7 @@ { "cssClasses": [], "type": "icon", - "id": "task_ChkWt_icon", + "id": "task:automated_223c_icon", "position": { "x": 5, "y": 5 @@ -222,16 +234,16 @@ "type": "label:heading", "alignment": { "x": 21, - "y": 13 + "y": 12.800000190734863 }, - "id": "task_ChkWt_label", + "id": "task:automated_223c_label", "text": "ChkWt", "position": { "x": 31, "y": 7 }, "size": { - "width": 42.1103515625, + "width": 42, "height": 16 }, "children": [] @@ -243,7 +255,7 @@ "decision" ], "type": "activityNode:decision", - "id": "decision_1", + "id": "activityNode:decision_6n87", "position": { "x": 330, "y": 50 @@ -266,7 +278,7 @@ "decision" ], "type": "activityNode:decision", - "id": "decision2", + "id": "activityNode:decision_caqw", "position": { "x": 330, "y": 150 @@ -288,27 +300,45 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_task_ChkWt_decision1", - "sourceId": "task_ChkWt", - "targetId": "decision_1", + "id": "edge_23c1", + "sourceId": "task:automated_223c", + "targetId": "activityNode:decision_6n87", + "args": { + "edgeSourcePointX": 303.1103515625, + "edgeSourcePointY": 65.49209855270233, + "edgeTargetPointX": 329.68729039942264, + "edgeTargetPointY": 65.80682404455813 + }, "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_fork1_task_ChkWt", - "sourceId": "fork_1", - "targetId": "task_ChkWt", + "id": "edge_4v56", + "sourceId": "activityNode:fork_4c22", + "targetId": "task:automated_223c", + "args": { + "edgeSourcePointX": 179.91511482913953, + "edgeSourcePointY": 112.16070426477935, + "edgeTargetPointX": 235.588623046875, + "edgeTargetPointY": 80 + }, "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_task_ChkTp_decision2", - "sourceId": "task_ChkTp", - "targetId": "decision2", + "id": "edge_1239", + "sourceId": "task:automated_23lk", + "targetId": "activityNode:decision_caqw", + "args": { + "edgeSourcePointX": 302.4482421875, + "edgeSourcePointY": 165.48627182195702, + "edgeTargetPointX": 329.686567408344, + "edgeTargetPointY": 165.80756987952083 + }, "children": [] }, { @@ -317,7 +347,7 @@ "automated" ], "type": "task:automated", - "id": "task_PreHeat", + "id": "task:automated_v45l", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -344,7 +374,7 @@ { "cssClasses": [], "type": "icon", - "id": "task_PreHeat_icon", + "id": "task:automated_v45l_icon", "position": { "x": 5, "y": 5 @@ -359,17 +389,17 @@ "cssClasses": [], "type": "label:heading", "alignment": { - "x": 25.6796875, - "y": 13 + "x": 25.67500114440918, + "y": 12.800000190734863 }, - "id": "task_PreHeat_label", + "id": "task:automated_v45l_label", "text": "PreHeat", "position": { "x": 31, "y": 7 }, "size": { - "width": 51.46875, + "width": 51.349998474121094, "height": 16 }, "children": [] @@ -382,7 +412,7 @@ "automated" ], "type": "task:automated", - "id": "task_KeepTp", + "id": "task:automated_193c", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -409,7 +439,7 @@ { "cssClasses": [], "type": "icon", - "id": "task_KeepTp_icon", + "id": "task:automated_193c_icon", "position": { "x": 5, "y": 5 @@ -424,17 +454,17 @@ "cssClasses": [], "type": "label:heading", "alignment": { - "x": 24.5234375, - "y": 13 + "x": 24.508333206176758, + "y": 12.800000190734863 }, - "id": "task_KeepTp_label", + "id": "task:automated_193c_label", "text": "KeepTp", "position": { "x": 31, "y": 7 }, "size": { - "width": 49.248046875, + "width": 49.016666412353516, "height": 16 }, "children": [] @@ -447,7 +477,7 @@ "manual" ], "type": "task:manual", - "id": "task_RflWt", + "id": "task:manual_2c39", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -474,7 +504,7 @@ { "cssClasses": [], "type": "icon", - "id": "task_RflWt_icon", + "id": "task:manual_2c39_icon", "position": { "x": 5, "y": 5 @@ -489,17 +519,17 @@ "cssClasses": [], "type": "label:heading", "alignment": { - "x": 17.109375, - "y": 13 + "x": 17.108333587646484, + "y": 12.800000190734863 }, - "id": "task_RflWt_label", + "id": "task:manual_2c39_label", "text": "RflWt", "position": { "x": 31, "y": 7 }, "size": { - "width": 34.32421875, + "width": 34.21666717529297, "height": 16 }, "children": [] @@ -512,7 +542,7 @@ "automated" ], "type": "task:automated", - "id": "task_WtOK", + "id": "task:automated_0954", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -539,7 +569,7 @@ { "cssClasses": [], "type": "icon", - "id": "task_WtOK_icon", + "id": "task:automated_0954_icon", "position": { "x": 5, "y": 5 @@ -554,17 +584,17 @@ "cssClasses": [], "type": "label:heading", "alignment": { - "x": 18.671875, - "y": 13 + "x": 18.65833282470703, + "y": 12.800000190734863 }, - "id": "task_WtOK_label", + "id": "task:automated_0954_label", "text": "WtOK", "position": { "x": 31, "y": 7 }, "size": { - "width": 37.9931640625, + "width": 37.31666564941406, "height": 16 }, "children": [] @@ -577,10 +607,16 @@ ], "type": "edge:weighted", "routingPoints": [], - "id": "edge_decision1_task_RflWt", - "sourceId": "decision_1", - "targetId": "task_RflWt", + "id": "edge_etvr", + "sourceId": "activityNode:decision_6n87", + "targetId": "task:manual_2c39", "probability": "medium", + "args": { + "edgeSourcePointX": 358.0649068859051, + "edgeSourcePointY": 61.42000425502652, + "edgeTargetPointX": 390, + "edgeTargetPointY": 49.29702709813207 + }, "children": [] }, { @@ -589,10 +625,16 @@ ], "type": "edge:weighted", "routingPoints": [], - "id": "edge_decision_1_task_WtOK", - "sourceId": "decision_1", - "targetId": "task_WtOK", + "id": "edge_aerv", + "sourceId": "activityNode:decision_6n87", + "targetId": "task:automated_0954", "probability": "medium", + "args": { + "edgeSourcePointX": 358.34775282071627, + "edgeSourcePointY": 70.28861664860428, + "edgeTargetPointX": 390, + "edgeTargetPointY": 81.28206267799604 + }, "children": [] }, { @@ -601,10 +643,16 @@ ], "type": "edge:weighted", "routingPoints": [], - "id": "edege_decision_2_task_KeepTp", - "sourceId": "decision2", - "targetId": "task_KeepTp", + "id": "edge_345v", + "sourceId": "activityNode:decision_caqw", + "targetId": "task:automated_193c", "probability": "medium", + "args": { + "edgeSourcePointX": 359.4355670963364, + "edgeSourcePointY": 162.83422159210613, + "edgeTargetPointX": 390, + "edgeTargetPointY": 155.6324249695934 + }, "children": [] }, { @@ -613,10 +661,16 @@ ], "type": "edge:weighted", "routingPoints": [], - "id": "edege_decision2_task_PreHeat", - "sourceId": "decision2", - "targetId": "task_PreHeat", + "id": "edge_3nzh", + "sourceId": "activityNode:decision_caqw", + "targetId": "task:automated_v45l", "probability": "medium", + "args": { + "edgeSourcePointX": 358.5845250668064, + "edgeSourcePointY": 170.0444811296957, + "edgeTargetPointX": 390, + "edgeTargetPointY": 180.14095238095237 + }, "children": [] }, { @@ -624,7 +678,7 @@ "forkOrJoin" ], "type": "activityNode:join", - "id": "join_1", + "id": "activityNode:join_45v7", "position": { "x": 560, "y": 90 @@ -642,7 +696,7 @@ "automated" ], "type": "task:automated", - "id": "task_Brew", + "id": "task:automated_vero", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -669,7 +723,7 @@ { "cssClasses": [], "type": "icon", - "id": "task_Brew_icon", + "id": "task:automated_vero_icon", "position": { "x": 5, "y": 5 @@ -684,17 +738,17 @@ "cssClasses": [], "type": "label:heading", "alignment": { - "x": 15.953125, - "y": 13 + "x": 15.949999809265137, + "y": 12.800000190734863 }, - "id": "task_Brew_label", + "id": "task:automated_vero_label", "text": "Brew", "position": { "x": 31, "y": 7 }, "size": { - "width": 31.90625, + "width": 31.899999618530273, "height": 16 }, "children": [] @@ -706,7 +760,7 @@ "merge" ], "type": "activityNode:merge", - "id": "merge_1", + "id": "activityNode:merge_4v59", "position": { "x": 500, "y": 50 @@ -729,7 +783,7 @@ "merge" ], "type": "activityNode:merge", - "id": "merge_2", + "id": "activityNode:merge_4c34", "position": { "x": 500, "y": 150 @@ -751,63 +805,105 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_task_RflWt_merge_1", - "sourceId": "task_RflWt", - "targetId": "merge_1", + "id": "edge_cnhg", + "sourceId": "task:manual_2c39", + "targetId": "activityNode:merge_4v59", + "args": { + "edgeSourcePointX": 465.32421875, + "edgeSourcePointY": 48.21658670322138, + "edgeTargetPointX": 503.68447295370714, + "edgeTargetPointY": 61.678170083823204 + }, "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_task_WTOK_merge_1", - "sourceId": "task_WtOK", - "targetId": "merge_1", + "id": "edge_n678", + "sourceId": "task:automated_0954", + "targetId": "activityNode:merge_4v59", + "args": { + "edgeSourcePointX": 468.9931640625, + "edgeSourcePointY": 81.75889455235128, + "edgeTargetPointX": 503.5431283350021, + "edgeTargetPointY": 70.17612721864288 + }, "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_task_KeepTp_merge_2", - "sourceId": "task_KeepTp", - "targetId": "merge_2", + "id": "edge_l8th", + "sourceId": "task:automated_193c", + "targetId": "activityNode:merge_4c34", + "args": { + "edgeSourcePointX": 480.248046875, + "edgeSourcePointY": 156.71676105147495, + "edgeTargetPointX": 502.8141762197149, + "edgeTargetPointY": 162.576210746933 + }, "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edege_task_PreHeat_merge_2", - "sourceId": "task_PreHeat", - "targetId": "merge_2", + "id": "edge_1x61", + "sourceId": "task:automated_v45l", + "targetId": "activityNode:merge_4c34", + "args": { + "edgeSourcePointX": 477.49245689655174, + "edgeSourcePointY": 180, + "edgeTargetPointX": 503.79614626691165, + "edgeTargetPointY": 170.43689569610422 + }, "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_merge_2_join_1", - "sourceId": "merge_2", - "targetId": "join_1", + "id": "edge_8n46", + "sourceId": "activityNode:merge_4c34", + "targetId": "activityNode:join_45v7", + "args": { + "edgeSourcePointX": 529.6166551369032, + "edgeSourcePointY": 168.97864331119757, + "edgeTargetPointX": 567.7910011819208, + "edgeTargetPointY": 127.09433845499017 + }, "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_merge_1_join_1", - "sourceId": "merge_1", - "targetId": "join_1", + "id": "edge_v435", + "sourceId": "activityNode:merge_4v59", + "targetId": "activityNode:join_45v7", + "args": { + "edgeSourcePointX": 524.3535533905932, + "edgeSourcePointY": 74.35355339059328, + "edgeTargetPointX": 560.4797799427402, + "edgeTargetPointY": 110.47977994274005 + }, "children": [] }, { "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_join_1_task_Brew", - "sourceId": "join_1", - "targetId": "task_Brew", + "id": "edge_v45z", + "sourceId": "activityNode:join_45v7", + "targetId": "task:automated_vero", + "args": { + "edgeSourcePointX": 570.5, + "edgeSourcePointY": 115, + "edgeTargetPointX": 600, + "edgeTargetPointY": 115 + }, "children": [] } ] From 48ff53d2d3da43e7422693ecc7507c53d452f503 Mon Sep 17 00:00:00 2001 From: Andreas Hell Date: Tue, 24 Mar 2026 22:03:32 +0100 Subject: [PATCH 5/6] Moved feature flags to initialize parameters --- .../src/client-server-protocol/mcp.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/protocol/src/client-server-protocol/mcp.ts b/packages/protocol/src/client-server-protocol/mcp.ts index a45a7794..0542e4f7 100644 --- a/packages/protocol/src/client-server-protocol/mcp.ts +++ b/packages/protocol/src/client-server-protocol/mcp.ts @@ -16,6 +16,27 @@ import { InitializeParameters, InitializeResult } from './types'; +export interface McpServerOptions { + /** + * Changes how resources are registered. + * This is relevant since some MCP clients are unable to deal with MCP resource endpoints + * and thus they must be provided as tools. + * + * true -> MCP resources + * + * false -> MCP tools + * + * @default false + */ + resources?: boolean; + /** + * Changes string-based IDs to integer strings for MCP communication + * + * @default true + */ + aliasIds?: boolean; +} + export interface McpServerConfiguration { /** * The host on which the MCP server should be started. @@ -33,6 +54,10 @@ export interface McpServerConfiguration { * The name of the MCP server. */ name?: string; + /** + * Additional features + */ + options?: McpServerOptions; } export interface McpInitializeParameters extends InitializeParameters { From af5dc7a42c0d4144a18de90afd6411dcd1942789 Mon Sep 17 00:00:00 2001 From: Andreas Hell Date: Wed, 25 Mar 2026 18:53:53 +0100 Subject: [PATCH 6/6] Changed to UUIDs in example --- examples/workflow-standalone/app/example1.wf | 156 +++++++++---------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/examples/workflow-standalone/app/example1.wf b/examples/workflow-standalone/app/example1.wf index 0aa67475..c0f13378 100644 --- a/examples/workflow-standalone/app/example1.wf +++ b/examples/workflow-standalone/app/example1.wf @@ -14,7 +14,7 @@ "automated" ], "type": "task:automated", - "id": "task:automated_23lk", + "id": "8bec1e4d-6154-4845-9edb-f55512b8d892", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -41,7 +41,7 @@ { "cssClasses": [], "type": "icon", - "id": "task:automated_23lk_icon", + "id": "8bec1e4d-6154-4845-9edb-f55512b8d892_icon", "position": { "x": 5, "y": 5 @@ -59,7 +59,7 @@ "x": 20.616666793823242, "y": 12.800000190734863 }, - "id": "task:automated_23lk_label", + "id": "8bec1e4d-6154-4845-9edb-f55512b8d892_label", "text": "ChkTp", "position": { "x": 31, @@ -79,7 +79,7 @@ "manual" ], "type": "task:manual", - "id": "task:manual_j3l4", + "id": "1127ebe7-fc0f-4383-9be0-4b6e28dd2a33", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -106,7 +106,7 @@ { "cssClasses": [], "type": "icon", - "id": "task:manual_j3l4_icon", + "id": "1127ebe7-fc0f-4383-9be0-4b6e28dd2a33_icon", "position": { "x": 5, "y": 5 @@ -124,7 +124,7 @@ "x": 15.949999809265137, "y": 12.800000190734863 }, - "id": "task:manual_j3l4_label", + "id": "1127ebe7-fc0f-4383-9be0-4b6e28dd2a33_label", "text": "Push", "position": { "x": 31, @@ -143,7 +143,7 @@ "forkOrJoin" ], "type": "activityNode:fork", - "id": "activityNode:fork_4c22", + "id": "7a5ce749-3d5f-4d4b-9a00-1d50dbf65c63", "position": { "x": 170, "y": 90 @@ -159,9 +159,9 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_123e", - "sourceId": "task:manual_j3l4", - "targetId": "activityNode:fork_4c22", + "id": "2d9ec8d6-119b-4a98-89c2-efc10305ea17", + "sourceId": "1127ebe7-fc0f-4383-9be0-4b6e28dd2a33", + "targetId": "7a5ce749-3d5f-4d4b-9a00-1d50dbf65c63", "args": { "edgeSourcePointX": 142.921875, "edgeSourcePointY": 115, @@ -174,9 +174,9 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_c344", - "sourceId": "activityNode:fork_4c22", - "targetId": "task:automated_23lk", + "id": "a04afa54-223c-45a3-b6e1-69d9870d829f", + "sourceId": "7a5ce749-3d5f-4d4b-9a00-1d50dbf65c63", + "targetId": "8bec1e4d-6154-4845-9edb-f55512b8d892", "args": { "edgeSourcePointX": 179.9129171124612, "edgeSourcePointY": 117.84892269711828, @@ -191,7 +191,7 @@ "automated" ], "type": "task:automated", - "id": "task:automated_223c", + "id": "cff5f5e0-5c3a-400c-aa17-ddeef46a0b32", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -218,7 +218,7 @@ { "cssClasses": [], "type": "icon", - "id": "task:automated_223c_icon", + "id": "cff5f5e0-5c3a-400c-aa17-ddeef46a0b32_icon", "position": { "x": 5, "y": 5 @@ -236,7 +236,7 @@ "x": 21, "y": 12.800000190734863 }, - "id": "task:automated_223c_label", + "id": "cff5f5e0-5c3a-400c-aa17-ddeef46a0b32_label", "text": "ChkWt", "position": { "x": 31, @@ -255,7 +255,7 @@ "decision" ], "type": "activityNode:decision", - "id": "activityNode:decision_6n87", + "id": "3f917a57-79b8-4e1b-84a1-9afab8593e50", "position": { "x": 330, "y": 50 @@ -278,7 +278,7 @@ "decision" ], "type": "activityNode:decision", - "id": "activityNode:decision_caqw", + "id": "ca289b6f-34b2-484d-9d9d-85e5e766ff1d", "position": { "x": 330, "y": 150 @@ -300,9 +300,9 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_23c1", - "sourceId": "task:automated_223c", - "targetId": "activityNode:decision_6n87", + "id": "de7cd71c-cd68-458f-8457-da1ddd654585", + "sourceId": "cff5f5e0-5c3a-400c-aa17-ddeef46a0b32", + "targetId": "3f917a57-79b8-4e1b-84a1-9afab8593e50", "args": { "edgeSourcePointX": 303.1103515625, "edgeSourcePointY": 65.49209855270233, @@ -315,9 +315,9 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_4v56", - "sourceId": "activityNode:fork_4c22", - "targetId": "task:automated_223c", + "id": "41f9bd15-2d87-4cf5-a33a-0f3757a82390", + "sourceId": "7a5ce749-3d5f-4d4b-9a00-1d50dbf65c63", + "targetId": "cff5f5e0-5c3a-400c-aa17-ddeef46a0b32", "args": { "edgeSourcePointX": 179.91511482913953, "edgeSourcePointY": 112.16070426477935, @@ -330,9 +330,9 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_1239", - "sourceId": "task:automated_23lk", - "targetId": "activityNode:decision_caqw", + "id": "ce11149f-3c9f-4c15-8a1e-6c4912bbc911", + "sourceId": "8bec1e4d-6154-4845-9edb-f55512b8d892", + "targetId": "ca289b6f-34b2-484d-9d9d-85e5e766ff1d", "args": { "edgeSourcePointX": 302.4482421875, "edgeSourcePointY": 165.48627182195702, @@ -347,7 +347,7 @@ "automated" ], "type": "task:automated", - "id": "task:automated_v45l", + "id": "275ecdaa-f189-4e83-8763-ad298ad65a50", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -374,7 +374,7 @@ { "cssClasses": [], "type": "icon", - "id": "task:automated_v45l_icon", + "id": "275ecdaa-f189-4e83-8763-ad298ad65a50_icon", "position": { "x": 5, "y": 5 @@ -392,7 +392,7 @@ "x": 25.67500114440918, "y": 12.800000190734863 }, - "id": "task:automated_v45l_label", + "id": "275ecdaa-f189-4e83-8763-ad298ad65a50_label", "text": "PreHeat", "position": { "x": 31, @@ -412,7 +412,7 @@ "automated" ], "type": "task:automated", - "id": "task:automated_193c", + "id": "0f139224-2f24-4c6a-8f47-6d9d32569fe7", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -439,7 +439,7 @@ { "cssClasses": [], "type": "icon", - "id": "task:automated_193c_icon", + "id": "0f139224-2f24-4c6a-8f47-6d9d32569fe7_icon", "position": { "x": 5, "y": 5 @@ -457,7 +457,7 @@ "x": 24.508333206176758, "y": 12.800000190734863 }, - "id": "task:automated_193c_label", + "id": "0f139224-2f24-4c6a-8f47-6d9d32569fe7_label", "text": "KeepTp", "position": { "x": 31, @@ -477,7 +477,7 @@ "manual" ], "type": "task:manual", - "id": "task:manual_2c39", + "id": "21331020-a1c6-41f5-af95-57c16c258412", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -504,7 +504,7 @@ { "cssClasses": [], "type": "icon", - "id": "task:manual_2c39_icon", + "id": "21331020-a1c6-41f5-af95-57c16c258412_icon", "position": { "x": 5, "y": 5 @@ -522,7 +522,7 @@ "x": 17.108333587646484, "y": 12.800000190734863 }, - "id": "task:manual_2c39_label", + "id": "21331020-a1c6-41f5-af95-57c16c258412_label", "text": "RflWt", "position": { "x": 31, @@ -542,7 +542,7 @@ "automated" ], "type": "task:automated", - "id": "task:automated_0954", + "id": "3fe12cec-9d7f-4a34-94e9-7ce157c94036", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -569,7 +569,7 @@ { "cssClasses": [], "type": "icon", - "id": "task:automated_0954_icon", + "id": "3fe12cec-9d7f-4a34-94e9-7ce157c94036_icon", "position": { "x": 5, "y": 5 @@ -587,7 +587,7 @@ "x": 18.65833282470703, "y": 12.800000190734863 }, - "id": "task:automated_0954_label", + "id": "3fe12cec-9d7f-4a34-94e9-7ce157c94036_label", "text": "WtOK", "position": { "x": 31, @@ -607,9 +607,9 @@ ], "type": "edge:weighted", "routingPoints": [], - "id": "edge_etvr", - "sourceId": "activityNode:decision_6n87", - "targetId": "task:manual_2c39", + "id": "78cdfbbd-c020-47b2-81fb-de37a672e236", + "sourceId": "3f917a57-79b8-4e1b-84a1-9afab8593e50", + "targetId": "21331020-a1c6-41f5-af95-57c16c258412", "probability": "medium", "args": { "edgeSourcePointX": 358.0649068859051, @@ -625,9 +625,9 @@ ], "type": "edge:weighted", "routingPoints": [], - "id": "edge_aerv", - "sourceId": "activityNode:decision_6n87", - "targetId": "task:automated_0954", + "id": "af0b8894-2c8d-4ba4-8e5f-ad8946a33db0", + "sourceId": "3f917a57-79b8-4e1b-84a1-9afab8593e50", + "targetId": "3fe12cec-9d7f-4a34-94e9-7ce157c94036", "probability": "medium", "args": { "edgeSourcePointX": 358.34775282071627, @@ -643,9 +643,9 @@ ], "type": "edge:weighted", "routingPoints": [], - "id": "edge_345v", - "sourceId": "activityNode:decision_caqw", - "targetId": "task:automated_193c", + "id": "9cd98bf4-661e-4093-bf33-abca00c546f6", + "sourceId": "ca289b6f-34b2-484d-9d9d-85e5e766ff1d", + "targetId": "0f139224-2f24-4c6a-8f47-6d9d32569fe7", "probability": "medium", "args": { "edgeSourcePointX": 359.4355670963364, @@ -661,9 +661,9 @@ ], "type": "edge:weighted", "routingPoints": [], - "id": "edge_3nzh", - "sourceId": "activityNode:decision_caqw", - "targetId": "task:automated_v45l", + "id": "3df7c173-05db-4d4f-b656-7aad162f7c3d", + "sourceId": "ca289b6f-34b2-484d-9d9d-85e5e766ff1d", + "targetId": "275ecdaa-f189-4e83-8763-ad298ad65a50", "probability": "medium", "args": { "edgeSourcePointX": 358.5845250668064, @@ -678,7 +678,7 @@ "forkOrJoin" ], "type": "activityNode:join", - "id": "activityNode:join_45v7", + "id": "9d5d4f26-a476-4d2f-8bfa-50445210012d", "position": { "x": 560, "y": 90 @@ -696,7 +696,7 @@ "automated" ], "type": "task:automated", - "id": "task:automated_vero", + "id": "7b999943-3b52-402a-a60c-0f963ab27928", "layout": "hbox", "args": { "radiusTopLeft": 5, @@ -723,7 +723,7 @@ { "cssClasses": [], "type": "icon", - "id": "task:automated_vero_icon", + "id": "7b999943-3b52-402a-a60c-0f963ab27928_icon", "position": { "x": 5, "y": 5 @@ -741,7 +741,7 @@ "x": 15.949999809265137, "y": 12.800000190734863 }, - "id": "task:automated_vero_label", + "id": "7b999943-3b52-402a-a60c-0f963ab27928_label", "text": "Brew", "position": { "x": 31, @@ -760,7 +760,7 @@ "merge" ], "type": "activityNode:merge", - "id": "activityNode:merge_4v59", + "id": "6d1d50c9-f71a-4de7-b75b-c13c4571cc80", "position": { "x": 500, "y": 50 @@ -783,7 +783,7 @@ "merge" ], "type": "activityNode:merge", - "id": "activityNode:merge_4c34", + "id": "fea6dcba-d939-407e-aa59-0ec617787a6f", "position": { "x": 500, "y": 150 @@ -805,9 +805,9 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_cnhg", - "sourceId": "task:manual_2c39", - "targetId": "activityNode:merge_4v59", + "id": "5fdc610e-4d52-4c06-9456-a857e2cb2c9e", + "sourceId": "21331020-a1c6-41f5-af95-57c16c258412", + "targetId": "6d1d50c9-f71a-4de7-b75b-c13c4571cc80", "args": { "edgeSourcePointX": 465.32421875, "edgeSourcePointY": 48.21658670322138, @@ -820,9 +820,9 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_n678", - "sourceId": "task:automated_0954", - "targetId": "activityNode:merge_4v59", + "id": "cf1a0983-2b74-4156-bd73-46dc49328637", + "sourceId": "3fe12cec-9d7f-4a34-94e9-7ce157c94036", + "targetId": "6d1d50c9-f71a-4de7-b75b-c13c4571cc80", "args": { "edgeSourcePointX": 468.9931640625, "edgeSourcePointY": 81.75889455235128, @@ -835,9 +835,9 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_l8th", - "sourceId": "task:automated_193c", - "targetId": "activityNode:merge_4c34", + "id": "0667fca8-9253-427f-bfe9-07bd30701475", + "sourceId": "0f139224-2f24-4c6a-8f47-6d9d32569fe7", + "targetId": "fea6dcba-d939-407e-aa59-0ec617787a6f", "args": { "edgeSourcePointX": 480.248046875, "edgeSourcePointY": 156.71676105147495, @@ -850,9 +850,9 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_1x61", - "sourceId": "task:automated_v45l", - "targetId": "activityNode:merge_4c34", + "id": "3f5c0522-fc7a-4e7e-9577-f4f72bb856d4", + "sourceId": "275ecdaa-f189-4e83-8763-ad298ad65a50", + "targetId": "fea6dcba-d939-407e-aa59-0ec617787a6f", "args": { "edgeSourcePointX": 477.49245689655174, "edgeSourcePointY": 180, @@ -865,9 +865,9 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_8n46", - "sourceId": "activityNode:merge_4c34", - "targetId": "activityNode:join_45v7", + "id": "61e84e6f-95dd-4381-8e5f-e7719f1faa61", + "sourceId": "fea6dcba-d939-407e-aa59-0ec617787a6f", + "targetId": "9d5d4f26-a476-4d2f-8bfa-50445210012d", "args": { "edgeSourcePointX": 529.6166551369032, "edgeSourcePointY": 168.97864331119757, @@ -880,9 +880,9 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_v435", - "sourceId": "activityNode:merge_4v59", - "targetId": "activityNode:join_45v7", + "id": "27398c7c-0640-4491-935b-f7a805f3c4fe", + "sourceId": "6d1d50c9-f71a-4de7-b75b-c13c4571cc80", + "targetId": "9d5d4f26-a476-4d2f-8bfa-50445210012d", "args": { "edgeSourcePointX": 524.3535533905932, "edgeSourcePointY": 74.35355339059328, @@ -895,9 +895,9 @@ "cssClasses": [], "type": "edge", "routingPoints": [], - "id": "edge_v45z", - "sourceId": "activityNode:join_45v7", - "targetId": "task:automated_vero", + "id": "0ac38008-dc12-450f-aefe-b495a3435c4e", + "sourceId": "9d5d4f26-a476-4d2f-8bfa-50445210012d", + "targetId": "7b999943-3b52-402a-a60c-0f963ab27928", "args": { "edgeSourcePointX": 570.5, "edgeSourcePointY": 115,