@artyfacts/claude 1.3.8 → 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/chunk-3SYZHE4Q.mjs +979 -0
- package/dist/chunk-CB7WCSN4.mjs +808 -0
- package/dist/chunk-NGZ6RZMV.mjs +999 -0
- package/dist/chunk-U7NYTXMZ.mjs +950 -0
- package/dist/chunk-VGAUMCRW.mjs +998 -0
- package/dist/cli.js +251 -22
- package/dist/cli.mjs +56 -16
- package/dist/index.d.mts +77 -2
- package/dist/index.d.ts +77 -2
- package/dist/index.js +195 -1
- package/dist/index.mjs +5 -1
- package/package.json +1 -1
- package/src/cli.ts +62 -16
- package/src/index.ts +14 -0
- package/src/listener.ts +18 -1
- package/src/mcp.ts +314 -0
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
|
+
}
|