@artyfacts/claude 1.3.9 → 1.3.11
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 +215 -7
- package/dist/cli.mjs +20 -1
- 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 +21 -1
- package/src/index.ts +14 -0
- package/src/listener.ts +18 -1
- package/src/mcp.ts +314 -0
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-
|
|
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
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(' 💡 Try using the tools now - OAuth will prompt when needed');
|
|
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
|
+
}
|