Skip to main content
Glama

Query Building Data (GraphQL)

tacit_graphql
Read-onlyIdempotent

Execute GraphQL queries to retrieve building data, analyze equipment relationships, and explore sensor information from digital twin models.

Instructions

Execute a GraphQL query against the Tacit building digital twin API.

Compose any query using the Brick-compliant schema. Supports nested fields, filtering by Brick class, supply chain traversal (upstream/downstream), and recursive location hierarchy.

Use tacit_list_sites first to get a valid site ID, then construct queries freely.

Args:

  • site_id (string, required): The site ID (injected as siteId into your query variables)

  • query (string, required): GraphQL query string

  • variables (string, optional): JSON-encoded variables object (siteId is auto-injected)

Tacit GraphQL Schema - Brick-compliant Building API

Root Queries

All root queries require siteId (get from tacit_list_sites).

building(siteId!, id, name, nameMatch) → [Building] equipment(siteId!, id, name, nameMatch, locationId, locationName, systemId, is, hasProperty, propertyValue) → [Equipment] point(siteId!, id, name, nameMatch, equipmentId, locationId, locationName, zoneId, systemId, is, equipmentIs, hasProperty, propertyValue) → [Point] zone(siteId!, id, name, nameMatch, locationId, is, hasProperty, propertyValue) → [Zone] system(siteId!, name, nameMatch, is, hasProperty, propertyValue) → [System] location(siteId!, locationId!) → Location entityByIfcId(siteId!, ifcId!) → KgEntity (union: Building | Location | Zone | System | Equipment)

Types and Fields

Building { uri, id, name, type, ifcId, properties { name value unit } locations(name, nameMatch, is, recursive) → [Location] zones(name, nameMatch, is, recursive) → [Zone] systems(name, nameMatch, is, recursive) → [System] equipment(name, nameMatch, is, recursive) → [Equipment] points(name, nameMatch, is, recursive) → [Point] }

Equipment { uri, id, name, type, typeHierarchy, ifcId, properties { name value unit } points(name, nameMatch, is) → [Point] # sensors/actuators on this equipment parts(name, nameMatch, is) → [Equipment] # sub-components partOf → Equipment # parent equipment feeds(name, nameMatch, is) → [Equipment] # what this equipment feeds fedBy(name, nameMatch, is) → [Equipment] # what feeds this equipment upstream(maxDepth, medium, is) → [Equipment] # full upstream chain downstream(maxDepth, medium, is) → [Equipment] # full downstream chain location → Location systems → [System] }

Point { uri, id, name, type, typeHierarchy, unit, equipmentId, timeseriesId currentValue { value timestamp quality } # latest live reading (null if no data) properties { name value unit } equipment → Equipment location → Location }

Zone { uri, id, name, type, typeHierarchy, ifcId, properties { name value unit } points(name, nameMatch, is) → [Point] fedBy(name, nameMatch, is) → [Equipment] # equipment feeding this zone upstream(maxDepth, medium, is) → [Equipment] locations → [Location] }

System { uri, id, name, type, ifcId, properties { name value unit } equipment(name, nameMatch, is, recursive) → [Equipment] points(name, nameMatch, is, recursive) → [Point] }

Location { uri, id, name, type, ifcId, properties { name value unit } locations(name, nameMatch, is, recursive) → [Location] # child locations parent → Location equipment(name, nameMatch, is, recursive) → [Equipment] points(name, nameMatch, is, recursive) → [Point] zones → [Zone] }

Enums

NameMatch: CONTAINS | EXACT (default: CONTAINS)

Filter Parameter Guide

  • "is" filters by Brick class: "AHU", "VAV", "FCU", "Temperature_Sensor", "HVAC_Zone", etc.

  • "recursive: true" traverses the full hierarchy (e.g. all equipment in a building, not just direct children)

  • "nameMatch: EXACT" for exact name match, CONTAINS for partial

  • "upstream/downstream" traces the feeds/fedBy supply chain (use maxDepth to limit)

  • "medium" on upstream/downstream filters by medium type (e.g. "HOT_WATER", "CHILLED_WATER", "AIR")

  • "hasProperty" + "propertyValue" filter entities by custom properties

  • "equipmentIs" on points filters by the Brick class of the parent equipment

Example Queries

List AHUs with their sensor points

{ equipment(siteId: "x", is: "AHU") { name type points { name type unit timeseriesId } } }

Trace what feeds a zone

{ zone(siteId: "x", name: "Atrium") { name upstream(maxDepth: 3) { name type } } }

Building floor hierarchy with equipment

{ building(siteId: "x") { name locations(recursive: true) { name type equipment { name type } } } }

Equipment detail with parts and supply chain

{ equipment(siteId: "x", name: "AHU-001") { name type parts { name type } feeds { name type } fedBy { name type } points { name type unit timeseriesId } } }

All temperature sensors with current values

{ point(siteId: "x", is: "Temperature_Sensor") { name unit timeseriesId currentValue { value timestamp } equipment { name type } location { name } } }

Points on VAVs in a specific location

{ point(siteId: "x", locationName: "Tower West", equipmentIs: "VAV") { name type unit timeseriesId equipment { name } } }

Look up an entity by its IFC Global ID

{ entityByIfcId(siteId: "x", ifcId: "3Zu5Bv0LOHrPC6") { ... on Equipment { name type points { name } } ... on Location { name type } } }

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
site_idYesSite ID from tacit_list_sites
queryYesGraphQL query string
variablesNoJSON-encoded variables (siteId is auto-injected)

Implementation Reference

  • The handler function that executes the GraphQL query against the Tacit building digital twin API.
      async ({ site_id, query, variables: varsJson }) => {
        try {
          let vars: Record<string, unknown> = {};
          if (varsJson) {
            try {
              vars = JSON.parse(varsJson);
            } catch {
              return {
                content: [
                  {
                    type: "text" as const,
                    text: "Error: 'variables' must be valid JSON.",
                  },
                ],
                isError: true,
              };
            }
          }
          vars.siteId = site_id;
    
          const data = await graphql<Record<string, unknown>>(query, vars);
          const json = JSON.stringify(data, null, 2);
    
          // Count top-level result items for context
          const counts: string[] = [];
          for (const [key, val] of Object.entries(data)) {
            if (Array.isArray(val)) {
              counts.push(`${val.length} ${key}`);
            }
          }
          const summary = counts.length ? `Returned: ${counts.join(", ")}.` : "";
    
          // Truncate oversized responses to protect context window
          if (json.length > MAX_RESPONSE_CHARS) {
            const truncated = json.slice(0, MAX_RESPONSE_CHARS);
            return {
              content: [
                {
                  type: "text" as const,
                  text: `${summary ? summary + "\n\n" : ""}${truncated}\n\n--- TRUNCATED (${json.length} chars, limit ${MAX_RESPONSE_CHARS}) ---\nResponse too large. Narrow your query with filters: "is" (Brick class), "name"/"nameMatch", specific "id", or remove nested fields like "recursive: true".`,
                },
              ],
            };
          }
    
          return {
            content: [
              {
                type: "text" as const,
                text: `${summary ? summary + "\n\n" : ""}${json}`,
              },
            ],
          };
        } catch (error) {
          return {
            content: [
              {
                type: "text" as const,
                text: `Error: ${error instanceof Error ? error.message : String(error)}`,
              },
            ],
            isError: true,
          };
        }
      },
    );
  • The registration of the tacit_graphql tool in the McpServer.
      server.registerTool(
        "tacit_graphql",
        {
          title: "Query Building Data (GraphQL)",
          description: `Execute a GraphQL query against the Tacit building digital twin API.
    
    Compose any query using the Brick-compliant schema. Supports nested fields, filtering by Brick class, supply chain traversal (upstream/downstream), and recursive location hierarchy.
    
    Use tacit_list_sites first to get a valid site ID, then construct queries freely.
    
    Args:
      - site_id (string, required): The site ID (injected as siteId into your query variables)
      - query (string, required): GraphQL query string
      - variables (string, optional): JSON-encoded variables object (siteId is auto-injected)
    
    ${SCHEMA_REFERENCE}`,
          inputSchema: {
            site_id: z.string().describe("Site ID from tacit_list_sites"),
            query: z.string().describe("GraphQL query string"),
            variables: z
              .string()
              .optional()
              .describe("JSON-encoded variables (siteId is auto-injected)"),
          },
          annotations: {
            readOnlyHint: true,
            destructiveHint: false,
            idempotentHint: true,
            openWorldHint: true,
          },
        },
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, destructiveHint=false, openWorldHint=true, and idempotentHint=true, covering safety and idempotency. The description adds valuable context about the API's Brick-compliant schema, query capabilities, and auto-injection of siteId into variables, which helps the agent understand the tool's behavior beyond annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is overly long and includes extensive schema documentation (e.g., root queries, types, enums, examples) that belongs in external documentation. While informative, it's not front-loaded and contains redundant details that could be streamlined for an agent-focused tool description.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (GraphQL API with rich querying) and lack of output schema, the description provides comprehensive context including schema overview, filter guides, and examples. However, the excessive detail reduces focus on core agent guidance, though it compensates for missing output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all parameters (site_id, query, variables). The description adds minimal extra semantics (e.g., 'siteId is auto-injected'), but most parameter details are covered by the schema, meeting the baseline for high coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description explicitly states the tool's purpose as 'Execute a GraphQL query against the Tacit building digital twin API' with specific capabilities like nested fields, filtering, and supply chain traversal. It clearly distinguishes from sibling tools like tacit_list_sites (which provides site IDs) and tacit_timeseries (which likely handles time-series data).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance: 'Use tacit_list_sites first to get a valid site ID, then construct queries freely.' It clearly indicates a prerequisite (site ID from sibling tool) and when to use this tool (for GraphQL queries) versus alternatives (tacit_list_sites for IDs).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ucl-sbde/tacit-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server