Files
patchy631--ai-engineering-hub/open-agent-builder/convex/userMCPs.ts
T
2026-07-13 12:37:47 +08:00

143 lines
3.5 KiB
TypeScript

import { v } from "convex/values";
import { mutation, query } from "./_generated/server";
/**
* User MCP Servers - Cursor-style configuration
*
* Allows users to import their MCP configs from Cursor/Claude Desktop
*/
// Add MCP server from cursor config
export const add = mutation({
args: {
name: v.string(),
url: v.string(),
headers: v.optional(v.any()),
env: v.optional(v.any()),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Not authenticated");
}
// Check if MCP with this name already exists for user
const existing = await ctx.db
.query("userMCPs")
.filter((q) =>
q.and(
q.eq(q.field("userId"), identity.subject),
q.eq(q.field("name"), args.name)
)
)
.first();
if (existing) {
// Update existing
await ctx.db.patch(existing._id, {
url: args.url,
headers: args.headers,
env: args.env,
updatedAt: new Date().toISOString(),
});
return existing._id;
}
// Create new
return await ctx.db.insert("userMCPs", {
userId: identity.subject,
name: args.name,
url: args.url,
headers: args.headers,
env: args.env,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
},
});
// List user's MCP servers
export const list = query({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) return [];
return await ctx.db
.query("userMCPs")
.filter((q) => q.eq(q.field("userId"), identity.subject))
.order("desc")
.collect();
},
});
// Remove MCP server
export const remove = mutation({
args: { id: v.id("userMCPs") },
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Not authenticated");
}
const mcp = await ctx.db.get(args.id);
if (!mcp) {
throw new Error("MCP not found");
}
if (mcp.userId !== identity.subject) {
throw new Error("Unauthorized");
}
await ctx.db.delete(args.id);
return { success: true };
},
});
// Import from cursor config
export const importFromCursor = mutation({
args: {
config: v.string(), // JSON string of cursor config
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Not authenticated");
}
try {
const parsed = JSON.parse(args.config);
if (!parsed.mcpServers) {
throw new Error("Invalid config: missing mcpServers field");
}
const imported: Array<{ id: any; name: string }> = [];
for (const [name, config] of Object.entries(parsed.mcpServers)) {
const mcpConfig = config as any;
const mcpId = await ctx.db.insert("userMCPs", {
userId: identity.subject,
name,
url: mcpConfig.url || mcpConfig.command || '',
headers: mcpConfig.headers,
env: mcpConfig.env,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
imported.push({ id: mcpId, name });
}
return {
success: true,
imported: imported.length,
servers: imported,
};
} catch (error) {
throw new Error(`Failed to import: ${error instanceof Error ? error.message : 'Invalid JSON'}`);
}
},
});