airbyte-agent-hubspot 0.15.23__tar.gz → 0.15.30__tar.gz

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 (59) hide show
  1. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/CHANGELOG.md +35 -0
  2. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/PKG-INFO +13 -9
  3. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/README.md +12 -8
  4. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/executor/local_executor.py +39 -19
  5. airbyte_agent_hubspot-0.15.30/airbyte_agent_hubspot/_vendored/connector_sdk/introspection.py +262 -0
  6. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/connector.py +89 -4
  7. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/connector_model.py +1 -1
  8. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/pyproject.toml +1 -1
  9. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/.gitignore +0 -0
  10. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/REFERENCE.md +0 -0
  11. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/__init__.py +0 -0
  12. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/__init__.py +0 -0
  13. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/__init__.py +0 -0
  14. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/auth_strategies.py +0 -0
  15. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/auth_template.py +0 -0
  16. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/cloud_utils/__init__.py +0 -0
  17. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/cloud_utils/client.py +0 -0
  18. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/connector_model_loader.py +0 -0
  19. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/constants.py +0 -0
  20. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/exceptions.py +0 -0
  21. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/executor/__init__.py +0 -0
  22. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/executor/hosted_executor.py +0 -0
  23. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/executor/models.py +0 -0
  24. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/extensions.py +0 -0
  25. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/http/__init__.py +0 -0
  26. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/http/adapters/__init__.py +0 -0
  27. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/http/adapters/httpx_adapter.py +0 -0
  28. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/http/config.py +0 -0
  29. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/http/exceptions.py +0 -0
  30. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/http/protocols.py +0 -0
  31. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/http/response.py +0 -0
  32. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/http_client.py +0 -0
  33. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/logging/__init__.py +0 -0
  34. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/logging/logger.py +0 -0
  35. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/logging/types.py +0 -0
  36. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/observability/__init__.py +0 -0
  37. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/observability/models.py +0 -0
  38. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/observability/redactor.py +0 -0
  39. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/observability/session.py +0 -0
  40. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/performance/__init__.py +0 -0
  41. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/performance/instrumentation.py +0 -0
  42. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/performance/metrics.py +0 -0
  43. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/schema/__init__.py +0 -0
  44. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/schema/base.py +0 -0
  45. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/schema/components.py +0 -0
  46. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/schema/connector.py +0 -0
  47. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/schema/extensions.py +0 -0
  48. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/schema/operations.py +0 -0
  49. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/schema/security.py +0 -0
  50. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/secrets.py +0 -0
  51. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/telemetry/__init__.py +0 -0
  52. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/telemetry/config.py +0 -0
  53. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/telemetry/events.py +0 -0
  54. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/telemetry/tracker.py +0 -0
  55. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/types.py +0 -0
  56. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/utils.py +0 -0
  57. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/_vendored/connector_sdk/validation.py +0 -0
  58. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/models.py +0 -0
  59. {airbyte_agent_hubspot-0.15.23 → airbyte_agent_hubspot-0.15.30}/airbyte_agent_hubspot/types.py +0 -0
@@ -1,5 +1,40 @@
1
1
  # Hubspot changelog
2
2
 
3
+ ## [0.15.30] - 2026-01-09
4
+ - Updated connector definition (YAML version 0.1.3)
5
+ - Source commit: da9b741b
6
+ - SDK version: 0.1.0
7
+
8
+ ## [0.15.29] - 2026-01-07
9
+ - Updated connector definition (YAML version 0.1.3)
10
+ - Source commit: d023e05f
11
+ - SDK version: 0.1.0
12
+
13
+ ## [0.15.28] - 2026-01-06
14
+ - Updated connector definition (YAML version 0.1.3)
15
+ - Source commit: 0580c727
16
+ - SDK version: 0.1.0
17
+
18
+ ## [0.15.27] - 2026-01-06
19
+ - Updated connector definition (YAML version 0.1.3)
20
+ - Source commit: e0e2f989
21
+ - SDK version: 0.1.0
22
+
23
+ ## [0.15.26] - 2026-01-05
24
+ - Updated connector definition (YAML version 0.1.3)
25
+ - Source commit: 3e274293
26
+ - SDK version: 0.1.0
27
+
28
+ ## [0.15.25] - 2025-12-22
29
+ - Updated connector definition (YAML version 0.1.3)
30
+ - Source commit: 0eb1b1c4
31
+ - SDK version: 0.1.0
32
+
33
+ ## [0.15.24] - 2025-12-19
34
+ - Updated connector definition (YAML version 0.1.2)
35
+ - Source commit: 12f6b994
36
+ - SDK version: 0.1.0
37
+
3
38
  ## [0.15.23] - 2025-12-19
4
39
  - Updated connector definition (YAML version 0.1.2)
5
40
  - Source commit: 5d11bfdf
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: airbyte-agent-hubspot
3
- Version: 0.15.23
3
+ Version: 0.15.30
4
4
  Summary: Airbyte Hubspot Connector for AI platforms
5
5
  Project-URL: Homepage, https://github.com/airbytehq/airbyte-embedded
6
6
  Project-URL: Documentation, https://github.com/airbytehq/airbyte-embedded/tree/main/integrations
@@ -41,10 +41,12 @@ tickets, and custom objects for customer relationship management and sales analy
41
41
 
42
42
  ## Example questions
43
43
 
44
- - Show me all deals from [Company] this quarter
44
+ The Hubspot connector is optimized to handle prompts like these.
45
+
46
+ - Show me all deals from \{company\} this quarter
45
47
  - What are the top 5 most valuable deals in my pipeline right now?
46
- - List recent tickets from [customerX] and analyze their support trends
47
- - Search for contacts in the marketing department at [Company]
48
+ - List recent tickets from \{customer\} and analyze their support trends
49
+ - Search for contacts in the marketing department at \{company\}
48
50
  - Give me an overview of my sales team's deals in the last 30 days
49
51
  - Identify the most active companies in our CRM this month
50
52
  - Compare the number of deals closed by different sales representatives
@@ -52,8 +54,10 @@ tickets, and custom objects for customer relationship management and sales analy
52
54
 
53
55
  ## Unsupported questions
54
56
 
55
- - Create a new contact record for [personX]
56
- - Update the contact information for [customerY]
57
+ The Hubspot connector isn't currently able to handle prompts like these.
58
+
59
+ - Create a new contact record for \{person\}
60
+ - Update the contact information for \{customer\}
57
61
  - Delete the ticket from last week's support case
58
62
  - Schedule a follow-up task for this deal
59
63
  - Send an email to all contacts in the sales pipeline
@@ -101,6 +105,6 @@ For the service's official API docs, see the [Hubspot API reference](https://dev
101
105
 
102
106
  ## Version information
103
107
 
104
- - **Package version:** 0.15.23
105
- - **Connector version:** 0.1.2
106
- - **Generated with Connector SDK commit SHA:** 5d11bfdff3ffc5c2e0242449d53cb172403e94b6
108
+ - **Package version:** 0.15.30
109
+ - **Connector version:** 0.1.3
110
+ - **Generated with Connector SDK commit SHA:** da9b741b7b9d8091113af16ba8ec5d5e5d13b749
@@ -7,10 +7,12 @@ tickets, and custom objects for customer relationship management and sales analy
7
7
 
8
8
  ## Example questions
9
9
 
10
- - Show me all deals from [Company] this quarter
10
+ The Hubspot connector is optimized to handle prompts like these.
11
+
12
+ - Show me all deals from \{company\} this quarter
11
13
  - What are the top 5 most valuable deals in my pipeline right now?
12
- - List recent tickets from [customerX] and analyze their support trends
13
- - Search for contacts in the marketing department at [Company]
14
+ - List recent tickets from \{customer\} and analyze their support trends
15
+ - Search for contacts in the marketing department at \{company\}
14
16
  - Give me an overview of my sales team's deals in the last 30 days
15
17
  - Identify the most active companies in our CRM this month
16
18
  - Compare the number of deals closed by different sales representatives
@@ -18,8 +20,10 @@ tickets, and custom objects for customer relationship management and sales analy
18
20
 
19
21
  ## Unsupported questions
20
22
 
21
- - Create a new contact record for [personX]
22
- - Update the contact information for [customerY]
23
+ The Hubspot connector isn't currently able to handle prompts like these.
24
+
25
+ - Create a new contact record for \{person\}
26
+ - Update the contact information for \{customer\}
23
27
  - Delete the ticket from last week's support case
24
28
  - Schedule a follow-up task for this deal
25
29
  - Send an email to all contacts in the sales pipeline
@@ -67,6 +71,6 @@ For the service's official API docs, see the [Hubspot API reference](https://dev
67
71
 
68
72
  ## Version information
69
73
 
70
- - **Package version:** 0.15.23
71
- - **Connector version:** 0.1.2
72
- - **Generated with Connector SDK commit SHA:** 5d11bfdff3ffc5c2e0242449d53cb172403e94b6
74
+ - **Package version:** 0.15.30
75
+ - **Connector version:** 0.1.3
76
+ - **Generated with Connector SDK commit SHA:** da9b741b7b9d8091113af16ba8ec5d5e5d13b749
@@ -674,16 +674,16 @@ class LocalExecutor:
674
674
  return {key: value for key, value in params.items() if key in allowed_params}
675
675
 
676
676
  def _extract_body(self, allowed_fields: list[str], params: dict[str, Any]) -> dict[str, Any]:
677
- """Extract body fields from params.
677
+ """Extract body fields from params, filtering out None values.
678
678
 
679
679
  Args:
680
680
  allowed_fields: List of allowed body field names
681
681
  params: All parameters
682
682
 
683
683
  Returns:
684
- Dictionary of body fields
684
+ Dictionary of body fields with None values filtered out
685
685
  """
686
- return {key: value for key, value in params.items() if key in allowed_fields}
686
+ return {key: value for key, value in params.items() if key in allowed_fields and value is not None}
687
687
 
688
688
  def _serialize_deep_object_params(self, params: dict[str, Any], deep_object_param_names: list[str]) -> dict[str, Any]:
689
689
  """Serialize deepObject parameters to bracket notation format.
@@ -837,7 +837,9 @@ class LocalExecutor:
837
837
  Request body dict or None if no body needed
838
838
  """
839
839
  if endpoint.graphql_body:
840
- return self._build_graphql_body(endpoint.graphql_body, params)
840
+ # Extract defaults from query_params_schema for GraphQL variable interpolation
841
+ param_defaults = {name: schema.get("default") for name, schema in endpoint.query_params_schema.items() if "default" in schema}
842
+ return self._build_graphql_body(endpoint.graphql_body, params, param_defaults)
841
843
  elif endpoint.body_fields:
842
844
  return self._extract_body(endpoint.body_fields, params)
843
845
  return None
@@ -903,12 +905,18 @@ class LocalExecutor:
903
905
 
904
906
  return query
905
907
 
906
- def _build_graphql_body(self, graphql_config: dict[str, Any], params: dict[str, Any]) -> dict[str, Any]:
908
+ def _build_graphql_body(
909
+ self,
910
+ graphql_config: dict[str, Any],
911
+ params: dict[str, Any],
912
+ param_defaults: dict[str, Any] | None = None,
913
+ ) -> dict[str, Any]:
907
914
  """Build GraphQL request body with variable substitution and field selection.
908
915
 
909
916
  Args:
910
917
  graphql_config: GraphQL configuration from x-airbyte-body-type extension
911
918
  params: Parameters from execute() call
919
+ param_defaults: Default values for params from query_params_schema
912
920
 
913
921
  Returns:
914
922
  GraphQL request body: {"query": "...", "variables": {...}}
@@ -922,7 +930,7 @@ class LocalExecutor:
922
930
 
923
931
  # Substitute variables from params
924
932
  if "variables" in graphql_config and graphql_config["variables"]:
925
- body["variables"] = self._interpolate_variables(graphql_config["variables"], params)
933
+ body["variables"] = self._interpolate_variables(graphql_config["variables"], params, param_defaults)
926
934
 
927
935
  # Add operation name if specified
928
936
  if "operationName" in graphql_config:
@@ -981,7 +989,12 @@ class LocalExecutor:
981
989
  fields_str = " ".join(graphql_fields)
982
990
  return query.replace("{{ fields }}", fields_str)
983
991
 
984
- def _interpolate_variables(self, variables: dict[str, Any], params: dict[str, Any]) -> dict[str, Any]:
992
+ def _interpolate_variables(
993
+ self,
994
+ variables: dict[str, Any],
995
+ params: dict[str, Any],
996
+ param_defaults: dict[str, Any] | None = None,
997
+ ) -> dict[str, Any]:
985
998
  """Recursively interpolate variables using params.
986
999
 
987
1000
  Preserves types (doesn't stringify everything).
@@ -990,15 +1003,18 @@ class LocalExecutor:
990
1003
  - Direct replacement: "{{ owner }}" → params["owner"] (preserves type)
991
1004
  - Nested objects: {"input": {"name": "{{ name }}"}}
992
1005
  - Arrays: [{"id": "{{ id }}"}]
993
- - Unsubstituted placeholders: "{{ states }}" → None (for optional params)
1006
+ - Default values: "{{ per_page }}" → param_defaults["per_page"] if not in params
1007
+ - Unsubstituted placeholders: "{{ states }}" → None (for optional params without defaults)
994
1008
 
995
1009
  Args:
996
1010
  variables: Variables dict with template placeholders
997
1011
  params: Parameters to substitute
1012
+ param_defaults: Default values for params from query_params_schema
998
1013
 
999
1014
  Returns:
1000
1015
  Interpolated variables dict with types preserved
1001
1016
  """
1017
+ defaults = param_defaults or {}
1002
1018
 
1003
1019
  def interpolate_value(value: Any) -> Any:
1004
1020
  if isinstance(value, str):
@@ -1012,8 +1028,15 @@ class LocalExecutor:
1012
1028
  value = value.replace(placeholder, str(param_value))
1013
1029
 
1014
1030
  # Check if any unsubstituted placeholders remain
1015
- # If so, return None (treats as "not provided" for optional params)
1016
1031
  if re.search(r"\{\{\s*\w+\s*\}\}", value):
1032
+ # Extract placeholder name and check for default value
1033
+ match = re.search(r"\{\{\s*(\w+)\s*\}\}", value)
1034
+ if match:
1035
+ param_name = match.group(1)
1036
+ if param_name in defaults:
1037
+ # Use default value (preserves type)
1038
+ return defaults[param_name]
1039
+ # No default found - return None (for optional params)
1017
1040
  return None
1018
1041
 
1019
1042
  return value
@@ -1151,21 +1174,18 @@ class LocalExecutor:
1151
1174
  if action not in (Action.CREATE, Action.UPDATE):
1152
1175
  return
1153
1176
 
1154
- # Check if endpoint has body fields defined
1155
- if not endpoint.body_fields:
1177
+ # Get the request schema to find truly required fields
1178
+ request_schema = endpoint.request_schema
1179
+ if not request_schema:
1156
1180
  return
1157
1181
 
1158
- # For now, we treat all body_fields as potentially required for CREATE/UPDATE
1159
- # In a more advanced implementation, we could parse the request schema
1160
- # to identify truly required fields
1161
- missing_fields = []
1162
- for field in endpoint.body_fields:
1163
- if field not in params:
1164
- missing_fields.append(field)
1182
+ # Only validate fields explicitly marked as required in the schema
1183
+ required_fields = request_schema.get("required", [])
1184
+ missing_fields = [field for field in required_fields if field not in params]
1165
1185
 
1166
1186
  if missing_fields:
1167
1187
  raise MissingParameterError(
1168
- f"Missing required body fields for {entity}.{action.value}: {missing_fields}. Provided parameters: {list(params.keys())}"
1188
+ f"Missing required body fields for {entity}.{action.value}: {missing_fields}. " f"Provided parameters: {list(params.keys())}"
1169
1189
  )
1170
1190
 
1171
1191
  async def close(self):
@@ -0,0 +1,262 @@
1
+ """
2
+ Shared introspection utilities for connector metadata.
3
+
4
+ This module provides utilities for introspecting connector metadata,
5
+ generating descriptions, and formatting parameter signatures. These
6
+ functions are used by both the runtime decorators and the generated
7
+ connector code.
8
+
9
+ The module is designed to work with any object conforming to the
10
+ ConnectorModel and EndpointDefinition interfaces from connector_sdk.types.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Any, Protocol
16
+
17
+ # Constants
18
+ MAX_EXAMPLE_QUESTIONS = 5 # Maximum number of example questions to include in description
19
+
20
+
21
+ class EndpointProtocol(Protocol):
22
+ """Protocol defining the expected interface for endpoint parameters.
23
+
24
+ This allows functions to work with any endpoint-like object
25
+ that has these attributes, including EndpointDefinition and mock objects.
26
+ """
27
+
28
+ path_params: list[str]
29
+ path_params_schema: dict[str, dict[str, Any]]
30
+ query_params: list[str]
31
+ query_params_schema: dict[str, dict[str, Any]]
32
+ body_fields: list[str]
33
+ request_schema: dict[str, Any] | None
34
+
35
+
36
+ class EntityProtocol(Protocol):
37
+ """Protocol defining the expected interface for entity definitions."""
38
+
39
+ name: str
40
+ actions: list[Any]
41
+ endpoints: dict[Any, EndpointProtocol]
42
+
43
+
44
+ class ConnectorModelProtocol(Protocol):
45
+ """Protocol defining the expected interface for connector model parameters.
46
+
47
+ This allows functions to work with any connector-like object
48
+ that has these attributes, including ConnectorModel and mock objects.
49
+ """
50
+
51
+ @property
52
+ def entities(self) -> list[EntityProtocol]: ...
53
+
54
+ @property
55
+ def openapi_spec(self) -> Any: ...
56
+
57
+
58
+ def format_param_signature(endpoint: EndpointProtocol) -> str:
59
+ """Format parameter signature for an endpoint action.
60
+
61
+ Returns a string like: (id*) or (limit?, starting_after?, email?)
62
+ where * = required, ? = optional
63
+
64
+ Args:
65
+ endpoint: Object conforming to EndpointProtocol (e.g., EndpointDefinition)
66
+
67
+ Returns:
68
+ Formatted parameter signature string
69
+ """
70
+ params = []
71
+
72
+ # Defensive: safely access attributes with defaults for malformed endpoints
73
+ path_params = getattr(endpoint, "path_params", []) or []
74
+ query_params = getattr(endpoint, "query_params", []) or []
75
+ query_params_schema = getattr(endpoint, "query_params_schema", {}) or {}
76
+ body_fields = getattr(endpoint, "body_fields", []) or []
77
+ request_schema = getattr(endpoint, "request_schema", None)
78
+
79
+ # Path params (always required)
80
+ for name in path_params:
81
+ params.append(f"{name}*")
82
+
83
+ # Query params
84
+ for name in query_params:
85
+ schema = query_params_schema.get(name, {})
86
+ required = schema.get("required", False)
87
+ params.append(f"{name}{'*' if required else '?'}")
88
+
89
+ # Body fields
90
+ if request_schema:
91
+ required_fields = set(request_schema.get("required", []))
92
+ for name in body_fields:
93
+ params.append(f"{name}{'*' if name in required_fields else '?'}")
94
+
95
+ return f"({', '.join(params)})" if params else "()"
96
+
97
+
98
+ def describe_entities(model: ConnectorModelProtocol) -> list[dict[str, Any]]:
99
+ """Generate entity descriptions from ConnectorModel.
100
+
101
+ Returns a list of entity descriptions with detailed parameter information
102
+ for each action. This is used by generated connectors' describe() method.
103
+
104
+ Args:
105
+ model: Object conforming to ConnectorModelProtocol (e.g., ConnectorModel)
106
+
107
+ Returns:
108
+ List of entity description dicts with keys:
109
+ - entity_name: Name of the entity (e.g., "contacts", "deals")
110
+ - description: Entity description from the first endpoint
111
+ - available_actions: List of actions (e.g., ["list", "get", "create"])
112
+ - parameters: Dict mapping action -> list of parameter dicts
113
+ """
114
+ entities = []
115
+ for entity_def in model.entities:
116
+ description = ""
117
+ parameters: dict[str, list[dict[str, Any]]] = {}
118
+
119
+ endpoints = getattr(entity_def, "endpoints", {}) or {}
120
+ if endpoints:
121
+ for action, endpoint in endpoints.items():
122
+ # Get description from first endpoint that has one
123
+ if not description:
124
+ endpoint_desc = getattr(endpoint, "description", None)
125
+ if endpoint_desc:
126
+ description = endpoint_desc
127
+
128
+ action_params: list[dict[str, Any]] = []
129
+
130
+ # Defensive: safely access endpoint attributes
131
+ path_params = getattr(endpoint, "path_params", []) or []
132
+ path_params_schema = getattr(endpoint, "path_params_schema", {}) or {}
133
+ query_params = getattr(endpoint, "query_params", []) or []
134
+ query_params_schema = getattr(endpoint, "query_params_schema", {}) or {}
135
+ body_fields = getattr(endpoint, "body_fields", []) or []
136
+ request_schema = getattr(endpoint, "request_schema", None)
137
+
138
+ # Path params (always required)
139
+ for param_name in path_params:
140
+ schema = path_params_schema.get(param_name, {})
141
+ action_params.append(
142
+ {
143
+ "name": param_name,
144
+ "in": "path",
145
+ "required": True,
146
+ "type": schema.get("type", "string"),
147
+ "description": schema.get("description", ""),
148
+ }
149
+ )
150
+
151
+ # Query params
152
+ for param_name in query_params:
153
+ schema = query_params_schema.get(param_name, {})
154
+ action_params.append(
155
+ {
156
+ "name": param_name,
157
+ "in": "query",
158
+ "required": schema.get("required", False),
159
+ "type": schema.get("type", "string"),
160
+ "description": schema.get("description", ""),
161
+ }
162
+ )
163
+
164
+ # Body fields
165
+ if request_schema:
166
+ required_fields = request_schema.get("required", [])
167
+ properties = request_schema.get("properties", {})
168
+ for param_name in body_fields:
169
+ prop = properties.get(param_name, {})
170
+ action_params.append(
171
+ {
172
+ "name": param_name,
173
+ "in": "body",
174
+ "required": param_name in required_fields,
175
+ "type": prop.get("type", "string"),
176
+ "description": prop.get("description", ""),
177
+ }
178
+ )
179
+
180
+ if action_params:
181
+ # Action is an enum, use .value to get string
182
+ action_key = action.value if hasattr(action, "value") else str(action)
183
+ parameters[action_key] = action_params
184
+
185
+ actions = getattr(entity_def, "actions", []) or []
186
+ entities.append(
187
+ {
188
+ "entity_name": entity_def.name,
189
+ "description": description,
190
+ "available_actions": [a.value if hasattr(a, "value") else str(a) for a in actions],
191
+ "parameters": parameters,
192
+ }
193
+ )
194
+
195
+ return entities
196
+
197
+
198
+ def generate_tool_description(model: ConnectorModelProtocol) -> str:
199
+ """Generate AI tool description from connector metadata.
200
+
201
+ Produces a detailed description that includes:
202
+ - Per-entity/action parameter signatures with required (*) and optional (?) markers
203
+ - Response structure documentation with pagination hints
204
+ - Example questions if available in the OpenAPI spec
205
+
206
+ This is used by the Connector.describe class method decorator to populate
207
+ function docstrings for AI framework integration.
208
+
209
+ Args:
210
+ model: Object conforming to ConnectorModelProtocol (e.g., ConnectorModel)
211
+
212
+ Returns:
213
+ Formatted description string suitable for AI tool documentation
214
+ """
215
+ lines = []
216
+
217
+ # Entity/action parameter details (including pagination params like limit, starting_after)
218
+ lines.append("ENTITIES AND PARAMETERS:")
219
+ for entity in model.entities:
220
+ lines.append(f" {entity.name}:")
221
+ actions = getattr(entity, "actions", []) or []
222
+ endpoints = getattr(entity, "endpoints", {}) or {}
223
+ for action in actions:
224
+ action_str = action.value if hasattr(action, "value") else str(action)
225
+ endpoint = endpoints.get(action)
226
+ if endpoint:
227
+ param_sig = format_param_signature(endpoint)
228
+ lines.append(f" - {action_str}{param_sig}")
229
+ else:
230
+ lines.append(f" - {action_str}()")
231
+
232
+ # Response structure (brief, includes pagination hint)
233
+ lines.append("")
234
+ lines.append("RESPONSE STRUCTURE:")
235
+ lines.append(" - list/search: {data: [...], meta: {has_more: bool}}")
236
+ lines.append(" - get: Returns entity directly (no envelope)")
237
+ lines.append(" To paginate: pass starting_after=<last_id> while has_more is true")
238
+
239
+ # Add example questions if available in openapi_spec
240
+ openapi_spec = getattr(model, "openapi_spec", None)
241
+ if openapi_spec:
242
+ info = getattr(openapi_spec, "info", None)
243
+ if info:
244
+ example_questions = getattr(info, "x_airbyte_example_questions", None)
245
+ if example_questions:
246
+ supported = getattr(example_questions, "supported", None)
247
+ if supported:
248
+ lines.append("")
249
+ lines.append("EXAMPLE QUESTIONS:")
250
+ for q in supported[:MAX_EXAMPLE_QUESTIONS]:
251
+ lines.append(f" - {q}")
252
+
253
+ # Generic parameter description for function signature
254
+ lines.append("")
255
+ lines.append("FUNCTION PARAMETERS:")
256
+ lines.append(" - entity: Entity name (string)")
257
+ lines.append(" - action: Operation to perform (string)")
258
+ lines.append(" - params: Operation parameters (dict) - see entity details above")
259
+ lines.append("")
260
+ lines.append("Parameter markers: * = required, ? = optional")
261
+
262
+ return "\n".join(lines)
@@ -4,14 +4,15 @@ hubspot connector.
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- from typing import TYPE_CHECKING, Any, overload
7
+ import logging
8
+ from typing import TYPE_CHECKING, Any, Callable, TypeVar, overload
8
9
  try:
9
10
  from typing import Literal
10
11
  except ImportError:
11
12
  from typing_extensions import Literal
12
13
 
13
14
  from .connector_model import HubspotConnectorModel
14
-
15
+ from ._vendored.connector_sdk.introspection import describe_entities, generate_tool_description
15
16
  from .types import (
16
17
  CompaniesGetParams,
17
18
  CompaniesListParams,
@@ -38,7 +39,6 @@ from .types import (
38
39
  TicketsSearchParamsFiltergroupsItem,
39
40
  TicketsSearchParamsSortsItem,
40
41
  )
41
-
42
42
  if TYPE_CHECKING:
43
43
  from .models import HubspotAuthConfig
44
44
  # Import response models and envelope models at runtime
@@ -63,6 +63,9 @@ from .models import (
63
63
  Ticket,
64
64
  )
65
65
 
66
+ # TypeVar for decorator type preservation
67
+ _F = TypeVar("_F", bound=Callable[..., Any])
68
+
66
69
 
67
70
  class HubspotConnector:
68
71
  """
@@ -72,7 +75,7 @@ class HubspotConnector:
72
75
  """
73
76
 
74
77
  connector_name = "hubspot"
75
- connector_version = "0.1.2"
78
+ connector_version = "0.1.3"
76
79
  vendored_sdk_version = "0.1.0" # Version of vendored connector-sdk
77
80
 
78
81
  # Map of (entity, action) -> has_extractors for envelope wrapping decision
@@ -402,6 +405,88 @@ class HubspotConnector:
402
405
  # No extractors - return raw response data
403
406
  return result.data
404
407
 
408
+ # ===== INTROSPECTION METHODS =====
409
+
410
+ @classmethod
411
+ def describe(cls, func: _F) -> _F:
412
+ """
413
+ Decorator that populates a function's docstring with connector capabilities.
414
+
415
+ This class method can be used as a decorator to automatically generate
416
+ comprehensive documentation for AI tool functions.
417
+
418
+ Usage:
419
+ @mcp.tool()
420
+ @HubspotConnector.describe
421
+ async def execute(entity: str, action: str, params: dict):
422
+ '''Execute operations.'''
423
+ ...
424
+
425
+ The decorated function's __doc__ will be updated with:
426
+ - Available entities and their actions
427
+ - Parameter signatures with required (*) and optional (?) markers
428
+ - Response structure documentation
429
+ - Example questions (if available in OpenAPI spec)
430
+
431
+ Args:
432
+ func: The function to decorate
433
+
434
+ Returns:
435
+ The same function with updated __doc__
436
+ """
437
+ description = generate_tool_description(HubspotConnectorModel)
438
+
439
+ original_doc = func.__doc__ or ""
440
+ if original_doc.strip():
441
+ func.__doc__ = f"{original_doc.strip()}\n\n{description}"
442
+ else:
443
+ func.__doc__ = description
444
+
445
+ return func
446
+
447
+ def list_entities(self) -> list[dict[str, Any]]:
448
+ """
449
+ Get structured data about available entities, actions, and parameters.
450
+
451
+ Returns a list of entity descriptions with:
452
+ - entity_name: Name of the entity (e.g., "contacts", "deals")
453
+ - description: Entity description from the first endpoint
454
+ - available_actions: List of actions (e.g., ["list", "get", "create"])
455
+ - parameters: Dict mapping action -> list of parameter dicts
456
+
457
+ Example:
458
+ entities = connector.list_entities()
459
+ for entity in entities:
460
+ print(f"{entity['entity_name']}: {entity['available_actions']}")
461
+ """
462
+ return describe_entities(HubspotConnectorModel)
463
+
464
+ def entity_schema(self, entity: str) -> dict[str, Any] | None:
465
+ """
466
+ Get the JSON schema for an entity.
467
+
468
+ Args:
469
+ entity: Entity name (e.g., "contacts", "companies")
470
+
471
+ Returns:
472
+ JSON schema dict describing the entity structure, or None if not found.
473
+
474
+ Example:
475
+ schema = connector.entity_schema("contacts")
476
+ if schema:
477
+ print(f"Contact properties: {list(schema.get('properties', {}).keys())}")
478
+ """
479
+ entity_def = next(
480
+ (e for e in HubspotConnectorModel.entities if e.name == entity),
481
+ None
482
+ )
483
+ if entity_def is None:
484
+ logging.getLogger(__name__).warning(
485
+ f"Entity '{entity}' not found. Available entities: "
486
+ f"{[e.name for e in HubspotConnectorModel.entities]}"
487
+ )
488
+ return entity_def.entity_schema if entity_def else None
489
+
405
490
 
406
491
 
407
492
  class ContactsQuery:
@@ -26,7 +26,7 @@ from uuid import (
26
26
  HubspotConnectorModel: ConnectorModel = ConnectorModel(
27
27
  id=UUID('36c891d9-4bd9-43ac-bad2-10e12756272c'),
28
28
  name='hubspot',
29
- version='0.1.2',
29
+ version='0.1.3',
30
30
  base_url='https://api.hubapi.com',
31
31
  auth=AuthConfig(
32
32
  type=AuthType.OAUTH2,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "airbyte-agent-hubspot"
3
- version = "0.15.23"
3
+ version = "0.15.30"
4
4
  description = "Airbyte Hubspot Connector for AI platforms"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"