> For the complete documentation index, see [llms.txt](https://help.cubelet.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.cubelet.ai/platform-and-integration/mcp-servers/manufacture-cubelet-manufacturing/catalog-query-surface.md).

# Catalog Query Surface

*The read-only, two-tool query surface over the GRID42 Catalog — and the one part of the manufacture server's \~159-tool surface that gets a hand-maintained reference, on purpose.*

## What it is

Elsewhere in this space, the manufacture — Cubelet Manufacturing page catalogs every tool family on the server, and the API reference page explains why there is no hand-maintained page per tool: the surface is wide, it drifts, and `tools/list` is the authoritative contract. `catalog_search` and `catalog_narrative` are the deliberate exception. They are the two tools meant for external agents and integrations that want to browse the Catalog without holding the other \~157 tools' worth of context — and because that is their whole job, this page documents them in full as the source of truth for how to query Catalog content, not a supplement to `tools/list`.

The pattern is **resolve → traverse**:

```mermaid
flowchart LR
    Q["query: phrase, framework code,<br/>competency name, or domain"] --> S[catalog_search]
    S --> M["matches[] — cheap identity only"]
    M -->|"catalog_narrative(id, lens)"| N[catalog_narrative]
    N --> R["narrative object — faces, prerequisites,<br/>forward, adjacent, crosswalk, cases, coverage"]
```

* **`catalog_search`** turns a phrase, a framework code, a Competency name, or a Domain into Catalog node identities — cheap, identity-only.
* **`catalog_narrative`** takes one of those identities and walks its edges, returning everything that tells that node's story in a single call.

Keeping resolve cheap and traverse progressive (gated by `lens`) is a token-economy choice: the calling agent decides what to expand and how deep, instead of every query paying for a full ego-network whether it needs one or not. Both tools are strictly read-only — neither has a mutation path, and neither can trigger Cubelet production.

## catalog\_search — resolve

### Parameters

| Param             | Type                     | Notes                                                               |
| ----------------- | ------------------------ | ------------------------------------------------------------------- |
| `query`           | string, required         | Natural-language phrase, framework code, Competency name, or Domain |
| `domain`          | string, optional         | Restrict matches to one Domain                                      |
| `type`            | enum, optional           | `competency` \| `cubelet` \| `case` \| `any` — default `any`        |
| `cross_framework` | boolean, default `false` | Also return crosswalk equivalents in adjacent frameworks            |
| `limit`           | integer, default `10`    | Maximum matches returned                                            |

### Framework-code resolution

`query` recognizes framework codes directly and resolves them to their Catalog node id — no need to already know the internal id:

| Query (framework code) | Resolves to     |
| ---------------------- | --------------- |
| `CMMC.L2.AC.3.1.1`     | `comp-ac-3.1.1` |
| `GOVERN 1.1`           | `govern-1.1`    |

### Return shape

| Field                             | Type      | Notes                                                                                             |
| --------------------------------- | --------- | ------------------------------------------------------------------------------------------------- |
| `query_interpretation`            | string    | How the query was classified: `framework_code` \| `keyword` \| `competency` \| `domain`           |
| `matches[].id`                    | string    | Catalog node id — pass straight into `catalog_narrative`                                          |
| `matches[].type`                  | string    | `competency` \| `cubelet` \| `case`                                                               |
| `matches[].title`                 | string    | Human-readable Competency or Cubelet title                                                        |
| `matches[].framework_element_ids` | string\[] | Stored as `frameworkId:elementId`, e.g. `nist-sp-800-171-r3:03.01.01` — see Data model & taxonomy |
| `matches[].domain`                | string    | Owning Domain                                                                                     |
| `matches[].cubelet_built`         | boolean   | Whether a Cubelet has been produced for this Competency yet                                       |
| `matches[].snippet`               | string    | Short excerpt for quick relevance scanning                                                        |
| `matches[].score`                 | number    | Match relevance score                                                                             |
| `next`                            | string    | Hint: `call catalog_narrative(id, lens) to expand any match`                                      |

```json
{
  "query_interpretation": "framework_code",
  "matches": [
    {
      "id": "comp-ac-3.1.1",
      "type": "cubelet",
      "title": "Limit system access to authorized users, processes, and devices",
      "framework_element_ids": ["cmmc-l2:AC.3.1.1"],
      "domain": "CMMC",
      "cubelet_built": true,
      "snippet": "Access enforcement: authorized subjects only; deny by default.",
      "score": 0.98
    }
  ],
  "next": "call catalog_narrative(id, lens) to expand any match"
}
```

`cross_framework: true` additionally runs a crosswalk lookup and folds equivalent nodes from adjacent frameworks into `matches[]`.

## catalog\_narrative — traverse

### Parameters

| Param        | Type                      | Notes                                                                                                 |
| ------------ | ------------------------- | ----------------------------------------------------------------------------------------------------- |
| `id`         | string, required          | From `catalog_search`, or a known `framework_element_id` / Competency id                              |
| `lens`       | enum, default `standard`  | `overview` \| `standard` \| `full` — progressive disclosure                                           |
| `include`    | string\[], optional       | Overrides the lens: `faces`, `prerequisites`, `forward`, `adjacent`, `crosswalk`, `cases`, `coverage` |
| `max_depth`  | integer, 1–3, default `1` | Hops of prerequisite/forward traversal                                                                |
| `learner_id` | string, optional          | If present on an authenticated request, adds `learner_context` — see below                            |

### The lens ladder

| Lens       | Adds                                                        | Use it when                                                              |
| ---------- | ----------------------------------------------------------- | ------------------------------------------------------------------------ |
| `overview` | Node identity + edge **counts** only                        | Cheap entry point — deciding whether a node is worth expanding further   |
| `standard` | + `faces`, `prerequisites`, `forward`                       | The default reading unit — studying one Cubelet in its immediate context |
| `full`     | + `adjacent`, `crosswalk` (default-on), `cases`, `coverage` | Cross-framework and cross-domain work — everything the node connects to  |

`include` overrides the lens outright rather than adding to it — pass the exact section list you want if the ladder's defaults don't fit.

### Return shape

```json
{
  "node": {
    "id": "comp-ac-3.1.1",
    "type": "cubelet",
    "competency": "Limit system access to authorized users, processes, and devices",
    "framework_element_ids": ["cmmc-l2:AC.3.1.1"],
    "domain": "CMMC"
  },
  "faces": {
    "WHAT": "…", "WHY": "…", "HOW": "…", "WHERE": "…", "WHEN": "…", "APPLY": "…"
  },
  "prerequisites": [
    { "id": "ia-l2-3.5.1", "competency": "Identify and authenticate organizational users",
      "framework_element_ids": ["cmmc-l2:IA.3.5.1"], "relation": "requires" }
  ],
  "forward": [
    { "id": "…", "competency": "…", "relation": "enables" }
  ],
  "adjacent": [
    { "id": "…", "competency": "Access control policy (governance)", "domain": "ISACA", "resonance": "cross-domain" }
  ],
  "crosswalk": [
    { "framework": "NIST SP 800-171", "element_id": "3.1.1", "equivalence": "same-intent" }
  ],
  "cases": [
    { "case_id": "…", "title": "Vendor access recall drill" }
  ],
  "coverage": { "faces_grounded": 6, "faces_gap": 0 },
  "learner_context": { "path_position": 4, "mastery": "…" }
}
```

`node` and edge counts ship at every lens; `faces`/`prerequisites`/`forward` join at `standard`; `adjacent`/`crosswalk`/`cases`/`coverage` join at `full`. `learner_context` is conditional — see below, not lens-gated.

## Gap semantics: `faces: null` is a feature

{% hint style="info" %}
A Competency with no built Cubelet still resolves. `catalog_narrative` never 404s a mapped Competency just because production hasn't reached it yet.
{% endhint %}

When a Competency has no built Cubelet, `catalog_narrative` returns `faces: null` — and still returns a `coverage` block reporting `faces_grounded: 0` out of 6. The gap stays visible by design rather than disappearing behind an error. This matters at scale: the PJRC/labor Domain alone has 1,016 mapped Competencies and 0 built Cubelets today, and every one of them is fully `catalog_narrative`-reachable. A `null` faces response is signal, not failure — it tells the caller exactly which Competency is waiting on content production, which is the point of exposing gaps through the same surface that serves built content.

## Quality gate

Only Cubelets that have cleared the platform's quality gate — 42/60 aggregate minimum, 6+ points on every face, no face scoring 0, reviewed by the Opus QA stage — are ever returned as content through this surface. `catalog_search` and `catalog_narrative` inherit the same gate-passed filtering as every other discovery surface on the platform (`cubelet_list`, `catalog_cubelets`, `cubelet_get`): there is no path through either tool to unpublished or failed Cubelet content. A Competency without a passed Cubelet behaves exactly like a Competency with no Cubelet at all — `faces: null`, `coverage` visible.

## learner\_context — auth-gated

{% hint style="info" %}
Anonymous callers — including every call through the public Portal — never receive `learner_context`, no matter what `learner_id` they pass.
{% endhint %}

`learner_context` is attached to a `catalog_narrative` response only when **both** hold: the caller passed a `learner_id`, and the request itself carries an authenticated identity through the worker's own auth context — not merely the presence of a `learner_id` string in the arguments. Anonymous consumers get Catalog structure and faces only; learner state requires authentication. This is the tool-level enforcement layer; the Portal's Access policy on learner-data paths is the second, outer layer — see Demo Path: Cross-Framework Story for how that plays out for an anonymous caller.

## For AI agents

How to combine this catalog's tools with the GitBook docs tools when both are available in the same context (for example, through the GRID42 Catalog portal):

* Use the docs tools (`askQuestion`, `searchDocumentation`, `getPage`) to learn how the Catalog works — id formats, lenses, the resolve → traverse pattern.
* Use `catalog_search` to resolve a framework code, Competency name, or topic into Catalog node ids; use `catalog_narrative` to retrieve the actual content (six faces, prerequisites, forward links, coverage).
* Answer content questions only from Catalog tool results, citing node ids. Answer how-does-this-work questions from the docs, citing page links. Never state Catalog content from documentation prose or training knowledge.
* Start unknown topics with `catalog_search`; start "how do I…" questions with `askQuestion`.
* If the documentation contradicts a tool result, trust the tool result and report the discrepancy with `sendFeedback`.
