Connect Frags to your data | Diaphora Docs | Diaphora
Connect Frags to your data
The hello-world blueprint only reasoned over its own prompts. Real blueprints pull in live data — answers from a web search, messages from an MCP server, rows from a database, results from an API. In Frags these are all tools, and a session can call them either deterministically or by letting the model decide.
This guide covers both approaches, then walks through three tools: a built-in web search, a real MCP example (Slack), and a database example (Postgres).
Prerequisites
- The hello-world blueprint guide — you should be comfortable with sessions, prompts, and schemas.
- Your tools configured in
tools.json— see Setting up tools. A blueprint can only reach connections that exist there.
How tools work
Every tool a blueprint uses must be declared at the root with require, using its type and name:
require mcp Slack
require collection postgres
require apicp stripe
The three tool types are mcp (an MCP server), collection (built-in connectors like postgres, http, fs), and apicp (an OpenAPI-mapped API). The names match the connections in your tools.json.
The one exception is the built-in web search tool: it needs no require and no tools.json entry — you declare it only inside a session as use search, with no name after it. See Search the web below.
There are then two ways to actually call a tool:
- PreCall —
call("function") { … }invokes a specific tool function deterministically, before the session's prompts run. Use it when you know the exact function and arguments. - prePrompt — a
+line withuse <type> <name>in the session lets the LLM decide how to call the tool. Use it when the call depends on reasoning.
require mcp Slack
session("summary") {
use mcp Slack
+ Fetch the last 7 days of messages from the #sales channel and read them.
- Summarize the key wins and blockers.
schema { summary: string }
}
The examples below use PreCalls, since they call known functions with known arguments.
Search the web
The lowest-friction live-data tool is web search. It's built in, so — unlike every other tool — it needs no root require and no tools.json entry. Just add use search to a session and the model runs searches as it reasons:
parameter("query", type=string, title="Search Query")
session("search_web") {
use search
+ Search the internet for information about: {{ .params.query }}
- Provide a comprehensive answer based on the search results.
}
What's going on here:
use searchis the one tool declared only at the session level — no rootrequire, and no name after it. (Every other tool type needs both arequireand a name.)- The
+prePrompt is where search actually runs: the model issues queries and reads the results before the main prompt. - The
-prompt turns those results into the session's answer.
This session declares no schema, so its output is the model's raw text answer, landing in context.search_web as a plain string:
{
"search_web": "Spain won the 2026 FIFA World Cup, held across Canada, Mexico, and the United States, defeating Argentina 1–0 in the final. Ferran Torres scored the winning goal in the 106th minute of extra time at New York/New Jersey Stadium on July 19, 2026."
}
Add a schema when you need to pull specific fields out of the results instead of a prose answer — see Sessions without a schema.
Call an MCP server
Here's a trimmed version of a CEO-briefing blueprint that reads a Slack channel. It declares the Slack MCP, fetches a channel's history with a PreCall, cleans the raw payload with a transformer, and hands the result to a session:
parameter("lookBackDays", type=string, default="7", title="Look back days")
require mcp Slack
# Reshape the raw tool output down to just the messages array
transformer("clean_history") {
onFunctionOutput = "conversations_history"
jmesPath = "structuredContent.messages || messages"
}
# Deterministically fetch the channel history before the session runs.
# The result is routed into vars.salesRaw.
call("conversations_history") -> salesRaw {
channel = "C091MES33UK"
limit = 200
}
session("sales-pulse", target="salesSignals") {
- You are a business analyst preparing a CEO briefing.
Here is the recent Slack history for the sales channel:
```json
{{ json .vars.salesRaw }}
```
Summarize the wins, blockers, and the team's overall sentiment for the week.
schema {
wins: string[] # Deals closed, milestones landed
blockers: string[] # Active blockers or slowdowns raised in the channel
sentiment: strong|steady|concerning|unclear # Team momentum this week
headline: string # The single most important thing this week
}
}
What each piece does:
require mcp Slackdeclares the Slack MCP server (configured intools.json).transformer(...)runs whenever the named function (conversations_history) returns, reshaping its output with a JMESPath expression before the LLM ever sees it — great for trimming noisy API payloads. UsejmesPathwhen you know the exact response shape.call("conversations_history") -> salesRaw { … }is a PreCall: it runs before the prompts, calls the Slack tool with those arguments, and stores the cleaned result invars.salesRaw.- The session's prompt reads that data with
{{ json .vars.salesRaw }}and maps it into the schema.
Note the schema field sentiment: strong|steady|concerning|unclear - an inline enum that constrains the model to one of those exact values.
Query a database
A database connection is a collection. A Postgres collection named X exposes a query function named X_postgres_query that takes a query string. Declare the collection, run your SQL in a PreCall, and route the rows into context:
parameter("listingId", type=string) # The listing to analyze
require collection listings
# Run a query and route the rows into context.listings_offers
call("listings_postgres_query") -> context:offers {
query = "SELECT id, buyer_email, offer_price, status, lender_name
FROM listing_offers
WHERE listing_id = '{{ .params.listingId }}'
AND status NOT IN ('withdrawn', 'draft')
ORDER BY created_at DESC"
}
session("rank-offers") {
context "Offers retrieved from the database:\n{{ json .context.offers }}"
- You are a real estate analyst. Rank the offers from strongest to weakest
by close certainty, and explain each ranking in one sentence.
schema {
offer_id: string
rank: int # 1 = strongest offer
note: string # Why this offer ranks where it does
}[]
}
The new ideas here:
require collection listingsdeclares a Postgres connection defined intools.json; its query function islistings_postgres_query.-> context:offersroutes the PreCall result intocontext.offers. (Compare the Slack example's-> salesRaw, which routes intovars.salesRaw—-> namewrites tovars,-> context:namewrites tocontext.)context "…"injects those rows into the session's LLM context so the prompt can reason over them.- The
querystring interpolates the parameter with{{ .params.listingId }}. Because parameters are interpolated as text, validate or constrain any untrusted input before it reaches a query.
Parameters are substituted into the SQL as template strings, so treat query construction like any other place you build SQL from input — keep untrusted values constrained (e.g. validate that an ID is a UUID) rather than passing them through raw.
Next steps
- Revisit
tools.jsonin Running Frags Locally to wire up your own MCP servers and collections. - Read the Frags reference on Sessions and Flow Control for
after,expect, anditerate. - Browse full multi-session blueprints in the blueprint marketplace, then run yours over HTTP with the API reference.
Pull live data into a blueprint — run a web search, call an MCP server (Slack), and query a database (Postgres) with PreCalls, transformers, and context routing.