/ src / main / mcp / dxt.ts
dxt.ts
 1  import {
 2    unpackExtension,
 3    getMcpConfigForManifest,
 4    v0_3 as McpbVersion,
 5    McpbManifestAny,
 6    McpbUserConfigValues,
 7    Logger
 8  } from '@anthropic-ai/mcpb'
 9  
10  import type { McpDxtErrors } from '@/types/mcp'
11  
12  import { existsSync, readFileSync, statSync } from 'fs'
13  import { join, resolve, sep } from 'path'
14  
15  export type McpServerConfig = McpbManifestAny['server']['mcp_config']
16  
17  const mockSystemDirs = {
18    home: '/home/user',
19    data: '/data'
20  }
21  
22  export async function getMcpConfigForDxt(
23    basePath: string,
24    baseManifest: McpbManifestAny,
25    userConfig: McpbUserConfigValues
26  ): Promise<McpServerConfig> {
27    const logMessages: string[] = []
28    const logger: Logger = {
29      log: (...args: unknown[]) => logMessages.push(args.join(' ')),
30      warn: (...args: unknown[]) => logMessages.push(args.join(' ')),
31      error: (...args: unknown[]) => logMessages.push(args.join(' '))
32    }
33  
34    const mcpConfig = await getMcpConfigForManifest({
35      manifest: baseManifest,
36      extensionPath: basePath,
37      systemDirs: mockSystemDirs,
38      userConfig: userConfig,
39      pathSeparator: sep,
40      logger
41    })
42  
43    if (mcpConfig === undefined) {
44      throw new Error(logMessages.join('\n'))
45    } else {
46      return mcpConfig
47    }
48  }
49  export async function unpackDxt(dxtUnpackOption: {
50    mcpbPath: string
51    outputDir: string
52  }): Promise<boolean> {
53    return unpackExtension(dxtUnpackOption)
54  }
55  
56  export function getManifest(inputPath: string): McpDxtErrors | McpbManifestAny {
57    try {
58      const resolvedPath = resolve(inputPath)
59      let manifestPath = resolvedPath
60  
61      // If input is a directory, look for manifest.json inside it
62      if (existsSync(resolvedPath) && statSync(resolvedPath).isDirectory()) {
63        manifestPath = join(resolvedPath, 'manifest.json')
64      }
65  
66      const manifestContent = readFileSync(manifestPath, 'utf-8')
67      const manifestData = JSON.parse(manifestContent)
68  
69      const result = McpbVersion.McpbManifestSchema.safeParse(manifestData)
70  
71      if (result.success) {
72        console.log('Manifest is valid!')
73        return result.data
74      } else {
75        console.log('ERROR: Manifest validation failed:\n')
76        const errors = result.error.issues.map((issue) => {
77          const path = issue.path.join('.')
78          console.log(`  - ${path ? `${path}: ` : ''}${issue.message}`)
79          return {
80            field: path,
81            message: issue.message
82          }
83        })
84        return { errors: errors }
85      }
86    } catch (error) {
87      const dxtError = {
88        field: 'manifest',
89        message: error instanceof Error ? error.message : String(error)
90      }
91  
92      console.error(dxtError.message)
93      return { errors: [dxtError] }
94    }
95  }