Skip to main content
Glama
us-all

openmetadata-mcp-server

by us-all

quality-rollup

Aggregates data quality status across a table, test suite, or organization. Shows pass rate, top failing cases, and status breakdown for quick assessment of quality issues.

Instructions

Aggregated DQ status across a scope (table / test suite / org-wide). Lists test cases under the scope, buckets them by current status (Success/Failed/Aborted/Queued), surfaces the top failing cases (with fqn/result/timestamp), reports passRatePct + most-recent-run timestamp. Replaces the recursive list-test-cases + list-test-case-results walk LLMs do for 'what's broken in ?'. Provide ONE of entityLink / tableFqn / testSuiteId / testSuiteFqn (or none for org-wide).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
entityLinkNoOM entityLink, e.g. '<#E::table::svc.db.schema.orders>'
tableFqnNoConvenience: build entityLink from a table fully-qualified name
testSuiteIdNoTest Suite UUID — restricts to cases belonging to this suite
testSuiteFqnNoTest Suite FQN — resolved to UUID before listing
limitNoMaximum test cases to inspect (default 100, max 500)
topFailingLimitNoNumber of top failing cases to surface in the response (default 5)

Implementation Reference

  • The main handler function for the quality-rollup tool. Accepts scope parameters (entityLink, tableFqn, testSuiteId, testSuiteFqn) to filter test cases, fetches them from OpenMetadata's /dataQuality/testCases endpoint, buckets them by status, computes pass rate, and surfaces top failing cases sorted by recency.
    export async function qualityRollup(params: z.infer<typeof qualityRollupSchema>) {
      const caveats: string[] = [];
    
      // Build the entityLink from tableFqn convenience.
      let entityLink = params.entityLink;
      if (!entityLink && params.tableFqn) {
        entityLink = `<#E::table::${params.tableFqn}>`;
      }
    
      // Resolve suite FQN → UUID when needed.
      let testSuiteId = params.testSuiteId;
      if (!testSuiteId && params.testSuiteFqn) {
        try {
          const suite = await omClient.get<{ id?: string }>(
            `/dataQuality/testSuites/name/${encodeURIComponent(params.testSuiteFqn)}`,
          );
          testSuiteId = suite.id;
        } catch (err) {
          caveats.push(`testSuite lookup failed: ${err instanceof Error ? err.message : String(err)}`);
        }
      }
    
      const query: Record<string, string | number | boolean | undefined> = {
        limit: params.limit,
        fields: "testSuite,testCaseStatus,testCaseResult",
      };
      if (entityLink) query.entityLink = entityLink;
      if (testSuiteId) query.testSuiteId = testSuiteId;
    
      const { listing } = await aggregate(
        {
          listing: () => omClient.get<{ data?: TestCaseSummary[]; paging?: { total?: number } }>(
            "/dataQuality/testCases",
            query,
          ),
        },
        caveats,
      );
    
      const cases = listing?.data ?? [];
      const total = listing?.paging?.total ?? cases.length;
    
      // Bucket by current status.
      const counts: Record<string, number> = {};
      let mostRecentTs: number | null = null;
      for (const c of cases) {
        const s = statusOf(c);
        counts[s] = (counts[s] ?? 0) + 1;
        const ts = c.testCaseResult?.timestamp;
        if (typeof ts === "number" && (mostRecentTs == null || ts > mostRecentTs)) {
          mostRecentTs = ts;
        }
      }
    
      // Top failing cases (Failed first, then Aborted, ordered by recency).
      const failing = cases
        .filter((c) => /Fail|Abort|Error/i.test(statusOf(c)))
        .sort((a, b) => (b.testCaseResult?.timestamp ?? 0) - (a.testCaseResult?.timestamp ?? 0))
        .slice(0, params.topFailingLimit)
        .map((c) => ({
          name: c.name ?? null,
          fqn: c.fullyQualifiedName ?? null,
          status: statusOf(c),
          result: c.testCaseResult?.result ?? null,
          lastRun: c.testCaseResult?.timestamp ?? null,
          testSuiteFqn: c.testSuite?.fullyQualifiedName ?? null,
        }));
    
      const success = (counts["Success"] ?? 0);
      const passRate = cases.length > 0 ? Math.round((success / cases.length) * 1000) / 10 : null;
    
      let scopeLabel: string;
      if (entityLink) scopeLabel = `entityLink=${entityLink}`;
      else if (testSuiteId) scopeLabel = `testSuite=${params.testSuiteFqn ?? testSuiteId}`;
      else scopeLabel = "org-wide";
    
      return {
        scope: { entityLink, testSuiteId, testSuiteFqn: params.testSuiteFqn ?? null, tableFqn: params.tableFqn ?? null, label: scopeLabel },
        summary: {
          casesInspected: cases.length,
          casesAvailable: total,
          truncated: total > cases.length,
          counts,
          passRatePct: passRate,
          mostRecentRunTs: mostRecentTs,
        },
        topFailing: failing,
        caveats,
      };
    }
  • Zod schema defining the input parameters for quality-rollup: entityLink, tableFqn, testSuiteId, testSuiteFqn (scope filters), limit (max 500), and topFailingLimit (max 50).
    export const qualityRollupSchema = z.object({
      entityLink: z.string().optional()
        .describe("OM entityLink, e.g. '<#E::table::svc.db.schema.orders>'"),
      tableFqn: z.string().optional()
        .describe("Convenience: build entityLink from a table fully-qualified name"),
      testSuiteId: z.string().optional()
        .describe("Test Suite UUID — restricts to cases belonging to this suite"),
      testSuiteFqn: z.string().optional()
        .describe("Test Suite FQN — resolved to UUID before listing"),
      limit: z.coerce.number().int().min(1).max(500).optional().default(100)
        .describe("Maximum test cases to inspect (default 100, max 500)"),
      topFailingLimit: z.coerce.number().int().min(1).max(50).optional().default(5)
        .describe("Number of top failing cases to surface in the response (default 5)"),
    }).describe(SCOPE_DESCRIPTION);
  • src/index.ts:488-490 (registration)
    Registration of the quality-rollup tool in the MCP server using the tool() function, with its schema shape and handler wrapped via wrapToolHandler.
    tool("quality-rollup",
      "Aggregated DQ status across a scope (table / test suite / org-wide). Lists test cases under the scope, buckets them by current status (Success/Failed/Aborted/Queued), surfaces the top failing cases (with fqn/result/timestamp), reports passRatePct + most-recent-run timestamp. Replaces the recursive list-test-cases + list-test-case-results walk LLMs do for 'what's broken in <scope>?'. Provide ONE of entityLink / tableFqn / testSuiteId / testSuiteFqn (or none for org-wide).",
      qualityRollupSchema.shape, wrapToolHandler(qualityRollup));
  • Helper function that extracts the test case status from a TestCaseSummary, falling back through testCaseResult.testCaseStatus, then testCaseStatus, then 'Unknown'.
    function statusOf(tc: TestCaseSummary): string {
      return tc.testCaseResult?.testCaseStatus ?? tc.testCaseStatus ?? "Unknown";
    }
  • src/index.ts:36-36 (registration)
    Import of qualityRollup and its schema from the quality-rollup module into the main server entry point.
    import { qualityRollupSchema, qualityRollup, runTestSuiteSchema, runTestSuite } from "./tools/quality-rollup.js";
Behavior4/5

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

Describes return values in detail (bucketed test cases, top failing with fqn/result/timestamp, pass rate, timestamp). Though no annotations exist, the description implies read-only aggregation. No contradictory info.

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

Conciseness5/5

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

Two dense sentences covering purpose, output, and usage. No wasted words, front-loaded with main functionality.

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?

Covers input options, output content, and use case. Lacks explicit output schema but describes return fields. Minor gap: no mention of pagination or default limits on bucketed counts.

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

Parameters4/5

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

Schema coverage 100%, but description adds crucial meaning: parameters are mutually exclusive ('Provide ONE of...') and 'none for org-wide' which is not in schema. Adds context beyond field descriptions.

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?

Clearly states it provides aggregated DQ status across a scope, listing test cases bucketed by status, top failures, pass rate, and timestamp. Distinguishes from siblings like list-test-cases and list-test-case-results by replacing their recursive walk.

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?

Explicitly says it replaces the recursive list-test-cases + list-test-case-results walk for 'what's broken in <scope>?', guiding LLM to use this tool instead. Also specifies parameter usage: provide ONE of the scope identifiers or none for org-wide.

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/us-all/openmetadata-mcp-server'

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