penpot/mcp/packages/server/src/ToolResponse.ts

112 lines
4.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { CallToolResult } from "@modelcontextprotocol/server";
type CallToolContent = CallToolResult["content"][number];
type TextItem = Extract<CallToolContent, { type: "text" }>;
type ImageItem = Extract<CallToolContent, { type: "image" }>;
export class TextContent implements TextItem {
[x: string]: unknown;
readonly type = "text" as const;
constructor(public text: string) {}
/**
* @param data - Text data as string or as object (wrapped base64 data or JSON representation of array where
* indices are mapped to character codes)
*/
public static textData(data: string | object): string {
if (typeof data === "object") {
// check for wrapped base64 data
if ("__type" in data && data["__type"] === "base64" && "data" in data && typeof data["data"] === "string") {
return Buffer.from(data["data"], "base64").toString("utf-8");
}
// legacy fallback: assume object containing character codes (as obtained from direct JSON conversion)
else {
return String.fromCharCode(...(Object.values(data) as number[]));
}
} else {
return data;
}
}
}
export class ImageContent implements ImageItem {
[x: string]: unknown;
readonly type = "image" as const;
/**
* @param data - Base64-encoded image data
* @param mimeType - MIME type of the image (e.g., "image/png")
*/
constructor(
public data: string,
public mimeType: string
) {}
/**
* Utility function for ensuring a consistent Uint8Array representation of byte data.
* Input can be one of:
* - a `Uint8Array` (already in the desired form);
* - a base64 envelope `{ __type: "base64", data: <base64 string> }` produced by the plugin
* to avoid the ~10x JSON expansion of typed arrays (see penpot/penpot#9420);
* - a numeric-keyed object obtained from `JSON.stringify`-ing a `Uint8Array` (legacy fallback).
*
* @param data - data as `Uint8Array`, base64 envelope, or numeric-keyed object
* @return data as `Uint8Array`
*/
public static byteData(data: Uint8Array | object): Uint8Array {
if (data instanceof Uint8Array) {
return data;
}
// recognize the base64 envelope produced by the plugin's ExecuteCodeTaskHandler
const envelope = data as { __type?: unknown; data?: unknown };
if (envelope.__type === "base64" && typeof envelope.data === "string") {
return new Uint8Array(Buffer.from(envelope.data, "base64"));
}
// legacy fallback: object (as obtained from JSON conversion of Uint8Array) back to Uint8Array
return new Uint8Array(Object.values(data) as number[]);
}
}
export class PNGImageContent extends ImageContent {
/**
* @param data - PNG image data as Uint8Array or as object (from JSON conversion of Uint8Array)
*/
constructor(data: Uint8Array | object) {
let array = ImageContent.byteData(data);
super(Buffer.from(array).toString("base64"), "image/png");
}
}
export class ToolResponse implements CallToolResult {
[x: string]: unknown;
content: CallToolContent[]; // <- IMPORTANT: protocol’s union
constructor(content: CallToolContent[]) {
this.content = content;
}
}
export class TextResponse extends ToolResponse {
constructor(text: string) {
super([new TextContent(text)]);
}
/**
* Creates a TextResponse from text data given as string or as object (wrapped base64 data or JSON representation
* of array where indices are mapped to character codes).
*
* @param data - Text data as string or as object (from JSON representation where indices are mapped to character codes)
*/
public static fromData(data: string | object): TextResponse {
return new TextResponse(TextContent.textData(data));
}
}
export class PNGResponse extends ToolResponse {
/**
* @param data - PNG image data as Uint8Array or as object (from JSON conversion of Uint8Array)
*/
constructor(data: Uint8Array | object) {
super([new PNGImageContent(data)]);
}
}