feat: Implement clipboard serialization/deserialization for MCP prompts

This commit is contained in:
Aleksander Grygier 2026-01-27 11:56:02 +01:00
parent 99d177d442
commit 770f993086
1 changed files with 76 additions and 15 deletions

View File

@ -104,11 +104,18 @@ export function formatMessageForClipboard(
extras?: DatabaseMessageExtra[],
asPlainText: boolean = false
): string {
// Filter only text attachments (TEXT type and legacy CONTEXT type)
// Filter text-like attachments (TEXT, LEGACY_CONTEXT, and MCP_PROMPT types)
const textAttachments =
extras?.filter(
(extra): extra is DatabaseMessageExtraTextFile | DatabaseMessageExtraLegacyContext =>
extra.type === AttachmentType.TEXT || extra.type === AttachmentType.LEGACY_CONTEXT
(
extra
): extra is
| DatabaseMessageExtraTextFile
| DatabaseMessageExtraLegacyContext
| DatabaseMessageExtraMcpPrompt =>
extra.type === AttachmentType.TEXT ||
extra.type === AttachmentType.LEGACY_CONTEXT ||
extra.type === AttachmentType.MCP_PROMPT
) ?? [];
if (textAttachments.length === 0) {
@ -123,11 +130,24 @@ export function formatMessageForClipboard(
return parts.join('\n\n');
}
const clipboardAttachments: ClipboardTextAttachment[] = textAttachments.map((att) => ({
type: AttachmentType.TEXT,
name: att.name,
content: att.content
}));
const clipboardAttachments: ClipboardAttachment[] = textAttachments.map((att) => {
if (att.type === AttachmentType.MCP_PROMPT) {
const mcpAtt = att as DatabaseMessageExtraMcpPrompt;
return {
type: AttachmentType.MCP_PROMPT,
name: mcpAtt.name,
serverName: mcpAtt.serverName,
promptName: mcpAtt.promptName,
content: mcpAtt.content,
arguments: mcpAtt.arguments
} as ClipboardMcpPromptAttachment;
}
return {
type: AttachmentType.TEXT,
name: att.name,
content: att.content
} as ClipboardTextAttachment;
});
return `${JSON.stringify(content)}\n${JSON.stringify(clipboardAttachments, null, 2)}`;
}
@ -142,7 +162,8 @@ export function formatMessageForClipboard(
export function parseClipboardContent(clipboardText: string): ParsedClipboardContent {
const defaultResult: ParsedClipboardContent = {
message: clipboardText,
textAttachments: []
textAttachments: [],
mcpPromptAttachments: []
};
if (!clipboardText.startsWith('"')) {
@ -184,17 +205,28 @@ export function parseClipboardContent(clipboardText: string): ParsedClipboardCon
if (!remainingPart || !remainingPart.startsWith('[')) {
return {
message,
textAttachments: []
textAttachments: [],
mcpPromptAttachments: []
};
}
const attachments = JSON.parse(remainingPart) as unknown[];
const validAttachments: ClipboardTextAttachment[] = [];
const validTextAttachments: ClipboardTextAttachment[] = [];
const validMcpPromptAttachments: ClipboardMcpPromptAttachment[] = [];
for (const att of attachments) {
if (isValidTextAttachment(att)) {
validAttachments.push({
if (isValidMcpPromptAttachment(att)) {
validMcpPromptAttachments.push({
type: AttachmentType.MCP_PROMPT,
name: att.name,
serverName: att.serverName,
promptName: att.promptName,
content: att.content,
arguments: att.arguments
});
} else if (isValidTextAttachment(att)) {
validTextAttachments.push({
type: AttachmentType.TEXT,
name: att.name,
content: att.content
@ -204,13 +236,42 @@ export function parseClipboardContent(clipboardText: string): ParsedClipboardCon
return {
message,
textAttachments: validAttachments
textAttachments: validTextAttachments,
mcpPromptAttachments: validMcpPromptAttachments
};
} catch {
return defaultResult;
}
}
/**
* Type guard to validate an MCP prompt attachment object
* @param obj The object to validate
* @returns true if the object is a valid MCP prompt attachment
*/
function isValidMcpPromptAttachment(obj: unknown): obj is {
type: string;
name: string;
serverName: string;
promptName: string;
content: string;
arguments?: Record<string, string>;
} {
if (typeof obj !== 'object' || obj === null) {
return false;
}
const record = obj as Record<string, unknown>;
return (
(record.type === AttachmentType.MCP_PROMPT || record.type === 'MCP_PROMPT') &&
typeof record.name === 'string' &&
typeof record.serverName === 'string' &&
typeof record.promptName === 'string' &&
typeof record.content === 'string'
);
}
/**
* Type guard to validate a text attachment object
* @param obj The object to validate
@ -243,5 +304,5 @@ export function hasClipboardAttachments(clipboardText: string): boolean {
}
const parsed = parseClipboardContent(clipboardText);
return parsed.textAttachments.length > 0;
return parsed.textAttachments.length > 0 || parsed.mcpPromptAttachments.length > 0;
}