/ services / mcp / types.ts
types.ts
  1  import type { Client } from '@modelcontextprotocol/sdk/client/index.js'
  2  import type {
  3    Resource,
  4    ServerCapabilities,
  5  } from '@modelcontextprotocol/sdk/types.js'
  6  import { z } from 'zod/v4'
  7  import { lazySchema } from '../../utils/lazySchema.js'
  8  
  9  // Configuration schemas and types
 10  export const ConfigScopeSchema = lazySchema(() =>
 11    z.enum([
 12      'local',
 13      'user',
 14      'project',
 15      'dynamic',
 16      'enterprise',
 17      'claudeai',
 18      'managed',
 19    ]),
 20  )
 21  export type ConfigScope = z.infer<ReturnType<typeof ConfigScopeSchema>>
 22  
 23  export const TransportSchema = lazySchema(() =>
 24    z.enum(['stdio', 'sse', 'sse-ide', 'http', 'ws', 'sdk']),
 25  )
 26  export type Transport = z.infer<ReturnType<typeof TransportSchema>>
 27  
 28  export const McpStdioServerConfigSchema = lazySchema(() =>
 29    z.object({
 30      type: z.literal('stdio').optional(), // Optional for backwards compatibility
 31      command: z.string().min(1, 'Command cannot be empty'),
 32      args: z.array(z.string()).default([]),
 33      env: z.record(z.string(), z.string()).optional(),
 34    }),
 35  )
 36  
 37  // Cross-App Access (XAA / SEP-990): just a per-server flag. IdP connection
 38  // details (issuer, clientId, callbackPort) come from settings.xaaIdp — configured
 39  // once, shared across all XAA-enabled servers. clientId/clientSecret (parent
 40  // oauth config + keychain slot) are for the MCP server's AS.
 41  const McpXaaConfigSchema = lazySchema(() => z.boolean())
 42  
 43  const McpOAuthConfigSchema = lazySchema(() =>
 44    z.object({
 45      clientId: z.string().optional(),
 46      callbackPort: z.number().int().positive().optional(),
 47      authServerMetadataUrl: z
 48        .string()
 49        .url()
 50        .startsWith('https://', {
 51          message: 'authServerMetadataUrl must use https://',
 52        })
 53        .optional(),
 54      xaa: McpXaaConfigSchema().optional(),
 55    }),
 56  )
 57  
 58  export const McpSSEServerConfigSchema = lazySchema(() =>
 59    z.object({
 60      type: z.literal('sse'),
 61      url: z.string(),
 62      headers: z.record(z.string(), z.string()).optional(),
 63      headersHelper: z.string().optional(),
 64      oauth: McpOAuthConfigSchema().optional(),
 65    }),
 66  )
 67  
 68  // Internal-only server type for IDE extensions
 69  export const McpSSEIDEServerConfigSchema = lazySchema(() =>
 70    z.object({
 71      type: z.literal('sse-ide'),
 72      url: z.string(),
 73      ideName: z.string(),
 74      ideRunningInWindows: z.boolean().optional(),
 75    }),
 76  )
 77  
 78  // Internal-only server type for IDE extensions
 79  export const McpWebSocketIDEServerConfigSchema = lazySchema(() =>
 80    z.object({
 81      type: z.literal('ws-ide'),
 82      url: z.string(),
 83      ideName: z.string(),
 84      authToken: z.string().optional(),
 85      ideRunningInWindows: z.boolean().optional(),
 86    }),
 87  )
 88  
 89  export const McpHTTPServerConfigSchema = lazySchema(() =>
 90    z.object({
 91      type: z.literal('http'),
 92      url: z.string(),
 93      headers: z.record(z.string(), z.string()).optional(),
 94      headersHelper: z.string().optional(),
 95      oauth: McpOAuthConfigSchema().optional(),
 96    }),
 97  )
 98  
 99  export const McpWebSocketServerConfigSchema = lazySchema(() =>
100    z.object({
101      type: z.literal('ws'),
102      url: z.string(),
103      headers: z.record(z.string(), z.string()).optional(),
104      headersHelper: z.string().optional(),
105    }),
106  )
107  
108  export const McpSdkServerConfigSchema = lazySchema(() =>
109    z.object({
110      type: z.literal('sdk'),
111      name: z.string(),
112    }),
113  )
114  
115  // Config type for Claude.ai proxy servers
116  export const McpClaudeAIProxyServerConfigSchema = lazySchema(() =>
117    z.object({
118      type: z.literal('claudeai-proxy'),
119      url: z.string(),
120      id: z.string(),
121    }),
122  )
123  
124  export const McpServerConfigSchema = lazySchema(() =>
125    z.union([
126      McpStdioServerConfigSchema(),
127      McpSSEServerConfigSchema(),
128      McpSSEIDEServerConfigSchema(),
129      McpWebSocketIDEServerConfigSchema(),
130      McpHTTPServerConfigSchema(),
131      McpWebSocketServerConfigSchema(),
132      McpSdkServerConfigSchema(),
133      McpClaudeAIProxyServerConfigSchema(),
134    ]),
135  )
136  
137  export type McpStdioServerConfig = z.infer<
138    ReturnType<typeof McpStdioServerConfigSchema>
139  >
140  export type McpSSEServerConfig = z.infer<
141    ReturnType<typeof McpSSEServerConfigSchema>
142  >
143  export type McpSSEIDEServerConfig = z.infer<
144    ReturnType<typeof McpSSEIDEServerConfigSchema>
145  >
146  export type McpWebSocketIDEServerConfig = z.infer<
147    ReturnType<typeof McpWebSocketIDEServerConfigSchema>
148  >
149  export type McpHTTPServerConfig = z.infer<
150    ReturnType<typeof McpHTTPServerConfigSchema>
151  >
152  export type McpWebSocketServerConfig = z.infer<
153    ReturnType<typeof McpWebSocketServerConfigSchema>
154  >
155  export type McpSdkServerConfig = z.infer<
156    ReturnType<typeof McpSdkServerConfigSchema>
157  >
158  export type McpClaudeAIProxyServerConfig = z.infer<
159    ReturnType<typeof McpClaudeAIProxyServerConfigSchema>
160  >
161  export type McpServerConfig = z.infer<ReturnType<typeof McpServerConfigSchema>>
162  
163  export type ScopedMcpServerConfig = McpServerConfig & {
164    scope: ConfigScope
165    // For plugin-provided servers: the providing plugin's LoadedPlugin.source
166    // (e.g. 'slack@anthropic'). Stashed at config-build time so the channel
167    // gate doesn't have to race AppState.plugins.enabled hydration.
168    pluginSource?: string
169  }
170  
171  export const McpJsonConfigSchema = lazySchema(() =>
172    z.object({
173      mcpServers: z.record(z.string(), McpServerConfigSchema()),
174    }),
175  )
176  
177  export type McpJsonConfig = z.infer<ReturnType<typeof McpJsonConfigSchema>>
178  
179  // Server connection types
180  export type ConnectedMCPServer = {
181    client: Client
182    name: string
183    type: 'connected'
184    capabilities: ServerCapabilities
185    serverInfo?: {
186      name: string
187      version: string
188    }
189    instructions?: string
190    config: ScopedMcpServerConfig
191    cleanup: () => Promise<void>
192  }
193  
194  export type FailedMCPServer = {
195    name: string
196    type: 'failed'
197    config: ScopedMcpServerConfig
198    error?: string
199  }
200  
201  export type NeedsAuthMCPServer = {
202    name: string
203    type: 'needs-auth'
204    config: ScopedMcpServerConfig
205  }
206  
207  export type PendingMCPServer = {
208    name: string
209    type: 'pending'
210    config: ScopedMcpServerConfig
211    reconnectAttempt?: number
212    maxReconnectAttempts?: number
213  }
214  
215  export type DisabledMCPServer = {
216    name: string
217    type: 'disabled'
218    config: ScopedMcpServerConfig
219  }
220  
221  export type MCPServerConnection =
222    | ConnectedMCPServer
223    | FailedMCPServer
224    | NeedsAuthMCPServer
225    | PendingMCPServer
226    | DisabledMCPServer
227  
228  // Resource types
229  export type ServerResource = Resource & { server: string }
230  
231  // MCP CLI State types
232  export interface SerializedTool {
233    name: string
234    description: string
235    inputJSONSchema?: {
236      [x: string]: unknown
237      type: 'object'
238      properties?: {
239        [x: string]: unknown
240      }
241    }
242    isMcp?: boolean
243    originalToolName?: string // Original unnormalized tool name from MCP server
244  }
245  
246  export interface SerializedClient {
247    name: string
248    type: 'connected' | 'failed' | 'needs-auth' | 'pending' | 'disabled'
249    capabilities?: ServerCapabilities
250  }
251  
252  export interface MCPCliState {
253    clients: SerializedClient[]
254    configs: Record<string, ScopedMcpServerConfig>
255    tools: SerializedTool[]
256    resources: Record<string, ServerResource[]>
257    normalizedNames?: Record<string, string> // Maps normalized names to original names
258  }