Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/add-dbt-mcp-preset.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@agent-native/core": patch
---

Add a dbt hosted MCP integration preset with organization-scoped manual setup for account-specific endpoints and service-token headers, including parity for source-ejected integration catalogs.
1 change: 1 addition & 0 deletions packages/core/agent-native.eject.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"catalog": "remote-mcp-presets",
"catalogItems": [
"context7",
"dbt",
"sentry",
"notion",
"granola",
Expand Down
41 changes: 39 additions & 2 deletions packages/core/src/client/resources/mcp-integration-catalog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,43 @@ describe("MCP integration catalog", () => {
).toEqual(["sentry"]);
});

it("registers an editable organization-scoped dbt hosted MCP preset", () => {
const dbt = DEFAULT_MCP_INTEGRATIONS.find(
(integration) => integration.id === "dbt",
)!;

for (const query of ["dbt", "MetricFlow", "Semantic Layer", "lineage"]) {
expect(filterMcpIntegrations(query).map((item) => item.id)).toContain(
"dbt",
);
}
expect(dbt).toMatchObject({
url: "https://<dbt-host>/api/ai/v1/mcp/",
authMode: "headers",
connectionMode: "manual",
availability: "provider-setup",
verification: "restricted",
supportsOrganizationScope: true,
docsUrl: "https://docs.getdbt.com/docs/dbt-ai/mcp-quickstart-remote",
setupNoteKey: "mcpIntegrations.catalog.dbt.setupNote",
headerPlaceholder:
"Authorization: Token <DBT_SERVICE_TOKEN>\nx-dbt-prod-environment-id: <DBT_PROD_ENVIRONMENT_ID>",
});
expect(createMcpIntegrationFormDefaults(dbt)).toEqual({
name: "dbt",
url: "https://<dbt-host>/api/ai/v1/mcp/",
description: "Explore governed dbt metadata, lineage, and metrics.",
headersText: "",
});
expect(supportsMcpIntegrationOrganizationScope(dbt)).toBe(true);
expect(shouldOfferMcpIntegrationOrganizationScope(dbt, true, true)).toBe(
true,
);
expect(shouldOfferMcpIntegrationOrganizationScope(dbt, true, false)).toBe(
false,
);
});

it("prefills form values from a selected preset without fabricating headers", () => {
const sentry = DEFAULT_MCP_INTEGRATIONS.find(
(integration) => integration.id === "sentry",
Expand Down Expand Up @@ -177,11 +214,11 @@ describe("MCP integration catalog", () => {
});
expect(getMcpIntegrationApiFallback(figma, "analytics")).toBeNull();
expect(getMcpIntegrationApiFallback(figma, null)).toBeNull();
expect(DEFAULT_MCP_INTEGRATIONS).toHaveLength(35);
expect(DEFAULT_MCP_INTEGRATIONS).toHaveLength(36);
expect(
new Set(DEFAULT_MCP_INTEGRATIONS.map((integration) => integration.id))
.size,
).toBe(35);
).toBe(36);
for (const integration of DEFAULT_MCP_INTEGRATIONS) {
expect(integration.logoUrl).toMatch(
/^data:image\/(?:png|svg\+xml|x-icon|vnd\.microsoft\.icon)(?:;base64,|,)/,
Expand Down
32 changes: 32 additions & 0 deletions packages/core/src/client/resources/mcp-integration-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,37 @@ export const DEFAULT_MCP_INTEGRATIONS: DefaultMcpIntegration[] = [
"Grafana Cloud",
],
},
{
id: "dbt",
name: "dbt",
provider: "dbt",
description: "Explore governed dbt metadata, lineage, and metrics.",
descriptionKey: "mcpIntegrations.catalog.dbt.description",
useCase:
"data models, sources, lineage, model health, Semantic Layer, MetricFlow",
useCaseKey: "mcpIntegrations.catalog.dbt.useCase",
url: "https://<dbt-host>/api/ai/v1/mcp/",
authMode: "headers",
connectionMode: "manual",
availability: "provider-setup",
verification: "restricted",
logoUrl: mcpIntegrationLogo("dbt"),
supportsOrganizationScope: true,
docsUrl: "https://docs.getdbt.com/docs/dbt-ai/mcp-quickstart-remote",
setupNoteKey: "mcpIntegrations.catalog.dbt.setupNote",
headerPlaceholder:
"Authorization: Token <DBT_SERVICE_TOKEN>\nx-dbt-prod-environment-id: <DBT_PROD_ENVIRONMENT_ID>",
aliases: ["MetricFlow", "Semantic Layer", "lineage"],
keywords: [
"dbt",
"MetricFlow",
"Semantic Layer",
"lineage",
"models",
"sources",
"metrics",
],
},
{
id: "gitlab",
name: "GitLab",
Expand Down Expand Up @@ -1027,6 +1058,7 @@ const MCP_LINK_HOSTS: Record<string, string[]> = {
sentry: ["sentry.io", "sentry.dev"],
gong: ["gong.io"],
grafana: ["grafana.com", "grafana.net"],
dbt: ["getdbt.com"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Bot review from Codex\n\n#### 🟡 Support dbt multi-cell endpoint hosts\n\nMCP_LINK_HOSTS.dbt only recognizes *.getdbt.com, but dbt remote MCP also documents multi-cell endpoints such as ACCOUNT_PREFIX.us1.dbt.com. Because findMcpIntegrationForText() uses this allowlist for URL-first matching, a bare multi-cell MCP URL will not map to the dbt preset and will not trigger the connection suggestion. Please include dbt.com in the allowlist and add a regression test for https://acct.us1.dbt.com/api/ai/v1/mcp/.

"builder-cms": ["builder.io"],
notion: ["notion.so", "notion.site"],
granola: ["granola.ai"],
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/client/resources/mcp-integration-logos.ts

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions packages/core/src/eject/catalog-wrappers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { afterEach, describe, expect, it, vi } from "vitest";

import { DEFAULT_MCP_INTEGRATIONS as RUNTIME_DEFAULT_MCP_INTEGRATIONS } from "../client/resources/mcp-integration-catalog.js";
import {
getProviderApiConfig,
listProviderApiCatalog,
Expand All @@ -20,6 +21,26 @@ describe("ejected catalog wrappers", () => {
vi.unstubAllGlobals();
});

it("keeps the dbt preset identical in runtime and ejected catalogs", () => {
const runtimeDbt = RUNTIME_DEFAULT_MCP_INTEGRATIONS.find(
(integration) => integration.id === "dbt",
);
const ejectedDbt = DEFAULT_MCP_INTEGRATIONS.find(
(integration) => integration.id === "dbt",
);

expect(ejectedDbt).toEqual(runtimeDbt);
expect(ejectedDbt).toMatchObject({
authMode: "headers",
connectionMode: "manual",
availability: "provider-setup",
supportsOrganizationScope: true,
setupNoteKey: "mcpIntegrations.catalog.dbt.setupNote",
headerPlaceholder:
"Authorization: Token <DBT_SERVICE_TOKEN>\nx-dbt-prod-environment-id: <DBT_PROD_ENVIRONMENT_ID>",
});
});

it("lets caller MCP overrides win and injects the merged catalog into UI", () => {
const github = DEFAULT_MCP_INTEGRATIONS.find(
(integration) => integration.id === "github",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/eject/remote-mcp-presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const DEFAULT_MCP_INTEGRATIONS: DefaultMcpIntegration[] = [
preset("fullstory"),
preset("notion"),
preset("granola"),
preset("dbt"),
preset("semgrep"),
preset("linear"),
preset("atlassian"),
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/localization/default-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1363,6 +1363,13 @@ const messages = {
setupNote:
"Grafana Cloud MCP is in public preview and requires Grafana Cloud Assistant MCP access. It is hosted Grafana Cloud only; self-hosted Grafana needs the local MCP server.",
},
dbt: {
description: "Explore governed dbt metadata, lineage, and metrics.",
useCase:
"Data models, sources, lineage, model health, Semantic Layer, MetricFlow",
setupNote:
"A workspace owner or admin must copy the account-specific MCP Endpoint URL from dbt Account settings, then add a dbt service token and production environment ID. This organization connection uses one shared dbt service identity, so access and dbt-side auditing are workspace-level rather than per-user.",
},
gitlab: {
description:
"Read and manage GitLab projects, issues, and merge requests.",
Expand Down
4 changes: 2 additions & 2 deletions templates/analytics/.agents/skills/bigquery/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ table; `BIGQUERY_PROJECT_ID` is only the default project.
are deduplicated, tested, and have canonical column names. Raw tables may have
duplicates, schema drift, and inconsistent naming.

**Avoid `dbt_dev.*`** — development schema, excluded globally.
**Avoid `dbt_dev.*` and `dbt_backup.*` unless explicitly requested.** These schemas hold development, testing, or archived data. Do not discover or query them unless the latest end-user request explicitly names the schema and asks to inspect or query it. Never infer permission from agent-generated SQL.

## Always Bound Queries by Date

Expand Down Expand Up @@ -151,7 +151,7 @@ ON signups.user_id = contacts.user_id
```

IDs can be reassigned after deletes/merges. Email alone over-matches shared addresses.
Require both for exact matches; flag email-only or id-only joins as low-confidence caveats.
Require both for exact matches. When the relationship or grain is undocumented and the query proceeds with an inferred, email-only, ID-only, fuzzy, or row-multiplying join, state that the join was inferred and the result is lower confidence. Ask for clarification instead when the ambiguity could materially change the answer. Documented joins need no generic hedge.

## SQL Patterns

Expand Down
13 changes: 12 additions & 1 deletion templates/analytics/.agents/skills/data-querying/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The analytics app connects to multiple data sources. This skill covers general p

0. **Orient catalog-first** — before querying, consult what already exists: the injected `<data-dictionary>` and data-source status tell you which sources are configured and which table/columns/join paths to use. Use them to pick the one source that owns the fact instead of fanning out blind queries.
1. **Route named account health deliberately** — for a customer/org health, QBR, renewal, contract-utilization, risk, or adoption request, read `account-health` before writing SQL. It adds identity-lock and metric-definition checks that an ordinary lookup does not need.
2. **Read the relevant provider skill first** — check `.agents/skills/<provider>/SKILL.md` for table names, column mappings, auth, and gotchas. For BigQuery, read `.agents/skills/bigquery/SKILL.md` and use `search-bigquery-schema` before guessing table or column names.
2. **Read the relevant source skill first** — for dbt-backed models or metrics, read `.agents/skills/dbt/SKILL.md` before deciding semantics, lineage, grain, or SQL. Otherwise check `.agents/skills/<provider>/SKILL.md` for source-specific mappings and gotchas. For BigQuery, read `.agents/skills/bigquery/SKILL.md` and use `search-bigquery-schema` before guessing table or column names.
3. **Clarify if ambiguous** — if the metric definition, date range, or grain is unclear and a wrong guess would change the numbers, use the `ask-question` clarifying tool (multiple-choice) before querying. Ask at most once per turn; skip it when the dictionary or the user already answered.
4. **Use existing actions or connected provider MCP tools** — call the provider action/tool with structured arguments, then filter or aggregate the returned records in your answer
5. **Write ad-hoc scripts** — if no existing script covers the question, create one in `actions/`
Expand Down Expand Up @@ -196,6 +196,17 @@ Keep each entry short and actionable: what to do, what not to do, and why.
This is the learnings flywheel — discoveries persist across sessions and improve
future analyses.

## Conditional Caveats

Keep routine exploratory answers direct. Add a concise caveat only when the evidence or requested use triggers one:

- **Known stale data:** warn only when authoritative source metadata explicitly reports data beyond its expected refresh window. Include the observed refresh timestamp or window when available. Query-cache age and availability of freshness tools do not establish freshness.
- **Unknown freshness:** do not claim that data is current. Mention unverified freshness only when it materially affects the answer.
- **High-stakes distribution:** when the user explicitly says the output is for a client, board, investor, QBR, or executive distribution, recommend verifying figures against the source of record before distribution. Do not add this warning to routine internal exploration.
- **Complex inferred joins:** if an undocumented relationship or grain requires an inferred, email-only, ID-only, fuzzy, or row-multiplying join, label the join as inferred and the result as lower confidence. If the ambiguity could materially change the answer, clarify instead of merely hedging. Documented joins need no generic warning.

Combine applicable caveats into one short note rather than stacking repetitive warnings.

## Important Notes

- Always query real data — never guess or approximate. Only present numbers you actually retrieved; do not claim a figure you did not query.
Expand Down
44 changes: 44 additions & 0 deletions templates/analytics/.agents/skills/dbt/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
name: dbt
description: >-
Governed dbt model, lineage, freshness, and Semantic Layer routing for analytics.
Use when a question depends on dbt models, sources, metrics, or warehouse SQL.
scope: runtime
---

# dbt

dbt is authoritative for dbt model semantics, lineage, freshness, and governed metrics. Connected dbt MCP tools are dynamic: find them with `tool-search` when needed rather than assuming they are on the initial tool surface.

## Decision Order

1. Reuse a current certified query or dashboard when it already answers the request with the right definition, filters, grain, and time range.
2. Before guessing a dbt table, grain, join, meaning, freshness, or lineage, use dbt Discovery. Use the exact official tools that fit the question: `get_node_details` for model semantics and relation metadata, `get_lineage` for dependencies, `get_model_health` or `get_model_performance` for health and performance, and `get_all_sources` for declared sources and freshness context.
3. Before writing SQL, verify the physical BigQuery relation and columns with `search-bigquery-schema`. dbt metadata describes governed meaning; warehouse schema proves what can be queried now.
4. Run direct SQL with the existing `bigquery` action. Never use dbt `execute_sql` or `text_to_sql`.
5. If dbt metadata does not establish a grain or relationship, keep it unknown. Do not infer a join or silently turn uncertainty into a metric.

## Phase 2: MetricFlow / Semantic Layer

When Semantic Layer tools are available:

1. Call `list_metrics` to find the governed metric.
2. Inspect `get_dimensions` and `get_entities`; use `get_dimension_values` when a requested filter value needs validation.
3. Call `query_metrics` with the requested dimensions, filters, range, and time grain.
4. Call `get_metrics_compiled_sql` only when the user requests provenance or query validation. It is not a default preflight.

Report the metric name, dimensions, filters, date range and time grain, plus environment or freshness caveats. dbt calls use the shared workspace dbt identity, not a personal warehouse identity.

## Restricted Schemas

`dbt_dev` and `dbt_backup` are testing or archival schemas. Do not discover or query either schema unless the latest end-user request explicitly names it and asks to inspect or query it. Never infer permission from SQL that the agent generated.

## Freshness

A visible dbt health or freshness capability does not mean the data is fresh. Warn that data is stale only when returned dbt source/model metadata explicitly says it is beyond the expected refresh window, and include the observed timestamp or window when available. If freshness is unknown, do not claim freshness; mention that it could not be verified only when freshness materially affects the answer.

If MetricFlow cannot express the request, fall back to dbt metadata plus the `bigquery` action and label the result as ad hoc SQL. Missing Semantic Layer tools are a capability gap, not evidence that no metrics exist. Use Discovery metadata and BigQuery only when they can answer without inventing semantic definitions.

## Failure Semantics

A dbt connection or tool-list error means capability status is unreadable, not that dbt is disconnected or that no dbt models or metrics exist. Preserve the actual error, try dynamic tool discovery when appropriate, and do not replace a failed dbt lookup with guessed semantics.
44 changes: 16 additions & 28 deletions templates/analytics/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ analyses remain readable.

Read the relevant skill before deeper work:

- `data-querying` for source inspection, SQL generation, result handling, and
`/chart` embeds; `bigquery`, `hubspot`, `gong`, `prometheus` for provider
specifics.
- `data-querying` for source inspection, SQL, result handling, and `/chart` embeds.
- `dbt` for governed model semantics, lineage, freshness, MetricFlow, and dbt-backed SQL.
Read provider skills such as `bigquery`, `hubspot`, `gong`, and `prometheus`
for provider specifics.
- `account-health` for named customer health, QBR, renewal, contract usage,
identity, and product adoption.
- `cross-source-analysis` for questions spanning sources (identity stitching,
Expand All @@ -24,6 +25,9 @@ Read the relevant skill before deeper work:
- `creative-context` for governed contexts and immutable dashboard revisions.
- `admin-surfaces` for the `/agents` fleet flags, usage audit, and connected DBs.

Before building common workspace or agent UI, read `agent-native-toolkit` and use
`customizing-agent-native` to follow the customization ladder.

## How To Answer A Data Question

1. **Search existing work first.** For a metric question, call
Expand All @@ -47,23 +51,18 @@ Read the relevant skill before deeper work:

## Core Rules

- A sibling app sends natural-language or shaped input over A2A, never SQL; this
app owns schema, source selection, and tools. Prefer natural-language
delegation; shaped reads are stable contracts.
- Analytics owns first-party product usage, app/template events, agent-native
signups, conversions, and other curated product metrics. Answer sibling-app
delegations with the built-in source and query catalog; sibling agents should
send a natural-language question, never SQL.
- A sibling app delegates natural-language or shaped input over A2A, never SQL;
this app owns schema, source selection, and tools.
- Delegated requests: choose a safe default; label partial.
- Data integrity first. Never invent numbers, dimensions, filters, or source
semantics; present only retrieved values with source, window, filters,
row-count/sample-size, join method, and caveats.
- dbt is authoritative for dbt model semantics and lineage. When connected,
discover its dynamic tools with `tool-search`; read `dbt` before using them.
- Use actions for data and sharing; don't bypass ownable-resource access checks
with raw SQL.
- Provider actions are bounded shortcuts, not limits. For broad or
absence-sensitive Gong work, stage raw API data and use `query-staged-dataset`
or a Data Program; see `provider-api`, `data-programs`, and `gong` for secure
provider and hosted-endpoint boundaries.
absence-sensitive work, read `provider-api` and `data-programs`.
- Create dashboards, panels, or saved artifacts only when explicitly asked;
suggest and wait otherwise. Scope them to the question, avoid decorative
metrics, and never modify existing dashboards without a directive.
Expand All @@ -80,12 +79,8 @@ Read the relevant skill before deeper work:
- Never hardcode API keys, tokens, webhook URLs, secrets, private Builder data,
or customer data. Use secrets/OAuth and obvious placeholders in examples.
- For external integrations, inspect the workspace/provider connection catalog first; reuse its scoped resolver.
- External MCP callers should prefer a cataloged direct action for a bounded
read or explicitly requested mutation. Use `ask_app` for interpretation,
source selection, multi-step analysis, or when the requested capability is
not exposed directly. Writes remain limited to intentionally allowlisted
actions.
- Reports/alerts use SQL actions; reports cap at five recipients.
- External MCP callers use cataloged actions for bounded reads and `ask_app` for
interpretation or multi-step analysis. Writes require intentional allowlisting.

## Actions

Expand All @@ -95,19 +90,12 @@ Read the relevant skill before deeper work:
| `search-dashboard-references` | Find dashboards to replicate. |
| `get-sql-dashboard` | Read the dashboard and exact panel SQL. |
| `certify-dashboard` | Admin-only approval of its current version. |
| DB | `list-db-admin-connections`, `list-connected-database-tables`, `db-admin-federated-read`: registry, schema, bounded joins. |
| DB | Registry, schema, and bounded federated reads. |

## Application State

- `navigation` exposes the current dashboard, analysis, source, chart, and
selection. `navigate` moves the user between supported Analytics surfaces,
`"sessions"`, `"monitoring"`, and `"agents"`. Use `view-screen` when the
active context is unclear.
selection. Use `view-screen` when active context is unclear.
- Clicking a panel stages it as a chat context chip and writes `selected-object`
with `type="dashboard-panel"`. Read `dashboard-management` for the
`/dashboards` overview and folder actions.

## Shared UI

Before building common workspace or agent UI, read `agent-native-toolkit`; read
`customizing-agent-native` before adapting shared UI.
Loading
Loading