Connect Frags to your data
The hello-world plan only reasoned over its own prompts. Real plans 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 plan guide — you should be comfortable with sessions, prompts, and schemas.
- Your tools configured in
tools.json— see Configuration files. A plan can only reach connections that exist there.
How tools work
Every tool a plan uses must be declared at the root with require, using its type and name:
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.
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:
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:
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 plan 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:
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 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:
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 plans in the plan marketplace, then run yours over HTTP with the API reference.
Pull live data into a plan — run a web search, call an MCP server (Slack), and query a database (Postgres) with PreCalls, transformers, and context routing.