@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.
Files changed (2) hide show
  1. package/dist/index.js +304 -68
  2. 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 propertyKeys = [];
651
+ let propertyKeyCount = 0;
653
652
  if (count > 0) {
654
653
  const sampleReader = await conn.runAndReadAll(
655
- `SELECT properties FROM "${tableName}" LIMIT ${PROPERTY_SAMPLE_SIZE}`
654
+ `SELECT properties FROM "${tableName}" LIMIT 1`
656
655
  );
657
656
  const sampleRows = sampleReader.getRowObjects();
658
- const keySet = /* @__PURE__ */ new Set();
659
- for (const row of sampleRows) {
657
+ if (sampleRows[0]) {
660
658
  try {
661
- const props = JSON.parse(row.properties);
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
- propertyKeys,
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, null, 2) }]
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: z28.record(z28.unknown()).optional()
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: "HubSpot filter definition for DYNAMIC lists (passed as-is to API)" }
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: "Create a new HubSpot list. MANUAL lists are static (members added manually). DYNAMIC lists are active (membership determined by filter criteria). For DYNAMIC lists, provide filter_branch with the HubSpot filter definition.",
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: "Hot Leads",
4239
+ name: "Active Leads",
4227
4240
  object_type_id: "0-1",
4228
- processing_type: "MANUAL"
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}/lists`,
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.catchAll(() => Effect44.succeed(null))
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.catchAll(() => Effect44.succeed(null))
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.catchAll(() => Effect44.succeed(null))
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.catchAll(() => Effect44.succeed(null))
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
- index,
6133
- type: "update_pipeline",
6134
- status: "success",
6135
- records_affected: 1,
6136
- affected_ids: [op.pipeline_id]
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: () => Effect49.succeed([]),
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
- (hs) => pipe38(
6694
- hs.batchDeleteAssociations({
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
- () => hs.batchCreateAssociations({
6704
- fromObjectType: op.from_object_type,
6705
- toObjectType: op.to_object_type,
6706
- inputs: op.associations.map((a) => ({
6707
- fromId: a.from_id,
6708
- toId: a.to_id,
6709
- associationTypeId: op.new_association_type_id
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 updates = {};
7287
- if (op.new_singular_label !== void 0 || op.new_plural_label !== void 0) {
7288
- updates.labels = {
7289
- singular: op.new_singular_label ?? "",
7290
- plural: op.new_plural_label ?? ""
7291
- };
7292
- }
7293
- if (op.new_primary_display_property !== void 0) {
7294
- updates.primaryDisplayProperty = op.new_primary_display_property;
7295
- }
7296
- if (op.new_required_properties !== void 0) {
7297
- updates.requiredProperties = [...op.new_required_properties];
7298
- }
7299
- return hs.updateCustomObject(op.object_type, updates);
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: () => Effect64.succeed([]),
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: `Create, view, and edit a write plan for modifying HubSpot CRM data. This single tool handles the entire plan-building workflow.
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 available write operation types and their field schemas. Use this before build_plan to understand what fields each operation type requires.
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.0",
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": "./dist/index.js"
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",