{
  "schema": 1,
  "as_of": "2026-10-04",
  "grants": [
    {
      "id": "materials_project_data",
      "description": "Unlocks `compute_pourbaix_stability`.",
      "tools": [
        "compute_pourbaix_stability"
      ]
    },
    {
      "id": "professional_database",
      "description": "Lets the account read every dataset, including the restricted ones such as the full catalogue.",
      "tools": [
        "benchmark_against",
        "classify_material",
        "count_materials",
        "find_material_entries",
        "find_property_proxies",
        "get_all_material_properties",
        "get_material_property",
        "get_result_set",
        "graph_neighbors",
        "graph_shortest_path",
        "graph_top_connected",
        "list_applications",
        "ontology_expand",
        "ontology_substitutes",
        "plot_distribution",
        "plot_knowledge_graph",
        "plot_results",
        "property_stats",
        "refine_result_set",
        "rerank_result_set",
        "resolve_material_name",
        "score_results",
        "search_materials",
        "show_eliminated"
      ]
    },
    {
      "id": "result_set_export",
      "description": "Unlocks `export_result_set`.",
      "tools": [
        "export_result_set"
      ]
    },
    {
      "id": "trial_database",
      "description": "Lets the account call the corpus tools against the trial subset, which is not restricted. Without professional_database the account cannot read a restricted dataset.",
      "tools": [
        "benchmark_against",
        "classify_material",
        "count_materials",
        "find_material_entries",
        "find_property_proxies",
        "get_all_material_properties",
        "get_material_property",
        "get_result_set",
        "graph_neighbors",
        "graph_shortest_path",
        "graph_top_connected",
        "list_applications",
        "ontology_expand",
        "ontology_substitutes",
        "plot_distribution",
        "plot_knowledge_graph",
        "plot_results",
        "property_stats",
        "refine_result_set",
        "rerank_result_set",
        "resolve_material_name",
        "score_results",
        "search_materials",
        "show_eliminated"
      ]
    }
  ],
  "groups": [
    {
      "id": "search",
      "title": "Find materials",
      "description": "Search the corpus with a plain-language query, or count how many materials meet some criteria.",
      "tools": [
        "count_materials",
        "search_materials"
      ]
    },
    {
      "id": "lookup",
      "title": "Look up a material",
      "description": "Resolve a name, list every entry for a formula, read one property or all of them, and classify materials as metals, semiconductors or insulators.",
      "tools": [
        "classify_material",
        "find_material_entries",
        "get_all_material_properties",
        "get_material_property",
        "resolve_material_name"
      ]
    },
    {
      "id": "statistics",
      "title": "Property distributions and coverage",
      "description": "See how a property is distributed across a dataset, and find routes to a property we do not hold directly.",
      "tools": [
        "find_property_proxies",
        "plot_distribution",
        "property_stats"
      ]
    },
    {
      "id": "result_sets",
      "title": "Work with a result set",
      "description": "A search returns its matches as a result set. These tools page through it, show what was eliminated, narrow it, rank it, chart it, compare it with an incumbent and export it.",
      "tools": [
        "benchmark_against",
        "export_result_set",
        "get_result_set",
        "plot_results",
        "refine_result_set",
        "rerank_result_set",
        "score_results",
        "show_eliminated"
      ]
    },
    {
      "id": "knowledge_graph",
      "title": "Explore the knowledge graph",
      "description": "Walk MatKG, the literature-derived knowledge graph: neighbours, paths, applications and substitutes.",
      "tools": [
        "graph_neighbors",
        "graph_shortest_path",
        "graph_top_connected",
        "list_applications",
        "ontology_expand",
        "ontology_substitutes",
        "plot_knowledge_graph"
      ]
    },
    {
      "id": "catalogue",
      "title": "Orient yourself",
      "description": "List the datasets you can query and the capabilities of the wider platform.",
      "tools": [
        "list_capabilities",
        "list_material_sources"
      ]
    },
    {
      "id": "computed",
      "title": "Computed on demand",
      "description": "Values that are not stored and are calculated from outside data when you ask.",
      "tools": [
        "compute_pourbaix_stability"
      ]
    }
  ],
  "tools": [
    {
      "name": "benchmark_against",
      "title": "Benchmark against an incumbent",
      "group": "result_sets",
      "summary": "Compares the top five of a scored result set with a named incumbent material across the properties the set was scored on.",
      "description": "Compare an earlier result set's top five against a named incumbent\n(\"Inconel 718\", \"alumina\", \"GaN\") across every property the set was\nscored on, so run score_results first and benchmark its result_set_id.\n\nThe incumbent is resolved and its values read from the same dataset\nthe result set came from (`source`). Returns the `axes`, the\n`incumbent` series and the `candidates` series, each value raw and\nnormalised 0-1; an incumbent value of null means it has no data on\nthat axis, not that it scores zero. Fails with the candidates listed\nwhen the name is ambiguous, use resolve_material_name or\nfind_material_entries, then pass a Chemia ID.",
      "parameters": [
        {
          "name": "result_set_id",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "A scored result set. Run score_results first, then benchmark the set it returns.",
          "fields": null
        },
        {
          "name": "incumbent_name",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The material to compare against: a Chemia ID, a common name or a formula. A name that fits several structures fails and lists the candidates; pass a Chemia ID instead.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "Compare a shortlist with a common incumbent.",
          "arguments": {
            "result_set_id": "3f2b8e9a-0c1d-4e7f-9a6b-5d4c3b2a1f00",
            "incumbent_name": "alumina"
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Trap",
          "text": "Where the incumbent has no data, its value is raw null with normalized 0.0. That is \"no data\", not a score of zero; read `raw`."
        },
        {
          "kind": "Convention",
          "text": "The five candidates are the first five rows of the set in its current order. The incumbent is read from the same dataset the set came from. Normalized values use each property's realistic range, oriented the way the set was ranked."
        }
      ],
      "annotations": {
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "classify_material",
      "title": "Classify material",
      "group": "lookup",
      "summary": "Classifies materials as metal, semi-metal, semiconductor or insulator from their band gap, one result per input in the same order.",
      "description": "Classify one or more materials as metal / semi-metal /\nsemiconductor / insulator / unknown, from each material's band_gap\n(eV) and, when available, its is_metal flag.\n\nOne row per input in `results`, in the same order, each carrying\nchemia_id / band_gap_eV / is_metal_flag / evidence_source / caveat\nalongside the classification.\n\nA formula with several polymorphs (\"Cu\", \"GaN\") is classified across\nall of them: when every entry with data agrees, status is \"found\" with\nclassification_basis \"all_polymorphs_agree\", polymorphs_classified and\ntotal, and chemia_id null (the formula was classified, not one\nstructure). When they disagree, status is \"ambiguous\" with\nclass_breakdown and ground_state (the lowest-energy entry and its\nclass); classify one structure by passing its chemia_id.\n\n`materials` is capped at 50 (MAX_ROWS) per call, check `materials_requested`/`materials_applied`/`notice` rather than\nassuming every input was classified.\n\nsource_id behaves exactly like get_material_property's: the\nclassification is computed from whichever dataset this call resolves\nto (see `source`/`source_downgraded`), not a fixed dataset regardless\nof caller, band_gap and is_metal are read from that dataset's own\nproperty values.",
      "parameters": [
        {
          "name": "materials",
          "type": "array<string>",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The materials to classify, each a Chemia ID, a common name or a formula. At most 50 are classified per call; the rest are ignored and `materials_requested`, `materials_applied` and `notice` say so.",
          "fields": null
        },
        {
          "name": "source_id",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Dataset whose band gap values are used, an `id` from list_material_sources. Null (the default) uses the server's default dataset. `source` and `source_downgraded` say what ran. An id that is not in the catalogue falls back to the registry default without an error.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "Three materials in one call.",
          "arguments": {
            "materials": [
              "Si",
              "GaN",
              "Cu"
            ]
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "The class follows the band gap. A gap of zero is metal, and so is a metal flag when no positive gap is stored. A gap above zero and below 0.1 eV is semi-metal; from there up to, but not including, 3 eV is semiconductor; 3 eV and above is insulator. A material with no band gap value is \"unknown\". Superconductors are not classified."
        },
        {
          "kind": "Convention",
          "text": "A formula with several structures is classified across all of them. When every entry that has data agrees, the status is \"found\" with classification_basis all_polymorphs_agree and no single Chemia ID. When they disagree the status is \"ambiguous\" with `class_breakdown` and the `ground_state`'s class; pass a Chemia ID to classify one structure."
        },
        {
          "kind": "Trap",
          "text": "The band gap used is the largest value in the best evidence tier, not the oldest value that get_material_property returns, so the two can differ for a material with several stored values. Separately, a short fixed list of well-known semiconductors whose DFT gap collapses to zero is reported as semiconductor, with a `caveat`; the raw DFT evidence stays visible in the row."
        }
      ],
      "annotations": {
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "compute_pourbaix_stability",
      "title": "Compute Pourbaix stability",
      "group": "computed",
      "summary": "Computes the Pourbaix decomposition energy of a material, its thermodynamic tendency to decompose in water, from Materials Project data. The value is calculated when you ask and is never stored.",
      "description": "Compute the Pourbaix decomposition energy (ΔG_pbx, eV/atom) of a\nmaterial, its thermodynamic tendency to decompose in water, e.g.\n\"the Pourbaix decomposition energy of CuO\". The value is never\nstored, so get_material_property does not return it.\n\nA formula is used as written; a name is resolved in the dataset\n`source` first. `status` is \"computed\" (with the conditions: pH\n5.5-8.0, V=0 vs. SHE, 25 °C, and the ≤0.05 eV/atom screen verdict in\n`note`), \"not_computable\" (no Materials Project solid entry at that\ncomposition, so no value exists), or an error code such as\n\"mp_auth_error\". The value is computed, not measured, from Materials\nProject data (`attribution`: CC BY 4.0, cited as [MP:dft]), and\n`caveat` states its limit: an equilibrium tendency at 25 °C, not a\nrate, lifetime or durability claim.",
      "parameters": [
        {
          "name": "material",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "A chemical formula, used as written, or a name that resolves in the dataset. A string that parses as a formula is used as a formula without a database lookup.",
          "fields": null
        },
        {
          "name": "source_id",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Dataset used only to resolve a name, an `id` from list_material_sources. Null (the default) uses the server's default dataset. An id that is not in the catalogue falls back to the registry default without an error.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "A formula used as written.",
          "arguments": {
            "material": "CuO"
          }
        }
      ],
      "grants": [
        "materials_project_data"
      ],
      "grants_rule": "any_one_of",
      "charged": true,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "The value is computed, not measured, so get_material_property cannot return it. The conditions (pH window, potential, temperature) and the screening verdict are in `note`. `caveat` always travels with it: this is an equilibrium tendency, not a rate, a lifetime or a durability claim. `attribution` credits the Materials Project data."
        },
        {
          "kind": "Gap",
          "text": "Status not_computable means we hold nothing to compute from: there is no Materials Project solid entry at that composition, the formula has no element other than hydrogen and oxygen, or the name did not resolve to one material. No value is invented. A status such as mp_auth_error instead means the calculation could not run, which is a server problem and not a fact about the material."
        },
        {
          "kind": "Convention",
          "text": "Each call is charged against the daily allowance and needs the materials_project_data grant, because it calls the live Materials Project API on the platform's shared allowance. A search that asks for water inertness is gated by the same grant."
        }
      ],
      "annotations": {
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": true,
        "readOnlyHint": true
      }
    },
    {
      "name": "count_materials",
      "title": "Count materials",
      "group": "search",
      "summary": "Returns one exact number: how many materials meet some criteria, or how many the dataset holds when no criteria are given. Use it instead of a search whenever the question is \"how many\".",
      "description": "Count the materials that meet some criteria and return ONE exact\nnumber, use this instead of search_materials whenever the question\nis \"how many\": \"how many materials have a band gap between 3 and 5\neV and energy above hull below 0.1 eV/atom\", \"how many materials are\nin the semiconductor database\".\n\n`criteria` is natural language, parsed exactly as search_materials\nparses its query; leave it empty to count every material in the\ndatabase. The count is exact, each material's best-source value is\ntested, the same rule search_materials applies, and is never \"too\nbroad\": 15,000 matches is an answer here, not a request to narrow.\n\n`count` is None when the count did not run, and exactly one of these\nsays why: `not_countable` (a criterion SQL cannot evaluate: cost,\nmaterial class, form factor, operating conditions, or a property\ncomputed at query time, all of which search_materials can evaluate),\n`clarification_needed` (the criteria are too ambiguous to count; it\ncarries the question that would resolve them), or `property_offer`\n(as on search_materials, a `note` written to be shown as is).\n\nsource_id picks a dataset from list_material_sources; a database\nnamed in `criteria` (\"in the semiconductor database\") is honoured\ntoo. Check `source` against `requested_source`: they differ when this\naccount may not reach the database asked for, and the count then\ndescribes `source`, not the one named.",
      "parameters": [
        {
          "name": "criteria",
          "type": "string",
          "nullable": false,
          "required": false,
          "default": "",
          "enum": null,
          "description": "The conditions to count, in plain language, parsed exactly as the query of search_materials is. Empty (the default) counts every material in the dataset. A database named in the text, such as \"in the semiconductor database\", is honoured.",
          "fields": null
        },
        {
          "name": "source_id",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Dataset to count in, an `id` from list_material_sources. Null (the default) follows the same rule as search_materials. Compare `source` with `requested_source` in the response: they differ when your account cannot reach the dataset asked for, and the count then describes `source`. An id that is not in the catalogue falls back to the registry default without an error.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "A count with two property limits.",
          "arguments": {
            "criteria": "band gap between 3 and 5 eV and energy above hull below 0.1 eV/atom"
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": true,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "`count` is None when the count did not run, and exactly one of `not_countable`, `clarification_needed` or `property_offer` says why. A count of 0 is a real answer; None is not one."
        },
        {
          "kind": "Convention",
          "text": "Every call is charged against the daily allowance, including a call with empty criteria. The charge is taken before the work runs and is refunded only when the reply is a clarification question."
        },
        {
          "kind": "Gap",
          "text": "Criteria that SQL cannot evaluate (cost, material class, form factor, operating conditions, and properties computed at query time) are not countable. They come back as `not_countable`; search_materials can evaluate them."
        }
      ],
      "annotations": {
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "export_result_set",
      "title": "Export result set",
      "group": "result_sets",
      "summary": "Prepares a CSV or JSON file of an earlier result set's full stored match list and returns a download link and a resource to read it from.",
      "description": "Prepare a CSV or JSON file of an earlier result set's full survivor\nlist, \"export\", \"download\", \"give me a spreadsheet\".\n\nReturns `download_url`, an HTTPS link that downloads the file in a\nbrowser with no sign-in until `download_expires_at` (15 minutes), and\n`resource_uri` (chemia://result-sets/<id>/export.<format>), the same\ncontent through the MCP resources/read request; plus `row_count`. The\ncontent is deliberately not inline: an export can hold thousands of\nrows. `truncated_at_persist` is true when the search matched more\nmaterials than were saved, so the file cannot contain them all.",
      "parameters": [
        {
          "name": "result_set_id",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The set to export. Only result sets created by your own account can be exported.",
          "fields": null
        },
        {
          "name": "format",
          "type": "string",
          "nullable": false,
          "required": false,
          "default": "csv",
          "enum": [
            "csv",
            "json"
          ],
          "description": "csv (the default) or json. The JSON adds the query, the parsed constraints, the dataset and the creation time.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "Export a result set as CSV.",
          "arguments": {
            "result_set_id": "3f2b8e9a-0c1d-4e7f-9a6b-5d4c3b2a1f00",
            "format": "csv"
          }
        }
      ],
      "grants": [
        "result_set_export"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "The file is not returned inline. On the hosted server you get a `download_url` that works in a browser without signing in for 15 minutes, and a `resource_uri` that returns the same content through the MCP resources/read request. `row_count` is the number of stored rows."
        },
        {
          "kind": "Trap",
          "text": "The link is the credential. Anyone who holds the URL can download the file until it expires, so do not paste it where others can read it."
        },
        {
          "kind": "Gap",
          "text": "The file holds the stored rows only, up to 5000. `truncated_at_persist` is true when the search matched more, and the file cannot contain them."
        }
      ],
      "annotations": {
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "find_material_entries",
      "title": "Find material entries",
      "group": "lookup",
      "summary": "Lists every entry that matches a formula, a common name or a Chemia ID, with its structure and stability, so you can see all the polymorphs of a compound and pick the one you mean.",
      "description": "List EVERY material matching a formula (\"AlN\"), common name\n(\"alumina\") or Chemia ID, \"what AlN entries do you have\", \"how many\nAl2O3 polymorphs\". A compound usually has several entries (different\nstructures and data sources); resolve_material_name returns only one.\n\nOne row per material with its Chemia ID, formula, space group, crystal\nsystem, source ID, dataset tag and `energy_above_hull`. Rows come most\nstable first, entries with no stability energy last. For a formula or\ncommon-name match, `ground_state: true` marks the formula's entry on\nthe convex hull, the structure to use when the user names only a\ncompound (\"density of Fe\"). When this dataset holds no hull entry for\nit, `lowest_energy_in_source: true` marks its most stable entry\ninstead, which is not the ground state. Neither is set for a Chemia\nID lookup or a partial match. `total` is the full match count\neven when `rows` is capped at `limit` (max 50). `match_type` says how\nit matched; \"partial\" is a substring fallback, whose rows only\ncontain the text.\nsource_id picks a dataset; `source`/`source_downgraded` report what ran.",
      "parameters": [
        {
          "name": "query",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "A formula such as AlN, a common name such as alumina, or a Chemia ID. Matching tries the Chemia ID, the internal id, the formula, the name table and, as a last resort, a partial text match; `match_type` says which one answered.",
          "fields": null
        },
        {
          "name": "limit",
          "type": "integer",
          "nullable": false,
          "required": false,
          "default": 25,
          "enum": null,
          "description": "Most rows to return, capped at 50. `total` is the full match count even when `rows` is shorter.",
          "fields": null
        },
        {
          "name": "source_id",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Dataset to list from, an `id` from list_material_sources. Null (the default) uses the server's default dataset. `source` and `source_downgraded` in the response say what ran. An id that is not in the catalogue falls back to the registry default without an error.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "Every aluminium nitride entry.",
          "arguments": {
            "query": "AlN",
            "limit": 10
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "Rows come most stable first (lowest energy above hull; entries with no stability energy last). `ground_state` is true for the formula's entry on the hull. When the dataset holds no hull entry for the formula, `lowest_energy_in_source` marks its most stable entry instead, which is not the ground state. Neither flag is set for a Chemia ID lookup or a partial match."
        },
        {
          "kind": "Trap",
          "text": "A `match_type` of partial is a substring fallback. Its rows only contain the text you typed, so they can be unrelated compounds."
        }
      ],
      "annotations": {
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "find_property_proxies",
      "title": "Find property proxies",
      "group": "statistics",
      "summary": "When a property is missing or too thin in the corpus, finds ranked routes that reach it through related properties, with each route's equation, stated error, failure modes and the data we actually hold for its inputs.",
      "description": "When a requested property isn't in Chemia's corpus (or is too thin\nto answer from directly), find ranked routes that reach it through\nthe proxy-equation graph instead of a bare \"untracked\".\n\nEach route is a chain of one or more proxy-equation hops, each\ncarrying its equation id/formula/stated error band, documented\nfailure modes (`fails_for`), and the corpus coverage actually\nmeasured for that hop's input property, a route through a property\nthis deployment also has zero rows of is never offered as usable.\nSuch routes are still returned under `unusable_routes` (not\ndropped silently) so a caller can explain WHY a property is\nirreducible, not just assert that it is.\n\n`verdict` is one of:\n- `backfillable_now`: a class-B route is usable; error is input\n  error only.\n- `rankable_with_scatter`: only class-C routes are usable; a\n  screening/ranking signal with the stated scatter, NEVER a\n  substitute for the real value (\"proxies rank; they never\n  qualify\", see each route's `note`).\n- `elimination_only`: only BOUND routes are usable; one-sided,\n  necessary-not-sufficient.\n- `irreducible`: no usable route; needs literature extraction or\n  direct measurement.\n- `held_directly`: the corpus stores the property itself, under its\n  own name (`held_directly` gives that name and how many materials\n  in scope have it): read it with get_material_property or\n  property_stats; there is nothing to estimate.\n\n`target_property` takes an ontology id or any name for one: a label\n(\"dielectric breakdown strength\"), a corpus name (\"kappa_lat\",\n\"thermal_conductivity\"), a symbol with its case (E_f is formation\nenergy, E_F the Fermi energy). A name that fits several properties\nresolves to none; `did_you_mean` then lists them, as it lists the\nnearest names after a miss.\n\n`material_id` scopes every hop's coverage check to one material (0\nor 1, instead of a corpus-wide count); `result_set_id` scopes it to\na prior `search_materials` call's survivors. Neither given scopes it\nto the whole dataset: `source_id` picks one from\nlist_material_sources, as on search_materials (default: the same\ndataset a search would use), and a result set's own dataset is used\nfor `result_set_id` (a `source_id` given with it is not used, and\n`notice` says so). `source` on the response says which was\ncounted. If both are given, `material_id` wins. `max_hops`\n(default 3) bounds how many proxy hops may compose before a chain\nis dropped, and so how much proxy error compounds across hops. The\nontology has longer chains; the default leaves them out on purpose.",
      "parameters": [
        {
          "name": "target_property",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The property you want but cannot read directly: an ontology id, a label such as \"dielectric breakdown strength\", a corpus name such as thermal_conductivity, or a symbol with its case (E_f is formation energy, E_F the Fermi energy). A name that fits several properties resolves to none and `did_you_mean` lists them; an unknown name lists the nearest names.",
          "fields": null
        },
        {
          "name": "material_id",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Limits every hop's coverage check to one material. Give the internal `material_id` that appears on result rows (a UUID), not the Chemia ID. Takes precedence over result_set_id when both are given.",
          "fields": null
        },
        {
          "name": "result_set_id",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Limits every hop's coverage check to the materials in one of your result sets. The set's own dataset is used and a source_id given with it is ignored (`notice` says so).",
          "fields": null
        },
        {
          "name": "max_hops",
          "type": "integer",
          "nullable": false,
          "required": false,
          "default": 3,
          "enum": null,
          "description": "Longest chain of proxy equations to compose. Every extra hop stacks another equation's error on the last, so the default leaves the ontology's longer chains out on purpose.",
          "fields": null
        },
        {
          "name": "source_id",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Dataset whose data the coverage counts use, an `id` from list_material_sources. Null (the default) uses the dataset a search would use. `source` says which was counted. An id that is not in the catalogue falls back to the registry default without an error.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "Routes to a property we do not store directly.",
          "arguments": {
            "target_property": "dielectric breakdown strength",
            "max_hops": 3
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "`verdict` is backfillable_now, rankable_with_scatter, elimination_only, irreducible or held_directly. Proxies rank and screen; they never qualify. Only a backfillable route estimates a value, and its error is the input error. A rankable route gives an ordering with stated scatter, an elimination route can only rule materials out, and neither replaces a measurement. held_directly means the corpus stores the property under its own name: read it instead."
        },
        {
          "kind": "Gap",
          "text": "A route is usable only when every hop's input property has at least one stored value in scope; we set no higher bar for \"dense\". Routes through a property we hold nothing for are returned under `unusable_routes`, not dropped, and a property with no usable route is irreducible: it needs literature extraction or direct measurement."
        },
        {
          "kind": "Trap",
          "text": "material_id is compared with the internal id column. The Chemia ID shown to users is not accepted there; use the `material_id` from a result row."
        }
      ],
      "annotations": {
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "get_all_material_properties",
      "title": "Get all material properties",
      "group": "lookup",
      "summary": "Lists every stored property of one material in a single call, each with its value, unit and kind of evidence, plus the material's structure.",
      "description": "List EVERY stored property of one material in one call, \"list the\nproperties of LiFePO4\", \"what do you know about GaAs\", instead of one\nget_material_property call per property.\n\n`properties` has one entry per property with its most authoritative\nvalue (experimental, then dft, then ml_predicted), `unit` and\n`source` (the evidence: experimental, dft, ml_predicted or\nliterature), plus `caveat` when a computed value's method has a known\nsystematic error and `suspect` when the value is physically\nimpossible. The entry's chemia_id, space_group and crystal_system\ncome with it; `count` is the number of properties.\n\nmaterial is a Chemia ID, common name or formula. A formula usually\nfits several structures: status \"ambiguous\" lists `candidates` and\nnames the `ground_state`. Pass most_stable=true to answer for the\nformula's lowest-energy entry instead (\"in its most stable\nconfiguration\"); `resolved_to_ground_state` then says it did. Other\nstatuses: \"found\", \"not_found\" (known material, no values), \"unknown\".\nsource_id picks a dataset like search_materials; `source` and\n`source_downgraded` report what ran.",
      "parameters": [
        {
          "name": "material",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "A Chemia ID, a common name or a formula. A formula that fits several structures returns status \"ambiguous\" with candidates and the `ground_state` unless most_stable is true.",
          "fields": null
        },
        {
          "name": "most_stable",
          "type": "boolean",
          "nullable": false,
          "required": false,
          "default": false,
          "enum": null,
          "description": "When true and the formula fits several structures, the answer is for the formula's lowest-energy entry in the dataset (the entry find_material_entries marks), and `resolved_to_ground_state` is true. When false, an ambiguous formula returns the candidates instead.",
          "fields": null
        },
        {
          "name": "source_id",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Dataset to read from, an `id` from list_material_sources. Null (the default) uses the server's default dataset. `source` and `source_downgraded` say what ran. An id that is not in the catalogue falls back to the registry default without an error.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "Every property of GaAs in its most stable structure.",
          "arguments": {
            "material": "GaAs",
            "most_stable": true
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "Each property is chosen by the same rules as get_material_property (experimental, then DFT, then ML; oldest row on a tie), with the same `caveat` and `suspect` fields. `count` is the number of properties returned."
        },
        {
          "kind": "Trap",
          "text": "\"Most stable\" is the lowest energy above hull among the formula's entries in the dataset you query. A smaller dataset can lack the true ground state, in which case the answer is the best entry it holds."
        }
      ],
      "annotations": {
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "get_material_property",
      "title": "Get material property",
      "group": "lookup",
      "summary": "Reads one stored property of one material, with its unit, the kind of evidence it comes from, and the material's structure.",
      "description": "Look up one measured or computed property for one material, for\nexample material=\"Si\", property=\"band_gap\".\n\nstatus is \"found\" (value/unit/evidence_source populated, with the\nentry's chemia_id, space_group and crystal_system), \"not_found\" (the\nmaterial is known but has no value for this property), \"ambiguous\"\n(the name matches several entries: `candidates` lists up to 8 with\nChemia ID and space group, `total` counts them all, and one Chemia ID\npicks exactly one), or \"unknown\" (the material itself could not be\nresolved). Candidates are in Chemia ID order, not by stability, so the\nfirst is not the ground state: `ground_state` names the formula's\nlowest-energy entry (null when the name fits several formulas or none\nhas an energy), and find_material_entries lists every entry.\nevidence_source is \"experimental\", \"dft\" (computed, not measured),\n\"ml_predicted\" or \"literature\". When a computed value's method has\na known systematic error, `caveat` says what it typically gets wrong\nfor this value; the number itself is as stored, not corrected.\n\nsource_id picks a dataset from list_material_sources exactly like\nsearch_materials's own parameter, omit it to use this deployment's\ndefault. `source` on the response is the dataset this call actually\nran against; `source_downgraded` is true if that differs from\n`requested_source` because this account's grants (or this\ndeployment's tier) could not reach what was asked for.",
      "parameters": [
        {
          "name": "material",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "A Chemia ID, a common name, a formula or an internal id. A formula that fits several structures returns status \"ambiguous\" with candidates and the `ground_state` rather than picking one.",
          "fields": null
        },
        {
          "name": "property",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The stored property key, for example band_gap, density or bulk_modulus. It is matched as typed: this tool does not resolve other spellings the way property_stats does, so \"band gap\" returns not_found. The structural answers space_group and crystal_system are also accepted and come back as text in `text_value`.",
          "fields": null
        },
        {
          "name": "source_id",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Dataset to read from, an `id` from list_material_sources. Null (the default) uses the server's default dataset. `source` is the dataset that ran; `source_downgraded` is true when it differs from `requested_source`. An id that is not in the catalogue falls back to the registry default without an error.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "One property of one material.",
          "arguments": {
            "material": "Si",
            "property": "band_gap"
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Trap",
          "text": "A property name that is not the stored key is not an error. It reads as status not_found, \"no measured value\", which looks like missing data. Use the canonical key; property_stats and plot_distribution return `did_you_mean` for names they do not know."
        },
        {
          "kind": "Convention",
          "text": "When a property has several stored values, one is returned, chosen by the kind of evidence: experimental first, then DFT, then ML. Any other source type, such as literature, is used only when nothing ranked exists, and a tie goes to the oldest stored row. `evidence_source` says which kind you got; you do not get the range of values."
        },
        {
          "kind": "Convention",
          "text": "`caveat` appears when a DFT value comes from a method with a known systematic error, for example a DFT band gap that typically underestimates the measured one. The number is returned as stored, not corrected. A value carrying `suspect` is physically impossible for that property and is a data error, not a measurement."
        }
      ],
      "annotations": {
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "get_result_set",
      "title": "Get result set",
      "group": "result_sets",
      "summary": "Pages through the stored matches of an earlier search, up to 50 rows at a time, with the match totals and a next_offset to continue from.",
      "description": "Page through the full survivor list of an earlier search, using the\nresult_set_id that search_materials returned. Only result sets created\nby this server are readable.\n\nlimit is capped at 50 (MAX_ROWS) regardless of what's asked for, the\nsame context-budget ceiling search_materials applies, since this\nresult also lands directly in the model's context. Walk offset ->\nnext_offset (returned on every call, None once there is nothing left)\nto page through everything up to `retrievable`; check\n`limit_applied`/`notice` on the response rather than assuming a page\nholds as many rows as requested.",
      "parameters": [
        {
          "name": "result_set_id",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The `result_set_id` returned by search_materials, refine_result_set, score_results or rerank_result_set. Only result sets created by your own account are readable. An id that does not exist and an id that belongs to someone else give the same \"not found\" error.",
          "fields": null
        },
        {
          "name": "limit",
          "type": "integer",
          "nullable": false,
          "required": false,
          "default": 50,
          "enum": null,
          "description": "Rows per page, capped at 50; values below 1 are raised to 1. `limit_applied` and `notice` show what was used.",
          "fields": null
        },
        {
          "name": "offset",
          "type": "integer",
          "nullable": false,
          "required": false,
          "default": 0,
          "enum": null,
          "description": "Index of the first row to return, starting at 0. Negative values are treated as 0. Use `next_offset` from the previous response; it is null when no rows remain.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "The second page of twenty rows of an earlier search.",
          "arguments": {
            "result_set_id": "3f2b8e9a-0c1d-4e7f-9a6b-5d4c3b2a1f00",
            "limit": 20,
            "offset": 20
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "`total_survivors` is the true match count of the original search. `retrievable` is how many matches were stored, up to 5000, and is the number you can page through. `truncated_at_persist` is true when the search matched more than were stored; no offset reaches those rows."
        },
        {
          "kind": "Trap",
          "text": "Owning a result set is not enough. A set that came from a restricted dataset stops being readable if your account no longer holds the professional_database grant, even though the rows were yours when the search ran."
        }
      ],
      "annotations": {
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "graph_neighbors",
      "title": "Graph neighbors",
      "group": "knowledge_graph",
      "summary": "Lists the entities within a few steps of a material in MatKG, closest first.",
      "description": "List materials/concepts within `hops` of `material` in Chemia's\nMatKG knowledge graph, sorted by distance then by strongest direct\nedge weight to `material`.\n\nOne shared graph regardless of which database (trial/production)\nsearch_materials would use, MatKG is not scoped by dataset.\nlimit is capped at 50 (MAX_ROWS) and hops at 3 (MAX_HOPS); check\nlimit_applied/hops_applied/notice rather than assuming the request\nwas honoured as asked.",
      "parameters": [
        {
          "name": "material",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "An entity name in the graph: a material, element, application or property. It is matched by exact name, then lower case, then a normalised key, then aliases. An unknown name returns an empty list, the same as an entity with no neighbours.",
          "fields": null
        },
        {
          "name": "hops",
          "type": "integer",
          "nullable": false,
          "required": false,
          "default": 1,
          "enum": null,
          "description": "How many edges to walk, capped at 3 and raised to 1 if lower; `hops_applied` shows the value used. Edges are followed in either direction.",
          "fields": null
        },
        {
          "name": "limit",
          "type": "integer",
          "nullable": false,
          "required": false,
          "default": 20,
          "enum": null,
          "description": "Most neighbours to return, capped at 50.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "The direct neighbours of gallium nitride.",
          "arguments": {
            "material": "GaN",
            "hops": 1,
            "limit": 10
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "Rows are ordered by distance, then by the weight of the direct edge, which is how often the literature links the two. Only neighbours one step away carry a relation and a weight; farther rings have an empty relation and a weight of 0."
        },
        {
          "kind": "Trap",
          "text": "An empty list does not say why. It can mean the name is not in the graph or that the entity has no neighbours. If the graph is not loaded the reply carries a `note` saying so. The graph is shared by every dataset: it is not scoped to trial or production."
        }
      ],
      "annotations": {
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "graph_shortest_path",
      "title": "Graph shortest path",
      "group": "knowledge_graph",
      "summary": "Finds the shortest path between two entities in MatKG, step by step.",
      "description": "Shortest path between two entities in MatKG (materials, elements,\napplications, or properties the graph knows). length=0 with no steps\nmeans no path exists between them.",
      "parameters": [
        {
          "name": "source",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The entity to start from, matched the way graph_neighbors matches a name.",
          "fields": null
        },
        {
          "name": "target",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The entity to reach, matched the same way.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "A path between two materials.",
          "arguments": {
            "source": "GaN",
            "target": "Si"
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Trap",
          "text": "A `length` of 0 with no steps does not say why. It covers an unknown name, no path within 6 edges, a search that grew past its budget, and a source and target that resolve to the same entity."
        },
        {
          "kind": "Convention",
          "text": "The path follows edges in their stored direction, from source to target. A path that exists only the other way is not found, so try swapping the two."
        }
      ],
      "annotations": {
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "graph_top_connected",
      "title": "Graph top connected entities",
      "group": "knowledge_graph",
      "summary": "Lists the most connected entities in MatKG, to show what the graph covers before you ask about a specific material.",
      "description": "The most-connected entities in MatKG by edge count, a way to\norient on what the graph actually covers before asking about a\nspecific material. limit is capped at 50 (MAX_ROWS).",
      "parameters": [
        {
          "name": "limit",
          "type": "integer",
          "nullable": false,
          "required": false,
          "default": 20,
          "enum": null,
          "description": "How many entities to return, capped at 50.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "The ten best-connected entities.",
          "arguments": {
            "limit": 10
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "Entities are ranked by their number of distinct neighbours, counted when the graph was loaded. Each row's `material` is an entity name, which can be an element, an application or a property as well as a material."
        }
      ],
      "annotations": {
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "list_applications",
      "title": "List applications",
      "group": "knowledge_graph",
      "summary": "Lists the applications that MatKG associates with a material, ranked by how often the literature pairs them.",
      "description": "Applications MatKG associates with `material`, ranked by\nliterature co-occurrence (e.g. \"GaN\" -> power electronics, LEDs).\nlimit is capped at 50 (MAX_ROWS).",
      "parameters": [
        {
          "name": "material",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "An entity name, matched the way graph_neighbors matches a name.",
          "fields": null
        },
        {
          "name": "limit",
          "type": "integer",
          "nullable": false,
          "required": false,
          "default": 10,
          "enum": null,
          "description": "Most applications to return, capped at 50.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "Applications of gallium nitride.",
          "arguments": {
            "material": "GaN",
            "limit": 5
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "The ranking is a literature co-occurrence count. It says how often a material and an application appear together, not how well the material performs in it."
        },
        {
          "kind": "Gap",
          "text": "Only materials with application coverage in MatKG return rows. An empty list means no coverage or an unknown name, and cannot tell the two apart."
        }
      ],
      "annotations": {
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "list_capabilities",
      "title": "List capabilities",
      "group": "catalogue",
      "summary": "Lists what this server and the wider Chemia platform can do, grouped by category.",
      "description": "List what this server (and the wider Chemia platform it fronts)\ncan do, grouped by category: materials search, ontology reasoning,\nDigital Lab (training/generation), data extraction, and patent\nintelligence. Some categories are reachable only through this\nserver's tools today; others describe capabilities Chemia's\nplatform provides that aren't wired to MCP yet.",
      "parameters": [],
      "examples": [
        {
          "note": "No arguments.",
          "arguments": {}
        }
      ],
      "grants": [],
      "grants_rule": "none",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Gap",
          "text": "Some entries describe capabilities of the wider platform that this server cannot call. An entry with an `mcp_tool_name` can be called here; an entry without one is described but not callable from this server."
        }
      ],
      "annotations": {
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "list_material_sources",
      "title": "List material sources",
      "group": "catalogue",
      "summary": "Lists the datasets this server can query, with their ids, descriptions and whether access to them is restricted.",
      "description": "List the material datasets this server can query, with their ids,\ndescriptions, and whether they are access-restricted.",
      "parameters": [],
      "examples": [
        {
          "note": "No arguments.",
          "arguments": {}
        }
      ],
      "grants": [],
      "grants_rule": "none",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "`restricted` means reading the dataset needs the professional_database grant. `is_default` marks the dataset a search would use for you when neither a source_id nor the query text names one, so it can differ between accounts. `specialized` marks a slice of the full catalogue chosen by application."
        }
      ],
      "annotations": {
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "ontology_expand",
      "title": "Expand query via MatKG",
      "group": "knowledge_graph",
      "summary": "Broadens a plain-language request through MatKG into candidate materials, material classes and property filters you can use to build a search.",
      "description": "Broaden a natural-language query via MatKG before running\nsearch_materials: resolves candidate materials, material classes,\nand derived property filters a plain constraint parse would miss\n(e.g. \"materials used in solid-state batteries\" has no direct\nproperty translation without this).\n\nconfidence is MatKG's own score for the expansion, not a probability\nthe candidates are correct; a low value means the expansion is\nuncertain.",
      "parameters": [
        {
          "name": "query_text",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "A request in plain language, for example \"materials used in solid-state batteries\". A short concept phrase is taken from the text and looked up in the graph.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "A request with no direct property translation.",
          "arguments": {
            "query_text": "materials used in solid-state batteries"
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Trap",
          "text": "`confidence` is the average confidence of the derived property filters, not a probability that the candidates are right. It is 0.0 whenever no filter was derived, even if candidate materials were found."
        },
        {
          "kind": "Convention",
          "text": "`derived_property_filters` are operator and value pairs read from graph text, with the property word as MatKG wrote it. Treat them as hints for a query; they are not checked against Chemia's property keys or against the corpus."
        },
        {
          "kind": "Gap",
          "text": "When the concept is not in the graph, or the graph is not loaded, every list is empty and confidence is 0.0."
        }
      ],
      "annotations": {
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "ontology_substitutes",
      "title": "Substitutes via MatKG",
      "group": "knowledge_graph",
      "summary": "Suggests candidate substitutes for a material from shared applications in MatKG, each with its match in the dataset.",
      "description": "Candidate substitutes for an incumbent material from literature\nco-occurrence in MatKG, \"what could replace X\", \"alternatives to Y\nfor turbine blades\". `application` restricts candidates to those\nsharing that application.\n\nEach candidate carries a `cooccurrence_score`, the `shared_applications`\nbehind it, and `db_match` (its row in the dataset `source`, or null\nwhen the database has no entry for it). Co-occurrence is a literature\nsignal, not a property comparison: check candidates with\nget_material_property or search_materials before recommending one.\nlimit is capped at 50.",
      "parameters": [
        {
          "name": "material",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The incumbent material, as a name or formula that MatKG knows.",
          "fields": null
        },
        {
          "name": "application",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Keeps candidates that share an application whose name contains this text, ignoring case. If none of the incumbent's applications contains the text, the filter is ignored and all of them are used. Null (the default) uses all.",
          "fields": null
        },
        {
          "name": "limit",
          "type": "integer",
          "nullable": false,
          "required": false,
          "default": 10,
          "enum": null,
          "description": "Most candidates to return, capped at 50.",
          "fields": null
        },
        {
          "name": "source_id",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Dataset used only for each candidate's `db_match`, an `id` from list_material_sources. Null (the default) uses the server's default dataset. An id that is not in the catalogue falls back to the registry default without an error.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "Substitutes for an incumbent in one application.",
          "arguments": {
            "material": "GaN",
            "application": "LED",
            "limit": 5
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "`cooccurrence_score` is relative to the best candidate for this incumbent, so the top candidate always scores 1. Candidates below a fixed cutoff are dropped. It measures shared literature applications, not similarity of properties: check a candidate with get_material_property or search_materials before recommending it."
        },
        {
          "kind": "Trap",
          "text": "An application filter that matches nothing is silently ignored, and the substitutes are found across all the incumbent's applications. Check `shared_applications` on each candidate."
        },
        {
          "kind": "Trap",
          "text": "`db_match` is the first dataset entry whose formula equals the candidate's name exactly, not the most stable structure, and it carries no Chemia ID. It is null when the name is not a formula in the dataset."
        }
      ],
      "annotations": {
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "plot_distribution",
      "title": "Plot distribution",
      "group": "statistics",
      "summary": "Returns the histogram and summary statistics of one property across a whole dataset, or across a value range, as data you can draw or describe.",
      "description": "The histogram of ONE property across a whole dataset, optionally\nonly values above `min_value` / below `max_value`, for \"plot the\ndistribution of band gap above 0 eV\", \"histogram of density\", \"how is\nbulk modulus distributed\". Not a search: every matching value is\ncounted, however many, and nothing asks to narrow it.\n\n`property_name` is a canonical key (band_gap, density, bulk_modulus,\nenergy_above_hull, ...) or a name for one (\"band gap\", \"bandgap\",\n\"Eg\"); `property` is the key used and `requested_as` what was\ntyped. Returns `stats` (material_count, value_count,\nmin, max, mean, p05/p25/p50/p75/p95) and `bins`, {lo, hi, count}\nbuckets to draw as a histogram: twenty equal-width bins between P1\nand P99, plus one bin at each end holding every value beyond them\n(`bins_note`), so a few corrupt extremes cannot flatten the chart.\nWhen `outlier_note` is set, min and max are outliers and the\npercentiles describe the data.\n\n`status` is \"ok\", \"unknown_property\" (no property by that name;\n`did_you_mean` lists the nearest) or \"no_data\" (none stored in this\ndataset). source_id picks a dataset\nfrom list_material_sources; `source` / `requested_source` /\n`source_downgraded` report what was actually read, as on every other\ntool here.",
      "parameters": [
        {
          "name": "property_name",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "A canonical property key or a name for one, such as band_gap, \"band gap\", bandgap or Eg. Names are matched ignoring case, spaces, hyphens and a trailing unit; a symbol keeps its case; a name that fits several properties resolves to none. `property` is the key used and `requested_as` is what you typed. An unknown name returns `status` unknown_property with `did_you_mean`.",
          "fields": null
        },
        {
          "name": "min_value",
          "type": "number",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Lower bound, exclusive: only stored values greater than this are used. In the property's canonical unit (`unit` in the response). Null (the default) means no lower bound.",
          "fields": null
        },
        {
          "name": "max_value",
          "type": "number",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Upper bound, exclusive: only stored values smaller than this are used. In the property's canonical unit. Null (the default) means no upper bound.",
          "fields": null
        },
        {
          "name": "source_id",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Dataset to describe, an `id` from list_material_sources. Null (the default) uses the server's default dataset. `source` and `source_downgraded` say what ran. An id that is not in the catalogue falls back to the registry default without an error.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "The distribution of band gap above zero.",
          "arguments": {
            "property_name": "band_gap",
            "min_value": 0
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "`bins` holds 20 equal-width bins between the 1st and 99th percentile, plus one wider bin at each end that holds every value beyond them. Draw the end bins as open-ended: a few corrupt extremes would otherwise flatten the chart."
        },
        {
          "kind": "Convention",
          "text": "`stats` counts stored values, not materials. A material with values from several kinds of evidence counts once per value, so `value_count` can exceed `material_count`, and the percentiles are over values."
        },
        {
          "kind": "Trap",
          "text": "min_value and max_value are exclusive and filter stored values. A value exactly equal to a bound is left out, and a material is kept only for the values inside the range, not for having any value there."
        }
      ],
      "annotations": {
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "plot_knowledge_graph",
      "title": "Plot knowledge graph",
      "group": "knowledge_graph",
      "summary": "Builds a hub-and-spoke graph of nodes and edges around one entity from entities you already have, for you to draw.",
      "description": "Build a hub-and-spoke graph (nodes + edges) around `center` from\nentities you already have, e.g. the applications list_applications\nreturned, or graph_neighbors' neighbours. `weights` is index-aligned\nto `related` (default 1.0). When some weights are missing or not\nfinite numbers, those edges are drawn at 1.0 and `weights_note`\nnames them: those are placeholders, not weights.\nReads no data: it shapes what you pass into a graph to draw.\n`related` is capped at 50.",
      "parameters": [
        {
          "name": "center",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The hub entity.",
          "fields": null
        },
        {
          "name": "related",
          "type": "array<string>",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The spoke entities, typically taken from list_applications or graph_neighbors. At most 50 are used. Blanks, repeats and the hub itself are dropped, comparing without case.",
          "fields": null
        },
        {
          "name": "weights",
          "type": "array<number>",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Edge weights, matched to `related` by position. A missing or non-finite weight draws that edge at 1.0 and `weights_note` names it. Null (the default) draws every edge at 1.0.",
          "fields": null
        },
        {
          "name": "title",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Chart title. Null (the default) lets the tool choose one.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "Draw the applications of a material around it.",
          "arguments": {
            "center": "GaN",
            "related": [
              "power electronics",
              "LEDs"
            ],
            "weights": [
              12,
              8
            ]
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "The tool reads no data. It shapes the entities you pass in into nodes and edges, and checks nothing against MatKG or the corpus."
        },
        {
          "kind": "Trap",
          "text": "An edge drawn at 1.0 because its weight was missing or not a number is a placeholder, not a weight. Do not read it as data."
        }
      ],
      "annotations": {
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "plot_results",
      "title": "Plot results",
      "group": "result_sets",
      "summary": "Returns the data behind a chart of an earlier result set (bar, spider or Ashby) for you to draw or describe.",
      "description": "The data behind a chart of an earlier result set, for you to draw\nor describe. `kind`:\n\n- \"bar\": the top `top_n` on one property (`axis` required).\n- \"spider\": the top five across every scored property, each value\n  raw and normalised to 0-1 against the property's realistic range.\n  Needs a scored set: run score_results first.\n- \"ashby\": `axis_a` vs `axis_b` (canonical names, both required) in\n  native units, each point flagged `on_frontier` when no other point\n  beats it on both. Capped at 50 points, frontier first, `points_total` says how many exist.\n\n`spec` is the chart specification; `omitted` names candidates left\nout for lacking a value. For one property's distribution over a\nwhole dataset rather than a shortlist, use plot_distribution.",
      "parameters": [
        {
          "name": "result_set_id",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The set to chart. Spider charts need a set that has been scored: run score_results first.",
          "fields": null
        },
        {
          "name": "kind",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": [
            "spider",
            "bar",
            "ashby"
          ],
          "description": "bar (the top rows on one property, needs axis), spider (the top five across every scored property) or ashby (axis_a against axis_b, needs both).",
          "fields": null
        },
        {
          "name": "axis",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Property key for a bar chart. Required when kind is bar.",
          "fields": null
        },
        {
          "name": "axis_a",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Property key for the first Ashby axis. Required when kind is ashby, and must be a property in the vocabulary.",
          "fields": null
        },
        {
          "name": "axis_b",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Property key for the second Ashby axis. Required when kind is ashby, and must be a property in the vocabulary.",
          "fields": null
        },
        {
          "name": "top_n",
          "type": "integer",
          "nullable": false,
          "required": false,
          "default": 10,
          "enum": null,
          "description": "How many rows a bar chart shows, capped at 50. A spider chart never shows more than five. An Ashby plot ignores it and returns up to 50 points.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "A bar chart of band gap.",
          "arguments": {
            "result_set_id": "3f2b8e9a-0c1d-4e7f-9a6b-5d4c3b2a1f00",
            "kind": "bar",
            "axis": "band_gap",
            "top_n": 10
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "A search's own result set is unscored, so a spider chart on it fails with no axes. Run score_results first and chart the set it returns. The spider axes are the properties the set was scored on."
        },
        {
          "kind": "Trap",
          "text": "A missing value is reported as raw null with normalized 0.0. Read `raw` to tell \"no data\" from \"the worst value\"; the normalized number alone cannot."
        },
        {
          "kind": "Convention",
          "text": "In an Ashby plot a point is on the frontier when no other point is at least as good on both axes and better on one, using each axis's direction. Only points with a value on both axes are plotted. Frontier points come first, and `points_total` says how many exist when fewer are returned."
        }
      ],
      "annotations": {
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "property_stats",
      "title": "Property statistics",
      "group": "statistics",
      "summary": "Summarises how one property is distributed across a dataset: how many values and materials there are, the minimum and maximum, the mean and the 5th, 25th, 50th, 75th and 95th percentiles.",
      "description": "How one property is distributed across a dataset: how many\nmaterials have it, min/max, and P5/P25/median/P75/P95, \"what's a\ntypical density\", \"is 5 g/cm3 high\". Useful for grounding a numeric\nthreshold in the real data.\n\n`property_name` is a canonical key or a name for one (\"band gap\",\n\"Young's modulus\"); an unknown name returns `did_you_mean`.\n`min_value`/`max_value` restrict the numbers to a range, such as the\nuser's own threshold. When an outlier note is present, min and max\nare outliers and the percentiles describe the data. plot_distribution\nreturns the histogram bins.",
      "parameters": [
        {
          "name": "property_name",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "A canonical property key or a name for one, such as band_gap, \"band gap\", bandgap or Eg. Names are matched ignoring case, spaces, hyphens and a trailing unit; a symbol keeps its case (E_f is formation energy, E_F the Fermi energy); a name that fits several properties resolves to none. An unknown name returns `status` unknown_property with `did_you_mean`.",
          "fields": null
        },
        {
          "name": "min_value",
          "type": "number",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Lower bound, exclusive: only stored values greater than this are counted. In the property's canonical unit (`unit` in the response). It filters individual values, not materials. Null (the default) means no lower bound.",
          "fields": null
        },
        {
          "name": "max_value",
          "type": "number",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Upper bound, exclusive: only stored values smaller than this are counted. In the property's canonical unit. Null (the default) means no upper bound.",
          "fields": null
        },
        {
          "name": "source_id",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Dataset to describe, an `id` from list_material_sources. Null (the default) uses the server's default dataset. `source` and `source_downgraded` say what ran. An id that is not in the catalogue falls back to the registry default without an error.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "How density is distributed across the default dataset.",
          "arguments": {
            "property_name": "density"
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "The statistics describe every stored value, from every kind of evidence, while search_materials and count_materials test one value per material (the best-ranked source). A material with a DFT value and an ML value counts twice here, so `value_count` can exceed `material_count` and the percentiles are over values, not materials."
        },
        {
          "kind": "Trap",
          "text": "The minimum and maximum can be physically impossible values, because the corpus holds some data errors. `outlier_note` says when they look unreliable. Judge a threshold by the percentiles, not by the extremes."
        }
      ],
      "annotations": {
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "refine_result_set",
      "title": "Refine result set",
      "group": "result_sets",
      "summary": "Narrows an earlier search's stored matches with extra property limits or element filters, without re-running the search. Returns a new result set; the original is unchanged.",
      "description": "Narrow an earlier search's survivors without re-running the search:\nadd property thresholds (`additional_constraints`, canonical property\nnames such as band_gap / density with operator lt/lte/gt/gte/eq/between)\nand/or element filters (`additional_composition_constraints`, e.g.\n{\"rule\": \"must_not_contain\", \"elements\": [\"Pb\", \"Cd\"]}).\n\nReturns a NEW `result_set_id` (its `parent_set_id` is the one given),\nthe new `total_survivors`, and the first rows. A survivor with no value\nfor an added property is dropped, not kept, see show_eliminated on the\nnew set for what fell out and why. `limit` (default 20) bounds the\nrows returned here, which come from the new set's first page of 20;\n`notice` says when fewer came back than asked. Page the rest with\nget_result_set.",
      "parameters": [
        {
          "name": "result_set_id",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The set to narrow. It is left unchanged; the new set's `parent_set_id` is this id.",
          "fields": null
        },
        {
          "name": "additional_constraints",
          "type": "array<PropertyConstraintArg>",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Extra property limits, each an object. Null (the default) adds none. At least one of this and additional_composition_constraints is required. A match with no stored value for a property named here is dropped, not kept.",
          "fields": [
            {
              "name": "property_name",
              "type": "string",
              "nullable": false,
              "required": true,
              "default": null,
              "enum": null,
              "description": "Property key exactly as stored, for example band_gap. It must be a property the rows carry; check a row's `properties`."
            },
            {
              "name": "operator",
              "type": "string",
              "nullable": false,
              "required": true,
              "default": null,
              "enum": [
                "lt",
                "lte",
                "gt",
                "gte",
                "between",
                "eq"
              ],
              "description": "One of lt, lte, gt, gte, eq or between. between includes both ends. eq compares for exact equality, which rarely matches a measured number."
            },
            {
              "name": "value",
              "type": "number",
              "nullable": false,
              "required": true,
              "default": null,
              "enum": null,
              "description": "The threshold, or the lower bound when the operator is between."
            },
            {
              "name": "value_max",
              "type": "number",
              "nullable": false,
              "required": false,
              "default": null,
              "enum": null,
              "description": "Upper bound. Required when the operator is between, where it must be greater than value; leave it out for every other operator."
            },
            {
              "name": "unit",
              "type": "string",
              "nullable": false,
              "required": true,
              "default": null,
              "enum": null,
              "description": "Required by the schema. A label for the threshold. It is not used to convert: give the value in the property's canonical unit."
            },
            {
              "name": "weight",
              "type": "number",
              "nullable": false,
              "required": false,
              "default": null,
              "enum": null,
              "description": "Ranking weight. This tool only filters, so it does not change the result here."
            }
          ]
        },
        {
          "name": "additional_composition_constraints",
          "type": "array<CompositionConstraintArg>",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Extra element filters, each an object. Null (the default) adds none.",
          "fields": [
            {
              "name": "rule",
              "type": "string",
              "nullable": false,
              "required": true,
              "default": null,
              "enum": [
                "must_contain",
                "must_not_contain"
              ],
              "description": "must_contain keeps matches whose formula contains every listed element. must_not_contain drops matches that contain any listed element."
            },
            {
              "name": "elements",
              "type": "array<string>",
              "nullable": false,
              "required": true,
              "default": null,
              "enum": null,
              "description": "Element symbols, for example Pb and Cd."
            }
          ]
        },
        {
          "name": "limit",
          "type": "integer",
          "nullable": false,
          "required": false,
          "default": 20,
          "enum": null,
          "description": "How many rows of the new set to return here. The new set's first page holds 20 rows, so no more than that come back, and never more than 50; `notice` says when fewer came back than asked. Page the rest with get_result_set.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "Keep matches with a wider gap and drop two toxic elements.",
          "arguments": {
            "result_set_id": "3f2b8e9a-0c1d-4e7f-9a6b-5d4c3b2a1f00",
            "additional_constraints": [
              {
                "property_name": "band_gap",
                "operator": "gte",
                "value": 3,
                "unit": "eV"
              }
            ],
            "additional_composition_constraints": [
              {
                "rule": "must_not_contain",
                "elements": [
                  "Pb",
                  "Cd"
                ]
              }
            ],
            "limit": 10
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "Refining works on the stored matches of the named set, which is at most 5000 rows, and never queries the database again. Use show_eliminated on the new set to see what fell out and why."
        },
        {
          "kind": "Trap",
          "text": "Thresholds are compared with the stored value in the property's canonical unit. The `unit` you send is only a label, so a value given in another unit filters wrongly and raises no error."
        }
      ],
      "annotations": {
        "destructiveHint": false,
        "idempotentHint": false,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "rerank_result_set",
      "title": "Rerank result set",
      "group": "result_sets",
      "summary": "Re-orders an earlier result set, either by one property or by a new weighting of its constraints, and returns a new result set.",
      "description": "Re-order an earlier result set. Two different operations, give\nONE of `sort_by`, `primary_constraint` or `weights`:\n\n- `sort_by` + `direction`: order by that one property, ignoring every\n  other axis (\"sort by band gap, highest first\").\n- `primary_constraint` (a 10x weight) or `weights`: a multi-objective\n  re-weighting (\"put more weight on stability\"). This does NOT\n  produce a sorted column when other axes are present.\n\nReturns a NEW `result_set_id` in the new order; scores are not returned.\n`limit` (default 20) bounds the rows returned here, which come from\nthe new set's first page of 20; `notice` says when fewer came back\nthan asked. Page the rest with get_result_set.",
      "parameters": [
        {
          "name": "result_set_id",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The set to re-order. It is left unchanged.",
          "fields": null
        },
        {
          "name": "sort_by",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "A property key. Sorts by that one property and ignores every other axis; materials with no value for it are listed last in both directions. Give exactly one of sort_by, primary_constraint and weights.",
          "fields": null
        },
        {
          "name": "direction",
          "type": "string",
          "nullable": false,
          "required": false,
          "default": "asc",
          "enum": [
            "asc",
            "desc"
          ],
          "description": "asc (the default) or desc. Used with sort_by.",
          "fields": null
        },
        {
          "name": "primary_constraint",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "A property key to weight ten times higher than the others in a multi-objective re-weighting. This does not produce a sorted column.",
          "fields": null
        },
        {
          "name": "weights",
          "type": "object<string, number>",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Map of property key to relative weight for a multi-objective re-weighting, merged over a default of 1 for the set's own constraints.",
          "fields": null
        },
        {
          "name": "limit",
          "type": "integer",
          "nullable": false,
          "required": false,
          "default": 20,
          "enum": null,
          "description": "How many rows of the new set to return here. The new set's first page holds 20 rows, so no more than that come back, and never more than 50; `notice` says when fewer came back than asked. Page the rest with get_result_set.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "Sort by band gap, highest first.",
          "arguments": {
            "result_set_id": "3f2b8e9a-0c1d-4e7f-9a6b-5d4c3b2a1f00",
            "sort_by": "band_gap",
            "direction": "desc",
            "limit": 10
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "There are two different operations. sort_by is a true single-column sort. primary_constraint and weights re-weight a combined score, so the result is not sorted by any one column when the set has other axes. Exactly one of the three must be given, and an empty weights object counts as not given."
        },
        {
          "kind": "Convention",
          "text": "Materials with no value for the sort property are listed last in both directions, because unknown is not the lowest value. The reply's `note` says how many were left unmeasured."
        }
      ],
      "annotations": {
        "destructiveHint": false,
        "idempotentHint": false,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "resolve_material_name",
      "title": "Resolve material name",
      "group": "lookup",
      "summary": "Resolves a commercial or common name, a formula or a Chemia ID to one canonical material, with its Chemia ID, formula and dataset.",
      "description": "Resolve a commercial or common material name to one canonical\nmaterial, for example \"sapphire\" -> \"Al2O3\".\n\nReturns that material's `chemia_id` (the user-facing Chemia\nidentifier), its canonical formula, and the `dataset_tag` it belongs\nto. status is \"found\" (exactly one match), \"ambiguous\" (several\nmatched, check `candidates`), or \"not_found\". This tool answers with\nONE material by design; for \"how many entries exist for X\", that is a\ndifferent question than this tool answers.\n\nResolved against the database, so it is dataset-scoped like every\nother lookup here: source_id explicitly picks a dataset from\nlist_material_sources, and `source`/`source_downgraded` on the\nresponse report which one actually ran and whether your request was\nnarrowed.",
      "parameters": [
        {
          "name": "name",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "What to resolve: a common name such as \"sapphire\", a formula such as Al2O3, a Chemia ID such as CH-MAT-1599, or an internal id. Resolution tries the Chemia ID, then the internal id, then the name table (which pins one structure for a known name), then a formula match in the dataset.",
          "fields": null
        },
        {
          "name": "source_id",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Dataset to resolve in, an `id` from list_material_sources. Null (the default) uses the server's default dataset. `source`, `requested_source` and `source_downgraded` in the response say what ran. An id that is not in the catalogue falls back to the registry default without an error.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "A common name.",
          "arguments": {
            "name": "sapphire"
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "The tool answers with one material by design. A formula that fits several structures returns status \"ambiguous\" with up to 8 candidates, the full `total` and the `ground_state`. The candidates are in Chemia ID order, not stability order, so the first is not the most stable; find_material_entries lists every entry."
        },
        {
          "kind": "Trap",
          "text": "A common name is pinned to one specific structure. When that structure is not in the dataset you queried, the lowest-energy entry of the formula in that dataset stands in, so the Chemia ID returned can be a different structure than the name usually means."
        }
      ],
      "annotations": {
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "score_results",
      "title": "Score results",
      "group": "result_sets",
      "summary": "Ranks an earlier search's stored matches by a weighted score across its constraints and returns a new result set whose rows are in ranked order.",
      "description": "Rank an earlier search's survivors, \"top 10\", \"best\", \"put thermal\nconductivity first\". `weights` maps canonical property names to\nrelative weights (e.g. {\"thermal_conductivity\": 2, \"density\": 1});\nomit it for equal weight across the search's own constraints.\n\nReturns a NEW `result_set_id` whose rows are in ranked order. The order\nIS the ranking: scores are not returned. For a plain single-column\nsort (\"sort by density ascending\") use rerank_result_set with sort_by.",
      "parameters": [
        {
          "name": "result_set_id",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The set to rank. It is left unchanged.",
          "fields": null
        },
        {
          "name": "weights",
          "type": "object<string, number>",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Map of property key to relative weight, for example {\"thermal_conductivity\": 2, \"density\": 1}. The search's own constraints default to weight 1; a property named here that the search did not constrain is added as an extra axis. Null (the default) weights the search's constraints equally.",
          "fields": null
        },
        {
          "name": "top_n",
          "type": "integer",
          "nullable": false,
          "required": false,
          "default": 20,
          "enum": null,
          "description": "How many top rows to number. At most 50 rows come back in this response whatever the value; page the rest with get_result_set.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "Count thermal conductivity twice as much as density.",
          "arguments": {
            "result_set_id": "3f2b8e9a-0c1d-4e7f-9a6b-5d4c3b2a1f00",
            "weights": {
              "thermal_conductivity": 2,
              "density": 1
            },
            "top_n": 10
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "The score is a weighted mean of per-property scores between 0 and 1. For a limit such as \"above X\" a property scores 0.5 at X and rises with the margin past X, counted as a fraction of the property's realistic range; \"below X\" mirrors it, and inside a \"between\" range the score is 1. A property added through weights has no limit, so it is scored from 0 to 1 across its realistic range in the direction the vocabulary prefers. The scores are not returned: the row order is the ranking."
        },
        {
          "kind": "Trap",
          "text": "A material with no value for an axis is skipped on that axis rather than marked down, so a material with fewer measured properties can outrank one with data on every axis."
        }
      ],
      "annotations": {
        "destructiveHint": false,
        "idempotentHint": false,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "search_materials",
      "title": "Search materials",
      "group": "search",
      "summary": "Finds materials from a plain-language description of the properties and composition you want. Returns up to 50 matching rows, the constraints we understood, how many materials were eliminated, and a result_set_id that the other tools use to page, refine, rank and export the full match.",
      "description": "Search Chemia's materials corpus with a natural-language constraint\nquery, for example \"semiconductors with a band gap between 1.1 and\n1.6 eV and bulk modulus above 100 GPa\".\n\nReturns the matching materials, the constraints parsed from the query,\nelimination counts, and a result_set_id that get_result_set\ncan page through. If the query is too ambiguous to run, returns\nclarification_needed instead of guessing.\n\nIf a property named in the query has zero or below-threshold corpus\ncoverage, the response carries property_offer instead: its `note` is\nprose written to be shown as is, naming the proxy routes that reach\nthe property and their caveats (or, with no usable route, the\nirreducible verdict and what extraction would take). A ranking by\nsuch a property still returns its matches, with a property_offer\nsaying they are not ordered by it, and `ordering` reads \"cursor\".\n\nA property value carrying `suspect` is physically impossible for that\nproperty (a negative shear modulus, a density of 1,248 g/cm3): a data\nerror in the corpus, kept with its number so it can be pointed out,\nand not a measurement. Real extremes (osmium's density, diamond's\nstiffness) are not flagged.\n\nEvery constraint is applied together in one query. `eliminations`\nholds that query's totals: `matched` is the number of materials\nmeeting every constraint (equal to `total_survivors`) and\n`eliminated` the rest of the dataset. How many each constraint\nremoved is in `constraint_ledger`.\n\nRanking by how much of an element a material contains works\ndirectly: \"rank Sc compounds by Sc content\" returns the highest\natomic fraction first (weight fraction if the query says by weight),\nranked across the whole match, with the value in each row's\nmetadata `element_fraction`.\n\nRows come most stable first (lowest energy above hull, entries\nwithout one last) unless the query asks for a sort; `ordering` says\nwhich. That is an order, not a ranking; score_results ranks.\n\nsource_id picks a dataset from list_material_sources (\"trial\",\n\"production\") explicitly; a database named only in the query text is\nnot guaranteed to be selected.\n\nCompound classes are exact: \"nitrides\", \"oxides\", \"sulfides\",\n\"halides\" and the like mean compounds of two or more elements in\nwhich that element is the anion. So \"nitrides\" excludes N2 and\nnitrates, and \"sulfides\" excludes sulfates. Say \"contains N\" in the\nquery to mean mere presence instead.\n\nSearches cover real solids by default: frozen gases (N2, O2, He) and\ncompounds of nonmetals alone (NH3, CO, H2O, organic crystals) are left\nout, and a `real_solids` entry in `constraint_ledger` says so. Say\n\"including molecular solids\" in the query to keep them.\n\n`constraint_ledger` covers every constraint that is not a plain\nproperty threshold (cost ceiling, material class, operating\nconditions, form factor, composition, a \"top N\" sort). Each entry's\n`state` is `applied` (enforced; `count` is how many candidates it\neliminated), `not_supported` (a parsed constraint that this server\ndoes not check, so no count is given: it means \"not tracked\", not\n\"none match\"; a condition the parser cannot map at all is dropped and\nshows only in `constraints.open_questions`), or\n`no_data` (candidates excluded from a real property check only for\nlacking that measurement: not a constraint failure, and not part of a\nsame-property `applied` count). Plain thresholds are counted in\n`eliminations`.\n\nmax_results is capped at 50 per response; `limit_applied` and\n`notice` say when fewer came back than were asked for, and\nget_result_set pages through the rest.",
      "parameters": [
        {
          "name": "query",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "Plain-language description of what you want, for example \"semiconductors with a band gap between 1.1 and 1.6 eV and bulk modulus above 100 GPa\". It can combine property limits, element filters, a compound class, a sort such as \"lowest density\" and the name of a database. A language model turns it into constraints, returned in `constraints`. Give each value in the property's canonical unit (the unit on result rows): other units are converted only for the few properties whose vocabulary lists them, and are otherwise not converted. A constraint on water inertness or Pourbaix decomposition energy is evaluated against live Materials Project data and also needs the materials_project_data grant; without it the reply is a clarification question, not a silent skip. A query too vague to run returns `clarification_needed` instead of guessing.",
          "fields": null
        },
        {
          "name": "max_results",
          "type": "integer",
          "nullable": false,
          "required": false,
          "default": 20,
          "enum": null,
          "description": "How many rows to return in this response. Values above 50 are lowered to 50 and values below 1 are raised to 1; `limit_requested`, `limit_applied` and `notice` show what happened. This does not limit the search: `total_survivors` is the full match count and get_result_set pages through the stored matches.",
          "fields": null
        },
        {
          "name": "source_id",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Dataset to search, an `id` from list_material_sources. Null (the default) uses the dataset the query text names, if it names one clearly (that is not guaranteed; pass the id to be sure), and otherwise the server's default dataset. In that case an account without the professional_database grant is narrowed to the trial subset. An id that is not in the catalogue is not rejected: it falls back to the registry default, the trial subset. Asking by id for a restricted dataset your account cannot reach is refused with an error that names the missing grant.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "A two-property search that returns ten rows.",
          "arguments": {
            "query": "semiconductors with a band gap between 1.1 and 1.6 eV and bulk modulus above 100 GPa",
            "max_results": 10
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": true,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "There are three different counts. `total_survivors` is the exact number of matches. `results` holds at most 50 of them, and `truncated` is true when it holds fewer than the total. Up to 5000 matches are stored for get_result_set; rows beyond that were counted but never stored. A query judged to match that many or more, or a scan that runs out of time, is not answered with rows: the reply is `clarification_needed` with a size estimate and ways to narrow it, and the call is refunded."
        },
        {
          "kind": "Trap",
          "text": "Check `source` in every response. A source_id that is misspelt or not in the catalogue raises no error: it resolves to the registry default, the small trial subset, and `requested_source` then names that default too, so only a comparison with the id you sent shows the swap. A restricted dataset that was inferred from the query text, or taken as the server default, is narrowed to the trial subset without an error when your account lacks the professional_database grant; there `requested_source` differs from `source`."
        },
        {
          "kind": "Gap",
          "text": "A search can answer only part of a question without saying so. A condition the parser cannot map to a property it knows (for example coercivity) is dropped: it is not in the parsed `constraints`, it has no `constraint_ledger` entry, and the matches reflect only the other conditions. Its only trace is a sentence in `constraints.open_questions`, and `clarification_needed` and `notice` stay empty. Compare the parsed constraints with what you asked. A ledger entry with state not_supported is the intended signal for a condition that is parsed but cannot be checked; extending it to dropped conditions is a planned fix, not current behaviour. Today it appears only for a parsed constraint whose property is outside the vocabulary or whose kind this server does not enforce (such as a cost ceiling or a material class); it never carries a count. Entries with state no_data count materials left out only because they have no measurement of that property."
        }
      ],
      "annotations": {
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    },
    {
      "name": "show_eliminated",
      "title": "Show eliminated candidates",
      "group": "result_sets",
      "summary": "Shows the candidates an earlier search eliminated, closest miss first, so you can see how near each one came to passing.",
      "description": "List candidates an earlier search eliminated from `set_id`, sorted\nby violation_amount ascending (closest miss first), \"how close did\nthe near-misses come\", not the survivors search_materials/\nget_result_set already return.\n\nconstraint filters to eliminations matching that name or stage\n(substring, case-insensitive), e.g. \"thermal\" matches\n\"thermal_conductivity\". Omit it to see every eliminated candidate.\n\n`total` is how many materials were eliminated (on that constraint,\nor on all): the search's own ledger count, plus every elimination a\nrefine_result_set made. `stored` is how many of them the result set\nkept to show (the search keeps its closest 50 per constraint), and\n`limit` (max 50) is how many come back; there is no offset, so\na narrower `constraint` reaches others. `total`, not `stored`, is\n\"how many failed\". Each candidate carries its `chemia_id` (the id\nusers see; `material_id` is internal), `violation_amount` to four\nsignificant figures, and the property's `unit`.\n\nlimit is capped at 50 (MAX_ROWS) regardless of what's asked for, the\nsame context-budget ceiling as this server's other tools; check\n`limit_applied`/`notice` rather than assuming the count matches what\nwas asked for. Only a result set this account owns AND is still\nentitled to the source of can be read here, same rule as\nget_result_set.",
      "parameters": [
        {
          "name": "set_id",
          "type": "string",
          "nullable": false,
          "required": true,
          "default": null,
          "enum": null,
          "description": "The `result_set_id` of an earlier search, or of a set derived from one. Only result sets created by your own account are readable.",
          "fields": null
        },
        {
          "name": "constraint",
          "type": "string",
          "nullable": true,
          "required": false,
          "default": null,
          "enum": null,
          "description": "Keeps only eliminations whose constraint name or stage contains this text, ignoring case: \"thermal\" matches \"thermal_conductivity\". Null (the default) shows every eliminated candidate.",
          "fields": null
        },
        {
          "name": "limit",
          "type": "integer",
          "nullable": false,
          "required": false,
          "default": 20,
          "enum": null,
          "description": "How many candidates to return, capped at 50. There is no offset: use a narrower `constraint` to reach others.",
          "fields": null
        }
      ],
      "examples": [
        {
          "note": "The ten closest misses on one constraint.",
          "arguments": {
            "set_id": "3f2b8e9a-0c1d-4e7f-9a6b-5d4c3b2a1f00",
            "constraint": "band_gap",
            "limit": 10
          }
        }
      ],
      "grants": [
        "professional_database",
        "trial_database"
      ],
      "grants_rule": "any_one_of",
      "charged": false,
      "charge_condition": null,
      "reading_notes": [
        {
          "kind": "Convention",
          "text": "`total` is how many materials were eliminated; `stored` is how many the set kept to show, which for a search is the closest 50 per constraint. `total`, not `stored`, answers \"how many failed\"."
        },
        {
          "kind": "Convention",
          "text": "Candidates are sorted by `violation_amount`, the shortfall in the property's `unit` to four significant figures. A candidate removed for lacking a value has no violation amount and is listed after every measured near miss."
        }
      ],
      "annotations": {
        "idempotentHint": true,
        "openWorldHint": false,
        "readOnlyHint": true
      }
    }
  ]
}
