42 lines
1.6 KiB
TypeScript
42 lines
1.6 KiB
TypeScript
import type { CallToolResult, Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
|
|
const READ_ONLY_PREFIXES = ["get_", "list_", "search_", "validate_", "audit_", "ping_"];
|
|
const DESTRUCTIVE_PREFIXES = ["delete_", "remove_", "restore_", "sync_"];
|
|
|
|
/** Advertise intent without changing the existing tool names or input contracts. */
|
|
export function presentToolDefinitions(tools: Tool[]): Tool[] {
|
|
return tools.map((tool) => {
|
|
const readOnly = READ_ONLY_PREFIXES.some((prefix) => tool.name.startsWith(prefix));
|
|
const destructive = DESTRUCTIVE_PREFIXES.some((prefix) => tool.name.startsWith(prefix));
|
|
|
|
return {
|
|
...tool,
|
|
annotations: {
|
|
...tool.annotations,
|
|
readOnlyHint: readOnly,
|
|
...(readOnly ? { idempotentHint: true, destructiveHint: false } : {}),
|
|
...(destructive ? { destructiveHint: true } : {}),
|
|
...(tool.name === "upload_product_image_from_url" ? { openWorldHint: true } : {}),
|
|
},
|
|
};
|
|
});
|
|
}
|
|
|
|
/** Keep text responses for existing clients while exposing JSON as machine-readable data. */
|
|
export function withStructuredContent(result: CallToolResult): CallToolResult {
|
|
if (result.structuredContent) return result;
|
|
const textBlocks = result.content.filter((item) => item.type === "text");
|
|
if (textBlocks.length !== 1) return result;
|
|
|
|
try {
|
|
const parsed: unknown = JSON.parse(textBlocks[0]!.text);
|
|
if (parsed === null || typeof parsed !== "object") return result;
|
|
return {
|
|
...result,
|
|
structuredContent: Array.isArray(parsed) ? { data: parsed } : parsed as Record<string, unknown>,
|
|
};
|
|
} catch {
|
|
return result;
|
|
}
|
|
}
|