@artyfacts/claude 1.3.9 → 1.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  ClaudeExecutor: () => ClaudeExecutor,
35
35
  ClaudeExecutorWithTools: () => ClaudeExecutorWithTools,
36
36
  ContextFetcher: () => ContextFetcher,
37
+ McpHandler: () => McpHandler,
37
38
  buildPromptWithContext: () => buildPromptWithContext,
38
39
  clearCredentials: () => clearCredentials,
39
40
  createApiClient: () => createApiClient,
@@ -41,6 +42,7 @@ __export(index_exports, {
41
42
  createExecutor: () => createExecutor,
42
43
  createExecutorWithTools: () => createExecutorWithTools,
43
44
  createListener: () => createListener,
45
+ createMcpHandler: () => createMcpHandler,
44
46
  executeTool: () => executeTool,
45
47
  getAllToolSchemas: () => getAllToolSchemas,
46
48
  getCredentials: () => getCredentials,
@@ -651,7 +653,9 @@ var EVENT_TYPES = [
651
653
  "task_assigned",
652
654
  "task_unblocked",
653
655
  "blocker_resolved",
654
- "notification"
656
+ "notification",
657
+ "mcp_connect_request",
658
+ "connection_status"
655
659
  ];
656
660
  var ArtyfactsListener = class {
657
661
  config;
@@ -6010,12 +6014,201 @@ ${DEFAULT_SYSTEM_PROMPT2}` : DEFAULT_SYSTEM_PROMPT2;
6010
6014
  function createExecutorWithTools(config) {
6011
6015
  return new ClaudeExecutorWithTools(config);
6012
6016
  }
6017
+
6018
+ // src/mcp.ts
6019
+ var import_child_process2 = require("child_process");
6020
+ var DEFAULT_BASE_URL3 = "https://artyfacts.dev/api/v1";
6021
+ var OAUTH_MCP_SERVERS = {
6022
+ supabase: {
6023
+ url: "https://mcp.supabase.com/mcp",
6024
+ name: "supabase"
6025
+ },
6026
+ figma: {
6027
+ url: "https://mcp.figma.com/mcp",
6028
+ name: "figma"
6029
+ }
6030
+ };
6031
+ var CREDENTIAL_MCP_CONFIGS = {
6032
+ postgres: (config) => ({
6033
+ command: "npx",
6034
+ args: ["-y", "@modelcontextprotocol/server-postgres", config.connection_string]
6035
+ }),
6036
+ github: (config) => ({
6037
+ command: "npx",
6038
+ args: ["-y", "@modelcontextprotocol/server-github"],
6039
+ env: {
6040
+ GITHUB_PERSONAL_ACCESS_TOKEN: config.api_key
6041
+ }
6042
+ }),
6043
+ filesystem: (config) => ({
6044
+ command: "npx",
6045
+ args: ["-y", "@modelcontextprotocol/server-filesystem", ...config.paths || []]
6046
+ })
6047
+ };
6048
+ function addMcpServer(options) {
6049
+ const { name, transport, url, command, args = [], env = {}, scope = "user" } = options;
6050
+ const cliArgs = ["mcp", "add", "-s", scope, "-t", transport];
6051
+ for (const [key, value] of Object.entries(env)) {
6052
+ cliArgs.push("-e", `${key}=${value}`);
6053
+ }
6054
+ cliArgs.push(name);
6055
+ if (transport === "http" && url) {
6056
+ cliArgs.push(url);
6057
+ } else if (transport === "stdio" && command) {
6058
+ cliArgs.push("--", command, ...args);
6059
+ } else {
6060
+ return { success: false, error: "Invalid configuration: missing url or command" };
6061
+ }
6062
+ console.log(`[MCP] Running: claude ${cliArgs.join(" ")}`);
6063
+ const result = (0, import_child_process2.spawnSync)("claude", cliArgs, {
6064
+ encoding: "utf-8",
6065
+ stdio: ["pipe", "pipe", "pipe"]
6066
+ });
6067
+ if (result.status === 0) {
6068
+ return { success: true };
6069
+ } else {
6070
+ return {
6071
+ success: false,
6072
+ error: result.stderr || result.stdout || `Exit code ${result.status}`
6073
+ };
6074
+ }
6075
+ }
6076
+ var McpHandler = class {
6077
+ config;
6078
+ constructor(config) {
6079
+ this.config = {
6080
+ ...config,
6081
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL3
6082
+ };
6083
+ }
6084
+ /**
6085
+ * Handle an MCP connect request event
6086
+ * Uses `claude mcp add` to configure the server dynamically
6087
+ */
6088
+ async handleConnectRequest(event) {
6089
+ const { connection_id, platform, config: mcpConfig } = event.data;
6090
+ try {
6091
+ const serverName = mcpConfig?.server_name || platform;
6092
+ const oauthServer = OAUTH_MCP_SERVERS[platform];
6093
+ if (oauthServer) {
6094
+ const result = addMcpServer({
6095
+ name: serverName,
6096
+ transport: "http",
6097
+ url: oauthServer.url
6098
+ });
6099
+ if (!result.success) {
6100
+ throw new Error(`Failed to add ${platform} MCP: ${result.error}`);
6101
+ }
6102
+ console.log(`[MCP] Added ${serverName} \u2192 ${oauthServer.url}`);
6103
+ console.log(`[MCP] OAuth will prompt when you use ${platform} tools`);
6104
+ } else {
6105
+ const configBuilder = CREDENTIAL_MCP_CONFIGS[platform];
6106
+ if (!configBuilder) {
6107
+ throw new Error(`Unsupported MCP platform: ${platform}. Supported: ${[...Object.keys(OAUTH_MCP_SERVERS), ...Object.keys(CREDENTIAL_MCP_CONFIGS)].join(", ")}`);
6108
+ }
6109
+ if (!mcpConfig) {
6110
+ throw new Error(`Platform ${platform} requires configuration (connection_string or api_key)`);
6111
+ }
6112
+ const serverConfig = configBuilder(mcpConfig);
6113
+ const result = addMcpServer({
6114
+ name: serverName,
6115
+ transport: "stdio",
6116
+ command: serverConfig.command,
6117
+ args: serverConfig.args,
6118
+ env: serverConfig.env
6119
+ });
6120
+ if (!result.success) {
6121
+ throw new Error(`Failed to add ${platform} MCP: ${result.error}`);
6122
+ }
6123
+ console.log(`[MCP] Added ${serverName} for ${platform}`);
6124
+ }
6125
+ await this.updateConnectionStatus(connection_id, {
6126
+ status: "active",
6127
+ mcp_configured: true
6128
+ });
6129
+ this.config.onConfigured?.(connection_id, platform);
6130
+ } catch (err) {
6131
+ console.error(`[MCP] Failed to configure ${platform}:`, err);
6132
+ await this.updateConnectionStatus(connection_id, {
6133
+ status: "error",
6134
+ mcp_configured: false,
6135
+ error_message: err.message
6136
+ });
6137
+ this.config.onError?.(err, connection_id);
6138
+ }
6139
+ }
6140
+ /**
6141
+ * Check if a platform supports OAuth (no credentials needed)
6142
+ */
6143
+ static supportsOAuth(platform) {
6144
+ return platform in OAUTH_MCP_SERVERS;
6145
+ }
6146
+ /**
6147
+ * Get list of platforms with OAuth support
6148
+ */
6149
+ static getOAuthPlatforms() {
6150
+ return Object.keys(OAUTH_MCP_SERVERS);
6151
+ }
6152
+ /**
6153
+ * Update connection status in Artyfacts
6154
+ */
6155
+ async updateConnectionStatus(connectionId, update) {
6156
+ try {
6157
+ const response = await fetch(`${this.config.baseUrl}/connections/${connectionId}`, {
6158
+ method: "PATCH",
6159
+ headers: {
6160
+ "Authorization": `Bearer ${this.config.apiKey}`,
6161
+ "Content-Type": "application/json"
6162
+ },
6163
+ body: JSON.stringify(update)
6164
+ });
6165
+ if (!response.ok) {
6166
+ console.error(`[MCP] Failed to update connection status: ${response.status}`);
6167
+ }
6168
+ } catch (err) {
6169
+ console.error("[MCP] Failed to update connection status:", err);
6170
+ }
6171
+ }
6172
+ /**
6173
+ * List configured MCP servers using `claude mcp list`
6174
+ */
6175
+ listServers() {
6176
+ const result = (0, import_child_process2.spawnSync)("claude", ["mcp", "list"], {
6177
+ encoding: "utf-8",
6178
+ stdio: ["pipe", "pipe", "pipe"]
6179
+ });
6180
+ if (result.status === 0 && result.stdout) {
6181
+ return result.stdout.trim().split("\n").filter(Boolean);
6182
+ }
6183
+ return [];
6184
+ }
6185
+ /**
6186
+ * Remove an MCP server using `claude mcp remove`
6187
+ */
6188
+ removeServer(serverName) {
6189
+ const result = (0, import_child_process2.spawnSync)("claude", ["mcp", "remove", serverName], {
6190
+ encoding: "utf-8",
6191
+ stdio: ["pipe", "pipe", "pipe"]
6192
+ });
6193
+ if (result.status === 0) {
6194
+ console.log(`[MCP] Removed server: ${serverName}`);
6195
+ return true;
6196
+ } else {
6197
+ console.error(`[MCP] Failed to remove ${serverName}: ${result.stderr || result.stdout}`);
6198
+ return false;
6199
+ }
6200
+ }
6201
+ };
6202
+ function createMcpHandler(config) {
6203
+ return new McpHandler(config);
6204
+ }
6013
6205
  // Annotate the CommonJS export names for ESM import in node:
6014
6206
  0 && (module.exports = {
6015
6207
  ArtyfactsListener,
6016
6208
  ClaudeExecutor,
6017
6209
  ClaudeExecutorWithTools,
6018
6210
  ContextFetcher,
6211
+ McpHandler,
6019
6212
  buildPromptWithContext,
6020
6213
  clearCredentials,
6021
6214
  createApiClient,
@@ -6023,6 +6216,7 @@ function createExecutorWithTools(config) {
6023
6216
  createExecutor,
6024
6217
  createExecutorWithTools,
6025
6218
  createListener,
6219
+ createMcpHandler,
6026
6220
  executeTool,
6027
6221
  getAllToolSchemas,
6028
6222
  getCredentials,
package/dist/index.mjs CHANGED
@@ -2,17 +2,19 @@ import {
2
2
  ArtyfactsListener,
3
3
  ClaudeExecutor,
4
4
  ContextFetcher,
5
+ McpHandler,
5
6
  buildPromptWithContext,
6
7
  clearCredentials,
7
8
  createContextFetcher,
8
9
  createExecutor,
9
10
  createListener,
11
+ createMcpHandler,
10
12
  getCredentials,
11
13
  loadCredentials,
12
14
  promptForApiKey,
13
15
  runDeviceAuth,
14
16
  saveCredentials
15
- } from "./chunk-G6HSQFKJ.mjs";
17
+ } from "./chunk-VGAUMCRW.mjs";
16
18
 
17
19
  // node_modules/@anthropic-ai/sdk/internal/tslib.mjs
18
20
  function __classPrivateFieldSet(receiver, state, value, kind, f) {
@@ -5179,6 +5181,7 @@ export {
5179
5181
  ClaudeExecutor,
5180
5182
  ClaudeExecutorWithTools,
5181
5183
  ContextFetcher,
5184
+ McpHandler,
5182
5185
  buildPromptWithContext,
5183
5186
  clearCredentials,
5184
5187
  createApiClient,
@@ -5186,6 +5189,7 @@ export {
5186
5189
  createExecutor,
5187
5190
  createExecutorWithTools,
5188
5191
  createListener,
5192
+ createMcpHandler,
5189
5193
  executeTool,
5190
5194
  getAllToolSchemas,
5191
5195
  getCredentials,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artyfacts/claude",
3
- "version": "1.3.9",
3
+ "version": "1.3.10",
4
4
  "description": "Claude adapter for Artyfacts - Execute tasks using Claude Code CLI",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
package/src/cli.ts CHANGED
@@ -13,7 +13,8 @@ import { Command } from 'commander';
13
13
  import { execSync, spawnSync } from 'child_process';
14
14
  import { getCredentials, clearCredentials, loadCredentials, promptForApiKey } from './auth';
15
15
  import { ClaudeExecutor, createExecutor, TaskContext } from './executor';
16
- import { ArtyfactsListener, createListener, TaskAssignedEvent } from './listener';
16
+ import { ArtyfactsListener, createListener, TaskAssignedEvent, McpConnectRequestEvent } from './listener';
17
+ import { McpHandler, createMcpHandler } from './mcp';
17
18
 
18
19
  // ============================================================================
19
20
  // Constants
@@ -431,6 +432,25 @@ async function runAgent(options: {
431
432
  // Track active tasks to prevent duplicate processing
432
433
  const activeTasks = new Set<string>();
433
434
 
435
+ // Create MCP handler for connection requests
436
+ const mcpHandler = createMcpHandler({
437
+ apiKey: credentials.apiKey,
438
+ baseUrl: options.baseUrl,
439
+ onConfigured: (connectionId, platform) => {
440
+ console.log(`\n🔧 [MCP] Configured ${platform} connection: ${connectionId}`);
441
+ console.log(' ⚠️ Restart Claude Desktop to use the new MCP server');
442
+ },
443
+ onError: (error, connectionId) => {
444
+ console.error(`\n❌ [MCP] Failed to configure connection ${connectionId}:`, error.message);
445
+ },
446
+ });
447
+
448
+ // Handle MCP connection requests
449
+ listener.on<McpConnectRequestEvent>('mcp_connect_request', async (event) => {
450
+ console.log(`\n🔗 [MCP] Connection request for ${event.data.platform}`);
451
+ await mcpHandler.handleConnectRequest(event);
452
+ });
453
+
434
454
  // Handle task assignments
435
455
  listener.on<TaskAssignedEvent>('task_assigned', async (event) => {
436
456
  const task = event.data;
package/src/index.ts CHANGED
@@ -73,6 +73,7 @@ export type {
73
73
  TaskAssignedEvent,
74
74
  HeartbeatEvent,
75
75
  ConnectedEvent,
76
+ McpConnectRequestEvent,
76
77
  ArtyfactsEvent,
77
78
  EventCallback,
78
79
  ListenerConfig,
@@ -135,3 +136,16 @@ export type {
135
136
  ToolCall,
136
137
  ToolCallResult,
137
138
  } from './tools';
139
+
140
+ // MCP Handler - Types
141
+ export type {
142
+ McpServerConfig,
143
+ ClaudeSettingsJson,
144
+ McpHandlerConfig,
145
+ } from './mcp';
146
+
147
+ // MCP Handler - Classes
148
+ export {
149
+ McpHandler,
150
+ createMcpHandler,
151
+ } from './mcp';
package/src/listener.ts CHANGED
@@ -27,6 +27,21 @@ export interface TaskAssignedEvent {
27
27
  };
28
28
  }
29
29
 
30
+ export interface McpConnectRequestEvent {
31
+ type: 'mcp_connect_request';
32
+ timestamp: string;
33
+ data: {
34
+ connection_id: string;
35
+ platform: string;
36
+ config: {
37
+ server_name?: string;
38
+ connection_string?: string;
39
+ api_key?: string;
40
+ [key: string]: unknown;
41
+ };
42
+ };
43
+ }
44
+
30
45
  export interface HeartbeatEvent {
31
46
  type: 'heartbeat';
32
47
  timestamp: string;
@@ -40,7 +55,7 @@ export interface ConnectedEvent {
40
55
  };
41
56
  }
42
57
 
43
- export type ArtyfactsEvent = TaskAssignedEvent | HeartbeatEvent | ConnectedEvent | {
58
+ export type ArtyfactsEvent = TaskAssignedEvent | HeartbeatEvent | ConnectedEvent | McpConnectRequestEvent | {
44
59
  type: string;
45
60
  timestamp: string;
46
61
  data?: Record<string, unknown>;
@@ -77,6 +92,8 @@ const EVENT_TYPES = [
77
92
  'task_unblocked',
78
93
  'blocker_resolved',
79
94
  'notification',
95
+ 'mcp_connect_request',
96
+ 'connection_status',
80
97
  ] as const;
81
98
 
82
99
  // ============================================================================
package/src/mcp.ts ADDED
@@ -0,0 +1,314 @@
1
+ /**
2
+ * MCP Connection Handler
3
+ *
4
+ * Handles mcp_connect_request events from Artyfacts and configures
5
+ * MCP servers for the Claude adapter using `claude mcp add`.
6
+ *
7
+ * @module mcp
8
+ */
9
+
10
+ import { spawnSync } from 'child_process';
11
+ import { McpConnectRequestEvent } from './listener';
12
+
13
+ // ============================================================================
14
+ // Types
15
+ // ============================================================================
16
+
17
+ export interface McpServerConfig {
18
+ command: string;
19
+ args?: string[];
20
+ env?: Record<string, string>;
21
+ }
22
+
23
+ export interface ClaudeSettingsJson {
24
+ mcpServers?: Record<string, McpServerConfig>;
25
+ }
26
+
27
+ export interface McpHandlerConfig {
28
+ /** Artyfacts API key for status updates */
29
+ apiKey: string;
30
+ /** Base URL for Artyfacts API */
31
+ baseUrl?: string;
32
+ /** Callback when MCP server is configured */
33
+ onConfigured?: (connectionId: string, platform: string) => void;
34
+ /** Callback on error */
35
+ onError?: (error: Error, connectionId: string) => void;
36
+ }
37
+
38
+ // ============================================================================
39
+ // Constants
40
+ // ============================================================================
41
+
42
+ const DEFAULT_BASE_URL = 'https://artyfacts.dev/api/v1';
43
+
44
+ // ============================================================================
45
+ // MCP Server Configurations
46
+ // ============================================================================
47
+
48
+ /**
49
+ * URL-based MCP servers with built-in OAuth
50
+ * These open a browser for authentication automatically - no credentials needed!
51
+ * Uses `claude mcp add --transport http <name> <url>`
52
+ */
53
+ const OAUTH_MCP_SERVERS: Record<string, { url: string; name: string }> = {
54
+ supabase: {
55
+ url: 'https://mcp.supabase.com/mcp',
56
+ name: 'supabase',
57
+ },
58
+ figma: {
59
+ url: 'https://mcp.figma.com/mcp',
60
+ name: 'figma',
61
+ },
62
+ };
63
+
64
+ /**
65
+ * Command-based MCP servers that need credentials
66
+ * Uses `claude mcp add <name> -- <command> <args...>`
67
+ */
68
+ const CREDENTIAL_MCP_CONFIGS: Record<string, (config: Record<string, unknown>) => {
69
+ command: string;
70
+ args: string[];
71
+ env?: Record<string, string>;
72
+ }> = {
73
+ postgres: (config) => ({
74
+ command: 'npx',
75
+ args: ['-y', '@modelcontextprotocol/server-postgres', config.connection_string as string],
76
+ }),
77
+ github: (config) => ({
78
+ command: 'npx',
79
+ args: ['-y', '@modelcontextprotocol/server-github'],
80
+ env: {
81
+ GITHUB_PERSONAL_ACCESS_TOKEN: config.api_key as string,
82
+ },
83
+ }),
84
+ filesystem: (config) => ({
85
+ command: 'npx',
86
+ args: ['-y', '@modelcontextprotocol/server-filesystem', ...(config.paths as string[] || [])],
87
+ }),
88
+ };
89
+
90
+ // ============================================================================
91
+ // Helper Functions
92
+ // ============================================================================
93
+
94
+ /**
95
+ * Add an MCP server using `claude mcp add`
96
+ */
97
+ function addMcpServer(options: {
98
+ name: string;
99
+ transport: 'http' | 'stdio';
100
+ url?: string;
101
+ command?: string;
102
+ args?: string[];
103
+ env?: Record<string, string>;
104
+ scope?: 'local' | 'user' | 'project';
105
+ }): { success: boolean; error?: string } {
106
+ const { name, transport, url, command, args = [], env = {}, scope = 'user' } = options;
107
+
108
+ const cliArgs = ['mcp', 'add', '-s', scope, '-t', transport];
109
+
110
+ // Add environment variables
111
+ for (const [key, value] of Object.entries(env)) {
112
+ cliArgs.push('-e', `${key}=${value}`);
113
+ }
114
+
115
+ // Add name
116
+ cliArgs.push(name);
117
+
118
+ if (transport === 'http' && url) {
119
+ // HTTP transport: claude mcp add -t http <name> <url>
120
+ cliArgs.push(url);
121
+ } else if (transport === 'stdio' && command) {
122
+ // Stdio transport: claude mcp add <name> -- <command> <args...>
123
+ cliArgs.push('--', command, ...args);
124
+ } else {
125
+ return { success: false, error: 'Invalid configuration: missing url or command' };
126
+ }
127
+
128
+ console.log(`[MCP] Running: claude ${cliArgs.join(' ')}`);
129
+
130
+ const result = spawnSync('claude', cliArgs, {
131
+ encoding: 'utf-8',
132
+ stdio: ['pipe', 'pipe', 'pipe'],
133
+ });
134
+
135
+ if (result.status === 0) {
136
+ return { success: true };
137
+ } else {
138
+ return {
139
+ success: false,
140
+ error: result.stderr || result.stdout || `Exit code ${result.status}`,
141
+ };
142
+ }
143
+ }
144
+
145
+ // ============================================================================
146
+ // McpHandler Class
147
+ // ============================================================================
148
+
149
+ export class McpHandler {
150
+ private config: Required<Pick<McpHandlerConfig, 'apiKey' | 'baseUrl'>> & McpHandlerConfig;
151
+
152
+ constructor(config: McpHandlerConfig) {
153
+ this.config = {
154
+ ...config,
155
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL,
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Handle an MCP connect request event
161
+ * Uses `claude mcp add` to configure the server dynamically
162
+ */
163
+ async handleConnectRequest(event: McpConnectRequestEvent): Promise<void> {
164
+ const { connection_id, platform, config: mcpConfig } = event.data;
165
+
166
+ try {
167
+ const serverName = mcpConfig?.server_name || platform;
168
+
169
+ // Check if this is an OAuth-based server (no credentials needed!)
170
+ const oauthServer = OAUTH_MCP_SERVERS[platform];
171
+ if (oauthServer) {
172
+ // URL-based MCP server with built-in OAuth
173
+ const result = addMcpServer({
174
+ name: serverName,
175
+ transport: 'http',
176
+ url: oauthServer.url,
177
+ });
178
+
179
+ if (!result.success) {
180
+ throw new Error(`Failed to add ${platform} MCP: ${result.error}`);
181
+ }
182
+
183
+ console.log(`[MCP] Added ${serverName} → ${oauthServer.url}`);
184
+ console.log(`[MCP] OAuth will prompt when you use ${platform} tools`);
185
+ } else {
186
+ // Command-based MCP server - needs credentials
187
+ const configBuilder = CREDENTIAL_MCP_CONFIGS[platform];
188
+ if (!configBuilder) {
189
+ throw new Error(`Unsupported MCP platform: ${platform}. Supported: ${[...Object.keys(OAUTH_MCP_SERVERS), ...Object.keys(CREDENTIAL_MCP_CONFIGS)].join(', ')}`);
190
+ }
191
+
192
+ if (!mcpConfig) {
193
+ throw new Error(`Platform ${platform} requires configuration (connection_string or api_key)`);
194
+ }
195
+
196
+ const serverConfig = configBuilder(mcpConfig);
197
+ const result = addMcpServer({
198
+ name: serverName,
199
+ transport: 'stdio',
200
+ command: serverConfig.command,
201
+ args: serverConfig.args,
202
+ env: serverConfig.env,
203
+ });
204
+
205
+ if (!result.success) {
206
+ throw new Error(`Failed to add ${platform} MCP: ${result.error}`);
207
+ }
208
+
209
+ console.log(`[MCP] Added ${serverName} for ${platform}`);
210
+ }
211
+
212
+ // Update connection status in Artyfacts
213
+ await this.updateConnectionStatus(connection_id, {
214
+ status: 'active',
215
+ mcp_configured: true,
216
+ });
217
+
218
+ // Callback
219
+ this.config.onConfigured?.(connection_id, platform);
220
+
221
+ } catch (err) {
222
+ console.error(`[MCP] Failed to configure ${platform}:`, err);
223
+
224
+ // Update connection with error status
225
+ await this.updateConnectionStatus(connection_id, {
226
+ status: 'error',
227
+ mcp_configured: false,
228
+ error_message: (err as Error).message,
229
+ });
230
+
231
+ this.config.onError?.(err as Error, connection_id);
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Check if a platform supports OAuth (no credentials needed)
237
+ */
238
+ static supportsOAuth(platform: string): boolean {
239
+ return platform in OAUTH_MCP_SERVERS;
240
+ }
241
+
242
+ /**
243
+ * Get list of platforms with OAuth support
244
+ */
245
+ static getOAuthPlatforms(): string[] {
246
+ return Object.keys(OAUTH_MCP_SERVERS);
247
+ }
248
+
249
+ /**
250
+ * Update connection status in Artyfacts
251
+ */
252
+ private async updateConnectionStatus(
253
+ connectionId: string,
254
+ update: { status: string; mcp_configured: boolean; error_message?: string }
255
+ ): Promise<void> {
256
+ try {
257
+ const response = await fetch(`${this.config.baseUrl}/connections/${connectionId}`, {
258
+ method: 'PATCH',
259
+ headers: {
260
+ 'Authorization': `Bearer ${this.config.apiKey}`,
261
+ 'Content-Type': 'application/json',
262
+ },
263
+ body: JSON.stringify(update),
264
+ });
265
+
266
+ if (!response.ok) {
267
+ console.error(`[MCP] Failed to update connection status: ${response.status}`);
268
+ }
269
+ } catch (err) {
270
+ console.error('[MCP] Failed to update connection status:', err);
271
+ }
272
+ }
273
+
274
+ /**
275
+ * List configured MCP servers using `claude mcp list`
276
+ */
277
+ listServers(): string[] {
278
+ const result = spawnSync('claude', ['mcp', 'list'], {
279
+ encoding: 'utf-8',
280
+ stdio: ['pipe', 'pipe', 'pipe'],
281
+ });
282
+
283
+ if (result.status === 0 && result.stdout) {
284
+ // Parse output - each line is a server name
285
+ return result.stdout.trim().split('\n').filter(Boolean);
286
+ }
287
+ return [];
288
+ }
289
+
290
+ /**
291
+ * Remove an MCP server using `claude mcp remove`
292
+ */
293
+ removeServer(serverName: string): boolean {
294
+ const result = spawnSync('claude', ['mcp', 'remove', serverName], {
295
+ encoding: 'utf-8',
296
+ stdio: ['pipe', 'pipe', 'pipe'],
297
+ });
298
+
299
+ if (result.status === 0) {
300
+ console.log(`[MCP] Removed server: ${serverName}`);
301
+ return true;
302
+ } else {
303
+ console.error(`[MCP] Failed to remove ${serverName}: ${result.stderr || result.stdout}`);
304
+ return false;
305
+ }
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Create an MCP handler instance
311
+ */
312
+ export function createMcpHandler(config: McpHandlerConfig): McpHandler {
313
+ return new McpHandler(config);
314
+ }