@vantageos/vantage-crm-mcp 0.1.0
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/README.md +260 -0
- package/dist/convex/crm/_helpers.js +24 -0
- package/dist/convex/crm/activities.js +220 -0
- package/dist/convex/crm/briefing.js +198 -0
- package/dist/convex/crm/calendarCron.js +92 -0
- package/dist/convex/crm/calendarCronDispatch.js +83 -0
- package/dist/convex/crm/calendarSync.js +294 -0
- package/dist/convex/crm/companies.js +323 -0
- package/dist/convex/crm/contacts.js +346 -0
- package/dist/convex/crm/deals.js +481 -0
- package/dist/convex/crm/emailActions.js +158 -0
- package/dist/convex/crm/emailCron.js +210 -0
- package/dist/convex/crm/emailCronDispatch.js +76 -0
- package/dist/convex/crm/emailSync.js +260 -0
- package/dist/convex/crm/onboarding.js +185 -0
- package/dist/convex/crm/stats.js +75 -0
- package/dist/convex/crm/tasks.js +109 -0
- package/dist/convex/crons.js +25 -0
- package/dist/convex/integrations.js +183 -0
- package/dist/convex/lib/auditLog.js +109 -0
- package/dist/convex/lib/auth.js +372 -0
- package/dist/convex/lib/rbac.js +123 -0
- package/dist/convex/lib/workspace.js +171 -0
- package/dist/convex/organizations.js +192 -0
- package/dist/convex/schema.js +690 -0
- package/dist/convex/users.js +217 -0
- package/dist/convex/workspaces.js +603 -0
- package/dist/mcp-server/lib/convexClient.js +50 -0
- package/dist/mcp-server/lib/scopeEnforcement.js +76 -0
- package/dist/mcp-server/registry.js +116 -0
- package/dist/mcp-server/server.js +97 -0
- package/dist/mcp-server/tests/registry.test.js +163 -0
- package/dist/mcp-server/tests/scopeEnforcement.test.js +137 -0
- package/dist/mcp-server/tests/security.test.js +257 -0
- package/dist/mcp-server/tests/tools.test.js +272 -0
- package/dist/mcp-server/tools/activities.js +207 -0
- package/dist/mcp-server/tools/admin.js +190 -0
- package/dist/mcp-server/tools/companies.js +233 -0
- package/dist/mcp-server/tools/contacts.js +306 -0
- package/dist/mcp-server/tools/customFields.js +222 -0
- package/dist/mcp-server/tools/customObjects.js +235 -0
- package/dist/mcp-server/tools/deals.js +297 -0
- package/dist/mcp-server/tools/rbac.js +177 -0
- package/dist/mcp-server/tools/search.js +155 -0
- package/dist/mcp-server/tools/workflows.js +234 -0
- package/dist/mcp-server/transport/http.js +257 -0
- package/dist/mcp-server/transport/stdio.js +90 -0
- package/package.json +45 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* User management functions
|
|
4
|
+
*
|
|
5
|
+
* Handles syncing Clerk users to Convex users table
|
|
6
|
+
* Enhanced with organization/workspace context support
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getByClerkId = exports.updateSettings = exports.getCurrentUser = exports.syncUser = void 0;
|
|
10
|
+
const values_1 = require("convex/values");
|
|
11
|
+
const server_1 = require("./_generated/server");
|
|
12
|
+
const auth_1 = require("./lib/auth");
|
|
13
|
+
/**
|
|
14
|
+
* Sync current Clerk user to Convex users table
|
|
15
|
+
*
|
|
16
|
+
* This should be called from the frontend after Clerk authentication
|
|
17
|
+
* to ensure the user exists in the Convex users table.
|
|
18
|
+
* Also syncs organization if user is in one, and ensures default workspace exists.
|
|
19
|
+
*/
|
|
20
|
+
exports.syncUser = (0, server_1.mutation)({
|
|
21
|
+
args: {},
|
|
22
|
+
handler: async (ctx) => {
|
|
23
|
+
const identity = await ctx.auth.getUserIdentity();
|
|
24
|
+
if (!identity) {
|
|
25
|
+
throw new Error('Not authenticated');
|
|
26
|
+
}
|
|
27
|
+
// Sync user
|
|
28
|
+
const user = await (0, auth_1.getOrCreateUser)(ctx, identity.subject, // clerkId
|
|
29
|
+
identity.tokenIdentifier, identity.email || '', identity.name || undefined, identity.pictureUrl || undefined);
|
|
30
|
+
if (!user) {
|
|
31
|
+
throw new Error('Failed to sync user');
|
|
32
|
+
}
|
|
33
|
+
// Ensure user has a default personal workspace
|
|
34
|
+
const defaultWorkspace = await ctx.db
|
|
35
|
+
.query('workspaces')
|
|
36
|
+
.withIndex('by_owner', (q) => q.eq('ownerId', user._id))
|
|
37
|
+
.filter((q) => q.and(q.eq(q.field('ownerType'), 'user'), q.eq(q.field('isDefault'), true)))
|
|
38
|
+
.first();
|
|
39
|
+
if (!defaultWorkspace) {
|
|
40
|
+
// Create default personal workspace
|
|
41
|
+
const workspaceId = await ctx.db.insert('workspaces', {
|
|
42
|
+
name: 'Personal',
|
|
43
|
+
ownerType: 'user',
|
|
44
|
+
ownerId: user._id,
|
|
45
|
+
isDefault: true,
|
|
46
|
+
createdAt: Date.now(),
|
|
47
|
+
updatedAt: Date.now(),
|
|
48
|
+
});
|
|
49
|
+
// Set as active workspace
|
|
50
|
+
await ctx.db.patch(user._id, { activeWorkspaceId: workspaceId });
|
|
51
|
+
}
|
|
52
|
+
// If in organization context, sync org and membership
|
|
53
|
+
const orgId = identity.org_id;
|
|
54
|
+
const orgRole = identity.org_role;
|
|
55
|
+
const orgSlug = identity.org_slug;
|
|
56
|
+
if (orgId) {
|
|
57
|
+
// Get org name from claims (Clerk includes it)
|
|
58
|
+
const orgName = identity.org_name ||
|
|
59
|
+
orgSlug ||
|
|
60
|
+
'Organization';
|
|
61
|
+
// Sync organization
|
|
62
|
+
const org = await (0, auth_1.getOrCreateOrganization)(ctx, orgId, orgName, orgSlug, identity.org_image_url);
|
|
63
|
+
// Sync membership
|
|
64
|
+
if (org) {
|
|
65
|
+
await (0, auth_1.getOrCreateMembership)(ctx, user._id, org._id, identity.subject, orgId, orgRole || 'org:member');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
success: true,
|
|
70
|
+
userId: user._id,
|
|
71
|
+
role: user.role,
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* Get current user info with workspace context
|
|
77
|
+
* Returns user data + workspace info if authenticated, null otherwise
|
|
78
|
+
*/
|
|
79
|
+
exports.getCurrentUser = (0, server_1.query)({
|
|
80
|
+
args: {},
|
|
81
|
+
handler: async (ctx) => {
|
|
82
|
+
const identity = await ctx.auth.getUserIdentity();
|
|
83
|
+
if (!identity) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
// Try to find user by Clerk ID first
|
|
87
|
+
let user = await ctx.db
|
|
88
|
+
.query('users')
|
|
89
|
+
.withIndex('by_clerk_id', (q) => q.eq('clerkId', identity.subject))
|
|
90
|
+
.first();
|
|
91
|
+
// Fallback to token identifier
|
|
92
|
+
if (!user) {
|
|
93
|
+
user = await ctx.db
|
|
94
|
+
.query('users')
|
|
95
|
+
.withIndex('by_token', (q) => q.eq('tokenIdentifier', identity.tokenIdentifier))
|
|
96
|
+
.first();
|
|
97
|
+
}
|
|
98
|
+
if (!user) {
|
|
99
|
+
// User not synced yet
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
// Get Clerk workspace context (org from token)
|
|
103
|
+
const clerkWorkspace = await (0, auth_1.getWorkspaceContext)(ctx);
|
|
104
|
+
// Get active Vantage workspace
|
|
105
|
+
let activeWorkspace = null;
|
|
106
|
+
if (user.activeWorkspaceId) {
|
|
107
|
+
activeWorkspace = await ctx.db.get(user.activeWorkspaceId);
|
|
108
|
+
}
|
|
109
|
+
// If no active workspace, get the default one
|
|
110
|
+
if (!activeWorkspace) {
|
|
111
|
+
activeWorkspace = await ctx.db
|
|
112
|
+
.query('workspaces')
|
|
113
|
+
.withIndex('by_owner', (q) => q.eq('ownerId', user._id))
|
|
114
|
+
.filter((q) => q.and(q.eq(q.field('ownerType'), 'user'), q.eq(q.field('isDefault'), true)))
|
|
115
|
+
.first();
|
|
116
|
+
}
|
|
117
|
+
// If in org context (from Clerk token), get org details
|
|
118
|
+
let organization = null;
|
|
119
|
+
const currentOrgId = clerkWorkspace.orgId;
|
|
120
|
+
if (currentOrgId) {
|
|
121
|
+
organization = await ctx.db
|
|
122
|
+
.query('organizations')
|
|
123
|
+
.withIndex('by_clerk_id', (q) => q.eq('clerkId', currentOrgId))
|
|
124
|
+
.first();
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
_id: user._id,
|
|
128
|
+
clerkId: user.clerkId,
|
|
129
|
+
email: user.email,
|
|
130
|
+
name: user.name,
|
|
131
|
+
firstName: user.firstName,
|
|
132
|
+
lastName: user.lastName,
|
|
133
|
+
role: user.role,
|
|
134
|
+
avatarUrl: user.avatarUrl,
|
|
135
|
+
settings: user.settings,
|
|
136
|
+
activeWorkspaceId: user.activeWorkspaceId,
|
|
137
|
+
createdAt: user.createdAt,
|
|
138
|
+
updatedAt: user.updatedAt,
|
|
139
|
+
// Active Vantage workspace
|
|
140
|
+
activeWorkspace: activeWorkspace
|
|
141
|
+
? {
|
|
142
|
+
_id: activeWorkspace._id,
|
|
143
|
+
name: activeWorkspace.name,
|
|
144
|
+
slug: activeWorkspace.slug,
|
|
145
|
+
icon: activeWorkspace.icon,
|
|
146
|
+
color: activeWorkspace.color,
|
|
147
|
+
ownerType: activeWorkspace.ownerType,
|
|
148
|
+
isDefault: activeWorkspace.isDefault,
|
|
149
|
+
}
|
|
150
|
+
: null,
|
|
151
|
+
// Clerk context (which org user is viewing)
|
|
152
|
+
clerkContext: {
|
|
153
|
+
orgId: clerkWorkspace.orgId,
|
|
154
|
+
entityId: clerkWorkspace.entityId,
|
|
155
|
+
isPersonal: clerkWorkspace.isPersonal,
|
|
156
|
+
organization: organization
|
|
157
|
+
? {
|
|
158
|
+
_id: organization._id,
|
|
159
|
+
name: organization.name,
|
|
160
|
+
slug: organization.slug,
|
|
161
|
+
imageUrl: organization.imageUrl,
|
|
162
|
+
plan: organization.plan,
|
|
163
|
+
}
|
|
164
|
+
: null,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
/**
|
|
170
|
+
* Update user settings
|
|
171
|
+
*/
|
|
172
|
+
exports.updateSettings = (0, server_1.mutation)({
|
|
173
|
+
args: {
|
|
174
|
+
theme: values_1.v.optional(values_1.v.string()),
|
|
175
|
+
defaultModel: values_1.v.optional(values_1.v.string()),
|
|
176
|
+
notifications: values_1.v.optional(values_1.v.boolean()),
|
|
177
|
+
},
|
|
178
|
+
handler: async (ctx, args) => {
|
|
179
|
+
const identity = await ctx.auth.getUserIdentity();
|
|
180
|
+
if (!identity)
|
|
181
|
+
throw new Error('Not authenticated');
|
|
182
|
+
const user = await ctx.db
|
|
183
|
+
.query('users')
|
|
184
|
+
.withIndex('by_clerk_id', (q) => q.eq('clerkId', identity.subject))
|
|
185
|
+
.first();
|
|
186
|
+
if (!user)
|
|
187
|
+
throw new Error('User not found');
|
|
188
|
+
const currentSettings = user.settings || {};
|
|
189
|
+
const newSettings = {
|
|
190
|
+
...currentSettings,
|
|
191
|
+
...(args.theme !== undefined && { theme: args.theme }),
|
|
192
|
+
...(args.defaultModel !== undefined && {
|
|
193
|
+
defaultModel: args.defaultModel,
|
|
194
|
+
}),
|
|
195
|
+
...(args.notifications !== undefined && {
|
|
196
|
+
notifications: args.notifications,
|
|
197
|
+
}),
|
|
198
|
+
};
|
|
199
|
+
await ctx.db.patch(user._id, {
|
|
200
|
+
settings: newSettings,
|
|
201
|
+
updatedAt: Date.now(),
|
|
202
|
+
});
|
|
203
|
+
return { success: true };
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
/**
|
|
207
|
+
* Get user by Clerk ID
|
|
208
|
+
*/
|
|
209
|
+
exports.getByClerkId = (0, server_1.query)({
|
|
210
|
+
args: { clerkId: values_1.v.string() },
|
|
211
|
+
handler: async (ctx, { clerkId }) => {
|
|
212
|
+
return await ctx.db
|
|
213
|
+
.query('users')
|
|
214
|
+
.withIndex('by_clerk_id', (q) => q.eq('clerkId', clerkId))
|
|
215
|
+
.first();
|
|
216
|
+
},
|
|
217
|
+
});
|