@daeda/mcp-pro 0.1.0 → 0.1.4
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/index.js +304 -68
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -416,7 +416,6 @@ var saveErrorCsv = (portalId, objectType, csvContent) => pipe2(
|
|
|
416
416
|
);
|
|
417
417
|
|
|
418
418
|
// src/plugins/crm-data.ts
|
|
419
|
-
var PROPERTY_SAMPLE_SIZE = 5;
|
|
420
419
|
var ASSOCIATIONS_TABLE_STATEMENTS = [
|
|
421
420
|
`CREATE TABLE IF NOT EXISTS associations (
|
|
422
421
|
from_object_type TEXT NOT NULL,
|
|
@@ -649,23 +648,18 @@ var crmDataPlugin = {
|
|
|
649
648
|
const count = Number(
|
|
650
649
|
countReader.getRowObjects()[0].count
|
|
651
650
|
);
|
|
652
|
-
let
|
|
651
|
+
let propertyKeyCount = 0;
|
|
653
652
|
if (count > 0) {
|
|
654
653
|
const sampleReader = await conn.runAndReadAll(
|
|
655
|
-
`SELECT properties FROM "${tableName}" LIMIT
|
|
654
|
+
`SELECT properties FROM "${tableName}" LIMIT 1`
|
|
656
655
|
);
|
|
657
656
|
const sampleRows = sampleReader.getRowObjects();
|
|
658
|
-
|
|
659
|
-
for (const row of sampleRows) {
|
|
657
|
+
if (sampleRows[0]) {
|
|
660
658
|
try {
|
|
661
|
-
|
|
662
|
-
for (const key of Object.keys(props)) {
|
|
663
|
-
keySet.add(key);
|
|
664
|
-
}
|
|
659
|
+
propertyKeyCount = Object.keys(JSON.parse(sampleRows[0].properties)).length;
|
|
665
660
|
} catch {
|
|
666
661
|
}
|
|
667
662
|
}
|
|
668
|
-
propertyKeys = Array.from(keySet).sort();
|
|
669
663
|
}
|
|
670
664
|
let lastSynced = null;
|
|
671
665
|
try {
|
|
@@ -680,7 +674,7 @@ var crmDataPlugin = {
|
|
|
680
674
|
return {
|
|
681
675
|
objectType: tableName,
|
|
682
676
|
recordCount: count,
|
|
683
|
-
|
|
677
|
+
propertyKeyCount,
|
|
684
678
|
lastSynced
|
|
685
679
|
};
|
|
686
680
|
})
|
|
@@ -701,11 +695,6 @@ var crmDataPlugin = {
|
|
|
701
695
|
to: group.to_object_type,
|
|
702
696
|
count: Number(group.count)
|
|
703
697
|
}))
|
|
704
|
-
},
|
|
705
|
-
queryHints: {
|
|
706
|
-
accessProperty: "json_extract_string(properties, '$.propertyName')",
|
|
707
|
-
exampleQuery: "SELECT id, json_extract_string(properties, '$.firstname') as first_name FROM contacts LIMIT 10",
|
|
708
|
-
allProperties: "SELECT id, properties FROM <table> LIMIT 1"
|
|
709
698
|
}
|
|
710
699
|
}
|
|
711
700
|
};
|
|
@@ -3182,7 +3171,7 @@ Use "all" to get everything at once.`,
|
|
|
3182
3171
|
result.schema = await buildSchemaSection(deps.getSelectedPortalId, deps.getEncryptionKey);
|
|
3183
3172
|
}
|
|
3184
3173
|
return {
|
|
3185
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
3174
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
3186
3175
|
};
|
|
3187
3176
|
} catch (e) {
|
|
3188
3177
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -4201,31 +4190,76 @@ var batchDeleteRecordsMeta = {
|
|
|
4201
4190
|
|
|
4202
4191
|
// ../shared/operations/create-list/meta.ts
|
|
4203
4192
|
import { z as z28 } from "zod";
|
|
4193
|
+
var FilterSchema = z28.object({
|
|
4194
|
+
filterType: z28.string(),
|
|
4195
|
+
property: z28.string().optional(),
|
|
4196
|
+
operation: z28.record(z28.unknown()).optional()
|
|
4197
|
+
}).passthrough();
|
|
4198
|
+
var FilterBranchSchema = z28.object({
|
|
4199
|
+
filterBranchType: z28.enum(["AND", "ASSOCIATION", "UNIFIED_EVENTS"]),
|
|
4200
|
+
filterBranches: z28.lazy(() => z28.array(FilterBranchSchema)).default([]),
|
|
4201
|
+
filters: z28.array(FilterSchema).default([])
|
|
4202
|
+
}).passthrough();
|
|
4203
|
+
var RootFilterBranchSchema = z28.object({
|
|
4204
|
+
filterBranchType: z28.literal("OR"),
|
|
4205
|
+
filterBranches: z28.array(FilterBranchSchema).min(1),
|
|
4206
|
+
filters: z28.array(FilterSchema).default([])
|
|
4207
|
+
}).passthrough();
|
|
4204
4208
|
var CreateListOperationSchema = z28.object({
|
|
4205
4209
|
type: z28.literal("create_list"),
|
|
4206
4210
|
description: z28.string().min(1),
|
|
4207
4211
|
name: z28.string().min(1),
|
|
4208
4212
|
object_type_id: z28.string().min(1),
|
|
4209
4213
|
processing_type: z28.enum(["MANUAL", "DYNAMIC"]),
|
|
4210
|
-
filter_branch:
|
|
4214
|
+
filter_branch: RootFilterBranchSchema.optional()
|
|
4211
4215
|
});
|
|
4216
|
+
var FILTER_BRANCH_DESCRIPTION = `HubSpot filter definition for DYNAMIC lists. MUST follow the OR\u2192AND hierarchy:
|
|
4217
|
+
- Root: { filterBranchType: "OR", filters: [], filterBranches: [<one or more AND branches>] }
|
|
4218
|
+
- Each AND branch: { filterBranchType: "AND", filters: [<actual filters>], filterBranches: [] }
|
|
4219
|
+
- For OR logic between filter groups, use multiple AND branches under the root OR.
|
|
4220
|
+
- For AND logic within a group, put multiple filters in the same AND branch's filters array.
|
|
4221
|
+
- ASSOCIATION and UNIFIED_EVENTS branches can be nested inside AND branches.
|
|
4222
|
+
Property filter example: { filterType: "PROPERTY", property: "lifecyclestage", operation: { operationType: "MULTISTRING", operator: "IS_EQUAL_TO", values: ["lead"] } }
|
|
4223
|
+
Common operationTypes: MULTISTRING (strings), NUMBER (numbers), BOOL (booleans), ENUMERATION (select/multi-select), ALL_PROPERTY (IS_KNOWN/IS_NOT_KNOWN), TIME_POINT (date comparison), TIME_RANGED (date ranges).`;
|
|
4212
4224
|
var fields17 = [
|
|
4213
4225
|
{ name: "name", type: "string", required: true, description: "Name for the new list" },
|
|
4214
|
-
{ name: "object_type_id", type: "string", required: true, description: "Object type ID (e.g. '0-1' for contacts, '0-2' for companies)" },
|
|
4226
|
+
{ name: "object_type_id", type: "string", required: true, description: "Object type ID (e.g. '0-1' for contacts, '0-2' for companies, '0-3' for deals)" },
|
|
4215
4227
|
{ name: "processing_type", type: "string", required: true, description: "List type: 'MANUAL' (static) or 'DYNAMIC' (active)" },
|
|
4216
|
-
{ name: "filter_branch", type: "object", required: false, description:
|
|
4228
|
+
{ name: "filter_branch", type: "object", required: false, description: FILTER_BRANCH_DESCRIPTION }
|
|
4217
4229
|
];
|
|
4218
4230
|
var createListMeta = {
|
|
4219
4231
|
type: "create_list",
|
|
4220
4232
|
category: "create",
|
|
4221
4233
|
summary: "Create a contact or company list (static or dynamic)",
|
|
4222
|
-
description:
|
|
4234
|
+
description: `Create a new HubSpot list. MANUAL lists are static (members added manually). DYNAMIC lists are active (membership determined by filter criteria).
|
|
4235
|
+
For DYNAMIC lists, provide filter_branch with the HubSpot filter definition. The root filterBranchType MUST be "OR" with filters: [] and one or more "AND" child branches containing the actual filters. This structure is enforced by HubSpot's API.`,
|
|
4223
4236
|
fields: fields17,
|
|
4224
4237
|
example: {
|
|
4225
4238
|
fields: {
|
|
4226
|
-
name: "
|
|
4239
|
+
name: "Active Leads",
|
|
4227
4240
|
object_type_id: "0-1",
|
|
4228
|
-
processing_type: "
|
|
4241
|
+
processing_type: "DYNAMIC",
|
|
4242
|
+
filter_branch: {
|
|
4243
|
+
filterBranchType: "OR",
|
|
4244
|
+
filters: [],
|
|
4245
|
+
filterBranches: [
|
|
4246
|
+
{
|
|
4247
|
+
filterBranchType: "AND",
|
|
4248
|
+
filterBranches: [],
|
|
4249
|
+
filters: [
|
|
4250
|
+
{
|
|
4251
|
+
filterType: "PROPERTY",
|
|
4252
|
+
property: "lifecyclestage",
|
|
4253
|
+
operation: {
|
|
4254
|
+
operationType: "MULTISTRING",
|
|
4255
|
+
operator: "IS_EQUAL_TO",
|
|
4256
|
+
values: ["lead"]
|
|
4257
|
+
}
|
|
4258
|
+
}
|
|
4259
|
+
]
|
|
4260
|
+
}
|
|
4261
|
+
]
|
|
4262
|
+
}
|
|
4229
4263
|
}
|
|
4230
4264
|
},
|
|
4231
4265
|
toNested: (description, fields29) => {
|
|
@@ -4241,13 +4275,21 @@ var createListMeta = {
|
|
|
4241
4275
|
},
|
|
4242
4276
|
requiredScopes: () => ["crm.lists.write"],
|
|
4243
4277
|
getSummary: (op) => `Create List \u2014 ${op.name} (${op.processing_type})`,
|
|
4244
|
-
getResourceUrl: (portalId) => `${HUBSPOT_BASE}/contacts/${portalId}/
|
|
4278
|
+
getResourceUrl: (portalId) => `${HUBSPOT_BASE}/contacts/${portalId}/objectLists/views/all`,
|
|
4245
4279
|
renderDetails: (op) => {
|
|
4246
4280
|
const rows = [
|
|
4247
4281
|
{ label: "Name", value: op.name },
|
|
4248
4282
|
{ label: "Object Type ID", value: op.object_type_id },
|
|
4249
4283
|
{ label: "Processing Type", value: op.processing_type }
|
|
4250
4284
|
];
|
|
4285
|
+
if (op.filter_branch) {
|
|
4286
|
+
const andCount = op.filter_branch.filterBranches?.length ?? 0;
|
|
4287
|
+
const totalFilters = op.filter_branch.filterBranches?.reduce(
|
|
4288
|
+
(sum, branch) => sum + (branch.filters?.length ?? 0),
|
|
4289
|
+
0
|
|
4290
|
+
) ?? 0;
|
|
4291
|
+
rows.push({ label: "Filter Groups", value: `${andCount} group(s), ${totalFilters} filter(s)` });
|
|
4292
|
+
}
|
|
4251
4293
|
return { rows };
|
|
4252
4294
|
}
|
|
4253
4295
|
};
|
|
@@ -5326,6 +5368,18 @@ var updatePropertyHandler = {
|
|
|
5326
5368
|
message: `Property '${op.property_name}' groupName: expected '${op.updates.group_name}', got '${updated.groupName}'`
|
|
5327
5369
|
});
|
|
5328
5370
|
}
|
|
5371
|
+
if (op.updates.options !== void 0) {
|
|
5372
|
+
const expectedValues = new Set(op.updates.options.map((o) => o.value));
|
|
5373
|
+
const actualValues = new Set(updated.options.map((o) => o.value));
|
|
5374
|
+
if (expectedValues.size !== actualValues.size || [...expectedValues].some((v) => !actualValues.has(v))) {
|
|
5375
|
+
issues.push({
|
|
5376
|
+
severity: "warning",
|
|
5377
|
+
operation_index: index,
|
|
5378
|
+
code: "POSTCHECK_OPTIONS_MISMATCH",
|
|
5379
|
+
message: `Property '${op.property_name}' options: expected ${expectedValues.size} options, got ${actualValues.size}`
|
|
5380
|
+
});
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5329
5383
|
return issues;
|
|
5330
5384
|
}),
|
|
5331
5385
|
catchAllToWarning(
|
|
@@ -5626,6 +5680,14 @@ var updatePropertyGroupHandler = {
|
|
|
5626
5680
|
message: `Property group '${op.group_name}' label: expected '${op.updates.label}', got '${updated.label}'`
|
|
5627
5681
|
});
|
|
5628
5682
|
}
|
|
5683
|
+
if (op.updates.display_order !== void 0 && updated.displayOrder !== op.updates.display_order) {
|
|
5684
|
+
issues.push({
|
|
5685
|
+
severity: "warning",
|
|
5686
|
+
operation_index: index,
|
|
5687
|
+
code: "POSTCHECK_DISPLAY_ORDER_MISMATCH",
|
|
5688
|
+
message: `Property group '${op.group_name}' displayOrder: expected ${op.updates.display_order}, got ${updated.displayOrder}`
|
|
5689
|
+
});
|
|
5690
|
+
}
|
|
5629
5691
|
return issues;
|
|
5630
5692
|
}),
|
|
5631
5693
|
catchAllToWarning(
|
|
@@ -6080,7 +6142,8 @@ var updatePipelineHandler = {
|
|
|
6080
6142
|
label: op.updates.label,
|
|
6081
6143
|
displayOrder: op.updates.display_order
|
|
6082
6144
|
}),
|
|
6083
|
-
Effect44.
|
|
6145
|
+
Effect44.map(() => ({ label: "update pipeline metadata", ok: true })),
|
|
6146
|
+
Effect44.catchAll(() => Effect44.succeed({ label: "update pipeline metadata", ok: false }))
|
|
6084
6147
|
)
|
|
6085
6148
|
);
|
|
6086
6149
|
}
|
|
@@ -6097,7 +6160,8 @@ var updatePipelineHandler = {
|
|
|
6097
6160
|
}
|
|
6098
6161
|
}
|
|
6099
6162
|
}),
|
|
6100
|
-
Effect44.
|
|
6163
|
+
Effect44.map(() => ({ label: `add stage '${stage.label}'`, ok: true })),
|
|
6164
|
+
Effect44.catchAll(() => Effect44.succeed({ label: `add stage '${stage.label}'`, ok: false }))
|
|
6101
6165
|
)
|
|
6102
6166
|
);
|
|
6103
6167
|
}
|
|
@@ -6114,7 +6178,8 @@ var updatePipelineHandler = {
|
|
|
6114
6178
|
}
|
|
6115
6179
|
}
|
|
6116
6180
|
}),
|
|
6117
|
-
Effect44.
|
|
6181
|
+
Effect44.map(() => ({ label: `update stage '${stageUpdate.stage_id}'`, ok: true })),
|
|
6182
|
+
Effect44.catchAll(() => Effect44.succeed({ label: `update stage '${stageUpdate.stage_id}'`, ok: false }))
|
|
6118
6183
|
)
|
|
6119
6184
|
);
|
|
6120
6185
|
}
|
|
@@ -6122,19 +6187,42 @@ var updatePipelineHandler = {
|
|
|
6122
6187
|
steps.push(
|
|
6123
6188
|
pipe33(
|
|
6124
6189
|
hs.deletePipelineStage(op.object_type, op.pipeline_id, stageId),
|
|
6125
|
-
Effect44.
|
|
6190
|
+
Effect44.map(() => ({ label: `remove stage '${stageId}'`, ok: true })),
|
|
6191
|
+
Effect44.catchAll(() => Effect44.succeed({ label: `remove stage '${stageId}'`, ok: false }))
|
|
6126
6192
|
)
|
|
6127
6193
|
);
|
|
6128
6194
|
}
|
|
6129
6195
|
return pipe33(
|
|
6130
6196
|
Effect44.forEach(steps, (step) => step, { concurrency: 1 }),
|
|
6131
|
-
Effect44.map(() =>
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6197
|
+
Effect44.map((results) => {
|
|
6198
|
+
const failed = results.filter((r) => !r.ok);
|
|
6199
|
+
if (failed.length === results.length) {
|
|
6200
|
+
return {
|
|
6201
|
+
index,
|
|
6202
|
+
type: "update_pipeline",
|
|
6203
|
+
status: "failed",
|
|
6204
|
+
records_affected: 0,
|
|
6205
|
+
error: `All ${failed.length} sub-steps failed: ${failed.map((f) => f.label).join(", ")}`
|
|
6206
|
+
};
|
|
6207
|
+
}
|
|
6208
|
+
if (failed.length > 0) {
|
|
6209
|
+
return {
|
|
6210
|
+
index,
|
|
6211
|
+
type: "update_pipeline",
|
|
6212
|
+
status: "failed",
|
|
6213
|
+
records_affected: 1,
|
|
6214
|
+
affected_ids: [op.pipeline_id],
|
|
6215
|
+
error: `Partial failure: ${failed.length}/${results.length} sub-steps failed (${failed.map((f) => f.label).join(", ")})`
|
|
6216
|
+
};
|
|
6217
|
+
}
|
|
6218
|
+
return {
|
|
6219
|
+
index,
|
|
6220
|
+
type: "update_pipeline",
|
|
6221
|
+
status: "success",
|
|
6222
|
+
records_affected: 1,
|
|
6223
|
+
affected_ids: [op.pipeline_id]
|
|
6224
|
+
};
|
|
6225
|
+
})
|
|
6138
6226
|
);
|
|
6139
6227
|
}),
|
|
6140
6228
|
catchAllToFailed("update_pipeline", index)
|
|
@@ -6174,6 +6262,18 @@ var updatePipelineHandler = {
|
|
|
6174
6262
|
});
|
|
6175
6263
|
}
|
|
6176
6264
|
}
|
|
6265
|
+
for (const stageUpdate of op.stages_to_update ?? []) {
|
|
6266
|
+
const stage = pipeline.stages.find((s) => s.id === stageUpdate.stage_id);
|
|
6267
|
+
if (!stage) continue;
|
|
6268
|
+
if (stageUpdate.label !== void 0 && stage.label !== stageUpdate.label) {
|
|
6269
|
+
issues.push({
|
|
6270
|
+
severity: "warning",
|
|
6271
|
+
operation_index: index,
|
|
6272
|
+
code: "POSTCHECK_STAGE_LABEL_MISMATCH",
|
|
6273
|
+
message: `Stage '${stageUpdate.stage_id}' label: expected '${stageUpdate.label}', got '${stage.label}'`
|
|
6274
|
+
});
|
|
6275
|
+
}
|
|
6276
|
+
}
|
|
6177
6277
|
const stageIds = new Set(pipeline.stages.map((s) => s.id));
|
|
6178
6278
|
for (const removedId of op.stage_ids_to_remove ?? []) {
|
|
6179
6279
|
if (stageIds.has(removedId)) {
|
|
@@ -6685,13 +6785,51 @@ var batchUpdateAssociationsHandler = {
|
|
|
6685
6785
|
}
|
|
6686
6786
|
return [];
|
|
6687
6787
|
},
|
|
6688
|
-
validateEffectful: () =>
|
|
6788
|
+
validateEffectful: (op, index) => pipe38(
|
|
6789
|
+
HubSpotService,
|
|
6790
|
+
Effect49.flatMap((hs) => hs.getAssociationLabels(op.from_object_type, op.to_object_type)),
|
|
6791
|
+
Effect49.map((labels) => {
|
|
6792
|
+
const issues = [];
|
|
6793
|
+
const match = labels.find((l) => l.typeId === op.new_association_type_id);
|
|
6794
|
+
if (!match) {
|
|
6795
|
+
issues.push({
|
|
6796
|
+
severity: "error",
|
|
6797
|
+
operation_index: index,
|
|
6798
|
+
code: "ASSOCIATION_TYPE_NOT_FOUND",
|
|
6799
|
+
message: `Association type ID ${op.new_association_type_id} does not exist between ${op.from_object_type} and ${op.to_object_type}`
|
|
6800
|
+
});
|
|
6801
|
+
}
|
|
6802
|
+
return issues;
|
|
6803
|
+
}),
|
|
6804
|
+
catchAllToWarning(
|
|
6805
|
+
"VALIDATION_API_UNAVAILABLE",
|
|
6806
|
+
index,
|
|
6807
|
+
`Unable to verify association labels between ${op.from_object_type} and ${op.to_object_type} (API error)`
|
|
6808
|
+
)
|
|
6809
|
+
),
|
|
6689
6810
|
preCheck: () => Effect49.succeed([]),
|
|
6690
6811
|
execute: (op, index) => pipe38(
|
|
6691
6812
|
HubSpotService,
|
|
6692
|
-
Effect49.flatMap(
|
|
6693
|
-
|
|
6694
|
-
|
|
6813
|
+
Effect49.flatMap((hs) => {
|
|
6814
|
+
const deleteInputs = {
|
|
6815
|
+
fromObjectType: op.from_object_type,
|
|
6816
|
+
toObjectType: op.to_object_type,
|
|
6817
|
+
inputs: op.associations.map((a) => ({
|
|
6818
|
+
fromId: a.from_id,
|
|
6819
|
+
toId: a.to_id
|
|
6820
|
+
}))
|
|
6821
|
+
};
|
|
6822
|
+
const createWithType = (typeId) => hs.batchCreateAssociations({
|
|
6823
|
+
fromObjectType: op.from_object_type,
|
|
6824
|
+
toObjectType: op.to_object_type,
|
|
6825
|
+
inputs: op.associations.map((a) => ({
|
|
6826
|
+
fromId: a.from_id,
|
|
6827
|
+
toId: a.to_id,
|
|
6828
|
+
associationTypeId: typeId
|
|
6829
|
+
}))
|
|
6830
|
+
});
|
|
6831
|
+
const recreateUnlabeled = pipe38(
|
|
6832
|
+
hs.batchCreateAssociations({
|
|
6695
6833
|
fromObjectType: op.from_object_type,
|
|
6696
6834
|
toObjectType: op.to_object_type,
|
|
6697
6835
|
inputs: op.associations.map((a) => ({
|
|
@@ -6699,19 +6837,23 @@ var batchUpdateAssociationsHandler = {
|
|
|
6699
6837
|
toId: a.to_id
|
|
6700
6838
|
}))
|
|
6701
6839
|
}),
|
|
6840
|
+
Effect49.catchAll(() => Effect49.succeed(void 0))
|
|
6841
|
+
);
|
|
6842
|
+
return pipe38(
|
|
6843
|
+
hs.batchDeleteAssociations(deleteInputs),
|
|
6702
6844
|
Effect49.flatMap(
|
|
6703
|
-
() =>
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6845
|
+
() => pipe38(
|
|
6846
|
+
createWithType(op.new_association_type_id),
|
|
6847
|
+
Effect49.catchAll(
|
|
6848
|
+
(createError) => pipe38(
|
|
6849
|
+
recreateUnlabeled,
|
|
6850
|
+
Effect49.flatMap(() => Effect49.fail(createError))
|
|
6851
|
+
)
|
|
6852
|
+
)
|
|
6853
|
+
)
|
|
6712
6854
|
)
|
|
6713
|
-
)
|
|
6714
|
-
),
|
|
6855
|
+
);
|
|
6856
|
+
}),
|
|
6715
6857
|
Effect49.map(() => ({
|
|
6716
6858
|
index,
|
|
6717
6859
|
type: "batch_update_associations",
|
|
@@ -6938,6 +7080,55 @@ var createListHandler = {
|
|
|
6938
7080
|
message: `Processing type '${op.processing_type}' is invalid \u2014 must be "MANUAL" or "DYNAMIC"`
|
|
6939
7081
|
});
|
|
6940
7082
|
}
|
|
7083
|
+
if (op.processing_type === "DYNAMIC" && !op.filter_branch) {
|
|
7084
|
+
issues.push({
|
|
7085
|
+
severity: "warning",
|
|
7086
|
+
operation_index: index,
|
|
7087
|
+
code: "DYNAMIC_LIST_NO_FILTER",
|
|
7088
|
+
message: `DYNAMIC list '${op.name}' has no filter_branch \u2014 it will match all records of the object type`
|
|
7089
|
+
});
|
|
7090
|
+
}
|
|
7091
|
+
if (op.filter_branch) {
|
|
7092
|
+
const result = RootFilterBranchSchema.safeParse(op.filter_branch);
|
|
7093
|
+
if (!result.success) {
|
|
7094
|
+
const zodIssues = result.error.issues.map((i) => i.message).join("; ");
|
|
7095
|
+
issues.push({
|
|
7096
|
+
severity: "error",
|
|
7097
|
+
operation_index: index,
|
|
7098
|
+
code: "INVALID_FILTER_BRANCH",
|
|
7099
|
+
message: `filter_branch is invalid: ${zodIssues}. Root must be { filterBranchType: "OR", filters: [], filterBranches: [{ filterBranchType: "AND", filters: [...], filterBranches: [] }] }`
|
|
7100
|
+
});
|
|
7101
|
+
} else {
|
|
7102
|
+
const fb = op.filter_branch;
|
|
7103
|
+
if (fb.filterBranchType !== "OR") {
|
|
7104
|
+
issues.push({
|
|
7105
|
+
severity: "error",
|
|
7106
|
+
operation_index: index,
|
|
7107
|
+
code: "INVALID_FILTER_BRANCH_ROOT",
|
|
7108
|
+
message: `Root filterBranchType must be "OR", got "${fb.filterBranchType}"`
|
|
7109
|
+
});
|
|
7110
|
+
}
|
|
7111
|
+
const branches = fb.filterBranches;
|
|
7112
|
+
const hasAndBranch = branches?.some((b) => b.filterBranchType === "AND");
|
|
7113
|
+
if (!hasAndBranch) {
|
|
7114
|
+
issues.push({
|
|
7115
|
+
severity: "error",
|
|
7116
|
+
operation_index: index,
|
|
7117
|
+
code: "INVALID_FILTER_BRANCH_NO_AND",
|
|
7118
|
+
message: `Root OR branch must contain at least one AND child branch`
|
|
7119
|
+
});
|
|
7120
|
+
}
|
|
7121
|
+
const rootFilters = fb.filters;
|
|
7122
|
+
if (rootFilters && rootFilters.length > 0) {
|
|
7123
|
+
issues.push({
|
|
7124
|
+
severity: "error",
|
|
7125
|
+
operation_index: index,
|
|
7126
|
+
code: "INVALID_FILTER_BRANCH_ROOT_FILTERS",
|
|
7127
|
+
message: `Root OR branch must have an empty filters array \u2014 filters belong inside AND child branches`
|
|
7128
|
+
});
|
|
7129
|
+
}
|
|
7130
|
+
}
|
|
7131
|
+
}
|
|
6941
7132
|
return issues;
|
|
6942
7133
|
},
|
|
6943
7134
|
validateEffectful: (op, index) => pipe42(
|
|
@@ -7283,20 +7474,33 @@ var updateCustomObjectHandler = {
|
|
|
7283
7474
|
execute: (op, index) => pipe45(
|
|
7284
7475
|
HubSpotService,
|
|
7285
7476
|
Effect56.flatMap((hs) => {
|
|
7286
|
-
const
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7477
|
+
const needsLabelMerge = (op.new_singular_label !== void 0 || op.new_plural_label !== void 0) && !(op.new_singular_label !== void 0 && op.new_plural_label !== void 0);
|
|
7478
|
+
const getExistingLabels = needsLabelMerge ? pipe45(
|
|
7479
|
+
hs.getCustomObjectSchemas,
|
|
7480
|
+
Effect56.map((schemas) => {
|
|
7481
|
+
const schema = schemas.find((s) => s.name === op.object_type || s.objectTypeId === op.object_type);
|
|
7482
|
+
return schema?.labels ?? { singular: "", plural: "" };
|
|
7483
|
+
})
|
|
7484
|
+
) : Effect56.succeed({ singular: "", plural: "" });
|
|
7485
|
+
return pipe45(
|
|
7486
|
+
getExistingLabels,
|
|
7487
|
+
Effect56.flatMap((existingLabels) => {
|
|
7488
|
+
const updates = {};
|
|
7489
|
+
if (op.new_singular_label !== void 0 || op.new_plural_label !== void 0) {
|
|
7490
|
+
updates.labels = {
|
|
7491
|
+
singular: op.new_singular_label ?? existingLabels.singular,
|
|
7492
|
+
plural: op.new_plural_label ?? existingLabels.plural
|
|
7493
|
+
};
|
|
7494
|
+
}
|
|
7495
|
+
if (op.new_primary_display_property !== void 0) {
|
|
7496
|
+
updates.primaryDisplayProperty = op.new_primary_display_property;
|
|
7497
|
+
}
|
|
7498
|
+
if (op.new_required_properties !== void 0) {
|
|
7499
|
+
updates.requiredProperties = [...op.new_required_properties];
|
|
7500
|
+
}
|
|
7501
|
+
return hs.updateCustomObject(op.object_type, updates);
|
|
7502
|
+
})
|
|
7503
|
+
);
|
|
7300
7504
|
}),
|
|
7301
7505
|
Effect56.map((result) => ({
|
|
7302
7506
|
index,
|
|
@@ -7808,7 +8012,27 @@ var batchAssignOwnerHandler = {
|
|
|
7808
8012
|
}
|
|
7809
8013
|
return issues;
|
|
7810
8014
|
},
|
|
7811
|
-
validateEffectful: () =>
|
|
8015
|
+
validateEffectful: (op, index) => pipe53(
|
|
8016
|
+
HubSpotService,
|
|
8017
|
+
Effect64.flatMap((hs) => hs.getOwners),
|
|
8018
|
+
Effect64.map((owners) => {
|
|
8019
|
+
const issues = [];
|
|
8020
|
+
if (!owners.find((o) => o.id === op.owner_id)) {
|
|
8021
|
+
issues.push({
|
|
8022
|
+
severity: "error",
|
|
8023
|
+
operation_index: index,
|
|
8024
|
+
code: "OWNER_NOT_FOUND",
|
|
8025
|
+
message: `Owner ID '${op.owner_id}' does not exist in this portal \u2014 use the query tool to find valid owner IDs`
|
|
8026
|
+
});
|
|
8027
|
+
}
|
|
8028
|
+
return issues;
|
|
8029
|
+
}),
|
|
8030
|
+
catchAllToWarning(
|
|
8031
|
+
"VALIDATION_API_UNAVAILABLE",
|
|
8032
|
+
index,
|
|
8033
|
+
"Unable to verify owner ID during validation (API error)"
|
|
8034
|
+
)
|
|
8035
|
+
),
|
|
7812
8036
|
preCheck: () => Effect64.succeed([]),
|
|
7813
8037
|
execute: (op, index) => pipe53(
|
|
7814
8038
|
HubSpotService,
|
|
@@ -8100,7 +8324,15 @@ var formatPlanView = () => {
|
|
|
8100
8324
|
};
|
|
8101
8325
|
function registerBuildPlanTool(server2) {
|
|
8102
8326
|
server2.registerTool("build_plan", {
|
|
8103
|
-
description: `
|
|
8327
|
+
description: `Use this tool whenever the user wants to make changes to their HubSpot CRM configuration or data. This is the ONLY way to modify HubSpot \u2014 there is no direct API access. All CRM write operations must go through a plan.
|
|
8328
|
+
|
|
8329
|
+
Supports 28 operation types across these categories:
|
|
8330
|
+
- Properties & groups: create, update, or delete custom properties and property groups
|
|
8331
|
+
- Pipelines & stages: create, update, or delete deal/ticket pipelines with stages
|
|
8332
|
+
- Records: batch create, update, delete, merge, or reassign owners for CRM records
|
|
8333
|
+
- Associations: create, update, or delete associations and labels between object types
|
|
8334
|
+
- Lists: create, update, delete lists, or add/remove members
|
|
8335
|
+
- Custom objects: create, update, or delete custom object schemas
|
|
8104
8336
|
|
|
8105
8337
|
CREATING A PLAN:
|
|
8106
8338
|
Provide title + description (+ optional operations) to start a new draft.
|
|
@@ -8122,7 +8354,7 @@ VIEWING THE PLAN:
|
|
|
8122
8354
|
IMPORTANT: After building the plan, present it to the user and ask for confirmation before calling submit_plan.
|
|
8123
8355
|
|
|
8124
8356
|
WORKFLOW:
|
|
8125
|
-
1. describe_operations - Discover available operation types
|
|
8357
|
+
1. describe_operations - Discover available operation types and their required fields
|
|
8126
8358
|
2. build_plan - Create draft with title, description, and operations
|
|
8127
8359
|
3. Present the plan to the user for review
|
|
8128
8360
|
4. submit_plan (dry_run: true) - Validate without saving (optional)
|
|
@@ -8258,7 +8490,11 @@ ${formatPlanView()}`
|
|
|
8258
8490
|
import { z as z43 } from "zod";
|
|
8259
8491
|
function registerDescribeOperationsTool(server2) {
|
|
8260
8492
|
server2.registerTool("describe_operations", {
|
|
8261
|
-
description: `Discover
|
|
8493
|
+
description: `Discover what CRM modifications are available and what fields they require. Call this tool when the user asks about making changes to HubSpot, or before using build_plan to check operation schemas.
|
|
8494
|
+
|
|
8495
|
+
Use cases:
|
|
8496
|
+
- User asks "can I delete custom properties?" or "what can you do with pipelines?" \u2014 call with no arguments to list all 28 operation types.
|
|
8497
|
+
- User wants to build a plan \u2014 call with specific types to see their full field schemas and examples (e.g. ['delete_property', 'create_pipeline']).
|
|
8262
8498
|
|
|
8263
8499
|
- Call with no arguments to list all available operation types.
|
|
8264
8500
|
- Call with specific types to see their full field schemas and examples.`,
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daeda/mcp-pro",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "MCP server for HubSpot CRM — sync, query, and manage your portal data",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"mcp-pro": "
|
|
7
|
+
"mcp-pro": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"exports": {
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
"dev": "bun run --watch src/index.ts",
|
|
22
22
|
"start": "bun run src/index.ts",
|
|
23
23
|
"prepublishOnly": "bun run build",
|
|
24
|
+
"release:patch": "npm version patch && npm publish --access public",
|
|
25
|
+
"release:minor": "npm version minor && npm publish --access public",
|
|
26
|
+
"release:major": "npm version major && npm publish --access public",
|
|
24
27
|
"db:stats": "bun run scripts/db-stats.ts",
|
|
25
28
|
"db:query": "bun run scripts/db-query.ts",
|
|
26
29
|
"db:schema": "bun run scripts/db-schema.ts",
|