Tools & Connectors
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 stableobject_ref
(object_1,object_2, ...), theobject_type, anupdated_at
timestamp, 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=True
to 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 withexclude_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 stablerecord_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=True
to 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 carrydestructive=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 notSELECT
orFIND. Use the explicit CRUD tools for writes.query_allrejects anything other thanSELECT;sosl_search
rejects 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_token
scopes; configure your Connected App accordingly.