@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.
Files changed (48) hide show
  1. package/README.md +260 -0
  2. package/dist/convex/crm/_helpers.js +24 -0
  3. package/dist/convex/crm/activities.js +220 -0
  4. package/dist/convex/crm/briefing.js +198 -0
  5. package/dist/convex/crm/calendarCron.js +92 -0
  6. package/dist/convex/crm/calendarCronDispatch.js +83 -0
  7. package/dist/convex/crm/calendarSync.js +294 -0
  8. package/dist/convex/crm/companies.js +323 -0
  9. package/dist/convex/crm/contacts.js +346 -0
  10. package/dist/convex/crm/deals.js +481 -0
  11. package/dist/convex/crm/emailActions.js +158 -0
  12. package/dist/convex/crm/emailCron.js +210 -0
  13. package/dist/convex/crm/emailCronDispatch.js +76 -0
  14. package/dist/convex/crm/emailSync.js +260 -0
  15. package/dist/convex/crm/onboarding.js +185 -0
  16. package/dist/convex/crm/stats.js +75 -0
  17. package/dist/convex/crm/tasks.js +109 -0
  18. package/dist/convex/crons.js +25 -0
  19. package/dist/convex/integrations.js +183 -0
  20. package/dist/convex/lib/auditLog.js +109 -0
  21. package/dist/convex/lib/auth.js +372 -0
  22. package/dist/convex/lib/rbac.js +123 -0
  23. package/dist/convex/lib/workspace.js +171 -0
  24. package/dist/convex/organizations.js +192 -0
  25. package/dist/convex/schema.js +690 -0
  26. package/dist/convex/users.js +217 -0
  27. package/dist/convex/workspaces.js +603 -0
  28. package/dist/mcp-server/lib/convexClient.js +50 -0
  29. package/dist/mcp-server/lib/scopeEnforcement.js +76 -0
  30. package/dist/mcp-server/registry.js +116 -0
  31. package/dist/mcp-server/server.js +97 -0
  32. package/dist/mcp-server/tests/registry.test.js +163 -0
  33. package/dist/mcp-server/tests/scopeEnforcement.test.js +137 -0
  34. package/dist/mcp-server/tests/security.test.js +257 -0
  35. package/dist/mcp-server/tests/tools.test.js +272 -0
  36. package/dist/mcp-server/tools/activities.js +207 -0
  37. package/dist/mcp-server/tools/admin.js +190 -0
  38. package/dist/mcp-server/tools/companies.js +233 -0
  39. package/dist/mcp-server/tools/contacts.js +306 -0
  40. package/dist/mcp-server/tools/customFields.js +222 -0
  41. package/dist/mcp-server/tools/customObjects.js +235 -0
  42. package/dist/mcp-server/tools/deals.js +297 -0
  43. package/dist/mcp-server/tools/rbac.js +177 -0
  44. package/dist/mcp-server/tools/search.js +155 -0
  45. package/dist/mcp-server/tools/workflows.js +234 -0
  46. package/dist/mcp-server/transport/http.js +257 -0
  47. package/dist/mcp-server/transport/stdio.js +90 -0
  48. package/package.json +45 -0
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ /**
3
+ * VantageCRM MCP — Custom Objects tools (7 tools)
4
+ *
5
+ * Tools: add_custom_object, list_custom_objects, create_record, update_record,
6
+ * delete_record, list_records, search_records
7
+ *
8
+ * Scope: admin for DDL (add_custom_object), write/read for DML
9
+ * Ref: spec §3.7
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.CUSTOM_OBJECT_TOOLS = void 0;
13
+ exports.add_custom_object = add_custom_object;
14
+ exports.list_custom_objects = list_custom_objects;
15
+ exports.create_record = create_record;
16
+ exports.update_record = update_record;
17
+ exports.delete_record = delete_record;
18
+ exports.list_records = list_records;
19
+ exports.search_records = search_records;
20
+ const zod_1 = require("zod");
21
+ const server_1 = require("convex/server");
22
+ const convexClient_1 = require("../lib/convexClient");
23
+ // ---------------------------------------------------------------------------
24
+ // Zod schemas
25
+ // ---------------------------------------------------------------------------
26
+ const AddCustomObjectSchema = zod_1.z.object({
27
+ workspaceId: zod_1.z.string().min(1),
28
+ objectType: zod_1.z.string().min(1).max(100).regex(/^[a-z0-9_]+$/, 'Type must be snake_case'),
29
+ label: zod_1.z.string().min(1).max(200),
30
+ labelPlural: zod_1.z.string().min(1).max(200),
31
+ description: zod_1.z.string().max(1000).optional(),
32
+ fields: zod_1.z.array(zod_1.z.object({
33
+ key: zod_1.z.string().min(1),
34
+ label: zod_1.z.string().min(1),
35
+ fieldType: zod_1.z.string().min(1),
36
+ required: zod_1.z.boolean().default(false),
37
+ })).optional(),
38
+ });
39
+ const ListCustomObjectsSchema = zod_1.z.object({
40
+ workspaceId: zod_1.z.string().min(1),
41
+ });
42
+ const CreateRecordSchema = zod_1.z.object({
43
+ workspaceId: zod_1.z.string().min(1),
44
+ objectType: zod_1.z.string().min(1),
45
+ fields: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
46
+ relatedContactId: zod_1.z.string().optional(),
47
+ relatedDealId: zod_1.z.string().optional(),
48
+ relatedCompanyId: zod_1.z.string().optional(),
49
+ });
50
+ const UpdateRecordSchema = zod_1.z.object({
51
+ recordId: zod_1.z.string().min(1),
52
+ fields: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
53
+ });
54
+ const DeleteRecordSchema = zod_1.z.object({
55
+ recordId: zod_1.z.string().min(1),
56
+ });
57
+ const ListRecordsSchema = zod_1.z.object({
58
+ workspaceId: zod_1.z.string().min(1),
59
+ objectType: zod_1.z.string().min(1),
60
+ limit: zod_1.z.number().int().min(1).max(100).default(20),
61
+ });
62
+ const SearchRecordsSchema = zod_1.z.object({
63
+ workspaceId: zod_1.z.string().min(1),
64
+ objectType: zod_1.z.string().min(1),
65
+ query: zod_1.z.string().min(1).max(200),
66
+ limit: zod_1.z.number().int().min(1).max(50).default(10),
67
+ });
68
+ // ---------------------------------------------------------------------------
69
+ // Handlers
70
+ // ---------------------------------------------------------------------------
71
+ async function add_custom_object(rawArgs) {
72
+ const args = AddCustomObjectSchema.parse(rawArgs);
73
+ return (0, convexClient_1.withEnvelope)(() => (0, convexClient_1.getConvexClient)().mutation(server_1.anyApi.crm.customObjects.defineCustomObject, {
74
+ workspaceId: args.workspaceId,
75
+ objectType: args.objectType,
76
+ label: args.label,
77
+ labelPlural: args.labelPlural,
78
+ description: args.description,
79
+ fields: args.fields,
80
+ }));
81
+ }
82
+ async function list_custom_objects(rawArgs) {
83
+ const args = ListCustomObjectsSchema.parse(rawArgs);
84
+ return (0, convexClient_1.withEnvelope)(() => (0, convexClient_1.getConvexClient)().query(server_1.anyApi.crm.customObjects.listCustomObjectDefinitions, {
85
+ workspaceId: args.workspaceId,
86
+ }));
87
+ }
88
+ async function create_record(rawArgs) {
89
+ const args = CreateRecordSchema.parse(rawArgs);
90
+ return (0, convexClient_1.withEnvelope)(() => (0, convexClient_1.getConvexClient)().mutation(server_1.anyApi.crm.customObjects.createCustomObjectRecord, {
91
+ workspaceId: args.workspaceId,
92
+ objectType: args.objectType,
93
+ fields: args.fields,
94
+ relatedContactId: args.relatedContactId,
95
+ relatedDealId: args.relatedDealId,
96
+ relatedCompanyId: args.relatedCompanyId,
97
+ }));
98
+ }
99
+ async function update_record(rawArgs) {
100
+ const args = UpdateRecordSchema.parse(rawArgs);
101
+ return (0, convexClient_1.withEnvelope)(() => (0, convexClient_1.getConvexClient)().mutation(server_1.anyApi.crm.customObjects.updateCustomObjectRecord, {
102
+ recordId: args.recordId,
103
+ fields: args.fields,
104
+ }));
105
+ }
106
+ async function delete_record(rawArgs) {
107
+ const args = DeleteRecordSchema.parse(rawArgs);
108
+ return (0, convexClient_1.withEnvelope)(() => (0, convexClient_1.getConvexClient)().mutation(server_1.anyApi.crm.customObjects.archiveCustomObjectRecord, {
109
+ recordId: args.recordId,
110
+ }));
111
+ }
112
+ async function list_records(rawArgs) {
113
+ const args = ListRecordsSchema.parse(rawArgs);
114
+ return (0, convexClient_1.withEnvelope)(() => (0, convexClient_1.getConvexClient)().query(server_1.anyApi.crm.customObjects.listCustomObjectRecords, {
115
+ workspaceId: args.workspaceId,
116
+ objectType: args.objectType,
117
+ limit: args.limit,
118
+ }));
119
+ }
120
+ async function search_records(rawArgs) {
121
+ const args = SearchRecordsSchema.parse(rawArgs);
122
+ // Client-side filter — no Convex search index for custom objects in V0.1.0
123
+ return (0, convexClient_1.withEnvelope)(async () => {
124
+ const all = await (0, convexClient_1.getConvexClient)().query(server_1.anyApi.crm.customObjects.listCustomObjectRecords, {
125
+ workspaceId: args.workspaceId,
126
+ objectType: args.objectType,
127
+ limit: 200,
128
+ });
129
+ const q = args.query.toLowerCase();
130
+ return all
131
+ .filter((r) => JSON.stringify(r['fields'] ?? {}).toLowerCase().includes(q))
132
+ .slice(0, args.limit);
133
+ });
134
+ }
135
+ // ---------------------------------------------------------------------------
136
+ // Tool definitions
137
+ // ---------------------------------------------------------------------------
138
+ exports.CUSTOM_OBJECT_TOOLS = [
139
+ {
140
+ name: 'add_custom_object',
141
+ description: 'DDL: define a new custom object type (admin only)',
142
+ requiredScope: 'admin',
143
+ inputSchema: {
144
+ type: 'object',
145
+ properties: {
146
+ workspaceId: { type: 'string' },
147
+ objectType: { type: 'string', description: 'snake_case type key' },
148
+ label: { type: 'string' },
149
+ labelPlural: { type: 'string' },
150
+ description: { type: 'string' },
151
+ fields: { type: 'array', items: { type: 'object' } },
152
+ },
153
+ required: ['workspaceId', 'objectType', 'label', 'labelPlural'],
154
+ },
155
+ },
156
+ {
157
+ name: 'list_custom_objects',
158
+ description: 'List custom object type definitions for a workspace',
159
+ requiredScope: 'read',
160
+ inputSchema: {
161
+ type: 'object',
162
+ properties: { workspaceId: { type: 'string' } },
163
+ required: ['workspaceId'],
164
+ },
165
+ },
166
+ {
167
+ name: 'create_record',
168
+ description: 'DML: create a custom object record',
169
+ requiredScope: 'write',
170
+ inputSchema: {
171
+ type: 'object',
172
+ properties: {
173
+ workspaceId: { type: 'string' },
174
+ objectType: { type: 'string' },
175
+ fields: { type: 'object' },
176
+ relatedContactId: { type: 'string' },
177
+ relatedDealId: { type: 'string' },
178
+ relatedCompanyId: { type: 'string' },
179
+ },
180
+ required: ['workspaceId', 'objectType', 'fields'],
181
+ },
182
+ },
183
+ {
184
+ name: 'update_record',
185
+ description: 'Update a custom object record',
186
+ requiredScope: 'write',
187
+ inputSchema: {
188
+ type: 'object',
189
+ properties: {
190
+ recordId: { type: 'string' },
191
+ fields: { type: 'object' },
192
+ },
193
+ required: ['recordId', 'fields'],
194
+ },
195
+ },
196
+ {
197
+ name: 'delete_record',
198
+ description: 'Soft delete a custom object record',
199
+ requiredScope: 'write',
200
+ inputSchema: {
201
+ type: 'object',
202
+ properties: { recordId: { type: 'string' } },
203
+ required: ['recordId'],
204
+ },
205
+ },
206
+ {
207
+ name: 'list_records',
208
+ description: 'List custom object records by type',
209
+ requiredScope: 'read',
210
+ inputSchema: {
211
+ type: 'object',
212
+ properties: {
213
+ workspaceId: { type: 'string' },
214
+ objectType: { type: 'string' },
215
+ limit: { type: 'number' },
216
+ },
217
+ required: ['workspaceId', 'objectType'],
218
+ },
219
+ },
220
+ {
221
+ name: 'search_records',
222
+ description: 'Full-text search custom object records',
223
+ requiredScope: 'read',
224
+ inputSchema: {
225
+ type: 'object',
226
+ properties: {
227
+ workspaceId: { type: 'string' },
228
+ objectType: { type: 'string' },
229
+ query: { type: 'string' },
230
+ limit: { type: 'number' },
231
+ },
232
+ required: ['workspaceId', 'objectType', 'query'],
233
+ },
234
+ },
235
+ ];
@@ -0,0 +1,297 @@
1
+ "use strict";
2
+ /**
3
+ * VantageCRM MCP — Deals tools (8 tools)
4
+ *
5
+ * Tools: create_deal, get_deal, update_deal, list_deals, search_deals,
6
+ * delete_deal, move_deal_stage, forecast_pipeline
7
+ *
8
+ * Scope: read for queries, write for mutations
9
+ * Ref: spec §3.4
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.DEAL_TOOLS = void 0;
13
+ exports.create_deal = create_deal;
14
+ exports.get_deal = get_deal;
15
+ exports.update_deal = update_deal;
16
+ exports.list_deals = list_deals;
17
+ exports.search_deals = search_deals;
18
+ exports.delete_deal = delete_deal;
19
+ exports.move_deal_stage = move_deal_stage;
20
+ exports.forecast_pipeline = forecast_pipeline;
21
+ const zod_1 = require("zod");
22
+ const api_1 = require("../../convex/_generated/api");
23
+ const convexClient_1 = require("../lib/convexClient");
24
+ // ---------------------------------------------------------------------------
25
+ // Zod schemas
26
+ // ---------------------------------------------------------------------------
27
+ const CreateDealSchema = zod_1.z.object({
28
+ workspaceId: zod_1.z.string().min(1),
29
+ title: zod_1.z.string().min(1).max(200),
30
+ stage: zod_1.z.string().min(1).max(100),
31
+ value: zod_1.z.number().min(0).optional(),
32
+ currency: zod_1.z.string().length(3).default('USD'),
33
+ probability: zod_1.z.number().min(0).max(100).optional(),
34
+ contactId: zod_1.z.string().optional(),
35
+ companyId: zod_1.z.string().optional(),
36
+ expectedCloseDate: zod_1.z.number().optional(),
37
+ description: zod_1.z.string().max(5000).optional(),
38
+ tags: zod_1.z.array(zod_1.z.string()).max(20).optional(),
39
+ customFields: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
40
+ });
41
+ const GetDealSchema = zod_1.z.object({
42
+ dealId: zod_1.z.string().min(1),
43
+ });
44
+ const UpdateDealSchema = zod_1.z.object({
45
+ dealId: zod_1.z.string().min(1),
46
+ title: zod_1.z.string().min(1).max(200).optional(),
47
+ stage: zod_1.z.string().optional(),
48
+ value: zod_1.z.number().min(0).optional(),
49
+ probability: zod_1.z.number().min(0).max(100).optional(),
50
+ contactId: zod_1.z.string().optional(),
51
+ companyId: zod_1.z.string().optional(),
52
+ expectedCloseDate: zod_1.z.number().optional(),
53
+ description: zod_1.z.string().max(5000).optional(),
54
+ tags: zod_1.z.array(zod_1.z.string()).max(20).optional(),
55
+ customFields: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
56
+ });
57
+ const ListDealsSchema = zod_1.z.object({
58
+ workspaceId: zod_1.z.string().min(1),
59
+ stage: zod_1.z.string().optional(),
60
+ contactId: zod_1.z.string().optional(),
61
+ companyId: zod_1.z.string().optional(),
62
+ limit: zod_1.z.number().int().min(1).max(100).default(20),
63
+ });
64
+ const SearchDealsSchema = zod_1.z.object({
65
+ workspaceId: zod_1.z.string().min(1),
66
+ query: zod_1.z.string().min(1).max(200),
67
+ limit: zod_1.z.number().int().min(1).max(50).default(10),
68
+ });
69
+ const DeleteDealSchema = zod_1.z.object({
70
+ dealId: zod_1.z.string().min(1),
71
+ });
72
+ const MoveDealStageSchema = zod_1.z.object({
73
+ dealId: zod_1.z.string().min(1),
74
+ newStage: zod_1.z.string().min(1),
75
+ probability: zod_1.z.number().min(0).max(100).optional(),
76
+ });
77
+ const ForecastPipelineSchema = zod_1.z.object({
78
+ workspaceId: zod_1.z.string().min(1),
79
+ });
80
+ // ---------------------------------------------------------------------------
81
+ // Handlers
82
+ // ---------------------------------------------------------------------------
83
+ async function create_deal(rawArgs) {
84
+ const args = CreateDealSchema.parse(rawArgs);
85
+ return (0, convexClient_1.withEnvelope)(() =>
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ (0, convexClient_1.getConvexClient)().mutation(api_1.api.crm.deals.create, {
88
+ workspaceId: args.workspaceId,
89
+ title: args.title,
90
+ stage: args.stage,
91
+ value: args.value,
92
+ currency: args.currency,
93
+ probability: args.probability,
94
+ contactId: args.contactId,
95
+ companyId: args.companyId,
96
+ expectedCloseDate: args.expectedCloseDate,
97
+ description: args.description,
98
+ tags: args.tags,
99
+ customFields: args.customFields,
100
+ }));
101
+ }
102
+ async function get_deal(rawArgs) {
103
+ const args = GetDealSchema.parse(rawArgs);
104
+ return (0, convexClient_1.withEnvelope)(() =>
105
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
106
+ (0, convexClient_1.getConvexClient)().query(api_1.api.crm.deals.get, {
107
+ dealId: args.dealId,
108
+ }));
109
+ }
110
+ async function update_deal(rawArgs) {
111
+ const args = UpdateDealSchema.parse(rawArgs);
112
+ return (0, convexClient_1.withEnvelope)(() =>
113
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
+ (0, convexClient_1.getConvexClient)().mutation(api_1.api.crm.deals.update, {
115
+ dealId: args.dealId,
116
+ title: args.title,
117
+ stage: args.stage,
118
+ value: args.value,
119
+ probability: args.probability,
120
+ contactId: args.contactId,
121
+ companyId: args.companyId,
122
+ expectedCloseDate: args.expectedCloseDate,
123
+ description: args.description,
124
+ tags: args.tags,
125
+ customFields: args.customFields,
126
+ }));
127
+ }
128
+ async function list_deals(rawArgs) {
129
+ const args = ListDealsSchema.parse(rawArgs);
130
+ return (0, convexClient_1.withEnvelope)(() =>
131
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
132
+ (0, convexClient_1.getConvexClient)().query(api_1.api.crm.deals.list, {
133
+ workspaceId: args.workspaceId,
134
+ stage: args.stage,
135
+ contactId: args.contactId,
136
+ companyId: args.companyId,
137
+ limit: args.limit,
138
+ }));
139
+ }
140
+ async function search_deals(rawArgs) {
141
+ const args = SearchDealsSchema.parse(rawArgs);
142
+ // Convex layer does not expose a standalone search query for deals yet — filter client-side
143
+ return (0, convexClient_1.withEnvelope)(async () => {
144
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
145
+ const all = await (0, convexClient_1.getConvexClient)().query(api_1.api.crm.deals.list, {
146
+ workspaceId: args.workspaceId,
147
+ limit: 200,
148
+ });
149
+ const q = args.query.toLowerCase();
150
+ return all
151
+ .filter((d) => String(d['title'] ?? '').toLowerCase().includes(q) ||
152
+ String(d['searchableText'] ?? '').toLowerCase().includes(q))
153
+ .slice(0, args.limit);
154
+ });
155
+ }
156
+ async function delete_deal(rawArgs) {
157
+ const args = DeleteDealSchema.parse(rawArgs);
158
+ return (0, convexClient_1.withEnvelope)(() =>
159
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
160
+ (0, convexClient_1.getConvexClient)().mutation(api_1.api.crm.deals.remove, {
161
+ dealId: args.dealId,
162
+ }));
163
+ }
164
+ async function move_deal_stage(rawArgs) {
165
+ const args = MoveDealStageSchema.parse(rawArgs);
166
+ return (0, convexClient_1.withEnvelope)(() =>
167
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
168
+ (0, convexClient_1.getConvexClient)().mutation(api_1.api.crm.deals.moveStage, {
169
+ dealId: args.dealId,
170
+ newStage: args.newStage,
171
+ probability: args.probability,
172
+ }));
173
+ }
174
+ async function forecast_pipeline(rawArgs) {
175
+ const args = ForecastPipelineSchema.parse(rawArgs);
176
+ return (0, convexClient_1.withEnvelope)(() =>
177
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
178
+ (0, convexClient_1.getConvexClient)().query(api_1.api.crm.deals.forecast, {
179
+ workspaceId: args.workspaceId,
180
+ }));
181
+ }
182
+ // ---------------------------------------------------------------------------
183
+ // Tool definitions
184
+ // ---------------------------------------------------------------------------
185
+ exports.DEAL_TOOLS = [
186
+ {
187
+ name: 'create_deal',
188
+ description: 'Create a deal in the CRM pipeline',
189
+ requiredScope: 'write',
190
+ inputSchema: {
191
+ type: 'object',
192
+ properties: {
193
+ workspaceId: { type: 'string' },
194
+ title: { type: 'string' },
195
+ stage: { type: 'string' },
196
+ value: { type: 'number' },
197
+ currency: { type: 'string' },
198
+ probability: { type: 'number' },
199
+ contactId: { type: 'string' },
200
+ companyId: { type: 'string' },
201
+ expectedCloseDate: { type: 'number' },
202
+ description: { type: 'string' },
203
+ },
204
+ required: ['workspaceId', 'title', 'stage'],
205
+ },
206
+ },
207
+ {
208
+ name: 'get_deal',
209
+ description: 'Retrieve a deal by ID',
210
+ requiredScope: 'read',
211
+ inputSchema: {
212
+ type: 'object',
213
+ properties: { dealId: { type: 'string' } },
214
+ required: ['dealId'],
215
+ },
216
+ },
217
+ {
218
+ name: 'update_deal',
219
+ description: 'Update deal fields',
220
+ requiredScope: 'write',
221
+ inputSchema: {
222
+ type: 'object',
223
+ properties: {
224
+ dealId: { type: 'string' },
225
+ title: { type: 'string' },
226
+ stage: { type: 'string' },
227
+ value: { type: 'number' },
228
+ probability: { type: 'number' },
229
+ },
230
+ required: ['dealId'],
231
+ },
232
+ },
233
+ {
234
+ name: 'list_deals',
235
+ description: 'List deals by workspace/stage/contact/company',
236
+ requiredScope: 'read',
237
+ inputSchema: {
238
+ type: 'object',
239
+ properties: {
240
+ workspaceId: { type: 'string' },
241
+ stage: { type: 'string' },
242
+ contactId: { type: 'string' },
243
+ companyId: { type: 'string' },
244
+ limit: { type: 'number' },
245
+ },
246
+ required: ['workspaceId'],
247
+ },
248
+ },
249
+ {
250
+ name: 'search_deals',
251
+ description: 'Full-text search deals by title',
252
+ requiredScope: 'read',
253
+ inputSchema: {
254
+ type: 'object',
255
+ properties: {
256
+ workspaceId: { type: 'string' },
257
+ query: { type: 'string' },
258
+ limit: { type: 'number' },
259
+ },
260
+ required: ['workspaceId', 'query'],
261
+ },
262
+ },
263
+ {
264
+ name: 'delete_deal',
265
+ description: 'Soft delete a deal (isArchived=true)',
266
+ requiredScope: 'write',
267
+ inputSchema: {
268
+ type: 'object',
269
+ properties: { dealId: { type: 'string' } },
270
+ required: ['dealId'],
271
+ },
272
+ },
273
+ {
274
+ name: 'move_deal_stage',
275
+ description: 'Move a deal to a new stage with optional probability override (OQ-2)',
276
+ requiredScope: 'write',
277
+ inputSchema: {
278
+ type: 'object',
279
+ properties: {
280
+ dealId: { type: 'string' },
281
+ newStage: { type: 'string' },
282
+ probability: { type: 'number', minimum: 0, maximum: 100 },
283
+ },
284
+ required: ['dealId', 'newStage'],
285
+ },
286
+ },
287
+ {
288
+ name: 'forecast_pipeline',
289
+ description: 'Compute weighted pipeline value (Σ value × probability) for a workspace',
290
+ requiredScope: 'read',
291
+ inputSchema: {
292
+ type: 'object',
293
+ properties: { workspaceId: { type: 'string' } },
294
+ required: ['workspaceId'],
295
+ },
296
+ },
297
+ ];