source dump of claude code
at main 88 lines 2.6 kB view raw
1import type { McpbManifest } from '@anthropic-ai/mcpb' 2import { errorMessage } from '../errors.js' 3import { jsonParse } from '../slowOperations.js' 4 5/** 6 * Parses and validates a DXT manifest from a JSON object. 7 * 8 * Lazy-imports @anthropic-ai/mcpb: that package uses zod v3 which eagerly 9 * creates 24 .bind(this) closures per schema instance (~300 instances between 10 * schemas.js and schemas-loose.js). Deferring the import keeps ~700KB of bound 11 * closures out of the startup heap for sessions that never touch .dxt/.mcpb. 12 */ 13export async function validateManifest( 14 manifestJson: unknown, 15): Promise<McpbManifest> { 16 const { McpbManifestSchema } = await import('@anthropic-ai/mcpb') 17 const parseResult = McpbManifestSchema.safeParse(manifestJson) 18 19 if (!parseResult.success) { 20 const errors = parseResult.error.flatten() 21 const errorMessages = [ 22 ...Object.entries(errors.fieldErrors).map( 23 ([field, errs]) => `${field}: ${errs?.join(', ')}`, 24 ), 25 ...(errors.formErrors || []), 26 ] 27 .filter(Boolean) 28 .join('; ') 29 30 throw new Error(`Invalid manifest: ${errorMessages}`) 31 } 32 33 return parseResult.data 34} 35 36/** 37 * Parses and validates a DXT manifest from raw text data. 38 */ 39export async function parseAndValidateManifestFromText( 40 manifestText: string, 41): Promise<McpbManifest> { 42 let manifestJson: unknown 43 44 try { 45 manifestJson = jsonParse(manifestText) 46 } catch (error) { 47 throw new Error(`Invalid JSON in manifest.json: ${errorMessage(error)}`) 48 } 49 50 return validateManifest(manifestJson) 51} 52 53/** 54 * Parses and validates a DXT manifest from raw binary data. 55 */ 56export async function parseAndValidateManifestFromBytes( 57 manifestData: Uint8Array, 58): Promise<McpbManifest> { 59 const manifestText = new TextDecoder().decode(manifestData) 60 return parseAndValidateManifestFromText(manifestText) 61} 62 63/** 64 * Generates an extension ID from author name and extension name. 65 * Uses the same algorithm as the directory backend for consistency. 66 */ 67export function generateExtensionId( 68 manifest: McpbManifest, 69 prefix?: 'local.unpacked' | 'local.dxt', 70): string { 71 const sanitize = (str: string) => 72 str 73 .toLowerCase() 74 .replace(/\s+/g, '-') 75 .replace(/[^a-z0-9-_.]/g, '') 76 .replace(/-+/g, '-') 77 .replace(/^-+|-+$/g, '') 78 79 const authorName = manifest.author.name 80 const extensionName = manifest.name 81 82 const sanitizedAuthor = sanitize(authorName) 83 const sanitizedName = sanitize(extensionName) 84 85 return prefix 86 ? `${prefix}.${sanitizedAuthor}.${sanitizedName}` 87 : `${sanitizedAuthor}.${sanitizedName}` 88}