# Krusgraph: full documentation Same content as the pages linked from https://krusgraph.com/llms.txt, in one file. --- # Krusgraph docs Connect Krusgraph to Claude to inspect a Fabric schema, review the suggested relationships, check them against your data and build from the plan. Start with the connection guide, or try the [guided demo](/demo) and the [demo graph](/explore) without an account. The tool reference explains what each tool reads, returns or changes, and how far it has been tested. ## Get started New to a Fabric lakehouse? Start by understanding how its tables fit together: connect the hosted server to Claude, then ask a first question. The [guided demo](/demo) follows one order to its delivery address in four steps, and the [demo graph](/explore) shows every table and relationship of the same sample; neither needs an account. ### Connect 1. **Your Entra admin grants consent once** for your organization: [grant admin consent](https://login.microsoftonline.com/organizations/adminconsent?client_id=6b92ed27-66e7-4767-8e72-eeb4b66da277). It allows the connector to call Fabric and SQL as each signed-in user — nothing more. 2. **In Claude:** Settings → Connectors → Add custom connector, with the URL `https://krusgraph.com/mcp`. Sign in with your Microsoft work account. The hosted service supports Claude, and that is the client it has been tested with. Claude Code and Cursor sign in through a local redirect that is not registered on the Entra app yet, so they can't sign in to krusgraph.com today. Other MCP clients should work when you host Krusgraph yourself (not tested yet). 3. **In Fabric:** you need access to the workspace you want to use, and a Fabric capacity (a trial is fine). Every call runs with your own Fabric permissions. The step-by-step version is on [/start](/start). The server is published in the official [MCP Registry](https://registry.modelcontextprotocol.io/v0.1/servers?search=com.krusgraph%2Fkrusgraph) as `com.krusgraph/krusgraph` (verified 26 October 2026). Prefer your own machine and a SQL Server? The same engine runs locally over ODBC: [/local](/local). ### What you can ask One example per tool, in the order a first session usually goes (list, plan, look at rows if needed, check, create, describe, query): - `list_workspaces` — *"Which Fabric workspaces can I see?"* - `list_sources` — *"List the data sources in the demo workspace."* - `list_tables` — *"Which tables are in demo_lh, schema shop?"* - `plan_backend` — *"Plan a backend over demo_lh, schema shop."* — nothing is deployed; you get keys, relationships, the rule for each, ambiguous columns, suggested composite keys and an ER diagram. *"Treat order_id plus line_no as the key of order_items"* passes `keys`. - `sample_rows` — *"Show me a few rows of orders."* — when a column's meaning is unclear before deciding on keys. - `validate_plan` — *"Check the plan against the data."* — every key counted for uniqueness and NULLs, every relationship for orphans; the [demo](/explore#plan-check) shows what the answer looks like. - `create_backend` — *"Create it as shop_api."* — creates the API for GraphQL item, or updates it in place if the name exists. - `describe_backend` — *"What can I query in shop_api?"* — the types, fields and relationship names a query uses. - `query_graphql` — *"Using shop_api, which customers ordered a trail bike in October?"* — a read-only query; relationships nest. - `rayfin_connector` — *"Generate Rayfin connector entities for it."* — TypeScript entity classes for Fabric Apps. - `ontology_draft` — *"Draft a Fabric IQ ontology from demo_lh."* — entity types, keys and relationship types with their rules, ready to import. - `compare_semantic_model` — *"Compare our Sales model with the plan for demo_lh."* — what the model lacks, what a rule or the data contradicts, and the measures it could add. - `gold_views_draft` — *"Draft gold views for demo_lh, schema shop."* — one materialized lake view per fact table over the silver tables, with data quality rules. - `export_item_definitions` — *"Export what you would create, in Fabric's Git format."* — the item folders to commit to your own repository. - `review_plan` — *"Keep this review as a file: accept ship_to_id as the ship-to address, leave audit_log without a key, and run the checks."* — a file to commit next to your code; next time, *"Read back krusgraph-review.demo-lh.shop.json and tell me what changed."* **Five first questions on a lakehouse nobody has documented** (each is useful on its own): 1. *"Read demo_lh, schema shop, as a graph: which tables are facts, which are dimensions, and what are the subject areas?"* (`analyze_schema`) 2. *"Plan the keys and relationships for schema shop and tell me which rule made each one."* (`plan_backend`) 3. *"Check that plan against the data: are the keys unique and do the references hold?"* (`validate_plan`) 4. *"Which columns look like they should point at another table but are in no relationship?"* (`suggest_relationships`) 5. *"Write the data dictionary for schema shop."* (`data_dictionary`; `dbt_schema` when the team tests with dbt) ### First-time errors What a new user meets first, what the tool answers (`kind`) and exactly what to do. Every such answer carries this section's link in its `hint`. | `kind` | What happened | What to do, in order | | --- | --- | --- | | `sign_in` | The connector has no valid sign-in for this request (expired, revoked, or never completed) | In Claude: **Settings → Connectors → Krusgraph → Disconnect**, then **Connect** again and finish the Microsoft sign-in with your work account. If it loops, your organisation requires multi-factor authentication for new apps: complete it when asked. | | `consent` | Your Microsoft Entra organisation has not consented to the app (Entra AADSTS65001), or the tool needs a permission the app has not been granted | Send your Entra admin the consent link on [/start](/start) (it opens Microsoft's own page); after they click **Accept**, disconnect and reconnect the connector. For `create_semantic_model`, `create_ontology`, `create_data_agent`, `create_silver_notebook` and `create_drift_monitor` the answer names the missing scope (such as `Item.ReadWrite.All` or `SemanticModel.ReadWrite.All`); that is a permission the operator adds to the app, see `docs/SCOPES.md`; the dry runs work meanwhile. | | `no_access` | You can sign in, but this workspace or item is not shared with you (HTTP 403) | Ask the workspace owner for a role on the workspace: **Viewer** is enough for everything that reads and plans, **Contributor** for `create_backend`, `create_views` and the other creating tools. Then run the same request again; `list_workspaces` shows what you can see. | | `capacity` | The workspace has no active Fabric capacity (paused, not assigned, or expired trial) | In Fabric: **Workspace settings → License info**, assign a capacity or resume the paused one; a trial capacity is enough. Lakehouse SQL endpoints answer again a minute after the capacity is up. | | `schema_not_found` | The schema has no tables for this sign-in | A lakehouse without "Lakehouse schemas" has everything in `dbo`; one created with schemas has them by name (`list_tables` with another schema). Tables created minutes ago are not in the SQL analytics endpoint yet: open the endpoint in Fabric and refresh its metadata, then try again. | | `sql_sign_in` | The SQL analytics endpoint refused the token | Reconnect the connector; if it persists, the account lacks access to the item itself (share the lakehouse or warehouse, not only the workspace). | Everything else (`not_found`, `timeout`, `throttled`, `bad_input`, …) is explained in the answer itself; the format is the same three fields. ## Explore and check How a plan is made, how to read it, how to check it against the rows before anything is built, and how to keep the review. ### Review, check, then create A plan is a proposal, so read it before anything is built. 1. **Review the plan.** Each key and relationship carries the rule that made it; ambiguous columns and tables without a key are listed. Correct a key with `keys` and add a relationship the rules did not make with `confirmed`. 2. **Check it against the data.** `validate_plan` counts duplicate and NULL key values and references with no matching row. `create_backend` does not run it for you, so ask for it. A check that passes shows the data agrees with the plan today; it does not show that the plan means the right thing. 3. **Create the API.** `create_backend` creates the API for GraphQL item, or updates the one with that name. It has no dry run: run `plan_drift` first to see what an update would change. An update keeps the field names the existing API has for the same relationships. If it would still remove relationships the API has, nothing is written and the answer lists them with the `confirmed` entries that keep them (both field names included); `allow_removals=true` drops them on purpose. 4. **Query it.** `describe_backend` gives the field names; `query_graphql` runs read-only queries and refuses mutations. Two portal steps stay manual (see below). 5. **Keep the review.** `review_plan` writes the plan, your decisions with their reasons and the check results as one file for your own repository. Pass it back later as `previous`: the corrections apply again and what changed since is listed (see *Keep your review*). ### Keep your review A review is worth keeping when the next report or app builds on the same tables. `review_plan` returns one JSON file (`krusgraph-review...json`) and a short Markdown report; it creates nothing in Fabric. - **What it holds:** the format and its version, a plan id, the source (workspace, item and schema names), a fingerprint of the tables' columns and types, the plan snapshot (the same one the drift monitor keeps), every key and relationship as an item (`key:orders`, `rel:orders.customer_id>customers`) with its rule and what the rules alone proposed when you corrected it, open questions (ambiguous columns, tables without a key, items you left open), and the `keys` and `confirmed` arguments that carry your corrections into the other tools. Names, types, rules, decisions and counts only: no row values, samples, connection strings or tokens. - **Two statuses per item.** *Review* is what a person decided: proposed, accepted, rejected or open, with a reason (`decisions`). *Check* is what the counting queries found: not checked, passed, failed or stale, with the counts and the time. They are kept apart: an accepted relationship can fail its check, and a passing check does not show that a relationship means the right thing. - **Reading it back.** With `previous`, the decisions apply again, the schema is compared with the file's (tables, columns, types, keys and relationships added or removed), decisions that no longer match the plan are listed, and a check on a table whose columns changed becomes *stale*: an old pass is not carried forward as new evidence. `validate=true` runs the checks again. - **Limits.** A relationship you reject stays rejected in the file, but the other tools still infer it from the names; leave it out of what you build. A large schema may not fit one answer; `tables` limits the file to an area. The file is unit-tested and not yet run against a live workspace (*Experimental*). ### How relationships are inferred A lakehouse has no declared keys, so names are all there is — and every result says which rule produced it, so you can overrule it. Declared keys and foreign keys (warehouses) always win. A column that could point at two tables is reported, never guessed; a column no rule matches is left unlinked and you can pass it as `confirmed`. The [demo](/explore) shows the rules on a synthetic schema. The rules, in the engine's own words: | Rule | Applies to | Means | | --- | --- | --- | | K0 | keys | declared primary key | | K1 | keys | `keys` override from the caller | | K2 | keys | a column named `id`, `_id`, `
id`, `_id`, `id`, `
_key`, `
_code`, `
_nr`, `
_no`, `
_sk`, `
_bk`, or an abbreviation of the table name (four letters or more) with one of those suffixes; a leading `dim_`/`fact_`/`tbl_` is not part of the table's name; when nothing matches literally, the same through the synonym table (`KundNr` in `kunder`, `cust_id` in `dim_customer`), reported under `synonyms` | | K3 | keys | nothing — the table gets no key; it can be queried but not by pk, and nothing can point at it | | K4 | keys | junction: no column names a key and the table starts with two or more reference columns; together they are the key (not applied to history tables with a start_*/valid_from/effective_* column) | | K5 | keys | line: no column names a key; the reference to the header table plus a line-number column (line_no, position, seq…) | | R0 | relationships | declared foreign key | | R1 | relationships | column named `_id` / `id` / `_id` / `id` (also _key, _code, _nr, _no, _sk, _bk) where B has a single key | | R2 | relationships | column has exactly the same name as B's single key column, and that name is not just `id` | | R3 | relationships | all of B's composite key columns appear in A | | R4 | relationships | weaker name match when R1 and R2 find nothing: the R1/R2 shapes through the synonym table (`Kunde_ID` → `Kunden`, `cust_id` → `customers`, noted on the relationship), a role before B's name (`manager_staff_id` → staff), or an abbreviation of B of four letters or more (`o_custkey` → customer); never across suffixes (`wwi_order_id` does not point at a table keyed by `order_key`) | Names are matched literally first. When nothing matches, a small documented **synonym table** is tried: abbreviations (`cust`, `prod`, `ord`, `emp`, `dept`, `acct`, …), Swedish and German business words in singular and plural (`kund`/`kunder`, `artikel`/`artiklar`, `leverantör`, `kunde`/`kunden`, `bestellung`, `rechnung`, `mitarbeiter`, …), after transliteration of `å ä ö ü ß`; a leading `dim_`, `fact_` or `tbl_` is not part of a table's name, and `_sk`/`_bk` count as key suffixes. Every match that rests on the table is listed under `synonyms` in `plan_backend` and on the relationship's `note`, and counts as the weakest rule (R4), so the plan never hides that it translated a name. Measured on eleven schemas in `docs/INFERENCE.md`; the five public schemas that need no synonyms are unchanged to the number. More on how they interact, with examples: https://github.com/Krusmaster/fabric-backend#how-the-relationships-are-inferred ### Sources **Tested end to end:** a lakehouse with schemas (the demo workspace's `demo_lh`, schema `shop`), read through its SQL analytics endpoint. **Experimental:** warehouses and Fabric SQL databases, read the same way through their SQL endpoint and covered by unit tests; warehouses also give declared keys and foreign keys (rules K0 and R0). **Experimental:** the mirrored and Snowflake item types below. Tables a lakehouse holds as OneLake shortcuts (to another lakehouse, a warehouse, ADLS or S3) are listed by its SQL analytics endpoint like its own tables, so they are planned with it. Krusgraph reads sources through Fabric, so data that lives elsewhere takes one of two routes: 1. **Bring it into Fabric.** A *Snowflake database* item, a *Mirrored database* (Snowflake, Azure SQL, Cosmos DB, SAP via Datasphere, …), a *Mirrored Azure Databricks catalog* or a *Mirrored catalog* (preview) is a source like a lakehouse: `list_sources` shows it, `plan_backend` plans it and `create_backend` builds the API over its SQL analytics endpoint. The item is set up once in the Fabric portal; Krusgraph reads the endpoint id from the item, as Microsoft's REST specification documents it for each of these item types. Experimental: unit-tested, not yet run against a live item. 2. **Plan it where it is.** The engine reads `INFORMATION_SCHEMA`, which Snowflake and Databricks (Unity Catalog) expose like the Fabric SQL endpoint does. The open-source library, the CLI and the local `fabric-backend-mcp` server give you the graph, the rules, the diagram and the Rayfin entities straight from those systems (for Databricks with its own adapter, [below](#databricks-draft-only)); the API for GraphQL item is a Fabric object, so that last step needs route 1. Snowflake has no `KEY_COLUMN_USAGE` view, so keys there come from names (rule K2) and are shown as such. Unity Catalog's informational primary and foreign keys are read as declared keys (K0, R0). The item types, routes and `sqlEndpointProperties` follow Microsoft's published REST specification; these source types are experimental until a live run. ### How Krusgraph uses graph theory The plan is a graph: tables are nodes, relationships are edges from the many side to the one side, and every edge carries a certainty from its rule (declared or confirmed 1.0; checked against the data 0.9; R1 0.8; R2 0.6; R3 and R4 0.5). `fabric_backend/graphs.py` is plain Python and deterministic: the same plan always gives the same answer. What each method does for you, in plain terms: - **Roles** (`analyze_schema`): a table that points at several others and nothing points at is a fact table; one that is pointed at and points at little is a dimension; a table whose key is just two references (a junction) is a bridge. The numbers that decided each role are in the answer, so you can disagree. - **Centrality**: in/out degree says what a table refers to and what refers to it; betweenness says how often a table sits on the route between two others; PageRank on the directed graph says where the references end up (shared dimensions score high). The top ten is where to start reading an unknown schema. - **Subject areas**: label propagation on the undirected graph groups tables that are more connected to each other than to the rest; each area is named after its most central table. A first cut at the domains in a lakehouse. - **Ambiguity**: every relationship beyond a spanning forest closes a cycle, and any two tables on a cycle can be reached two ways. That is exactly what makes filter paths ambiguous in a Power BI model, so the answer lists the cycles and the pairs, and the maximum spanning forest (highest certainty first) says which relationships can be active at once; `semantic_model_draft` uses it for active/inactive. - **Paths** (`join_path`): the shortest path counts hops; the safest path (Dijkstra on cost = 1/certainty) prefers declared and R1 links. When they differ the answer says why. - **Link prediction** (`suggest_relationships`): a column with a reference suffix and the type of another table's key, in no relationship yet, is a candidate; a name that resembles the target, or a target the table already reaches in two hops, ranks it higher; then the data decides — the share of its values that exist in the target key. Nothing is applied; you pass the suggestion you accept as `confirmed`. Unit-tested on the synthetic shop schema and the public benchmark schemas; `analyze_schema` and `suggest_relationships` were run against the demo workspace (synthetic shop data) on 26 October 2026: the one column outside every relationship, `orders.ship_to_id`, came back with every value present in `addresses.address_id` (15 distinct values) and well below that for the other candidates. On 27 October 2026, after the ranking was fixed, `addresses.address_id` came first before the data check (3.94 against 1.5 or less) and held for every value after it (inclusion 1.0). The [guided demo](/demo) walks through this column on the synthetic sample: the suggestion, its data check and the decision that stays yours. ## Build from a plan What you can build once the plan is reviewed. Each tool's status is in the [tool reference](#test-status). ### One plan, five Fabric pieces Everything Krusgraph creates comes from one plan: the keys and relationships inferred from a source, with the rule behind each one, checked against the rows and corrected by you through `keys` and `confirmed`. From that plan: `create_backend` makes the **API for GraphQL** with nested relationships (no dry run; `plan_drift` shows what an update would change); `create_views` the **wide T-SQL views** on the SQL analytics endpoint; and, once the operator has enabled the permission, `create_semantic_model` the **Power BI semantic model** in Direct Lake mode with the same relationships, `create_ontology` the **Fabric IQ ontology** with entity types, keys, relationship types and bindings, and `create_data_agent` the **data agent** with the plan as instructions and examples. Those four answer with a dry run first. Change the plan once (a confirmed relationship, a key) and every piece is rebuilt from it; `plan_drift` and `lineage` tell you what a change reaches. The pieces agree with each other because none of them was modelled by hand. From the same plan, further down the lakehouse: `silver_notebook_draft` writes de-duplicated silver tables with every reference checked, and `gold_views_draft` the **gold views** on top of them, as materialized lake views (generally available in Fabric since September 2026) whose data quality rules are the plan's keys and relationships. A model you already have is checked against the plan with `compare_semantic_model`. And when changes go through Git rather than straight into the workspace, `export_item_definitions` gives every piece as the files Fabric's Git integration reads. ### Fabric IQ: the same graph as an ontology Fabric IQ's **ontology (preview)** item describes a business as entity types with properties and a key, relationship types between them, and data bindings to tables. That is the information Krusgraph already infers for a source, with the rule behind every key and relationship. `ontology_draft` writes the plan in the ontology item's own shape, nothing deployed: - a **summary** with what needs a hand (keyless tables, composite keys, composite-column relationships); - a **binding checklist**: entity type → table, key column, property → column, relationship → columns, each with its rule; - a **Turtle file** the portal's *Import ontology* accepts into an empty ontology item, using the mappings Microsoft documents for import (`owl:Class` → entity type, `owl:DatatypeProperty` → property, `owl:ObjectProperty` → relationship type, `rdfs:label`, `rdfs:comment`, `skos:altLabel` → display name, description, synonym); bindings are then done in the portal following the checklist; - the **item definition** (generation 2, TMDL parts) that `POST /workspaces/{id}/ontologies` and Git integration take, with tables bound DirectLake-style to the source, entities with keys and properties, and the relationships. Creating the item through the API needs the delegated scope `Item.ReadWrite.All` and Contributor on the workspace, and the workspace on a Fabric capacity (`POST /workspaces/{id}/ontologies`, `updateDefinition`; Microsoft's REST specification, read 26 October 2026). `create_ontology` does that with a dry run by default; the hosted app does not have the permission yet, so it answers `consent` naming the scope when asked to create. The portal route (Import ontology with the Turtle file) needs nothing beyond what you already have. Once an ontology exists, Fabric IQ's own pieces take over: the ontology agent and data agents use it as context, and the built-in graph can be materialised from its entity and relationship types. Ontology is in preview and its definition format can change; the draft follows the item's own definition as of 2026 and has been checked against recorded definitions, not yet live from Krusgraph. ### Two steps stay manual Fabric has no API for these yet, and the connector tells you when they are needed: 1. **Introspection** on the GraphQL item (item → Settings → Introspection) — needed by tools such as Lovable that read the schema. 2. **Permissions** on the item for other apps or service principals that should query it. ### Use with Lovable Lovable's Microsoft Fabric connector reads an existing API for GraphQL item. Krusgraph creates that item; after the two manual steps above, Lovable connects to it. Step by step: [Use Krusgraph with Lovable](/lovable). `app_readiness` says what is still missing on the API; `app_starter` gives the prompt, the queries and a Supabase edge function that keeps the app's Entra secret on the server side. The whole chain, with who does each step and what still blocks, is in the repository's `docs/LOVABLE-END-TO-END.md`. ### Databricks (Draft only) The same engine reads Databricks too, in the local server only (`KRUSGRAPH_PLATFORM=databricks`): a Unity Catalog schema through its `information_schema`, where declared primary and foreign keys count as K0 and R0 and column tags feed the personal-data rules; the checks run as counting queries on a SQL warehouse. From the same plan it drafts informational constraints (with `RELY` only where the counts hold), views, a Lakeflow pipeline whose expectations are the plan's keys and relationships, metric views and a bundle. **Draft only:** unit-tested with Databricks mocked, not yet run against a Databricks workspace. The hosted service at krusgraph.com reads Fabric only. Set-up, the status of each tool and the documentation each draft rests on: [docs/DATABRICKS.md](https://github.com/Krusmaster/fabric-backend/blob/main/docs/DATABRICKS.md). Databricks' own managed MCP servers (Genie, Databricks SQL, Unity Catalog functions) answer questions and run SQL; Krusgraph is the part that works out how the tables relate. Krusgraph is not affiliated with Databricks. ## Tool reference The status words, then each tool: what it reads, returns or changes. ### Test status Each tool in the table below carries one of these. They come from one list of fields per tool (where it is available, what it changes, how it is tested and whether it ran live; `fabric_backend/tool_status.py`), so a tool has the same status on every page. *Draft only* says what a tool changes, *Tested end to end* how far it has been run: a draft tool that ran live is listed as tested, and its *What it changes* column says it creates nothing. - **Tested end to end** — run from Claude as a custom connector, through Microsoft sign-in, against a Fabric workspace with synthetic data (an F2 capacity, the lakehouse `demo_lh` with the shop schema from `fabric_backend/seed.py`) on 27 October 2026, through the hosted server's build src-df29c45febaec4b0 (main 5adace8), read-only, as drafts or as dry runs, and then, with the maintainer's approval, one creating call that updated an existing API in place: `list_workspaces`, `list_sources`, `list_tables`, `sample_rows`, `plan_backend`, `validate_plan`, `suggest_relationships`, `analyze_schema`, `join_path`, `describe_backend`, `query_graphql`, `answer_query_draft`, `app_readiness`, `app_starter`, `plan_drift`, `lineage`, `review_plan`, `compare_semantic_model` (as far as the model lookup, which answered not_found: the demo workspace has no model), `dbt_schema`, `data_dictionary`, `semantic_model_draft`, `ontology_draft`, `rayfin_connector`, `silver_notebook_draft`, `gold_views_draft`, `drift_monitor_draft`, `export_item_definitions`, `create_views` (dry run only; nothing was created), `create_semantic_model` (dry run only; nothing was created), `create_data_agent` (dry run only; nothing was created) and `create_backend` (as an update in place of shop_api with confirmed; no new item was created). A draft that ran so is marked *(a draft; creates nothing)*, a creating tool whose dry run ran *tested end to end as a dry run*; creating keeps its own status. Not run that day, not even as a dry run: `create_ontology`, `create_silver_notebook`, `create_drift_monitor` and `create_gold_views`. The first such run was on 26 October 2026 (14 steps). Changes made after the run are covered by unit tests. - **Experimental** — on the hosted server and unit-tested, not yet run against a live workspace. - **Draft only** — returns a draft or files and creates nothing in Fabric. - **Available when enabled by the operator** — needs a permission the hosted app does not ask for today. The dry run works; asked to create, the tool answers `consent` with the permission's name ([docs/SCOPES.md](https://github.com/Krusmaster/fabric-backend/blob/main/docs/SCOPES.md)). - **Planned** — not available yet, such as the Claude plugin (see *For agents*). ### Tools All tools take the workspace by id or display name. `schema` defaults to `dbo`; `keys` overrides a table's key (`{"order_items": ["order_id", "line_no"]}`), `confirmed` adds relationships the rules did not make (`[{"source": "orders", "source_cols": ["ship_to_id"], "target": "addresses", "target_cols": ["address_id"]}]`), `tables` limits what is built. A `confirmed` entry can also name the relationship's two GraphQL fields: `field_name` on the source (`"address"`: `orders.address`) and `reverse_name` on the target (`"orders"`: `addresses.orders`); without them the rules name the fields (`ship_to` and `orders_as_ship_to` here). When an API of that name exists, `create_backend` and `plan_drift` keep the field names it already has for the same relationship (the same columns in the same direction) unless `confirmed` names them otherwise, and their `keep_with_confirmed` entries carry both names. | Tool | Status | What it changes | Does | Arguments | | --- | --- | --- | --- | --- | | `list_workspaces` | Tested end to end | nothing | the workspaces the signed-in account can see, with id, name and capacity | — | | `list_sources` | Tested end to end | nothing | lakehouses, warehouses, SQL databases, Snowflake databases, mirrored databases and catalogs in the workspace, plus existing API for GraphQL items | `workspace` | | `list_tables` | Tested end to end | nothing | tables and columns with types in one schema; `tables` narrows to names or patterns (`["sales_*"]`), `detail` is `auto` (full up to 60 tables, then the column count per table), `full` or `summary` | `workspace`, `source`, `schema`, `tables`, `detail` | | `plan_backend` | Tested end to end | nothing | inferred keys and relationships, each with its rule, ambiguous columns, keyless tables, suggested composite keys, the names read through the synonym table (`synonyms`), a Mermaid ER diagram; `tables` narrows the answer to names or patterns while the plan is still made from the whole schema, `detail` is `auto` (full up to 60 tables, then one line per key and relationship without field names or diagram), `full` or `summary`, `mermaid=false` leaves the diagram out; every answer stays under 60 000 characters and says when it was cut | `workspace`, `source`, `schema`, `keys`, `confirmed`, `tables`, `detail` | | `validate_plan` | Tested end to end | nothing (counting queries) | checks the plan against the rows: is each key unique and free of NULLs, does every reference find its target (orphan count); one small counting query per key and relationship; returns counts and a verdict, no row values | `workspace`, `source`, `schema`, `keys`, `confirmed`, `max_items` | | `sample_rows` | Tested end to end | nothing | the first n rows (at most 20) of one table, long text shortened, to check what a column means; columns that look like personal data come back as `***` with the rule, and the source's sensitivity label is named | `workspace`, `source`, `table`, `schema`, `n`, `show_personal_data` | | `create_backend` | Tested end to end (27 October 2026: update in place of shop_api with confirmed) | creates/updates one item; never deletes | creates the API for GraphQL item with nested relationships, or updates the item of that name in place, keeping the field names it has for the same relationships; refuses an update that would drop relationships the API has unless `allow_removals=true`, and names the `confirmed` entries (with `field_name` and `reverse_name`) that keep them; returns the endpoint and the two portal steps; no dry run, and it does not run `validate_plan` | `workspace`, `source`, `name`, `schema`, `writable`, `keys`, `confirmed`, `tables`, `allow_removals` | | `describe_backend` | Tested end to end | nothing | types, fields and relationships of an existing API for GraphQL item, in the names a query uses, read from its definition (no introspection needed) | `workspace`, `api` | | `query_graphql` | Tested end to end | nothing | runs a read-only GraphQL query against an API (name or id); mutations are refused; with `question`, `tables` and `source` instead of `query` it builds the query `answer_query_draft` suggests, runs it, and shows it beside the result | `workspace`, `api`, `query`, `variables`, `question`, `tables`, `source`, `schema` | | `rayfin_connector` | Tested end to end (a draft; creates nothing) | nothing | Rayfin (Fabric Apps) connector entity classes and `schema.ts` | `workspace`, `source`, `connector`, `schema`, `keys`, `confirmed`, `tables`, `relationships` | | `ontology_draft` | Tested end to end (a draft; creates nothing) | nothing | the plan as a Fabric IQ ontology (preview) draft: summary, binding checklist, a Turtle file for the portal import, the item definition | `workspace`, `source`, `name`, `schema`, `keys`, `confirmed`, `tables` | | `semantic_model_draft` | Tested end to end (a draft; creates nothing) | nothing | a Power BI semantic model in TMDL (`definition/` files) with suggested measures (the row count of each fact table, sums only where `summarizeBy` sums, each with its reason) and a calendar marked as the date table when fact tables have date columns: one Direct Lake table per source table on the SQL analytics endpoint, relationships with cardinality and one active path per table pair, keyless tables unrelated, `README.generated.md` with every decision to check; unit-tested against Microsoft's sample definitions | `workspace`, `source`, `name`, `schema`, `keys`, `confirmed`, `tables` | | `join_path` | Tested end to end | nothing | the shortest chain of relationships between two tables with the rule per hop and a ready T-SQL `SELECT … JOIN …`; equally short paths all listed; with no path, which relationship is missing as a `confirmed` entry | `workspace`, `source`, `start`, `end`, `schema`, `keys`, `confirmed` | | `data_dictionary` | Tested end to end (a draft; creates nothing) | nothing | hand-over documentation in Markdown: tables, columns and types, keys and relationships with rules, keyless and ambiguous items, the ER diagram, an empty Description column; `validate=true` adds the counts from `validate_plan`; `tables` (names or patterns) documents one part of a large schema at a time, with the roles and subject areas of the whole schema, and an answer over 60 000 characters is cut on a line and says so | `workspace`, `source`, `schema`, `keys`, `confirmed`, `validate`, `max_items`, `tables` | | `dbt_schema` | Tested end to end (a draft; creates nothing) | nothing | a dbt `schema.yml` with sources: `unique` and `not_null` on key columns, `relationships` on single-column relationships, in the syntax of the dbt documentation; composite keys noted as comments; `tables` takes names or patterns, an answer over 60 000 characters is cut and says so | `workspace`, `source`, `schema`, `source_name`, `keys`, `confirmed`, `tables`, `arguments_block` | | `plan_drift` | Tested end to end | nothing | today's plan against an existing API for GraphQL item: new tables and columns, columns the API still maps that are gone, relationships the plan no longer supports, and whether `create_backend` with that name would change anything | `workspace`, `source`, `api`, `schema`, `keys`, `confirmed`, `tables`, `writable` | | `create_semantic_model` | Available when enabled by the operator; tested end to end as a dry run | creates/updates one item; never deletes | creates a Power BI semantic model item from the TMDL draft (Direct Lake on the SQL analytics endpoint) or updates the one of that name in place; `dry_run=true` (default) returns the parts and the REST call only; needs `SemanticModel.ReadWrite.All` or `Item.ReadWrite.All` | `workspace`, `source`, `name`, `schema`, `keys`, `confirmed`, `tables`, `dry_run` | | `create_views` | Tested end to end as a dry run; creating them: Experimental | creates/alters views in that schema only; never drops | `CREATE OR ALTER VIEW` in a schema of its own (default `krusgraph`, created if missing) on the source's SQL analytics endpoint or warehouse: one wide view per fact table with its dimensions, one per header table (`vw_orders`: each order with its customer and, once `orders.ship_to_id` is confirmed, its ship-to address), one per hard-to-read relationship; `dry_run=true` returns the SQL; needs the SQL right to create objects (Contributor) | `workspace`, `source`, `schema`, `views_schema`, `keys`, `confirmed`, `dry_run` | | `create_data_agent` | Available when enabled by the operator; tested end to end as a dry run | creates/updates one item and its staging config; never deletes | creates or updates a Fabric data agent through the staging REST API: the plan as instructions, the source as data source with the plan's tables selected, 5–10 question/T-SQL examples, then publishes; columns that look like personal data are left out of the instructions and the examples unless `show_personal_data=true` (the key stands in); `dry_run=true` returns instructions, examples and the calls; needs `Item.ReadWrite.All` | `workspace`, `source`, `name`, `schema`, `keys`, `confirmed`, `publish`, `show_personal_data`, `dry_run` | | `create_ontology` | Available when enabled by the operator; unit-tested, not yet run live | creates/updates one item; never deletes | creates a Fabric IQ ontology (preview) item from the plan or updates the one of that name in place: entity types with keys and properties, relationship types and Direct Lake-style bindings in the item's generation-2 definition; `dry_run=true` (default) returns the parts and the REST call only; needs `Item.ReadWrite.All` and Contributor on the workspace (`POST /workspaces/{id}/ontologies`, `updateDefinition`; fabric-rest-api-specs read 26 October 2026) | `workspace`, `source`, `name`, `schema`, `keys`, `confirmed`, `tables`, `dry_run` | | `app_starter` | Tested end to end (a draft; creates nothing) | nothing | from an existing API's definition: a Lovable prompt with the data model and main views, a connection description, five GraphQL queries walking the relationships, the portal checklist, and the backend an app needs so that no secret reaches the browser: a Supabase edge function (service principal, client credentials, secrets as Supabase secrets), the frontend helper, and the per-user alternative | `workspace`, `api` | | `app_readiness` | Tested end to end | nothing (one introspection query) | what an app still needs on an existing API: types without a key, types in no relationship, write access (from the definition), whether introspection is on (one introspection query to the endpoint), and "unknown" with the portal path for what the Fabric API cannot read (permissions, the tenant setting for service principals); a next step per item | `workspace`, `api` | | `analyze_schema` | Tested end to end | nothing | the plan as a graph, metadata only (`tables` and `detail` as in `plan_backend`; the summary groups tables by role and keeps the central tables, the areas by size and counts): table roles (fact, dimension, bridge, isolated) with the numbers behind them, the ten most central tables (degree, betweenness, PageRank), subject areas, cycles and table pairs with more than one path, and which relationships can be active at once | `workspace`, `source`, `schema`, `keys`, `confirmed`, `tables`, `detail` | | `lineage` | Tested end to end | nothing | which objects in the workspace build on the source's tables: GraphQL APIs (from their definition), semantic models (from their TMDL definition), views on the SQL analytics endpoint (`INFORMATION_SCHEMA.VIEW_TABLE_USAGE`), data agents (named in their definition, marked as not certain); a graph of tables and objects, the objects per table, the tables used by nothing, the items that could not be read; with `table`, what a change to it reaches | `workspace`, `source`, `schema`, `table` | | `silver_notebook_draft` | Tested end to end (a draft; creates nothing); the notebook ran on a local PySpark 4.0.1, not yet in Fabric | nothing | a Fabric notebook (PySpark) that builds silver tables from a lakehouse's tables: de-duplicated on each table's key (the latest row by `order_by` wins), every relationship checked with the rows whose reference is missing put in `krusgraph_quarantine` with the reason, Delta tables written only in `silver_schema`, each step commented with its rule; re-runnable. The steps and the notebook source | `workspace`, `source`, `schema`, `silver_schema`, `order_by`, `keys`, `confirmed`, `tables` | | `create_silver_notebook` | Available when enabled by the operator; unit-tested | creates/updates one item; never deletes | creates that notebook in the workspace, attached to the lakehouse, or updates the one of that name; creating runs nothing; `dry_run=true` (default) returns the notebook and the call; needs `Notebook.ReadWrite.All` or `Item.ReadWrite.All` (fabric-rest-api-specs `notebook/swagger.json`) | `workspace`, `source`, `name`, `schema`, `silver_schema`, `order_by`, `keys`, `confirmed`, `tables`, `dry_run` | | `drift_monitor_draft` | Tested end to end (a draft; creates nothing); the notebook ran two nights on a local PySpark, not yet in Fabric | nothing | a notebook and a pipeline that every night read the lakehouse's schema, compare the plan with the last saved one (the `plan_drift` and `lineage` logic as a library), save the new plan in `krusgraph.plans`, and send a short summary to a webhook (Teams or Power Automate) set as a pipeline parameter in Fabric; plus the daily schedule; the notebook installs the library at the tag of the server's version unless `library_ref` names another tag, a commit or `main` | `workspace`, `source`, `schema`, `name`, `at`, `time_zone`, `library_ref` | | `create_drift_monitor` | Available when enabled by the operator; unit-tested | creates/updates two items and a schedule; never deletes | creates or updates that notebook and pipeline and adds the daily schedule unless one at that time exists; no secret anywhere; `dry_run=true` (default) returns the items and the calls; needs `Notebook.ReadWrite.All` and `DataPipeline.ReadWrite.All` (or `Item.ReadWrite.All`), and `DataPipeline.Execute.All` (or `Item.Execute.All`) for the schedule | `workspace`, `source`, `name`, `schema`, `at`, `time_zone`, `library_ref`, `schedule`, `dry_run` | | `answer_query_draft` | Tested end to end (a draft; creates nothing) | nothing | a question in Swedish or English about the given tables as T-SQL over the safest join path (each step with its rule), and with `api` as GraphQL in that API's field names, or why GraphQL cannot answer it; reads counts, sums, averages, "per"/"för varje" groupings, top N and "latest", and lists what it assumed; runs nothing; columns that look like personal data are left out unless `show_personal_data=true` (the key stands in) | `workspace`, `source`, `question`, `tables`, `schema`, `api`, `keys`, `confirmed`, `show_personal_data` | | `suggest_relationships` | Tested end to end | nothing (counting queries) | columns that could point at another table's key but are in no relationship, ranked by name, type and triangular closure, the best `check` counted against the data (inclusion share, distinct values); each with a ready `confirmed` entry; suggests only, never applies | `workspace`, `source`, `schema`, `keys`, `confirmed`, `check` | | `compare_semantic_model` | Available when enabled by the operator; with `tmdl`: Experimental; live on 27 October 2026 only as far as the model lookup (no model in the demo workspace) | nothing | an existing semantic model (name or id) against the plan: relationships the model lacks, ones a rule or the data contradicts (with the rule or the count), other cardinality, filters in both directions, active against inactive, links the plan lacks (with a `confirmed` entry), columns summed that the `summarizeBy` rule would not sum, suggested measures it lacks; reads the model's TMDL through Fabric's getDefinition (needs `SemanticModel.ReadWrite.All` or `Item.ReadWrite.All`), or `tmdl` from a PBIP folder or Git, which needs nothing; `check_data=true` counts against the rows | `workspace`, `source`, `model`, `schema`, `keys`, `confirmed`, `check_data`, `max_items`, `tmdl` | | `gold_views_draft` | Tested end to end (a draft; creates nothing); the SQL ran on SQLite and a local PySpark 4.0.1, not yet in Fabric | nothing | materialized lake views in Spark SQL for a lakehouse with schemas: one wide view per fact table in `gold_schema` over the silver tables, joined over the safest many-to-one paths, with `CONSTRAINT … CHECK … ON MISMATCH DROP` rules for the plan's keys and relationships, each commented with its rule; personal-data columns left out unless `show_personal_data=true`; the notebook that creates them | `workspace`, `source`, `schema`, `silver_schema`, `gold_schema`, `keys`, `confirmed`, `tables`, `show_personal_data` | | `create_gold_views` | Available when enabled by the operator; unit-tested | creates/updates one notebook; never deletes | creates that notebook in the workspace, attached to the lakehouse, or updates the one of that name; the views are created when the notebook runs, and it stops before a source table, a silver table or any table it did not create; `dry_run=true` (default) returns the views, the notebook and the call; needs `Notebook.ReadWrite.All` or `Item.ReadWrite.All` | `workspace`, `source`, `name`, `schema`, `silver_schema`, `gold_schema`, `keys`, `confirmed`, `tables`, `show_personal_data`, `dry_run` | | `export_item_definitions` | Tested end to end (a draft; creates nothing) | nothing | what `create_backend`, `create_semantic_model`, `create_silver_notebook`, `create_drift_monitor` and `create_gold_views` would create, as folders in Fabric's Git format (`.` with `.platform` and the definition files), plus `create_views`' T-SQL as a script (Fabric's Git integration does not carry SQL analytics endpoint views) and a README; for your own repository, so that Fabric's Git integration or a deployment pipeline makes the change; calls no Git provider | `workspace`, `source`, `schema`, `items`, `names`, `keys`, `confirmed`, `tables`, `silver_schema`, `gold_schema`, `views_schema`, `writable`, `show_personal_data` | | `review_plan` | Tested end to end; reading a file back is unit-tested | nothing (counting queries with validate=true) | the plan with the decisions made about it and its checks as one JSON file for your own repository, plus a short Markdown report and what needs attention: each key and relationship with a review status (proposed, accepted, rejected, open, with a reason) and, apart from it, a check status (not checked, passed, failed, stale); `validate=true` records `validate_plan`'s counts with the time; `previous` takes an earlier file, applies its corrections again, lists what changed in the schema and marks checks on changed tables stale; names, types, rules, decisions and counts only, never row values or credentials | `workspace`, `source`, `schema`, `keys`, `confirmed`, `decisions`, `previous`, `validate`, `tables`, `max_items` | When a call cannot be completed, the tool answers with three fields instead of a bare failure: `error` (what happened, in plain words), `kind` (a stable code such as `not_found`, `no_access`, `capacity`, `sign_in`, `consent`, `schema_not_found`, `table_not_found`, `bad_input`, `timeout`, or `rate_limited` with `retry_after` in seconds when an account or organisation makes too many calls in a short time) and `hint` (what to do next). Tokens, request headers and stack traces never appear in these answers. A query result too large for one answer comes back as valid JSON with every list cut to its first items and a `truncated` note saying so; page with `first:`/`after` for the rest. ### For agents A Markdown version of these docs is at [/docs.md](/docs.md); [/llms.txt](/llms.txt) lists the rest and [/llms-full.txt](/llms-full.txt) has every page in one file. MCP clients can read the server's identity and transport from its Server Card at `https://krusgraph.com/mcp/server-card`, and the OAuth metadata at `/.well-known/oauth-protected-resource`. Claude Code takes the same URL (`claude mcp add --transport http krusgraph https://krusgraph.com/mcp`), but its local sign-in redirect is not registered on the Entra app yet, so it can't sign in to krusgraph.com today; Claude is the supported client. A Claude plugin that bundles this connector with a skill for the whole workflow and the commands `/krusgraph:plan`, `/krusgraph:document` and `/krusgraph:explain-schema` is built and tested in the repository (`plugin/krusgraph`) and coming soon (Planned); it is not published yet. ### With Microsoft's Fabric MCP servers Microsoft publishes its own MCP servers for Fabric and Power BI. They manage workspaces and items, read files and run queries; Krusgraph works out how your tables relate, with the rule behind every link, checks it against the rows, and builds from that plan. Both can be added to the same client, each doing its own part: - **Workspaces, items and OneLake files:** the local [Fabric MCP Server](https://github.com/microsoft/mcp/blob/main/servers/Fabric.Mcp.Server/README.md) ([Learn](https://learn.microsoft.com/fabric/onelake/onelake-local-mcp)) and the remote Fabric Core MCP server ([tools reference](https://learn.microsoft.com/rest/api/fabric/articles/mcp-servers/core-remote/tools-core-mcp-server)). - **Questions to a Power BI semantic model or report:** [Fabric IQ MCP](https://learn.microsoft.com/fabric/iq/connectors/fabric-iq-mcp), read-only, with DAX queries under your own permissions. - **Editing a semantic model:** the [Power BI Modeling MCP server](https://github.com/microsoft/powerbi-modeling-mcp). - **T-SQL on a warehouse:** the [Fabric Data Warehouse MCP server](https://learn.microsoft.com/fabric/data-warehouse/data-warehouse-mcp-server) (preview). - **Eventhouse and real-time data:** [Real-Time Intelligence MCP](https://learn.microsoft.com/fabric/real-time-intelligence/mcp-overview). - **Krusgraph:** the plan (keys and relationships with their rules, checked against the rows) and what is built from it: the GraphQL API, the semantic model, views, silver and gold layers, drift monitoring, the item files for Git, and a check of an existing model against the plan. A typical pairing: Microsoft's server finds the workspace and the lakehouse; Krusgraph plans it and drafts the model; Fabric IQ MCP then answers questions on the model once it exists. Krusgraph is an independent open-source project of Kruse Consulting AB and is not affiliated with Microsoft. ## Hosting and permissions What the hosted server reads and asks for, and what to know when you run it yourself. ### What it accesses The Entra app asks for delegated permissions only, all acting as the signed-in user: `User.Read` (sign-in), `Workspace.Read.All` and `Item.Read.All` (listing sources, reading metadata), `GraphQLApi.ReadWrite.All` (creating or updating the one item you ask for; it never deletes), `GraphQLApi.Execute.All` (your read-only queries) and Azure SQL `user_impersonation` (table and column names from the SQL analytics endpoint, and the checks and samples you ask for). The tools that create semantic models, ontologies, data agents, notebooks and pipelines need more (`Item.ReadWrite.All` or a narrower permission per item type), which the hosted app does not ask for today; they are marked *Available when enabled by the operator* below. **The data path.** Planning uses schema metadata: workspace, item, table and column names and types. `validate_plan` and `suggest_relationships` check keys and references against the rows in Fabric with counting queries and return only the counts. `sample_rows` returns up to 20 example rows of one table, and `query_graphql` returns the results you ask for. `describe_backend`, `plan_drift`, `app_starter` and `lineage` read the definitions of items in the workspace. Metadata, requested samples and query results pass through Krusgraph on their way to your MCP client and are not kept after the request. Your tokens are exchanged on your behalf and held in memory only; there is no database and no file storage. Each tool call leaves an anonymous usage record (the tool, how the call ended, how long it took), and the service writes technical logs. Details: [privacy policy](/privacy). **Personal data and sensitivity labels.** Columns whose name looks like personal data are flagged by one rule per pattern, from the name alone and never from the values: a person's name (P1: `first_name`, `last_name`, and `name` in a table that holds people such as customers or employees), e-mail (P2), telephone (P3), national identity number (P4, such as `personnummer`), address (P5: street, postal code, and the city in a table of addresses or people) and birth date (P6). By default `sample_rows` returns their values as `***` with a notice naming the rule, `answer_query_draft` and `gold_views_draft` leave them out (the key stands in), and the silver notebook masks them in its quarantine table; `show_personal_data=true` shows them, and your own Fabric permissions still decide what you can read. When Fabric returns a Microsoft Purview sensitivity label on the source item, `sample_rows` and `validate_plan` name it: Fabric gives the label's id only, and a label covers the whole item, so the column rules still decide what is masked. ### Cold start, and `min_replicas` if you run it yourself If the hosted service has been idle, the first request may take longer while Azure starts the container. If that request times out, try again after a short pause. If the problem continues, check the error message or [get help](/support). If you run the server yourself with `deploy/krusgraph.sh`, `min_replicas` in `deploy/krusgraph.json` decides whether it scales to zero: `0` (the default) scales to zero when idle and is the cheapest, `1` keeps one instance running so idle time does not cause a cold start, at the cost of one always-on instance. A new revision or a restart can still delay a request. The change takes effect with `--update`. A container on a VM or in Docker does not scale to zero. ### Support [Get help](/support): email Gustav at hello@krusgraph.com with the `error`, `kind` and `hint` from the tool's answer (there is no promised response time), or report a bug or a plan that came out wrong on [GitHub Issues](https://github.com/Krusmaster/fabric-backend/issues). Source (MIT): https://github.com/Krusmaster/fabric-backend --- # Connect Krusgraph to Claude You’ll need a Microsoft work account, access to a Fabric workspace with capacity, and your organisation’s approval for Krusgraph’s permissions. ## Explore without an account The demo runs on sample data in your browser: see how tables are linked, why, and what the checks found.Explore the demo → ## Connect your workspace Three steps below: your admin’s approval, a Fabric workspace, and the connector in Claude.Start with step 1 ↓ ## Get admin approval Your Microsoft Entra admin needs to approve the permissions Krusgraph requests. The [permission list below](#access) explains what each one is used for. If your organisation has already approved Krusgraph, you can move on. For your admin: Grant admin consent in Entra That link opens Microsoft’s own consent page for the Krusgraph app; there is no sign-in on this site. It allows Krusgraph to call Microsoft Fabric and SQL *as each signed-in user*, so every call runs with that person’s own Fabric permissions. The admin can revoke it at any time under Enterprise applications in Entra. ## Choose a Fabric workspace Choose a workspace you can access and a source you want to explore, such as a lakehouse or warehouse. Keep the workspace name or ID handy. Viewer is enough to read and plan; creating needs Contributor. The workspace needs a Fabric capacity, and a trial capacity works. No workspace yet? The [demo](https://krusgraph.com/explore) runs on a synthetic schema and needs no account, and `fabric-backend demo` runs the same engine on your own laptop against a SQL Server or a folder of files ([run it locally](https://krusgraph.com/local)). ## Add the connector in Claude In Claude, open **Settings → Connectors → Add custom connector**. Give it a name and paste this URL: ``` https://krusgraph.com/mcp ``` Sign in with your Microsoft work account. Your first prompt: *“List the data sources in workspace …”*, then *“Plan the shop lakehouse and check the plan against the data.”* The first goal is a plan you have read and checked: the suggested keys and relationships, the rule behind each, and the counts from your data. Correct what doesn’t match how your business works, and keep the result: *“Keep this review as a file.”* gives you the plan, your decisions and the checks to commit to your own repository and read back next time (tested end to end; reading a file back is unit-tested; [how it works](https://krusgraph.com/docs#keep-your-review)). Creating a GraphQL API from it can be the next step: *“Create it as shop_api.”* Creating a new API is experimental; updating one in place was tested end to end on 27 October 2026 ([test status](https://krusgraph.com/docs#test-status)). Five first questions for a lakehouse nobody has documented are in the [docs](https://krusgraph.com/docs#what-you-can-ask). The hosted service at krusgraph.com supports Claude, and that is the client it has been tested with. Claude Code and Cursor sign in through a local redirect that is not registered on the Entra app yet, so they can’t sign in to krusgraph.com today. Other MCP clients should work when you host Krusgraph yourself (not tested yet). Full usage notes are in the [docs](https://krusgraph.com/docs). [Explore a sample first →](https://krusgraph.com/explore) ## What will Krusgraph access? **In short:** Krusgraph acts as the signed-in user and reaches only what that person can already reach. It reads workspaces, items and table metadata, runs the checks and queries you ask for, and creates or updates only the one GraphQL API you ask for. It never deletes. Your admin can revoke the approval at any time. ### For your admin: the permissions in detail The hosted app asks for these delegated permissions, all approved by your admin once and all acting as the user: | Permission | Used for | | --- | --- | | `User.Read` (Microsoft Graph) | sign-in only | | `Workspace.Read.All`, `Item.Read.All` (Fabric) | listing sources and reading item metadata | | `GraphQLApi.ReadWrite.All` (Fabric) | creating or updating the one API for GraphQL item you ask for; never deleting | | `GraphQLApi.Execute.All` (Fabric) | running the read-only queries you ask for | | `user_impersonation` (Azure SQL Database) | reading table and column names from the SQL analytics endpoint | Why each one: `Workspace.Read.All` and `Item.Read.All` are what `list_workspaces`, `list_sources`, `describe_backend`, `plan_drift` and `lineage` read; `GraphQLApi.ReadWrite.All` is used by `create_backend` alone; `GraphQLApi.Execute.All` by `query_graphql` and `app_readiness`; the SQL permission by the tools that read table and column names or count rows (`list_tables`, `plan_backend`, `validate_plan`, `sample_rows`, the drafts). The tools that create semantic models, ontologies, data agents, notebooks and pipelines need `Item.ReadWrite.All` or a narrower permission per item type, which the hosted app does not ask for today. The docs mark them [available when enabled by the operator](https://krusgraph.com/docs#test-status): their dry runs work, and asked to create they answer `consent` with the permission’s name. What passes through Krusgraph: planning uses schema metadata (workspace, item, table and column names and types). `validate_plan` and `suggest_relationships` check keys and references against the rows in Fabric and return counts. `sample_rows` returns up to 20 example rows of one table, and `query_graphql` returns the results you ask for. Metadata, requested samples and query results pass through Krusgraph to your client and are not kept after the request. Your tokens are exchanged on your behalf and held in memory only. The [privacy policy](https://krusgraph.com/privacy) explains this, the usage records and the technical logs. Two steps stay manual in the Fabric portal because there is no API for them yet, and the connector tells you when they are needed: turning on **introspection** for the GraphQL item, and granting **permissions** on it to other apps. --- # Build a Lovable app on your Fabric tables Use Krusgraph in Claude to prepare a GraphQL API in Fabric. Then connect Lovable to that API with Lovable's Microsoft Fabric connector. The API in your workspace is what connects the two workflows. | Step | Where | Done by | | --- | --- | --- | | 1. Prepare the API | Claude, with Krusgraph | Krusgraph, when you ask. Creating a new API is experimental; updating one in place was tested end to end on 27 October 2026 ([test status](/docs#test-status)) | | 2. Finish the Fabric settings | the Fabric portal | you or a workspace admin, by hand: introspection and the app's permission | | 3. Connect | Lovable | you, by hand, in Lovable's Microsoft Fabric connector | Use Krusgraph from Claude for step 1; this guide does not cover adding Krusgraph as a custom MCP connector inside Lovable. ## Steps 1. **Create the API with Krusgraph.** In Claude, with the Krusgraph connector added ([get started](/start)), ask for a plan first, for example *"Plan a backend over the shop lakehouse"*. A plan creates nothing. Review the keys and relationships and the rule behind each, and ask Claude to check the plan against the data. Then say *"Create it as shop_api"*. Krusgraph creates one API for GraphQL item with nested relationships, or updates it if the name exists. Creating a new API is experimental; updating one in place was tested end to end on 27 October 2026; reading and querying an existing API are tested end to end. 2. **Turn on introspection in Fabric (manual).** Fabric creates the item with introspection off, and Lovable's connection test needs it. A workspace admin turns it on in the item's API settings. Fabric has no API for this yet, so Krusgraph cannot do it for you. 3. **Let Lovable's service principal query the item (manual).** Lovable connects with a Microsoft Entra service principal (an app registration with a client secret), not with your own sign-in. Grant that principal *Run Queries and Mutations* on the item, and make sure your tenant allows service principals to call Fabric public APIs. This is the second manual portal step. 4. **Add it in Lovable's Fabric connector (manual).** In Lovable, open Connectors, choose Microsoft Fabric and add a connection with the tenant ID, client ID, client secret and the item's endpoint URL (it ends in `/graphql`). Lovable runs an introspection query to check the connection. Lovable's own guide lists every prerequisite and limit: https://docs.lovable.dev/integrations/microsoft-fabric ## Good to know - **Lakehouse sources are read-only** through the API for GraphQL; writes work on warehouses and SQL databases. Krusgraph creates read-only items unless you ask for a writable one. - **What Lovable sees** is what the item exposes, under the service principal's permissions. - **Krusgraph is not a Lovable connector.** Adding Krusgraph itself as a custom MCP connector in Lovable is untested and not supported here: Krusgraph signs you in with a Microsoft Entra work account, and Entra does not offer the dynamic client registration that flow would need. Use Krusgraph from Claude, and Lovable's Fabric connector in Lovable. ## Next [Get started](/start) · [Docs](/docs) · [Try the demo graph](/explore) --- # Run Krusgraph locally The hosted connector at `https://krusgraph.com/mcp` reads Fabric through your Microsoft sign-in. The same engine also runs on your own machine as a small MCP server over one ODBC connection: against a SQL Server you reach with Windows sign-in, an Azure SQL database, or a Fabric SQL analytics endpoint. There is no account with us, and nothing goes to the hosted service or to Kruse Consulting AB. Keys and relationships are inferred the same way, with the same rules (K0–K5, R0–R4). **What leaves your machine**, as the code does it: - **The queries to the database in `FABRIC_ODBC`, and their results.** A SQL Server you reach with Windows sign-in stays on your network. An Azure SQL database or a Fabric SQL analytics endpoint is in Microsoft's cloud, and the ODBC driver signs you in with Microsoft (`Authentication=ActiveDirectoryInteractive`). - **The answers, to your MCP client.** Whatever a tool answers goes to the client, and Claude Desktop sends it to Anthropic as part of the conversation, like any tool result: table and column names, the counts from the checks, and the rows `sample_rows` returns. Columns whose name looks like personal data come back masked unless you ask with `show_personal_data=true`. - **`create_backend` only:** it calls the Fabric REST API with a token from `az login` (or `FABRIC_TOKEN`) to create or update the API for GraphQL item. No other local tool calls Fabric's REST API. The local server writes nothing to your disk; the drafts come back in the answer. The command line (`fabric-backend plan … --out …`, below) writes the files you name. Status: **Experimental.** The local server is unit-tested against recorded SQL Server metadata (declared keys, several schemas, names with spaces, `nvarchar(max)`, `datetimeoffset`, `uniqueidentifier`, computed columns, views) and against a Fabric SQL analytics endpoint in the test suite; it has not yet been run against a live SQL Server by us. ## What you need - Python 3.10 or later. - Microsoft ODBC Driver 18 for SQL Server (the local server speaks ODBC). - A database sign-in that can read metadata and run counting queries. Give it `db_datareader` and nothing more; `create_views` is the only tool that writes, and it is a dry run until you say otherwise. ## Install ``` pip install "fabric-backend[mcp] @ git+https://github.com/Krusmaster/fabric-backend" ``` This installs `fabric-backend-mcp` (the local MCP server) and `fabric-backend` (the command line). ## Claude Desktop Add the server to Claude Desktop's configuration (Settings → Developer → Edit config), with your own server and database. Windows sign-in needs no password in the string: ```json { "mcpServers": { "krusgraph-local": { "command": "fabric-backend-mcp", "env": { "FABRIC_ODBC": "Driver={ODBC Driver 18 for SQL Server};Server=sql01;Database=shopdb;Trusted_Connection=yes;TrustServerCertificate=yes", "FABRIC_SOURCE_KIND": "SQLServer" } } } } ``` If Windows cannot find `fabric-backend-mcp`, use the full path of the executable in your Python `Scripts` folder. `TrustServerCertificate=yes` accepts the server's own certificate; drop it when the server has one your machine trusts. For a Fabric endpoint instead, use `Server=;Database=;Authentication=ActiveDirectoryInteractive` and `FABRIC_SOURCE_KIND=Lakehouse` (or `Warehouse`, `SQLDatabase`). Restart Claude Desktop and ask, for example, *"Plan the tables in schema dbo"* or *"Check the plan against the data"*. ## What works locally `list_tables`, `plan_backend`, `validate_plan`, `sample_rows`, `join_path`, `data_dictionary`, `dbt_schema`, `analyze_schema`, `suggest_relationships`, `rayfin_connector`, `create_views` (dry run by default; `CREATE OR ALTER VIEW` in a schema of its own, never DROP), `create_backend` (calls Fabric, see above), `answer_query_draft` (a question as T-SQL over the safest join path; runs nothing) and, for a lakehouse, `silver_notebook_draft` (the notebook that builds silver tables; nothing created). On SQL Server, declared primary and foreign keys are read and become K0 and R0; relationships into another schema are left out rather than guessed. Two drafts are Fabric-specific and answer with an explanation when `FABRIC_SOURCE_KIND` is `SQLServer`: the Power BI semantic model in Direct Lake mode (it needs a Fabric SQL analytics endpoint; open the tables in Power BI Desktop instead, or mirror the database into Fabric) and the Fabric IQ ontology. The tools that need Fabric item ids (`describe_backend`, `plan_drift`, `app_starter`, `app_readiness`, `create_semantic_model`, `create_data_agent`, `lineage`, `create_silver_notebook` and the drift monitor) live in the hosted connector only. ## The 20-minute demo `fabric-backend demo` opens one local page on 127.0.0.1: connect a SQL Server over ODBC or a folder of CSV / Excel files (or try the synthetic shop schema), see the map with the rule behind every connection, check the plan against the rows with Accept and Skip on each suggestion, and get five questions answered on the data with the SQL shown, plus the exports (data dictionary, dbt tests, a Lovable prompt). No account and no service of ours: the page is served on 127.0.0.1, loads nothing from elsewhere, and talks only to the database or the files you choose. Status: Experimental (unit-tested). The script for running it with a customer is in the repository: `docs/DEMO-20-MIN.md`. ## Without any connection: the CSV path Export the columns once (`INFORMATION_SCHEMA.COLUMNS` of one schema as CSV, with the columns `table_schema`, `table_name`, `column_name`, `data_type`, `ordinal_position`) and run the command line on the file: ``` fabric-backend plan columns.csv --schema dbo --mermaid er.mmd fabric-backend dictionary columns.csv --schema dbo --out dictionary.md fabric-backend dbt columns.csv --schema dbo --out schema.yml fabric-backend analyze columns.csv --schema dbo fabric-backend views columns.csv --schema dbo ``` The plan, the diagram, the data dictionary, the dbt sources file and the view SQL come from the names alone, with the rule per key and relationship; the counting checks need a connection. ## Source Open source under the MIT licence: https://github.com/Krusmaster/fabric-backend. The hosted connector and its docs: [/docs](/docs). --- # Use cases: what do you need to understand about your data? Start with an unfamiliar lakehouse, a join you need to check or an app you want to build. These examples show where Krusgraph helps and what still needs your judgement. *The schemas are illustrative examples, not customer projects.* ## Start from your task - **“I inherited a lakehouse nobody has documented.”** An overview: the central tables, how they relate, and the questions you still need to clear up. Tools: `analyze_schema`, `plan_backend`, `data_dictionary`. Status: tested end to end on 27 October 2026. - **“I need to check how these tables should join.”** A suggested join with its reason, a check against the rows, and what stays uncertain. Tools: `join_path`, `validate_plan`, `suggest_relationships`. Status: tested end to end on 27 October 2026. - **“I want to build an app on this data.”** The reviewed plan, an API created from it in your workspace, and the setup steps the app still needs. Tools: `create_backend`, `app_starter`. Status: tested end to end on 27 October 2026. Plan from names (`plan_backend`, nothing is deployed), check against the rows (`validate_plan`, a step you ask for), then create (`create_backend`). Ambiguous columns are reported, never guessed. ## What comes out - **GraphQL API**: an API for GraphQL item in your workspace, for an app or for questions in Claude (creating a new API is experimental; updating one in place was tested end to end on 27 October 2026; querying an existing one: tested end to end) - **SQL views**: wide T-SQL views over the plan's joins on the SQL analytics endpoint (tested end to end as a dry run; creating them: experimental) - **Fabric IQ ontology draft**: entity types, keys and relationships (preview) (tested end to end (a draft; creates nothing)) - **Semantic model**: a Power BI model in TMDL from the same keys (the draft: tested end to end (a draft; creates nothing); the item: available when enabled by the operator; tested end to end as a dry run) - **Data agent**: a Fabric data agent with the plan as instructions (available when enabled by the operator; tested end to end as a dry run) ## How to read the rules - **K2**: a key found by name: `id`, `
_id`, `
_no`, … - **K1**: a key you give, for example a composite one - **K4**: a junction: the leading reference columns together, such as `customer_id + account_id` - **K5**: a line: the reference to the header plus a line number, such as `order_id + line_no` - **K3**: no key: the table can be queried, but nothing can point at it - **R1**: a column named after another table: `customer_id` → `customers` - **R2**: the same name as another table's single key column - **R3**: all columns of another table's composite key - **R4**: the weaker match: a role in front of a table's name, such as `billing_account_id` → `accounts`; check it - **to confirm**: a column that names no table, such as `manager_id`: linked only when you confirm it ## Retail & e-commerce **Follow a return back to the right order line.** A returned product belongs to an order line, not just an order. In this example, the plan uses `order_id` and `line_no` together so you can review that relationship before creating the API. An app can then read the order, its products and its returns through the same model. ### Typical tables - **customers**: `customer_id` (key, K2), `email`, `country`, `created_at` - **orders**: `order_id` (key, K2), `customer_id` (→ customers, R1), `order_date`, `status` - **order_lines**: `order_id` (key, K5), `line_no` (key, K5), `product_id` (→ products, R1), `quantity`, `unit_price` - **products**: `product_id` (key, K2), `sku`, `category_id` (→ categories, R1), `list_price` - **categories**: `category_id` (key, K2), `parent_category_id` (→ categories? to confirm), `name` - **returns**: `return_id` (key, K2), `order_id` (→ orders, R1), `line_no`, `reason`; `order_id + line_no` → order_lines (R3) ### What Krusgraph finds - **R1**: `orders.customer_id` → `customers`: the column is named after the table. The same rule links `order_lines.product_id` → `products` and `products.category_id` → `categories`. - **K5**: `order_lines` has no key column of its own. The line rule (K5) takes the reference to its header plus the line number, `order_id + line_no`, and `returns.order_id + line_no` → `order_lines` follows by R3. - **to confirm**: `categories.parent_category_id` points back at its own table, and no rule links a table to itself by name. Confirm it once and the API gets a `parent_category` field. ### What you build - A returns desk in Lovable over the GraphQL API: an order, its lines and products, and what came back, in one nested query. - Questions in Claude through `query_graphql`, such as which categories see the most returns. - A Power BI semantic model draft from the same keys. ### Why it matters - The line-level key is stated once, so a return is joined to its order line, not to the whole order. ## Manufacturing **Keep the parent item and the component apart.** A bill of materials can refer to the same item table twice. The plan shows both roles so you can check which column means the parent and which means the component. This example covers one level of the bill of materials; a full recursive breakdown is separate work. ### Typical tables - **items**: `item_no` (key, K2), `description`, `unit`, `item_type` - **bill_of_materials**: `parent_item_no` (key, K4), `component_item_no` (key, K4), `quantity` - **work_orders**: `work_order_id` (key, K2), `item_no` (→ items, R1), `machine_id` (→ machines, R1), `planned_start`, `status` - **machines**: `machine_id` (key, K2), `line_name`, `model` - **quality_checks**: `quality_check_id` (key, K2), `work_order_id` (→ work_orders, R1), `checked_at`, `result` ### What Krusgraph finds - **R1**: `items` gets `item_no` as its key (K2 also reads `_no`), and `work_orders.item_no` → `items` follows. `work_orders.machine_id` → `machines` and `quality_checks.work_order_id` → `work_orders` the same way. - **R4**: `bill_of_materials` points at `items` twice, through `parent_item_no` and `component_item_no`. Both are linked by R4, the weaker rule for a role in front of a table's name, as `parent_item` and `component_item` fields. Check them before you build. - **K4**: `bill_of_materials` has no key column and starts with those two references, so the junction rule (K4) makes `parent_item_no + component_item_no` its key. `validate_plan` counts duplicates and NULLs in it before anything is built. ### What you build - A shop-floor app over GraphQL: a work order with its item, machine and quality checks. - One level of a bill of materials per query. A full explosion is recursive, and that stays a SQL job. - Questions in Claude, such as which machines had failed checks last week. ### Why it matters - The two-way bill of materials is a classic wrong join. Both roles carry the weaker rule, so they get reviewed instead of assumed. ## SaaS & subscriptions **Which account uses the service, and which one pays?** Accounts on plans, invoices with lines, and a stream of usage events. ### Typical tables - **accounts**: `account_id` (key, K2), `name`, `owner_email`, `created_at` - **plans**: `plan_id` (key, K2), `name`, `monthly_price` - **subscriptions**: `subscription_id` (key, K2), `account_id` (→ accounts, R1), `plan_id` (→ plans, R1), `billing_account_id` (→ accounts, R4), `started_at` - **invoices**: `invoice_id` (key, K2), `account_id` (→ accounts, R1), `subscription_id` (→ subscriptions, R1), `issued_at`, `total` - **invoice_lines**: `invoice_id` (key, K5), `line_no` (key, K5), `description`, `amount` - **usage_events**: `event_id`, `subscription_id` (→ subscriptions, R1), `event_type`, `occurred_at`; no key (K3) ### What Krusgraph finds - **R1**: `subscriptions.account_id` → `accounts` and `subscriptions.plan_id` → `plans`; `invoices.subscription_id` → `subscriptions` as well. - **R4**: `subscriptions.billing_account_id` → `accounts` by R4, the weaker rule for a role in front of a table's name, as a `billing_account` field. Probably right, for example a parent company paying for a subsidiary, but only you know: check it, or confirm another target. - **K5**: `invoice_lines` gets `invoice_id + line_no` by the line rule (K5). `usage_events` has `event_id`, which is not named after the table, so it gets no key (K3). It still points at `subscriptions`, but nothing can point at it until you give the key (K1). ### What you build - A self-service billing page in Lovable: an account, its subscriptions, invoices and invoice lines. - Usage per plan, asked in Claude and answered through the API without writing SQL. - A semantic model draft for revenue reporting from the same keys. ### Why it matters - The using account and the paying account are kept apart from the start, not discovered in a wrong revenue figure later. ## Professional services **See which project hours are still unbilled.** Start with clients, projects, consultants and time entries. Review how an entry connects to its project and invoice, including entries with no invoice yet. The resulting API gives an internal tool a way to read those records together. ### Typical tables - **clients**: `client_id` (key, K2), `name`, `sector` - **consultants**: `consultant_id` (key, K2), `name`, `grade`, `manager_id` (→ consultants? to confirm) - **projects**: `project_id` (key, K2), `client_id` (→ clients, R1), `lead_consultant_id` (→ consultants, R4), `name` - **time_entries**: `time_entry_id` (key, K2), `consultant_id` (→ consultants, R1), `project_id` (→ projects, R1), `work_date`, `hours`, `invoice_id` (→ invoices, R1) - **invoices**: `invoice_id` (key, K2), `client_id` (→ clients, R1), `issued_at`, `amount` ### What Krusgraph finds - **R1**: `time_entries.project_id` → `projects`, `time_entries.consultant_id` → `consultants` and `time_entries.invoice_id` → `invoices`. An empty `invoice_id` is an unbilled hour: `validate_plan` counts rows with a value and orphans separately. - **R4**: `projects.lead_consultant_id` → `consultants` by R4, a role in front of the table's name, as a `lead_consultant` field. `consultants.manager_id` names no table at all, so it stays unlinked until you confirm it; then it is `manager`. - **K2**: Every table here has a key by name (`client_id`, `project_id`, `time_entry_id`, …), so the API can fetch any single row by its key. ### What you build - A utilisation app: hours per consultant and project, with the client one level up. - Questions in Claude, such as which projects have hours that are not invoiced yet. - A Fabric IQ ontology draft (preview) with Client, Project, Consultant and Time entry entity types. ### Why it matters - Hours to invoices is the join that decides revenue. Its rule and its orphan count are visible before anything is built. ## Financial services: AML **Trace a transaction through its account holders.** Anti-money laundering: customers, the accounts they hold, transactions, counterparties, alerts and KYC reviews. ### Typical tables - **customers**: `customer_id` (key, K2), `name`, `country`, `risk_rating` - **accounts**: `account_id` (key, K2), `product_type`, `opened_at`, `status` - **account_holders**: `customer_id` (key, K4), `account_id` (key, K4), `holder_role`, `since` - **transactions**: `transaction_id` (key, K2), `account_id` (→ accounts, R1), `counterparty_id` (→ counterparties, R1), `beneficiary_account_id` (→ accounts, R4), `amount`, `booked_at` - **counterparties**: `counterparty_id` (key, K2), `name`, `country`, `bank_code` - **alerts**: `alert_id` (key, K2), `transaction_id` (→ transactions, R1), `customer_id` (→ customers, R1), `scenario`, `raised_at` - **kyc_reviews**: `kyc_review_id` (key, K2), `customer_id` (→ customers, R1), `reviewed_at`, `outcome` ### What Krusgraph finds - **R1**: `transactions.account_id` → `accounts`, `transactions.counterparty_id` → `counterparties` and `alerts.transaction_id` → `transactions`. - **K4**: `account_holders` is a bridge with two outgoing references: `account_holders.customer_id` → `customers` and `account_holders.account_id` → `accounts`, both R1. The junction rule (K4) makes `customer_id + account_id` its key: one row per holding. - **R4**: `transactions.beneficiary_account_id` → `accounts` by R4, the weaker rule for a role in front of a table's name, as a `beneficiary_account` field. The plan says which rule made it, so it is checked rather than trusted. ### What you build - One explained model that investigators and an agent in Claude query the same way. - Questions such as all transactions between accounts held by the same customer in the last 90 days: one nested GraphQL query from customer through `account_holders`, accounts and transactions to the beneficiary account, with the rule or confirmation behind each step. - A read-only investigation app in Lovable over the same API. ### Why it matters - The bridge and the beneficiary account are each stated once with their rules, and `validate_plan` counts orphans before anyone relies on them. - This shows the joins an investigation tool needs. Krusgraph is not an investigation system: alert logic, scoring and the organisation's own regulatory validation are separate work. ### The next layer Krusgraph works on how the tables connect. Finding rings among individual customers and accounts (communities, shared addresses or phones, money moving in cycles) is row-level graph analysis, built on top of that model, for example with graph tools in Fabric. The explained model makes it possible; it is not a Krusgraph feature today. ## Financial crime prevention **Bring a customer’s cases and screening hits into one view.** Fraud, sanctions screening and KYC: alerts and cases from teams that usually keep separate tables. ### Typical tables - **customers**: `customer_id` (key, K2), `name`, `onboarded_at` - **cases**: `case_id` (key, K2), `customer_id` (→ customers, R1), `linked_case_id` (→ cases? to confirm), `case_type`, `status` - **alerts**: `alert_id` (key, K2), `case_id` (→ cases, R1), `customer_id` (→ customers, R1), `source_system`, `raised_at` - **screening_hits**: `screening_hit_id` (key, K2), `customer_id` (→ customers, R1), `list_entry_id`, `match_score` - **sanctions_lists**: `list_entry_id`, `list_name`, `entry_name`, `published_at`; no key (K3) - **devices**: `device_id` (key, K2), `fingerprint`, `first_seen` - **logins**: `login_id` (key, K2), `customer_id` (→ customers, R1), `device_id` (→ devices, R1), `ip_address`, `logged_in_at` ### What Krusgraph finds - **R1**: `logins.device_id` → `devices`, `logins.customer_id` → `customers` and `alerts.case_id` → `cases`: fraud signals, alerts and cases meet at the customer. - **R2**: `sanctions_lists` has one row per list entry, keyed by `list_entry_id`, which no key rule reads (K3). Give it as the key (K1) and `screening_hits.list_entry_id` → `sanctions_lists` follows by R2. - **to confirm**: `cases.linked_case_id` ties a case to an earlier one. It points back at its own table, which no rule links by name, so you confirm it; then each case has a `linked_case` field. ### What you build - One connected view for fraud, sanctions and AML analysts: a customer with cases, alerts, screening hits and logins. - Questions in Claude, such as which customers with an open screening hit also have a fraud alert this month. - A Fabric IQ ontology draft (preview) as a shared vocabulary across the teams. ### Why it matters - Each relationship includes its rule or confirmation, so a reviewer can inspect why it was added. ### The next layer Several customers sharing one device is a row-level question this model can answer (device, its logins, their customers). Scoring, network analysis and screening logic on top are separate work; Krusgraph supplies the explained joins. ## Pharmaceutical research **A visit number is not always a unique key.** Clinical studies and lab work: subjects at sites, visits, adverse events, compounds and assay results. ### Typical tables - **studies**: `study_id` (key, K2), `compound_id` (→ compounds, R1), `phase`, `start_date` - **sites**: `site_id` (key, K2), `country`, `investigator` - **subjects**: `subject_id` (key, K2), `study_id` (→ studies, R1), `site_id` (→ sites, R1), `arm_code`, `enrolled_at` - **visits**: `subject_id` (→ subjects, R1), `visit_no` (key, K2), `visit_date`, `visit_type` - **adverse_events**: `adverse_event_id` (key, K2), `subject_id` (→ subjects, R1), `visit_no` (→ visits, R1), `severity_code`, `meddra_code` - **compounds**: `compound_id` (key, K2), `code_name`, `modality` - **assays**: `assay_id` (key, K2), `assay_type`, `unit` - **results**: `result_id` (key, K2), `assay_id` (→ assays, R1), `subject_id` (→ subjects, R1), `visit_no` (→ visits, R1), `value` ### What Krusgraph finds - **R1**: `visits.subject_id` → `subjects`, `results.assay_id` → `assays`, `subjects.site_id` → `sites` and `studies.compound_id` → `compounds`. - **K1**: From names alone, `visits` gets `visit_no` as its key (K2) and `results.visit_no` → `visits` (R1). Both are wrong: visit numbers repeat per subject, and `validate_plan` shows the duplicates. The engine does not suggest the fix here; you give `subject_id + visit_no` (K1). The single-column links go, and `results` and `adverse_events` reach `visits` by R3 on both columns. - **to confirm**: Coded columns such as `adverse_events.severity_code`, `adverse_events.meddra_code` and `subjects.arm_code` point at code lists. No rule links them and nothing is guessed: add the code-list tables, or confirm each link. ### What you build - An agent in Claude that answers research questions through the explained plan, such as which compounds had adverse events at more than one site. - A study overview app: a subject with visits, results and adverse events. - A semantic model draft for study reporting. ### Why it matters - The visit key above is a confident-but-wrong join, caught before an answer depends on it. - Regulated use, such as GxP work, needs the organisation's own validation; Krusgraph does not provide it. ### Better answers Asked which compounds had adverse events at more than one site, Claude goes from adverse events to subjects, then to sites and to studies and compounds, through joins that were planned, explained and checked. The answer can show its path and the rule behind every step, and `validate_plan` has already shown whether rows go missing on the way. ## Environment & sustainability **See which emission factor an activity refers to.** Sites, energy meters and readings, activities with emission factors, and purchases from suppliers (Scope 3). ### Typical tables - **sites**: `site_id` (key, K2), `name`, `region` - **meters**: `meter_id` (key, K2), `site_id` (→ sites, R1), `energy_type` - **meter_readings**: `meter_id` (→ meters, R1), `read_at`, `value_kwh`; no key (K3) - **activities**: `activity_id` (key, K2), `site_id` (→ sites, R1), `emission_factor_id` (→ emission_factors, R1), `activity_type`, `quantity`, `period_start` - **emission_factors**: `emission_factor_id` (key, K2), `activity_type`, `valid_from`, `kg_co2e_per_unit` - **suppliers**: `supplier_id` (key, K2), `name`, `country` - **purchases**: `purchase_id` (key, K2), `supplier_id` (→ suppliers, R1), `site_id` (→ sites, R1), `spend_category`, `amount`, `purchased_at` ### What Krusgraph finds - **R1**: `activities.emission_factor_id` → `emission_factors`: the factor behind each activity is an explained join. `meters.site_id` → `sites` and `meter_readings.meter_id` → `meters` as well. - **R1**: `purchases.supplier_id` → `suppliers` and `purchases.site_id` → `sites`, for spend-based Scope 3 per site and supplier. - **K3**: `meter_readings` has no key, which is fine for rows that are read, not pointed at. Where a factor is picked by activity type and date range instead of an id, that is not a key relationship: Krusgraph does not infer it, and it belongs in a SQL view. ### What you build - A site app over GraphQL: a site with its meters, activities and purchases. - Questions in Claude, such as emissions per site and quarter with the factor behind each figure. - Factor matching by date range is not a key relationship; write it as a SQL view by hand. ### Why it matters - Each activity points at the factor row it used, so a changed factor shows up as a changed relationship. - It documents joins. It does not make a report meet any reporting standard. ## Property & real estate **Which unit does this maintenance request belong to?** Properties split into units, leased to tenants, with maintenance requests. ### Typical tables - **properties**: `property_id` (key, K2), `address`, `city` - **units**: `unit_id` (key, K2), `property_id` (→ properties, R1), `floor`, `area_m2` - **tenants**: `tenant_id` (key, K2), `name`, `contact_email` - **leases**: `lease_id` (key, K2), `unit_id` (→ units, R1), `tenant_id` (→ tenants, R1), `start_date`, `monthly_rent` - **maintenance_requests**: `maintenance_request_id` (key, K2), `unit_id` (→ units, R1), `reported_by_tenant_id` (→ tenants, R4), `status`, `opened_at` ### What Krusgraph finds - **R1**: `units.property_id` → `properties` (the plural is understood), `leases.unit_id` → `units` and `leases.tenant_id` → `tenants`. - **R4**: `maintenance_requests.reported_by_tenant_id` → `tenants` by R4, a role in front of the table's name, as a `reported_by_tenant` field. Check it before you build. - **K2**: All five tables have a key by name, so the API can fetch any unit, lease or request by its key. ### What you build - A caretaker app in Lovable: a unit with its lease, tenant and open requests. Creating requests from the app needs a warehouse or SQL database source; lakehouse sources are read-only through the API. - Questions in Claude, such as which properties have the most open requests per unit. - A Fabric IQ ontology draft (preview) with Property, Unit, Lease and Tenant entity types. ### Why it matters - Tenant, lease and unit are joined once, so a report does not have to guess which tenant belongs to which unit. ## Education **What happens when a student takes the same course twice?** Students enrolled in courses each term, taught by teachers, with grades. ### Typical tables - **students**: `student_id` (key, K2), `name`, `cohort` - **teachers**: `teacher_id` (key, K2), `name`, `department` - **courses**: `course_id` (key, K2), `teacher_id` (→ teachers, R1), `title`, `credits` - **enrolments**: `student_id` (key, K4), `course_id` (key, K4), `term`, `enrolled_at` - **grades**: `grade_id` (key, K2), `student_id` (→ students, R1), `course_id` (→ courses, R1), `term`, `grade`; `student_id + course_id` → enrolments (R3) ### What Krusgraph finds - **R1**: `courses.teacher_id` → `teachers`, `enrolments.student_id` → `students` and `enrolments.course_id` → `courses`. - **K4**: `enrolments` has no key column and starts with two references, so the junction rule (K4) makes `student_id + course_id` its key, and `grades` reaches it by R3. If a student can retake a course in another term, that key is wrong, and `validate_plan` shows the duplicates. - **K1**: Give `student_id + course_id + term` (K1). Then `grades.student_id + course_id + term` → `enrolments` follows by R3 on all three columns. ### What you build - A course overview app: a course, its teacher and the students enrolled this term. - Questions in Claude, such as grades per course and term. - A semantic model draft for term reports from the same keys. ### Why it matters - A three-column key is easy to half-write in a join and then double-count. Stating it once avoids that. ## For Fabric consultancies Start a client conversation from their own schema instead of a slide. The plan lists every key and relationship with the rule behind it, so a review with the client's data owners has something concrete to check. The rules and the engine are open source. Start at [https://krusgraph.com/start](https://krusgraph.com/start), read [https://krusgraph.com/docs](https://krusgraph.com/docs), and email Gustav at hello@krusgraph.com to discuss a pilot. --- # Privacy policy — Krusgraph Krusgraph is operated by Kruse Consulting AB, Sweden (org. nr 559174-8719). This page explains what passes through the service, how access tokens and technical logs are handled, and which other services are involved. Contact: hello@krusgraph.com. ## What the service does Krusgraph is a remote MCP server. When you use it from Claude (or another MCP client), it reads the metadata of the Microsoft Fabric items you choose and infers keys and relationships. Only when you ask for it does it count or read rows, run a read-only GraphQL query, or create or update an item in your Fabric workspace. ## Your Microsoft account You sign in with your own Microsoft Entra work account. The service exchanges that sign-in for Microsoft Fabric and Azure SQL access tokens on your behalf (OAuth 2.0 on-behalf-of). It acts as you, within two limits: the delegated permissions your administrator granted (listed under [What it accesses](/docs#what-it-accesses): reading workspaces, items and the SQL analytics endpoint, running your GraphQL queries, and creating or updating the API for GraphQL item or the views you ask for), and what your own account may do in Fabric. It cannot reach anything your account cannot. Your organization's administrator grants this once and can revoke it at any time in Microsoft Entra ID (Enterprise applications). ## Metadata, samples and query results What the service reads from Fabric depends on the tool you ask for: - **Metadata.** Workspace, item, table and column names and types, read to build the plan, a draft or the definition you asked for. Some tools also read the definitions of items in the workspace (GraphQL APIs, semantic models, data agents, views) to describe them or to show what builds on a table. - **Checks against the rows.** `validate_plan`, `suggest_relationships` and `data_dictionary` with `validate=true` run counting queries in Fabric. Only the counts come back, not the row values. - **Sample rows.** `sample_rows` returns up to 20 rows of one table, with long text shortened. Columns whose name looks like personal data (a person's name, e-mail, telephone, national identity number, address, birth date; one rule per pattern, judged by the column's name and never by its values) come back as `***` unless you ask with `show_personal_data=true`. `answer_query_draft` and the gold views leave those columns out the same way. - **Sensitivity labels.** When Fabric returns a Microsoft Purview sensitivity label on the source item, it comes with the item's metadata as the label's id only; `sample_rows` and `validate_plan` name it. The service does not look the label up anywhere else. - **A semantic model's definition.** `compare_semantic_model` reads the definition of the model you name (its tables, columns, measures and relationships; no rows), or the definition files you pass it. - **Query results.** `query_graphql` returns the results of the query you ran. - **What you create.** The creating tools write the definition you asked for into your Fabric workspace. `export_item_definitions` returns the same definitions as files and sends them nowhere: no call goes to GitHub, Azure DevOps or any other Git provider. All of this passes through the service's memory on its way to your MCP client, in the response to your request, and is then discarded. Apart from the few sign-in records described under *Sign-in from MCP clients other than Claude* below, the service has no database, no cache on disk and no file storage of its own, and none of what Fabric returns is ever stored. It counts how its tools are used, anonymously: which tool, how the call ended and how long it took (see *Usage statistics* below), never what you asked or what came back. ## Access tokens Your access tokens are validated and exchanged in memory. They are never written to disk, never logged, and are dropped shortly before they expire. When you sign in from an MCP client other than Claude, the service obtains the Microsoft tokens for that sign-in itself and hands the access token to your client. Until your client collects them (at most 60 seconds) they are kept encrypted; Microsoft's refresh token stays encrypted with the service for as long as your client keeps renewing its access, at most 90 days, and is never given to the client. Details below. ## What we process | Data | Why | Kept | | --- | --- | --- | | Your access tokens | to call Fabric and SQL as you | in memory only, until shortly before they expire; never written to disk or logs | | Workspace, item, table and column names, and item definitions | to build the plan, the draft or the definition you asked for | only for the duration of the request | | Counts from the checks, sample rows and query results you request | returned to your MCP client | not stored | | Sign-in records for MCP clients other than Claude: a sign-in in progress (the client's address, its redirect address and its request); the Microsoft tokens of a finished sign-in, encrypted; Microsoft's refresh token, encrypted, with a hash of the refresh token your client holds | signing in from VS Code, Cursor and other MCP clients through krusgraph.com/mcp/connect | in an Azure Table Storage table of ours in the service's Azure region: 10 minutes, 60 seconds, and at most 90 days, each deleted once used | | Your approval of an MCP client that receives the answer at a web address: a keyed hash of your user and organisation ids (not the ids themselves), the client's address, name and redirect address, and the date | not asking you again at every sign-in | the same place, 180 days | | A cookie in your browser on krusgraph.com with the same keyed hash, signed; and two short-lived cookies that protect the approval form and the sign-in | recognising your earlier approval; stopping forged approvals | 180 days, and 10 minutes, in your browser | | The IP address of a sign-in request, as a salted hash | limits on sign-in attempts per network | in memory only, about an hour; never written anywhere | | Your user id and organisation id from the sign-in (`oid`, `tid`), as salted hashes | fair-use limits: how many calls per user and per organisation in the last minute and hour | in memory only, until about an hour after your last call; never written anywhere | | A usage record per tool call: the tool's name, the outcome (`ok` or the error kind), the duration, and for the creating tools whether it was a dry run | counting use, errors and response times per tool | not stored: the console stream only; totals in memory until the server restarts | | Technical console log: time, method, path, HTTP status and the connection's IP address — no headers, no tokens, no query text | operating and securing the service | not forwarded or stored by us; see *Technical logs* below | We do not store your data, do not use it to train models, do not sell or share it, and do not combine it with other data. Row values pass through the service only when you ask for sample rows or run a query, and only in the response to you. ## Usage statistics For each tool call the server records exactly these fields: `tool` (the tool's name), `outcome` (`ok`, or the error kind such as `not_found` or `rate_limited`), `ms` (the duration) and, for the tools that create something, `dry_run` (true or false). Nothing else: no user id, no name or e-mail, no IP address, no workspace, item, table or column names, no queries, no plans and no results. The record is one line on the server's console; the totals since the server started are kept in memory and, when the operator turns it on, readable at a protected `/metrics` address. ## Technical logs The server writes a technical access line per request to its console (time, method, path, HTTP status and the connection's IP address; never headers, tokens or query text), the usage record above, and the hosting platform keeps its own system log about starting and stopping containers. The service runs in Azure Container Apps in an environment created *without a log destination*: console and system logs are not sent to Log Analytics or to any storage we control, and we keep no copy. The operator can watch the live console stream of a running container; nothing persists beyond that. We do not claim "zero logs": Microsoft retains platform-level metrics and diagnostics for Azure resources under Microsoft's own terms, and Microsoft Entra ID records sign-ins in your organization's own tenant. ## Sign-in from MCP clients other than Claude Claude signs in directly with Microsoft. Other MCP clients (VS Code, Cursor, Glama and others) sign in through the service's own sign-in at krusgraph.com/oauth, in front of Microsoft. Before you are sent to Microsoft, Krusgraph shows a page naming the client, its domain and where the answer goes, and asks you to approve it. For a client that receives the answer at a web address, the approval is remembered for that client and that address for 180 days; for a program on your own computer you are asked every time. To identify a client, the service fetches the client's published metadata document from the client's own domain (for example vscode.dev); that request carries nothing about you. What is stored, and for how long, is in the table above: no names, no e-mail addresses, no IP addresses and no tokens in clear. The records live in an Azure Table Storage table of ours in the service's Azure region, which only the service can read. The cookies are strictly necessary for the sign-in and are not used for anything else. To withdraw an approval or end a client's sign-in, write to hello@krusgraph.com; your organisation's administrator can also revoke the app in Microsoft Entra ID, which ends every renewal. ## Third parties: Microsoft and your MCP client The service runs on Microsoft Azure in the EU (Sweden Central). It calls Microsoft Fabric, Microsoft Entra ID and Azure SQL on your behalf; Microsoft handles that under its own terms. Your MCP client receives the responses: when you use the service from Claude, Anthropic's privacy policy covers what Claude does with them. The website loads no third-party resources: fonts, scripts and images are served from krusgraph.com, and there is no analytics or tracking. ## Contact and requests **Your rights.** Under the GDPR you can ask what we hold about you and have it deleted; in practice we hold nothing about you beyond the console log described above and, when you sign in from an MCP client other than Claude, the sign-in records described above, and the usage records carry no user id. Write to hello@krusgraph.com. You can complain to the Swedish Authority for Privacy Protection (IMY). **Changes.** We date every change to this page. Last updated: 2026-10-27.