@daeda/mcp-pro 0.1.1 → 0.1.5

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 +254 -21
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4190,31 +4190,94 @@ var batchDeleteRecordsMeta = {
4190
4190
 
4191
4191
  // ../shared/operations/create-list/meta.ts
4192
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();
4193
4208
  var CreateListOperationSchema = z28.object({
4194
4209
  type: z28.literal("create_list"),
4195
4210
  description: z28.string().min(1),
4196
4211
  name: z28.string().min(1),
4197
4212
  object_type_id: z28.string().min(1),
4198
4213
  processing_type: z28.enum(["MANUAL", "DYNAMIC"]),
4199
- filter_branch: z28.record(z28.unknown()).optional()
4214
+ filter_branch: RootFilterBranchSchema.optional()
4200
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
+
4222
+ operationType MUST match the HubSpot property type:
4223
+ - ENUMERATION \u2192 for enumeration properties (lifecyclestage, pipeline, dealstage, hs_lead_status, hs_pipeline, etc.). Operators: IS_ANY_OF, IS_NONE_OF, HAS_EVER_BEEN_ANY_OF, HAS_NEVER_BEEN_ANY_OF, IS_EXACTLY, IS_NOT_EXACTLY, CONTAINS_ALL_OF, DOES_NOT_CONTAIN_ALL_OF. WARNING: IS_EQUAL_TO is NOT valid for ENUMERATION \u2014 use IS_ANY_OF instead.
4224
+ - MULTISTRING \u2192 for string properties (firstname, lastname, email, city, company, etc.). Operators: IS_EQUAL_TO, IS_NOT_EQUAL_TO, CONTAINS, DOES_NOT_CONTAIN, STARTS_WITH, ENDS_WITH.
4225
+ - NUMBER \u2192 for number properties. Operators: IS_EQUAL_TO, IS_NOT_EQUAL_TO, IS_BETWEEN, IS_GREATER_THAN, IS_LESS_THAN.
4226
+ - BOOL \u2192 for boolean properties. Operators: IS_EQUAL_TO, IS_NOT_EQUAL_TO.
4227
+ - TIME_POINT \u2192 for date/datetime properties (single point comparison).
4228
+ - TIME_RANGED \u2192 for date/datetime properties (range with lowerBoundTimePoint / upperBoundTimePoint).
4229
+ - ALL_PROPERTY \u2192 any property type, for IS_KNOWN / IS_NOT_KNOWN checks.
4230
+ Enum filter example: { filterType: "PROPERTY", property: "lifecyclestage", operation: { operationType: "ENUMERATION", operator: "IS_ANY_OF", values: ["lead"] } }
4231
+
4232
+ ASSOCIATION branches filter by associated records and MUST include these required fields:
4233
+ - filterBranchType: "ASSOCIATION"
4234
+ - objectTypeId: the associated object type ID (e.g. "0-3" for deals)
4235
+ - operator: "IN_LIST"
4236
+ - associationTypeId: (number) the association type ID \u2014 required by HubSpot (e.g. 4 for contact\u2192deal, 1 for contact\u2192company, 5 for deal\u2192company)
4237
+ - associationCategory: (string) usually "HUBSPOT_DEFINED" (also: "USER_DEFINED", "INTEGRATOR_DEFINED")
4238
+ - filters: property filters on the associated object
4239
+ - filterBranches: [] (no further nesting except CONTACT\u2192LINE_ITEM)
4240
+ ASSOCIATION branches must be nested inside an AND branch.
4241
+ Association example: { filterBranchType: "ASSOCIATION", objectTypeId: "0-3", operator: "IN_LIST", associationTypeId: 4, associationCategory: "HUBSPOT_DEFINED", filters: [{ filterType: "PROPERTY", property: "dealstage", operation: { operationType: "ENUMERATION", operator: "IS_NONE_OF", values: ["closedwon", "closedlost"] } }], filterBranches: [] }`;
4201
4242
  var fields17 = [
4202
4243
  { name: "name", type: "string", required: true, description: "Name for the new list" },
4203
- { name: "object_type_id", type: "string", required: true, description: "Object type ID (e.g. '0-1' for contacts, '0-2' for companies)" },
4244
+ { 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)" },
4204
4245
  { name: "processing_type", type: "string", required: true, description: "List type: 'MANUAL' (static) or 'DYNAMIC' (active)" },
4205
- { name: "filter_branch", type: "object", required: false, description: "HubSpot filter definition for DYNAMIC lists (passed as-is to API)" }
4246
+ { name: "filter_branch", type: "object", required: false, description: FILTER_BRANCH_DESCRIPTION }
4206
4247
  ];
4207
4248
  var createListMeta = {
4208
4249
  type: "create_list",
4209
4250
  category: "create",
4210
4251
  summary: "Create a contact or company list (static or dynamic)",
4211
- 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.",
4252
+ description: `Create a new HubSpot list. MANUAL lists are static (members added manually). DYNAMIC lists are active (membership determined by filter criteria).
4253
+ 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.`,
4212
4254
  fields: fields17,
4213
4255
  example: {
4214
4256
  fields: {
4215
- name: "Hot Leads",
4257
+ name: "Active Leads",
4216
4258
  object_type_id: "0-1",
4217
- processing_type: "MANUAL"
4259
+ processing_type: "DYNAMIC",
4260
+ filter_branch: {
4261
+ filterBranchType: "OR",
4262
+ filters: [],
4263
+ filterBranches: [
4264
+ {
4265
+ filterBranchType: "AND",
4266
+ filterBranches: [],
4267
+ filters: [
4268
+ {
4269
+ filterType: "PROPERTY",
4270
+ property: "lifecyclestage",
4271
+ operation: {
4272
+ operationType: "ENUMERATION",
4273
+ operator: "IS_ANY_OF",
4274
+ values: ["lead"]
4275
+ }
4276
+ }
4277
+ ]
4278
+ }
4279
+ ]
4280
+ }
4218
4281
  }
4219
4282
  },
4220
4283
  toNested: (description, fields29) => {
@@ -4230,13 +4293,21 @@ var createListMeta = {
4230
4293
  },
4231
4294
  requiredScopes: () => ["crm.lists.write"],
4232
4295
  getSummary: (op) => `Create List \u2014 ${op.name} (${op.processing_type})`,
4233
- getResourceUrl: (portalId) => `${HUBSPOT_BASE}/contacts/${portalId}/lists`,
4296
+ getResourceUrl: (portalId) => `${HUBSPOT_BASE}/contacts/${portalId}/objectLists/views/all`,
4234
4297
  renderDetails: (op) => {
4235
4298
  const rows = [
4236
4299
  { label: "Name", value: op.name },
4237
4300
  { label: "Object Type ID", value: op.object_type_id },
4238
4301
  { label: "Processing Type", value: op.processing_type }
4239
4302
  ];
4303
+ if (op.filter_branch) {
4304
+ const andCount = op.filter_branch.filterBranches?.length ?? 0;
4305
+ const totalFilters = op.filter_branch.filterBranches?.reduce(
4306
+ (sum, branch) => sum + (branch.filters?.length ?? 0),
4307
+ 0
4308
+ ) ?? 0;
4309
+ rows.push({ label: "Filter Groups", value: `${andCount} group(s), ${totalFilters} filter(s)` });
4310
+ }
4240
4311
  return { rows };
4241
4312
  }
4242
4313
  };
@@ -7015,6 +7086,59 @@ var batchDeleteRecordsHandler = {
7015
7086
  // ../shared/operations/create-list/index.ts
7016
7087
  import { Effect as Effect53, pipe as pipe42 } from "effect";
7017
7088
  var VALID_PROCESSING_TYPES = /* @__PURE__ */ new Set(["MANUAL", "DYNAMIC"]);
7089
+ var OPERATION_TYPE_FOR_PROPERTY_TYPE = {
7090
+ enumeration: "ENUMERATION",
7091
+ string: "MULTISTRING",
7092
+ number: "NUMBER",
7093
+ bool: "BOOL",
7094
+ date: "TIME_POINT",
7095
+ datetime: "TIME_POINT"
7096
+ };
7097
+ var collectPropertyFilters = (branches, parentObjectTypeId) => {
7098
+ const refs = [];
7099
+ for (const branch of branches) {
7100
+ const ctxObjectTypeId = branch.filterBranchType === "ASSOCIATION" && typeof branch.objectTypeId === "string" ? branch.objectTypeId : parentObjectTypeId;
7101
+ const filters = branch.filters;
7102
+ if (filters) {
7103
+ for (const f of filters) {
7104
+ if (f.filterType === "PROPERTY" && typeof f.property === "string") {
7105
+ const op = f.operation;
7106
+ if (op && typeof op.operationType === "string") {
7107
+ refs.push({ property: f.property, operationType: op.operationType, objectTypeId: ctxObjectTypeId });
7108
+ }
7109
+ }
7110
+ }
7111
+ }
7112
+ const nested = branch.filterBranches;
7113
+ if (nested?.length) {
7114
+ refs.push(...collectPropertyFilters(nested, ctxObjectTypeId));
7115
+ }
7116
+ }
7117
+ return refs;
7118
+ };
7119
+ var collectAssociationIssues = (branches, index) => {
7120
+ const issues = [];
7121
+ for (const branch of branches) {
7122
+ if (branch.filterBranchType === "ASSOCIATION") {
7123
+ const missing = [];
7124
+ if (branch.associationTypeId == null) missing.push("associationTypeId");
7125
+ if (branch.associationCategory == null) missing.push("associationCategory");
7126
+ if (missing.length > 0) {
7127
+ issues.push({
7128
+ severity: "error",
7129
+ operation_index: index,
7130
+ code: "ASSOCIATION_MISSING_REQUIRED_FIELDS",
7131
+ message: `ASSOCIATION filter branch (objectTypeId: ${branch.objectTypeId ?? "unknown"}) is missing required field(s): ${missing.join(", ")}. ASSOCIATION branches require associationTypeId (number) and associationCategory (string, usually "HUBSPOT_DEFINED").`
7132
+ });
7133
+ }
7134
+ }
7135
+ const nested = branch.filterBranches;
7136
+ if (nested?.length) {
7137
+ issues.push(...collectAssociationIssues(nested, index));
7138
+ }
7139
+ }
7140
+ return issues;
7141
+ };
7018
7142
  var createListHandler = {
7019
7143
  type: "create_list",
7020
7144
  validate: (op, index) => {
@@ -7027,25 +7151,134 @@ var createListHandler = {
7027
7151
  message: `Processing type '${op.processing_type}' is invalid \u2014 must be "MANUAL" or "DYNAMIC"`
7028
7152
  });
7029
7153
  }
7030
- return issues;
7031
- },
7032
- validateEffectful: (op, index) => pipe42(
7033
- HubSpotService,
7034
- Effect53.flatMap((hs) => hs.getLists),
7035
- Effect53.map((lists) => {
7036
- const issues = [];
7037
- if (lists.find((l) => l.name === op.name)) {
7154
+ if (op.processing_type === "DYNAMIC" && !op.filter_branch) {
7155
+ issues.push({
7156
+ severity: "warning",
7157
+ operation_index: index,
7158
+ code: "DYNAMIC_LIST_NO_FILTER",
7159
+ message: `DYNAMIC list '${op.name}' has no filter_branch \u2014 it will match all records of the object type`
7160
+ });
7161
+ }
7162
+ if (op.filter_branch) {
7163
+ const result = RootFilterBranchSchema.safeParse(op.filter_branch);
7164
+ if (!result.success) {
7165
+ const zodIssues = result.error.issues.map((i) => i.message).join("; ");
7038
7166
  issues.push({
7039
7167
  severity: "error",
7040
7168
  operation_index: index,
7041
- code: "LIST_ALREADY_EXISTS",
7042
- message: `A list named '${op.name}' already exists \u2014 HubSpot may reject a duplicate`
7169
+ code: "INVALID_FILTER_BRANCH",
7170
+ message: `filter_branch is invalid: ${zodIssues}. Root must be { filterBranchType: "OR", filters: [], filterBranches: [{ filterBranchType: "AND", filters: [...], filterBranches: [] }] }`
7043
7171
  });
7172
+ } else {
7173
+ const fb = op.filter_branch;
7174
+ if (fb.filterBranchType !== "OR") {
7175
+ issues.push({
7176
+ severity: "error",
7177
+ operation_index: index,
7178
+ code: "INVALID_FILTER_BRANCH_ROOT",
7179
+ message: `Root filterBranchType must be "OR", got "${fb.filterBranchType}"`
7180
+ });
7181
+ }
7182
+ const branches = fb.filterBranches;
7183
+ const hasAndBranch = branches?.some((b) => b.filterBranchType === "AND");
7184
+ if (!hasAndBranch) {
7185
+ issues.push({
7186
+ severity: "error",
7187
+ operation_index: index,
7188
+ code: "INVALID_FILTER_BRANCH_NO_AND",
7189
+ message: `Root OR branch must contain at least one AND child branch`
7190
+ });
7191
+ }
7192
+ const rootFilters = fb.filters;
7193
+ if (rootFilters && rootFilters.length > 0) {
7194
+ issues.push({
7195
+ severity: "error",
7196
+ operation_index: index,
7197
+ code: "INVALID_FILTER_BRANCH_ROOT_FILTERS",
7198
+ message: `Root OR branch must have an empty filters array \u2014 filters belong inside AND child branches`
7199
+ });
7200
+ }
7201
+ if (branches?.length) {
7202
+ issues.push(...collectAssociationIssues(branches, index));
7203
+ }
7044
7204
  }
7045
- return issues;
7046
- }),
7047
- catchAllToWarning("VALIDATION_API_UNAVAILABLE", index, "Unable to verify existing lists during validation (API error)")
7048
- ),
7205
+ }
7206
+ return issues;
7207
+ },
7208
+ validateEffectful: (op, index) => {
7209
+ const checkDuplicateList = pipe42(
7210
+ HubSpotService,
7211
+ Effect53.flatMap((hs) => hs.getLists),
7212
+ Effect53.map((lists) => {
7213
+ const issues = [];
7214
+ if (lists.find((l) => l.name === op.name)) {
7215
+ issues.push({
7216
+ severity: "error",
7217
+ operation_index: index,
7218
+ code: "LIST_ALREADY_EXISTS",
7219
+ message: `A list named '${op.name}' already exists \u2014 HubSpot may reject a duplicate`
7220
+ });
7221
+ }
7222
+ return issues;
7223
+ }),
7224
+ catchAllToWarning("VALIDATION_API_UNAVAILABLE", index, "Unable to verify existing lists during validation (API error)")
7225
+ );
7226
+ const checkOperationTypes = () => {
7227
+ if (op.processing_type !== "DYNAMIC" || !op.filter_branch) return Effect53.succeed([]);
7228
+ const filterRefs = collectPropertyFilters(
7229
+ op.filter_branch.filterBranches ?? [],
7230
+ op.object_type_id
7231
+ );
7232
+ if (filterRefs.length === 0) return Effect53.succeed([]);
7233
+ const uniqueObjectTypes = Array.from(new Set(filterRefs.map((r) => r.objectTypeId)));
7234
+ return pipe42(
7235
+ HubSpotService,
7236
+ Effect53.flatMap(
7237
+ (hs) => Effect53.all(
7238
+ uniqueObjectTypes.map(
7239
+ (ot) => pipe42(
7240
+ hs.getObjectPropertyDefinitions(ot),
7241
+ Effect53.map((defs) => [ot, defs])
7242
+ )
7243
+ ),
7244
+ { concurrency: "unbounded" }
7245
+ )
7246
+ ),
7247
+ Effect53.map((results) => {
7248
+ const propTypeByObject = /* @__PURE__ */ new Map();
7249
+ for (const [ot, defs] of results) {
7250
+ const m = /* @__PURE__ */ new Map();
7251
+ for (const d of defs) m.set(d.name, d.type);
7252
+ propTypeByObject.set(ot, m);
7253
+ }
7254
+ const issues = [];
7255
+ for (const ref of filterRefs) {
7256
+ const propMap = propTypeByObject.get(ref.objectTypeId);
7257
+ if (!propMap) continue;
7258
+ const propType = propMap.get(ref.property);
7259
+ if (!propType) continue;
7260
+ const expected = OPERATION_TYPE_FOR_PROPERTY_TYPE[propType];
7261
+ if (!expected) continue;
7262
+ if (ref.operationType === expected) continue;
7263
+ if (ref.operationType === "ALL_PROPERTY") continue;
7264
+ if (ref.operationType === "TIME_RANGED" && (propType === "date" || propType === "datetime")) continue;
7265
+ issues.push({
7266
+ severity: "error",
7267
+ operation_index: index,
7268
+ code: "INVALID_OPERATION_TYPE",
7269
+ message: `Property '${ref.property}' has type '${propType}' but filter uses operationType '${ref.operationType}'. Use '${expected}' instead.${propType === "enumeration" ? " For enumeration properties use operators like IS_ANY_OF, IS_NONE_OF (not IS_EQUAL_TO)." : ""}`
7270
+ });
7271
+ }
7272
+ return issues;
7273
+ }),
7274
+ catchAllToWarning("VALIDATION_API_UNAVAILABLE", index, "Unable to verify filter operationTypes during validation (API error)")
7275
+ );
7276
+ };
7277
+ return pipe42(
7278
+ Effect53.all([checkDuplicateList, checkOperationTypes()], { concurrency: "unbounded" }),
7279
+ Effect53.map(([dupeIssues, opTypeIssues]) => [...dupeIssues, ...opTypeIssues])
7280
+ );
7281
+ },
7049
7282
  preCheck: (op, index, seen) => pipe42(
7050
7283
  HubSpotService,
7051
7284
  Effect53.flatMap((hs) => hs.getLists),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daeda/mcp-pro",
3
- "version": "0.1.1",
3
+ "version": "0.1.5",
4
4
  "description": "MCP server for HubSpot CRM — sync, query, and manage your portal data",
5
5
  "type": "module",
6
6
  "bin": {