Postgres + MongoDB — MCP data walkthrough
This guide replays a real Cursor session: explore two live data sources with MCP, understand why they need
to be linked, then build the federated crm_customer_relationship playbook and prove the
cross-source join with query_graph.
source_id values only. Playbooks and bindings are needed once you want graph-level queries
across sources.
Starting point — what data you have
Before any playbook work, the demo environment already has several configured sources in
profiles/local.yaml. This walkthrough focuses on two of them — the ones that hold related CRM
data in different systems:
| Source id | Adapter | Role in this story |
|---|---|---|
warehouse_pg |
Postgres (SQL) | Primary CRM warehouse — internal users and business accounts |
mongo_main |
MongoDB | Customer profiles — external-facing customer records |
payroll_csv |
CSV file | Not used here — payroll rows keyed by user (separate demo playbook) |
salesforce_main |
Salesforce (SOQL) | Not used here — optional third CRM source in the same profile |
Postgres — warehouse_pg
Schema public, two relational tables that model an internal CRM. Data is small and readable —
ideal for MCP sampling.
-
users— sales or account owners. Key:user_id(e.g.alex.ae). Attributes:full_name. -
accounts— companies the org tracks. Key:account_name(e.g. Northwind Traders). Attributes:industry,owner_user_id(FK →users.user_id).
At session start the live data looked like this:
| users (2 rows) | |
|---|---|
| user_id | full_name |
| alex.ae | Alex Anderson |
| jordan.ae | Jordan Ellis |
| accounts (3 rows) | ||
|---|---|---|
| account_name | industry | owner_user_id |
| Northwind Traders | Retail | alex.ae |
| Contoso Ltd | Technology | alex.ae |
| Fabrikam Inc | Manufacturing | jordan.ae |
MongoDB — mongo_main
Database anythinggraph (from AG_MONGODB_DATABASE), one collection of interest:
customers. Documents are schemaless JSON — no foreign keys, no shared UUID with Postgres.
Sample document discovered via sample_source:
| Field | Example value | Meaning |
|---|---|---|
name | john | Customer display name |
age | 30 | Demographic attribute |
region | uk | Geography |
company | Northwind Traders | Free-text company name — not a Postgres FK |
company and Postgres
account_name both contain the string Northwind Traders. There is no shared
primary key — that gap is exactly what the playbook must bridge.
The problem we're trying to solve
You have related business data split across two systems that were never designed to join at the database layer:
- Postgres knows who owns which account (
owns_accountviaowner_user_id). - MongoDB knows who the customers are and which company name they claim (
company). - Neither database alone can answer: “Customer john — which CRM account do they belong to, who owns it, and what industry is it?”
Why a normal query isn't enough
-
No shared ID — MongoDB uses
_idand string fields; Postgres usesuser_id/account_name. You cannotJOINon a foreign key. - Cross-database joins don't exist — even if keys matched, Postgres and MongoDB are separate connections; ad-hoc SQL or shell scripts don't scale for agents or governed access.
-
Business-key matching — the real join is
customer.company = account.account_name(string equality on a company name). That must be declared explicitly and validated against live samples.
What we build in this walkthrough
A federated playbook graph (crm_customer_relationship) that:
-
Declares three entities (
crm_user,crm_account,customer) and two relationships (owns_account,belongs_to_account). - Maps each entity to its physical store via per-source bindings (Postgres YAML + MongoDB YAML).
-
Aligns MongoDB
companywith playbook fieldaccount_nameso the cross-source relationship can resolve Northwind Traders on both sides. -
Lets agents (and users) run a single
query_graphcall instead of hand-written multi-hop scripts.
introspect_source and
sample_source to confirm real table names, column shapes, and the exact string
Northwind Traders in both stores. Guessing schema from memory is how cross-source joins fail.
Before you start
-
Start the stack —
./start-all.shinag-cli/(reasoning-service on:8787, MCP on:3334/mcp). -
Configure sources in
profiles/local.yaml:warehouse_pg(Postgres),mongo_main(MongoDB). -
Set env vars —
AG_SQL_DSN,AG_MONGODB_DSN,AG_MONGODB_DATABASE(e.g.anythinggraph). -
Connect MCP with an admin token (or
AG_AUTH_DISABLED=1for local dev).
| Source id | Adapter | What you will inspect |
|---|---|---|
warehouse_pg |
SQL (Postgres) | Schema public — tables users, accounts |
mongo_main |
MongoDB | Database anythinggraph — collection customers |
Visual flow
Five phases from raw data inspection to a verified cross-source graph query. Exploration (Phases 1–2) uses admin tools that do not require a playbook.
Full sequence (one line)
Phase 1 Check Postgres data
“Use MCP: introspect_source and sample_source, check my Postgres data.”
| Step | Tool | Parameters | Expected result |
|---|---|---|---|
| 1 | list_sources |
— | Find warehouse_pg (SQL adapter) |
| 2 | introspect_source |
source_id: warehouse_pgschema_name: public (optional) |
Schema catalog with tables and foreign keys |
| 3 | sample_source |
source_id: warehouse_pgresource: users |
Sample rows from public.users |
| 4 | sample_source |
resource: accounts |
Sample rows from public.accounts |
Schema discovered (introspect_source)
| Table | Columns | Relationships |
|---|---|---|
| users | user_id (text, PK), full_name (text) |
— |
| accounts | account_name, industry, owner_user_id |
FK owner_user_id → users.user_id |
Sample data (sample_source)
public.users — 2 rows
| user_id | full_name |
|---|---|
| alex.ae | Alex Anderson |
| jordan.ae | Jordan Ellis |
public.accounts — 3 rows
| account_name | industry | owner_user_id |
|---|---|---|
| Northwind Traders | Retail | alex.ae |
| Contoso Ltd | Technology | alex.ae |
| Fabrikam Inc | Manufacturing | jordan.ae |
alex.ae) owns 2 accounts; Jordan owns 1.
owner_user_id is the join key for owns_account.
Phase 2 Check MongoDB data
“Check my data in MongoDB, collection name: customers.”
| Step | Tool | Parameters | Expected result |
|---|---|---|---|
| 5 | introspect_source |
source_id: mongo_main |
Database anythinggraph, collection list includes customers |
| 6 | sample_source |
source_id: mongo_mainresource: customerslimit: 5 |
Raw documents from the collection |
Sample document
| Field | Value |
|---|---|
_id | 6a318065969e7b123841664e |
name | john |
age | 30 |
region | uk |
company | Northwind Traders |
company matches Postgres
accounts.account_name (Northwind Traders). This is the federated join you
will model in the playbook.
Phase 3 Create the playbook
“Create a new playbook called crm_customer_relationship, include the Postgres CRM data and
customer collection in MongoDB. The field company in MongoDB should map with
account_name in Postgres accounts table.”
| Step | Tool | Notes |
|---|---|---|
| 7 | get_adapter_guide(source_id) |
Once per source — binding rules before authoring |
| 8 | propose_playbook |
Validate compact JSON — no disk write |
| 9 | save_playbook |
Write playbooks/crm_customer_relationship.json |
Key design decisions from the session:
-
Three entities:
crm_user,crm_account(Postgres),customer(MongoDB). -
Relationship
owns_account:crm_user→crm_account(same source). -
Relationship
belongs_to_account:customer→crm_account(cross-source — join on shared account name). -
Customer entity uses
account_nameas logical identifier (mapped from MongoDBcompany) so the federated join can match Northwind Traders.
Playbook JSON (saved shape)
{
"id": "crm_customer_relationship",
"name": "CRM Customer Relationship",
"description": "Postgres CRM users and accounts linked to MongoDB customers.",
"entities": {
"crm_user": { "identifier": "user_id", "attributes": ["full_name"] },
"crm_account": { "identifier": "account_name", "attributes": ["industry"] },
"customer": {
"identifier": "account_name",
"attributes": ["name", "age", "region"]
}
},
"relationships": {
"owns_account": { "from": "crm_user", "to": "crm_account" },
"belongs_to_account": { "from": "customer", "to": "crm_account" }
},
"sources": {
"crm_user": "postgres",
"crm_account": "postgres",
"customer": "mongodb"
}
}
Phase 4 Author and save bindings
Create one binding file per source key in the playbook sources map. Each file
only maps entities from that source — warnings about missing entities on the other source are expected.
| Step | Tool | Postgres | MongoDB |
|---|---|---|---|
| 10 | propose_binding |
Validate YAML for each source | |
| 11 | test_binding(execute=true) |
entity=crm_user, by_name=Alex Anderson |
entity=customer, by_name=john |
| 12 | save_binding |
adapter_suffix: postgres |
adapter_suffix: mongodb |
Postgres binding — crm_customer_relationship.postgres.yaml
source_id: warehouse_pg
entities:
crm_user:
from: users
id: user_id
fields: [full_name]
crm_account:
from: accounts
id: account_name
fields: [industry]
relationships:
owns_account:
object: crm_account
link_column: owner_user_id
belongs_to_account:
object: crm_account
link_column: account_name
MongoDB binding — crm_customer_relationship.mongodb.yaml
source_id: mongo_main
entities:
customer:
from: customers
id: company
fields:
name: name
age: age
region: region
account_name: company
relationships:
belongs_to_account:
object: crm_account
link_column: account_name
company is mapped to playbook field
account_name (via binding fields and id: company). That aligns with
Postgres accounts.account_name for belongs_to_account.
customer is unmapped;
MongoDB binding warns that crm_user / crm_account are unmapped. That is normal for
federated playbooks.
Phase 5 Verify with query_graph
“Run query_graph to verify the cross-source link — customer john → Northwind Traders.”
| Step | Tool | Parameters | What to expect |
|---|---|---|---|
| 13 | query_graph |
playbook_id: crm_customer_relationshipentity: customerby_name: john
|
Resolve customer from MongoDB; traverse belongs_to_account |
| 14 | query_graph |
entity: crm_accountby_identifier: Northwind Traders
|
Resolve Postgres account directly |
Cross-source resolution (verified in session)
| Step | Source | Result |
|---|---|---|
Resolve by_name: john |
MongoDB | name=john, company=Northwind Traders, age=30, region=uk |
belongs_to_account |
Postgres | account Northwind Traders, industry=Retail |
SQL executed for the cross-source join:
SELECT industry FROM accounts WHERE account_name = 'Northwind Traders' LIMIT 10
Postgres-only checks
owns_account — Alex (alex.ae) owns 2 accounts: Retail + Technology.
Full relationship chain
End-to-end path from MongoDB customer to Postgres account owner:
The same company name appears in both stores — that is what makes the federated relationship work without a shared surrogate key.
Example prompts (copy-paste)
Explore Postgres only
Use MCP introspect_source and sample_source on warehouse_pg.
Show me tables, columns, and a few sample rows from users and accounts.
Explore MongoDB collection
Check my MongoDB source mongo_main. Introspect the database and
sample_source on collection customers.
Full federated onboarding
Using anythinggraph-thin MCP as admin:
1) Inspect warehouse_pg and mongo_main (introspect + sample)
2) Create playbook crm_customer_relationship linking MongoDB company to Postgres account_name
3) propose_binding, test_binding(execute=true), save_binding for postgres and mongodb
4) query_graph to verify customer john links to Northwind Traders and its industry
Troubleshooting
test_binding returns 422 “missing field playbook_id”
The MCP client must include playbook_id in the JSON body sent to
/playbooks/{id}/test-binding, not only in the URL. Update MCP or retry after pulling the
latest serverCore.ts fix.
save_binding hangs and never returns
A prior runtime bug held a read lock on the profile while reloading the catalog (deadlock). Restart
reasoning-service after updating runtime/src/lib.rs.
Cross-source join returns no rows
-
Confirm MongoDB
companyexactly matches Postgresaccount_name(case and spacing). -
Map
company→ playbook fieldaccount_namein the Mongo binding (id: company,account_name: companyin fields). -
Use
sample_sourceon both sides before authoring — do not guess field values.
Agent lists playbooks when asked “what data is in mongo_main?”
Direct the agent to introspect_source + sample_source — no playbook required for
source exploration. See
MCP authoring guide — Phase 1.