CRM
HubSpot and Salesforce toolsets.
#CRM
Sales and revenue CRMs. Each toolset gives an agent a uniform surface over contacts, companies, deals, and pipeline records for one platform. Every connector follows the standard @toolset / @toolify shape and the agent-ready toolset pattern (compact summaries by default, opt-in raw IDs, tolerant write inputs).
#HubSpotToolSet
HubSpotToolSet wraps the HubSpot CRM v3 API. It authenticates with a bearer token — either a private-app token (recommended) or an OAuth-issued access token.
from maivn_tools import HubSpotToolSet, register_connector
connector = HubSpotToolSet(token=secrets["HUBSPOT_TOKEN"])metadata.scopes lists the most common scope keys (crm.objects.contacts.read, crm.objects.deals.write, etc.). Configure your private app with the scopes the agent needs.
#Object types
The standard CRM tools take an object_type argument from the allowed set: contacts, companies, deals, tickets. Engagement tools take an engagement_type argument from: notes, calls, emails, meetings, tasks. Any other value raises ValueError.
#Tools
Object CRUD. list_objects, get_object, search_objects, create_object, update_object, archive_object (destructive).
Engagements. list_engagements, create_note, create_task, create_email_engagement (logs only — does not send).
Pipelines, owners, properties. list_pipelines, get_pipeline, list_owners, get_owner, list_properties, get_property.
Associations. associate_objects, remove_association (destructive).
Batch operations. batch_read_objects, batch_create_objects, batch_update_objects, batch_archive_objects (destructive).
#Agent-ready behavior
list_objectsandsearch_objectsreturn compact summaries by default. Each summary carries a response-localobject_ref(object_1,object_2, ...), theobject_type, anupdated_attimestamp, and a small set of useful display properties chosen per object type (e.g.email/firstname/lastnamefor contacts,dealname/dealstage/amountfor deals).- Default
limitis 25 for bothlist_objectsandsearch_objects(max 100).list_engagementsandlist_ownersuse the same 25 default. - Raw HubSpot record IDs are hidden by default. Pass
include_ids=Trueto addobject_idto each summary — only do this when a follow-up tool needs the raw handle, not for human-facing answers. - Pass
include_metadata=Falseto receive the raw HubSpot{"results": [...], "paging": {...}}payload unchanged. get_object,update_object,archive_object,batch_read_objects, andbatch_archive_objectsall accept either a raw ID string, a summary dict returned by the list/search tools (include_ids=Truemode), or a list of such dicts. The connector resolves the canonical HubSpot ID fromobject_idorid.
#Destructive tools
archive_object, remove_association, and batch_archive_objects carry destructive=True. Filter them out with exclude_tags=["destructive"] when constructing a non-destructive agent. Archive moves records to the HubSpot recycle bin; batch archive removes every listed record in one call.
#Example: summary-mode search
agent.add_toolset(HubSpotToolSet(token=secrets["HUBSPOT_TOKEN"]))
# Default: compact summaries, IDs hidden, safe to show in final answers.
response = connector.search_objects(
"deals",
query="acme",
limit=10,
)
# response == {
# "objects": [
# {"object_ref": "object_1", "object_type": "deals",
# "dealname": "ACME renewal", "dealstage": "negotiation",
# "amount": "45000", "updated_at": "..."},
# ...
# ],
# "paging": {...},
# }To pipe a summary straight into a write tool:
hits = connector.search_objects(
"deals", query="acme", include_ids=True
)["objects"]
connector.update_object("deals", hits[0], {"dealstage": "closedwon"})#SalesforceToolSet
SalesforceToolSet wraps the Salesforce REST API. It authenticates with an OAuth bearer token. Use OAuth2Flow against the Salesforce token URL (or the JWT-bearer flow) to obtain the access token; the connector accepts a string, an OAuth2Token, or a token callable.
from maivn_tools import SalesforceToolSet, register_connector
connector = SalesforceToolSet(
instance_url="https://acme.my.salesforce.com",
token=token_cache,
api_version="v59.0",
)#Tools
Schema and identity. describe_global, describe_object, list_versions, list_limits, get_user_info, get_recent_items.
Queries. soql_query (SELECT / FIND; mutations rejected), query_more, query_all (incl. soft-deleted records), sosl_search.
Record CRUD. get_record, create_record, update_record, upsert_record, delete_record (destructive).
Reports and dashboards. list_reports, get_report, run_report, list_dashboards.
Composite (batch). composite_request, composite_sobjects_create, composite_sobjects_update, composite_sobjects_delete (destructive).
#Agent-ready behavior
soql_queryreturns compact summaries by default. Each summary carries a response-localrecord_ref(record_1,record_2, ...), theobject_type(taken from the record'sattributes.type), and any of the common display fields that were actually selected (Name,Subject,Title,FirstName,LastName,Email,Phone,Status,StageName,Amount,CloseDate,Type).- Salesforce
Idvalues are hidden by default. Passinclude_ids=Trueto addrecord_idto each summary — needed before callingget_record,update_record,delete_record, orcomposite_sobjects_delete. - Pass
include_metadata=Falseonsoql_queryto receive the raw Salesforce response, includingnextRecordsUrlfor pagination viaquery_more.query_allandsosl_searchalways return the raw Salesforce payload. get_record,update_record,delete_record, andcomposite_sobjects_deleteaccept a rawIdstring, a record dict returned bysoql_query(withinclude_ids=True), or a list of such dicts. The connector resolves the ID fromId,id, orrecord_id.
#Destructive tools
delete_record and composite_sobjects_delete carry destructive=True. Both move rows to the recycle bin (or destroy them if the bin is purged). Confirm with the user before invoking either, or filter them with exclude_tags=["destructive"].
#Example: summary-mode SOQL
agent.add_toolset(SalesforceToolSet(instance_url=..., token=token_cache))
response = connector.soql_query(
"SELECT Id, Name, StageName, Amount FROM Opportunity "
"WHERE StageName = 'Negotiation' LIMIT 20"
)
# response == {
# "records": [
# {"record_ref": "record_1", "object_type": "Opportunity",
# "Name": "ACME renewal", "StageName": "Negotiation",
# "Amount": 45000},
# ...
# ],
# "totalSize": 12, "done": True,
# }To follow up with an update, request raw IDs:
hits = connector.soql_query(
"SELECT Id, Name FROM Opportunity WHERE Name LIKE 'ACME%'",
include_ids=True,
)["records"]
connector.update_record("Opportunity", hits[0], {"StageName": "Closed Won"})#Safety
soql_queryrejects any statement whose first token is notSELECTorFIND. Use the explicit CRUD tools for writes.query_allrejects anything other thanSELECT;sosl_searchrejects anything other thanFIND.object_nameand external-id field names are validated against^[A-Za-z][A-Za-z0-9_]*$so they cannot inject path segments.- The connector advertises only the standard
apiandrefresh_tokenscopes; configure your Connected App accordingly.