Query HubSpot and Stripe from LangGraph with MCP
Build a LangGraph business-data agent with Combined MCP. Download the graph, authenticated client setup and a local test that preserves structured query results.
Use Combined to supply your LangGraph agent with synced, granted business data across HubSpot, Stripe and other apps. This example connects one remote MCP endpoint, selects five query tools and runs an explicit model/tool loop. It also retains the structured query result and receipt ID separately from the model's answer, so your application can inspect the evidence behind a business number.
1. Get the graph and run the local result check
Download the LangGraph example bundle, or open the Python graph directly. The bundle includes pinned requirements, a synthetic MCP test and the recorded result. Extract it and use Python 3.12 in a fresh environment:
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install -r langgraph-requirements.txt
OTEL_SDK_DISABLED=true LANGCHAIN_TRACING_V2=false python verify_langgraph.pyOn Windows, the activation command is .venv\Scripts\Activate.ps1. The tested top-level pins are langchain[mcp]==1.4.0, langgraph==1.2.11 and fastmcp==4.0.3. Keep this environment separate from the CrewAI example, which has a different MCP dependency requirement.
The local check runs an in-memory MCP server and a scripted model through the actual graph. It verifies the allowed tools, the model/tool loop, structured-result retention, tool-error status and rejection of a missing required tool. It uses no Combined account or hosted model. The intentional error check may print a tool-error log before the final "passed": true result.
2. Prepare the sources and the agent's access
Connect the business sources in Combined and complete their initial sync. For this example, choose the HubSpot company/deal datasets and Stripe billing datasets needed for your reporting policy. Inspect their available fields and records in Explore before handing the task to an agent.
In Access, provision a runtime credential and grant that identity access to the selected sources. Configure COMBINED_ACCOUNT_ID and COMBINED_TOKEN in your runtime. The script validates the account UUID and creates https://platform.trycombined.com/mcp?account_id=ACCOUNT_UUID.
Your graph uses a bearer credential so it can run without an interactive browser login. That credential belongs to a particular identity; source grants must belong to the same identity. The account ID alone does not authorize access. The authentication guide separates endpoint, credential, identity and grant failures.
For a question joining CRM and billing, supply a maintained company-to-customer mapping. Discover its actual relation and fields along with the source schemas. If the mapping is missing, the agent should report that gap instead of inventing a match from similar company names.
3. Discover tools through the authenticated MCP adapter
This example uses langchain.mcp.MCPAdapter with a preconfigured FastMCP client. Authentication belongs to the client; the adapter accepts that client as its target:
client = Client(url, auth=os.environ["COMBINED_TOKEN"])
async with MCPAdapter(client) as adapter:
tools = select_tools(await adapter.list_tools())
# Build and invoke the graph within this context.The selected tools are list_sources, list_datasets, describe_dataset, get_freshness and query_sql. The script raises an error if a required tool is missing and excludes other discovered tools from this graph. Data visibility is still enforced by Combined.
LangChain's new MCP module is beta in the pinned version. Existing applications using MultiServerMCPClient should follow LangChain's migration guide; the old connection dictionary and get_tools() examples are a different interface. Retain these pins until you have checked a planned upgrade against your own agent and the included result test.
4. Run an explicit model and tool loop
The download defines a StateGraph with MessagesState. Its answer node calls the model with the selected tools bound. A response containing tool calls goes to ToolNode and then back to the answer node. A response without tool calls ends the graph. Invocation has a recursion limit of 40 to bound the loop.
Configure LANGCHAIN_MODEL with a supported provider/model identifier. Install that provider's LangChain integration and configure its credential separately from the Combined token. Then set BUSINESS_QUESTION to a question with an explicit reporting window, such as:
Compare current open HubSpot pipeline with Stripe paid-invoice amounts
for the UTC period I specify, by mapped company and currency.
Discover schemas, inspect the customer mapping and check freshness.
Aggregate each side before joining; preserve unmatched records.
Return at most 20 rows, the query details and any coverage gaps.Run python langgraph_combined.py after configuring the environment. The script's task instructions require discovered relation names, bounded SELECT queries and positional parameters. Follow source and dataset discovery cursors. Keep unknown mapping coverage separate from zero activity.
The join lab gives you an account-free SQL exercise for the same calculation problem. Several deals joined to several invoices can multiply both totals; aggregate each side to the intended grain first. Paid-invoice amounts are a defined billing metric, not automatically net revenue or ARR. The current pipeline also needs a different data source if you want a historical snapshot.
5. Inspect the result and follow its receipt ID
A model's final prose is only one output. The example also scans query_sql ToolMessages and extracts artifact["structured_content"], the tool-call ID and the tool status. A structured artifact is available to your application; retaining it does not mean the model automatically reads every field in that artifact.
for message in result["messages"]:
if isinstance(message, ToolMessage) and message.name == "query_sql":
print(message.status)
print(message.artifact.get("structured_content"))This excerpt shows the inspection point. The full download also handles an absent or non-dictionary artifact. Combined wraps the query result inside structured_content.data. Read data.rows, data.columns, data.rowCount, data.truncated and data.receiptId. An absent payload or error status means the evidence is missing; it does not establish a successful empty result.
For the recorded account, source and dataset scope, retrieve the separate QueryReceipt using that receipt ID and account ID. Source sync timestamps come from get_freshness. This script retains the query result and receipt reference; it does not call the receipt endpoint or combine freshness into that result.
Our recorded local check retained a synthetic result containing an Atlas USD 5,000 row, a synthetic receipt UUID and truncated: false. These are test records illustrating result transport, not a customer result or a promise about a real account's schema. The check also verified that an intentional MCP error reached the ToolMessage as an error.
Use the answer-verification lab to practice the next step: deciding whether the evidence supports a business claim. The downloadable test establishes local adapter and graph behavior; your first live run should also verify the hosted connection, source freshness and one small permitted query. Combined gives this workflow one managed business-data endpoint that you can reuse across agent runtimes.
Sources and further reading
Explore the documentation behind this guide. Product details checked on September 15, 2026.