Context Layer
The Context Layer is a component of the Renta MCP Server designed for data interrogation. It exposes Google BigQuery data models as structured dimensions and measures, enabling AI assistants to select fields programmatically rather than synthesizing raw SQL.
Renta compiles these selections into BigQuery SQL, executes the queries within your project using the connection's service account, and returns the resulting rows alongside comprehensive scan and cost metadata.
Within an MCP client, the Context Layer is exposed as a single tool named context_layer comprising four actions. The following sections detail the functionality and expected responses for each action.
Enterprise-grade architecture
The Context Layer is engineered for enterprise security, cost control, and performance, serving as a secure gateway between your data warehouse and AI assistants.
- Security by Design and data contracts.
The architecture enforces strict data contracts. AI assistants only gain access to data you explicitly permit. The interface is strictly read-only: synthesizing optimizedSELECTqueries without any capability to delete, modify, or expose unauthorized sensitive data. - Cost control and optimization.
For architectures like BigQuery, the Context Layer imposes hard limits on queries to prevent AI from triggering unexpected billing overruns. Queries are highly optimized, ensuring cost-efficient operations even when processing billions of rows. - Context efficiency.
By isolating raw data processing within the warehouse and returning only aggregated results, Renta prevents the pollution of the AI's context window. This dramatically increases the AI's reasoning capabilities and performance. - Synergy with data engineering.
Renta does not replace data engineers. Rather, it amplifies their work. Engineers continue to build efficient, pre-calculated data marts, while Renta acts as the managed gateway that controls security, performance, and accessibility for the AI.
How it works
Agents operate without prior knowledge of your warehouse topology and incrementally build context through a four-step process.
- List models.
The agent interrogates the workspace for exposed models and selects one via its identifier. - Describe the model.
Renta returns all dimensions and measures associated with the specified model, including types and descriptions. Agents are restricted to referencing only the fields enumerated in this response. - Sample a field.
Renta retrieves actual values, ranges, or statistical distributions for a designated field, ensuring agents construct filters based on empirical data rather than assumptions. - Run the query.
Renta compiles the selected fields, filters, and temporal boundaries into SQL, executes the query, and returns the resulting rows, column metadata, and execution statistics.
All requests are strictly scoped to the workspace associated with the bearer token. Renta validates membership on every invocation. Consequently, revoked access takes effect immediately.
Before you begin
The Context Layer interfaces with pre-existing models. Ensure the following prerequisites are met prior to utilization.
- A data model configured with an AI scope.
Models are managed within Tools > Data models. For creation instructions, refer to the Google BigQuery reverse ETL source documentation. - A connected MCP server.
The authorization process completed during server provisioning inherently includes Context Layer access. No additional scopes are required.
Only models explicitly scoped to AI agents only or AI agents & Reverse ETL are exposed to the Context Layer. Models scoped to Reverse ETL only remain inaccessible to agents.
What an agent sees in a model
Agents consume the native model configuration. There is no requirement to maintain discrete AI-specific descriptions.
Model columns are translated into dimensions categorized by type (string, number, boolean, or time). Structured columns, such as arrays and records, are cast as strings. The model's name and description provide the foundational context utilized by agents to interpret row semantics.
Measures are derived from the per-column configurations within the model's Measures tab. Enabling a toggle provisions a corresponding measure named according to the column and its applied aggregation.

Numeric columns support all eight aggregation functions. Non-numeric types are restricted to COUNT and DISTINCT, as arithmetic operations (sum, average) are inapplicable to strings or temporal data.
| Toggle | Measure name | What it returns |
|---|---|---|
| SUM | revenue_sum | Sum of the column. Numeric columns only. |
| AVG | revenue_avg | Average of the column. Numeric columns only. |
| MIN | revenue_min | Smallest value. Numeric columns only. |
| MAX | revenue_max | Largest value. Numeric columns only. |
| MEDIAN | revenue_median | Approximate median. Numeric columns only. |
| P95 | revenue_p95 | Approximate 95th percentile. Numeric columns only. |
| COUNT | order_id_count | Number of non-null values in the column. Any column type. |
| DISTINCT | order_id_distinct | Number of distinct values in the column. Any column type. |
Two default measures are implicitly available: count, returning the total number of rows, and unique_count, returning the distinct values of the designated unique key (if configured).
A column with no toggles is still queryable as a dimension. An agent can group by it, but it has nothing to aggregate on that column.
Assign aggregations to several columns at once
While individual toggle selection is suitable for narrow schemas, wide models benefit from bulk operations. Select the target columns first to apply aggregations globally with a single action.

- Select the columns.
Tick the checkbox in the leftmost column of each row you want. The checkbox in the header row takes everything currently listed, so narrow the list first with the search field or the Dimensions and Metrics filters. - Apply the aggregations.
The page header is replaced by a bar that counts the selection and carries the eight aggregations as chips. Clicking a chip turns that aggregation on for every selected column, and clicking it again turns it off. - Read the state from the chip.
A filled chip means the aggregation is on for all selected columns, a dashed chip means it is on for some of them, and an outlined chip means it is off everywhere. A chip that no selected column supports is greyed out, so SUM cannot land on a text column even by accident. - Start over if needed.
Clear strips every aggregation from the selected columns. Show Selected hides the rest of the table while you work.
The measure list is a contract
An agent can only compute what the model exposes. It has no SQL of its own, and an aggregation you left off does not exist for it, so the measure list is the contract between your data and the agent.
That is what makes the per-column choice worth the minute it takes. A column type says what is arithmetically possible, and you say what is meaningful.
| Column | Enable | Why |
|---|---|---|
| Order total | SUM, AVG, MEDIAN, P95 | Revenue is additive, and the average and the tail both answer real questions. |
| Average order value | AVG | The column already holds an average. Summing averages produces a number that means nothing, and the agent must never offer it. |
| Discount rate, conversion rate, margin | AVG, MEDIAN | A share is not additive. A sum of 0.10 and 0.25 is not a 0.35 discount. |
| Account balance, stock level | MIN, MAX, AVG | A level at a point in time. Summing snapshots double counts the same money or the same goods. |
| Order identifier | COUNT, DISTINCT | Counting orders is the question. Nothing else applies to an identifier. |
The screenshot above shows the rule in place: discount_rate is exposed as AVG and MEDIAN, and SUM stays off, so no agent can ever report a summed discount rate as a number.
A short measure list is also a cheaper one. Every measure travels in the agent's context on every discovery call, so exposing the aggregations that answer real questions beats ticking all eight on every numeric column.
Metadata the agent reads
An agent never sees your table. It sees the name of the model, its description, and a list of field names with their types, and every decision it makes comes from those. Metadata is filled in on the Metadata tab of the model.

- Model name.
What the agent and your teammates call the model. Lowercase with underscores. - Description.
What the model holds and how it should be used. This is the field that does the heavy lifting, and the next section covers what to write in it. - Column descriptions.
Stored with the model and shown in the interface. Auto-generate with AI fills them from the query and the sample data.
The panel on the right counts the tokens the model takes in an agent's context. That context is shared with everything else in the conversation, so a description earns its length by what it decides.
Through the Context Layer, a column arrives as a name and a type. Per-column titles, descriptions, and enum values reach an agent only as semantic overrides, written through the Context Layer API. Until then, anything a column name does not convey belongs in the model description.
Write the description as instructions for the agent
The description is free text, and it reaches the agent twice: in the list of models, where it is the only thing to choose on, and again in the full model. Anything you write there lands in the agent's context, so write it as instructions rather than as a label.
Worth stating in the description:
- What one row is.
Granularity decides whether a number is a total or a duplicate. - The period covered and the refresh time.
This is what keeps an agent from reporting today as a drop when today is not loaded yet. - Units and currency.
An amount without a currency gets summed anyway, just wrongly. - The questions this model answers.
Phrase them the way people ask them, so the agent matches a request to the model. - When to use a different model.
Name the other model. This is what stops an agent from answering a refunds question from a revenue table. - What is filtered out.
Test orders, cancelled rows, internal accounts.
One row per completed order, keyed by order_id. Covers 2024-01-01 to yesterday
and refreshes daily at 06:00 UTC, so today is always incomplete. Amounts are in
USD, net of VAT.
Use this model for revenue, average order value, and order counts by country,
channel, or campaign. For refunds and chargebacks use the payments model
instead: refunded orders stay in this table with their original amount.
Test orders are already excluded.A description that repeats the name gives the agent nothing to work with. A model called orders described as "orders table" is picked on the strength of its name alone, and what is actually in it is discovered only after a query has run.
Actions
The four actions of the context_layer tool are meant to be used in order. The first two read no data, and they are what keeps the final query from referencing a field that does not exist.
| Action | What it does | Reads the warehouse |
|---|---|---|
list_models | Returns the models of the workspace with their names and descriptions. | No |
describe_model | Returns the dimensions and measures of one model. | No |
sample_field | Returns real values, ranges, or statistics for one field. | Yes |
run_query | Returns aggregated rows for a selection of fields. | Yes |
List models
This is the entry point of every session. It takes no parameters, and an optional source_id narrows the result to the models of a single BigQuery connection.
The response is short: an identifier, a name, and a description per model. That is everything an agent has when it picks between models, which is why the description carries the instructions.
Describe a model
This action takes model_id and returns the full model: the table or query it reads, its dimensions with types and descriptions, and its measures with types and formatting hints.
{
"action": "describe_model",
"model_id": "e2f1a3c4-5b6d-4e7f-8a90-1b2c3d4e5f60"
}Field names are used verbatim from here on, and a field that was not in this response cannot be referenced. This is also where an agent learns which measures are calculated, because those behave differently in the next step.
Sample a field
This action takes model_id and field_name and reads the warehouse for that one field. It keeps filters tied to the data, so an agent does not filter on United States when the column holds US.
{
"action": "sample_field",
"model_id": "e2f1a3c4-5b6d-4e7f-8a90-1b2c3d4e5f60",
"field_name": "country",
"values_limit": 10
}What comes back depends on the type of the field.
| Field | Response |
|---|---|
| String dimension | The most frequent values with their row counts, the approximate number of distinct values, and a flag telling whether the list was cut off. |
| Boolean dimension | Row counts for true, false, and null. |
| Numeric dimension | Minimum, maximum, and the 10th, 50th, and 90th percentiles. |
| Time dimension | The earliest and the latest value, and the number of distinct days between them. |
| SUM, AVG, MIN, MAX, MEDIAN, and P95 measures | The same statistics as a numeric dimension, plus the aggregate over the whole model. |
| COUNT measures | The row count of the model. |
| DISTINCT measures | The approximate number of distinct values. |
| Calculated measures | An error with status 422. There is no column to sample, and the measure stays usable in a query. |
Two optional parameters apply to string dimensions. values_limit caps the number of returned values at 500 and defaults to 50. search filters by a case-insensitive prefix of up to 100 characters, which is the practical way to look up a value in a column with many distinct values, such as an order identifier.
For a model built on a table, the response also says whether the field is the partition column or part of the clustering key. Filtering on the partition column is what keeps a large model cheap, so an agent samples the date field before it filters on it.
Run a query
This is the only action that returns data rows. It takes the model, the fields to select, and the conditions to apply.
{
"action": "run_query",
"model_id": "e2f1a3c4-5b6d-4e7f-8a90-1b2c3d4e5f60",
"dimensions": ["e2f1a3c4-5b6d-4e7f-8a90-1b2c3d4e5f60.campaign"],
"measures": ["e2f1a3c4-5b6d-4e7f-8a90-1b2c3d4e5f60.leads_sum"],
"time_dimensions": [
{
"dimension": "e2f1a3c4-5b6d-4e7f-8a90-1b2c3d4e5f60.date",
"date_range": "last_7_days"
}
],
"order": [
{
"member": "e2f1a3c4-5b6d-4e7f-8a90-1b2c3d4e5f60.leads_sum",
"direction": "desc"
}
],
"limit": 20
}At least one of dimensions, measures, or time_dimensions has to be present. The result is always aggregated, so a dimension selected without a measure returns the distinct values of that dimension.
Query reference
The rules below are checked when the query is compiled. A request that breaks one of them comes back with status 400 and a message naming the problem, so an agent can fix the request without reading any data.
Field references
Every field is written as the model identifier, a dot, and the field name. That form applies in dimensions, measures, time_dimensions, filters, and order alike, and a bare field name is rejected.
Measure names always carry their aggregation, so a column called leads is queried as leads_sum. A raw column is never aggregated implicitly.
Filters
A filter is either one condition or a group that combines conditions with and or or. Groups nest, which covers conditions such as one country above one amount or another country above another amount.
| Operator | Values |
|---|---|
equals, not_equals | Exactly one |
gt, gte, lt, lte | Exactly one |
contains, not_contains, starts_with, ends_with | Exactly one |
in, not_in | One or more |
between | Exactly two, both ends included |
is_null, is_not_null | None |
A filter on a dimension restricts the rows that are read. A filter on a measure restricts the aggregated result, which is how a question such as campaigns above a hundred leads gets answered. The two cannot sit in the same or group, because they apply at different stages of the query. Combine them with a top level and, or ask twice.
Time dimensions
A time dimension selects a date column and, optionally, the granularity to group it by and the period to keep. Granularity accepts hour, day, week, month, quarter, and year.
The period is either two ISO dates, with both ends included, or one of the relative tokens below.
| Group | Tokens |
|---|---|
| Days | today, yesterday |
| Current period | this_week, this_month, this_quarter, this_year |
| Previous period | last_week, last_month, last_quarter, last_year |
| Rolling windows | last_7_days, last_14_days, last_30_days, last_60_days, last_90_days, last_365_days |
Sorting and paging
Sorting refers to a field the query selects. Sorting by something that was not selected is rejected instead of quietly ignored.
limit accepts 1 to 10,000 rows and defaults to 1,000. offset pages through a longer result, which matters because the limit truncates the answer without saying that more rows exist.
Response
A successful query returns the rows, the metadata of the columns, and the statistics of the query.
Column names combine the model identifier and the field name with a double underscore, and a time dimension with a granularity appends that granularity. A monthly trend of leads_sum therefore arrives in a column ending in __date_month.
The statistics report bytes processed, bytes billed, the estimated cost, whether the cache answered, the duration, the number of rows, and the BigQuery job identifier. An expensive question shows its price right after it ran.
Controlling query cost
Sampling and queries both run in your BigQuery project and land on your bill. Each of them is capped on scan size and on time.
| Action | Bytes scanned | Time limit |
|---|---|---|
sample_field | 5 GiB | 30 seconds |
run_query | 10 GiB | 60 seconds |
A query that would scan past its cap is refused before it runs, and the response says so. That is the point to add a filter, rather than after the invoice arrives.
The cheapest filter is the one on the partition column of the table, because BigQuery then reads only the matching partitions. Sampling a date field reports whether it is that column.
Quotas and limits
The values below are the defaults. Workspace limits configured for your account take precedence over the request limits.
| Limit | Value |
|---|---|
| Requests | 120 per minute and 2,000 per hour |
| Rows per query | 10,000, with 1,000 returned by default |
| Sampled values per field | 500, with 50 returned by default |
| Search prefix length | 100 characters |
| Models per query | One |
A query covers a single model. A question that spans two models is answered by querying each of them and combining the answers, or by building a model whose query already joins the tables.
Errors
Failures come back as a status code with a message rather than as an empty result.
| Code | Meaning |
|---|---|
| 400 | The query broke a compile rule, or it referenced a field the model does not have. The message names the offending part. |
| 401 | The token is missing, invalid, or expired. Authenticate the MCP server again. |
| 403 | The workspace membership behind the token was revoked. |
| 404 | The model or the field does not exist in this workspace. |
| 422 | The field cannot be sampled. Calculated measures have no underlying column. |
| 429 | The rate limit was exceeded. |
| 504 | The request did not complete in time. |
Access from the REST API
The same four actions are available over HTTP for clients that hold an API key, at the base URL of the Renta REST API.
| Endpoint | Action |
|---|---|
GET /v1/context_layer/models | List the models of the workspace |
GET /v1/context_layer/models/{model_id} | Describe one model |
GET /v1/context_layer/models/{model_id}/fields/{field_name}/values | Sample a field |
POST /v1/context_layer/query | Run a query |
An API key reaches these endpoints with the Read scope of the Context Layer category. The Create, Update, and Delete scopes of the same category cover the semantic overrides of a model, which are edited through the API and have no screen in the interface.
curl -X GET "https://api.eu.renta.im/v1/context_layer/models" \
-H "Authorization: Bearer YOUR_API_TOKEN"What's next
Ready to get started?
Build your data pipeline today or get a personalized demo. Start free!
Need help?
Get expert support to ensure your project succeeds. We're here to help!
Feature requests?
Help shape our product! Share your ideas for new features and integrations.