@daeda/mcp-pro 0.1.0 → 0.1.1

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 +195 -61
  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);
@@ -5326,6 +5315,18 @@ var updatePropertyHandler = {
5326
5315
  message: `Property '${op.property_name}' groupName: expected '${op.updates.group_name}', got '${updated.groupName}'`
5327
5316
  });
5328
5317
  }
5318
+ if (op.updates.options !== void 0) {
5319
+ const expectedValues = new Set(op.updates.options.map((o) => o.value));
5320
+ const actualValues = new Set(updated.options.map((o) => o.value));
5321
+ if (expectedValues.size !== actualValues.size || [...expectedValues].some((v) => !actualValues.has(v))) {
5322
+ issues.push({
5323
+ severity: "warning",
5324
+ operation_index: index,
5325
+ code: "POSTCHECK_OPTIONS_MISMATCH",
5326
+ message: `Property '${op.property_name}' options: expected ${expectedValues.size} options, got ${actualValues.size}`
5327
+ });
5328
+ }
5329
+ }
5329
5330
  return issues;
5330
5331
  }),
5331
5332
  catchAllToWarning(
@@ -5626,6 +5627,14 @@ var updatePropertyGroupHandler = {
5626
5627
  message: `Property group '${op.group_name}' label: expected '${op.updates.label}', got '${updated.label}'`
5627
5628
  });
5628
5629
  }
5630
+ if (op.updates.display_order !== void 0 && updated.displayOrder !== op.updates.display_order) {
5631
+ issues.push({
5632
+ severity: "warning",
5633
+ operation_index: index,
5634
+ code: "POSTCHECK_DISPLAY_ORDER_MISMATCH",
5635
+ message: `Property group '${op.group_name}' displayOrder: expected ${op.updates.display_order}, got ${updated.displayOrder}`
5636
+ });
5637
+ }
5629
5638
  return issues;
5630
5639
  }),
5631
5640
  catchAllToWarning(
@@ -6080,7 +6089,8 @@ var updatePipelineHandler = {
6080
6089
  label: op.updates.label,
6081
6090
  displayOrder: op.updates.display_order
6082
6091
  }),
6083
- Effect44.catchAll(() => Effect44.succeed(null))
6092
+ Effect44.map(() => ({ label: "update pipeline metadata", ok: true })),
6093
+ Effect44.catchAll(() => Effect44.succeed({ label: "update pipeline metadata", ok: false }))
6084
6094
  )
6085
6095
  );
6086
6096
  }
@@ -6097,7 +6107,8 @@ var updatePipelineHandler = {
6097
6107
  }
6098
6108
  }
6099
6109
  }),
6100
- Effect44.catchAll(() => Effect44.succeed(null))
6110
+ Effect44.map(() => ({ label: `add stage '${stage.label}'`, ok: true })),
6111
+ Effect44.catchAll(() => Effect44.succeed({ label: `add stage '${stage.label}'`, ok: false }))
6101
6112
  )
6102
6113
  );
6103
6114
  }
@@ -6114,7 +6125,8 @@ var updatePipelineHandler = {
6114
6125
  }
6115
6126
  }
6116
6127
  }),
6117
- Effect44.catchAll(() => Effect44.succeed(null))
6128
+ Effect44.map(() => ({ label: `update stage '${stageUpdate.stage_id}'`, ok: true })),
6129
+ Effect44.catchAll(() => Effect44.succeed({ label: `update stage '${stageUpdate.stage_id}'`, ok: false }))
6118
6130
  )
6119
6131
  );
6120
6132
  }
@@ -6122,19 +6134,42 @@ var updatePipelineHandler = {
6122
6134
  steps.push(
6123
6135
  pipe33(
6124
6136
  hs.deletePipelineStage(op.object_type, op.pipeline_id, stageId),
6125
- Effect44.catchAll(() => Effect44.succeed(null))
6137
+ Effect44.map(() => ({ label: `remove stage '${stageId}'`, ok: true })),
6138
+ Effect44.catchAll(() => Effect44.succeed({ label: `remove stage '${stageId}'`, ok: false }))
6126
6139
  )
6127
6140
  );
6128
6141
  }
6129
6142
  return pipe33(
6130
6143
  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
- }))
6144
+ Effect44.map((results) => {
6145
+ const failed = results.filter((r) => !r.ok);
6146
+ if (failed.length === results.length) {
6147
+ return {
6148
+ index,
6149
+ type: "update_pipeline",
6150
+ status: "failed",
6151
+ records_affected: 0,
6152
+ error: `All ${failed.length} sub-steps failed: ${failed.map((f) => f.label).join(", ")}`
6153
+ };
6154
+ }
6155
+ if (failed.length > 0) {
6156
+ return {
6157
+ index,
6158
+ type: "update_pipeline",
6159
+ status: "failed",
6160
+ records_affected: 1,
6161
+ affected_ids: [op.pipeline_id],
6162
+ error: `Partial failure: ${failed.length}/${results.length} sub-steps failed (${failed.map((f) => f.label).join(", ")})`
6163
+ };
6164
+ }
6165
+ return {
6166
+ index,
6167
+ type: "update_pipeline",
6168
+ status: "success",
6169
+ records_affected: 1,
6170
+ affected_ids: [op.pipeline_id]
6171
+ };
6172
+ })
6138
6173
  );
6139
6174
  }),
6140
6175
  catchAllToFailed("update_pipeline", index)
@@ -6174,6 +6209,18 @@ var updatePipelineHandler = {
6174
6209
  });
6175
6210
  }
6176
6211
  }
6212
+ for (const stageUpdate of op.stages_to_update ?? []) {
6213
+ const stage = pipeline.stages.find((s) => s.id === stageUpdate.stage_id);
6214
+ if (!stage) continue;
6215
+ if (stageUpdate.label !== void 0 && stage.label !== stageUpdate.label) {
6216
+ issues.push({
6217
+ severity: "warning",
6218
+ operation_index: index,
6219
+ code: "POSTCHECK_STAGE_LABEL_MISMATCH",
6220
+ message: `Stage '${stageUpdate.stage_id}' label: expected '${stageUpdate.label}', got '${stage.label}'`
6221
+ });
6222
+ }
6223
+ }
6177
6224
  const stageIds = new Set(pipeline.stages.map((s) => s.id));
6178
6225
  for (const removedId of op.stage_ids_to_remove ?? []) {
6179
6226
  if (stageIds.has(removedId)) {
@@ -6685,13 +6732,51 @@ var batchUpdateAssociationsHandler = {
6685
6732
  }
6686
6733
  return [];
6687
6734
  },
6688
- validateEffectful: () => Effect49.succeed([]),
6735
+ validateEffectful: (op, index) => pipe38(
6736
+ HubSpotService,
6737
+ Effect49.flatMap((hs) => hs.getAssociationLabels(op.from_object_type, op.to_object_type)),
6738
+ Effect49.map((labels) => {
6739
+ const issues = [];
6740
+ const match = labels.find((l) => l.typeId === op.new_association_type_id);
6741
+ if (!match) {
6742
+ issues.push({
6743
+ severity: "error",
6744
+ operation_index: index,
6745
+ code: "ASSOCIATION_TYPE_NOT_FOUND",
6746
+ message: `Association type ID ${op.new_association_type_id} does not exist between ${op.from_object_type} and ${op.to_object_type}`
6747
+ });
6748
+ }
6749
+ return issues;
6750
+ }),
6751
+ catchAllToWarning(
6752
+ "VALIDATION_API_UNAVAILABLE",
6753
+ index,
6754
+ `Unable to verify association labels between ${op.from_object_type} and ${op.to_object_type} (API error)`
6755
+ )
6756
+ ),
6689
6757
  preCheck: () => Effect49.succeed([]),
6690
6758
  execute: (op, index) => pipe38(
6691
6759
  HubSpotService,
6692
- Effect49.flatMap(
6693
- (hs) => pipe38(
6694
- hs.batchDeleteAssociations({
6760
+ Effect49.flatMap((hs) => {
6761
+ const deleteInputs = {
6762
+ fromObjectType: op.from_object_type,
6763
+ toObjectType: op.to_object_type,
6764
+ inputs: op.associations.map((a) => ({
6765
+ fromId: a.from_id,
6766
+ toId: a.to_id
6767
+ }))
6768
+ };
6769
+ const createWithType = (typeId) => hs.batchCreateAssociations({
6770
+ fromObjectType: op.from_object_type,
6771
+ toObjectType: op.to_object_type,
6772
+ inputs: op.associations.map((a) => ({
6773
+ fromId: a.from_id,
6774
+ toId: a.to_id,
6775
+ associationTypeId: typeId
6776
+ }))
6777
+ });
6778
+ const recreateUnlabeled = pipe38(
6779
+ hs.batchCreateAssociations({
6695
6780
  fromObjectType: op.from_object_type,
6696
6781
  toObjectType: op.to_object_type,
6697
6782
  inputs: op.associations.map((a) => ({
@@ -6699,19 +6784,23 @@ var batchUpdateAssociationsHandler = {
6699
6784
  toId: a.to_id
6700
6785
  }))
6701
6786
  }),
6787
+ Effect49.catchAll(() => Effect49.succeed(void 0))
6788
+ );
6789
+ return pipe38(
6790
+ hs.batchDeleteAssociations(deleteInputs),
6702
6791
  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
- })
6792
+ () => pipe38(
6793
+ createWithType(op.new_association_type_id),
6794
+ Effect49.catchAll(
6795
+ (createError) => pipe38(
6796
+ recreateUnlabeled,
6797
+ Effect49.flatMap(() => Effect49.fail(createError))
6798
+ )
6799
+ )
6800
+ )
6712
6801
  )
6713
- )
6714
- ),
6802
+ );
6803
+ }),
6715
6804
  Effect49.map(() => ({
6716
6805
  index,
6717
6806
  type: "batch_update_associations",
@@ -7283,20 +7372,33 @@ var updateCustomObjectHandler = {
7283
7372
  execute: (op, index) => pipe45(
7284
7373
  HubSpotService,
7285
7374
  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);
7375
+ 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);
7376
+ const getExistingLabels = needsLabelMerge ? pipe45(
7377
+ hs.getCustomObjectSchemas,
7378
+ Effect56.map((schemas) => {
7379
+ const schema = schemas.find((s) => s.name === op.object_type || s.objectTypeId === op.object_type);
7380
+ return schema?.labels ?? { singular: "", plural: "" };
7381
+ })
7382
+ ) : Effect56.succeed({ singular: "", plural: "" });
7383
+ return pipe45(
7384
+ getExistingLabels,
7385
+ Effect56.flatMap((existingLabels) => {
7386
+ const updates = {};
7387
+ if (op.new_singular_label !== void 0 || op.new_plural_label !== void 0) {
7388
+ updates.labels = {
7389
+ singular: op.new_singular_label ?? existingLabels.singular,
7390
+ plural: op.new_plural_label ?? existingLabels.plural
7391
+ };
7392
+ }
7393
+ if (op.new_primary_display_property !== void 0) {
7394
+ updates.primaryDisplayProperty = op.new_primary_display_property;
7395
+ }
7396
+ if (op.new_required_properties !== void 0) {
7397
+ updates.requiredProperties = [...op.new_required_properties];
7398
+ }
7399
+ return hs.updateCustomObject(op.object_type, updates);
7400
+ })
7401
+ );
7300
7402
  }),
7301
7403
  Effect56.map((result) => ({
7302
7404
  index,
@@ -7808,7 +7910,27 @@ var batchAssignOwnerHandler = {
7808
7910
  }
7809
7911
  return issues;
7810
7912
  },
7811
- validateEffectful: () => Effect64.succeed([]),
7913
+ validateEffectful: (op, index) => pipe53(
7914
+ HubSpotService,
7915
+ Effect64.flatMap((hs) => hs.getOwners),
7916
+ Effect64.map((owners) => {
7917
+ const issues = [];
7918
+ if (!owners.find((o) => o.id === op.owner_id)) {
7919
+ issues.push({
7920
+ severity: "error",
7921
+ operation_index: index,
7922
+ code: "OWNER_NOT_FOUND",
7923
+ message: `Owner ID '${op.owner_id}' does not exist in this portal \u2014 use the query tool to find valid owner IDs`
7924
+ });
7925
+ }
7926
+ return issues;
7927
+ }),
7928
+ catchAllToWarning(
7929
+ "VALIDATION_API_UNAVAILABLE",
7930
+ index,
7931
+ "Unable to verify owner ID during validation (API error)"
7932
+ )
7933
+ ),
7812
7934
  preCheck: () => Effect64.succeed([]),
7813
7935
  execute: (op, index) => pipe53(
7814
7936
  HubSpotService,
@@ -8100,7 +8222,15 @@ var formatPlanView = () => {
8100
8222
  };
8101
8223
  function registerBuildPlanTool(server2) {
8102
8224
  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.
8225
+ 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.
8226
+
8227
+ Supports 28 operation types across these categories:
8228
+ - Properties & groups: create, update, or delete custom properties and property groups
8229
+ - Pipelines & stages: create, update, or delete deal/ticket pipelines with stages
8230
+ - Records: batch create, update, delete, merge, or reassign owners for CRM records
8231
+ - Associations: create, update, or delete associations and labels between object types
8232
+ - Lists: create, update, delete lists, or add/remove members
8233
+ - Custom objects: create, update, or delete custom object schemas
8104
8234
 
8105
8235
  CREATING A PLAN:
8106
8236
  Provide title + description (+ optional operations) to start a new draft.
@@ -8122,7 +8252,7 @@ VIEWING THE PLAN:
8122
8252
  IMPORTANT: After building the plan, present it to the user and ask for confirmation before calling submit_plan.
8123
8253
 
8124
8254
  WORKFLOW:
8125
- 1. describe_operations - Discover available operation types
8255
+ 1. describe_operations - Discover available operation types and their required fields
8126
8256
  2. build_plan - Create draft with title, description, and operations
8127
8257
  3. Present the plan to the user for review
8128
8258
  4. submit_plan (dry_run: true) - Validate without saving (optional)
@@ -8258,7 +8388,11 @@ ${formatPlanView()}`
8258
8388
  import { z as z43 } from "zod";
8259
8389
  function registerDescribeOperationsTool(server2) {
8260
8390
  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.
8391
+ 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.
8392
+
8393
+ Use cases:
8394
+ - 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.
8395
+ - 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
8396
 
8263
8397
  - Call with no arguments to list all available operation types.
8264
8398
  - 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.1",
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",