Skip to content

Provider-first registration

Most users know the provider they want, not every protocol or SDK that provider exposes.

SchemaRouter keeps its protocol-neutral ingestion architecture internally, but known providers can be registered from a provider identity:

from schemarouter import SchemaRouter

router = SchemaRouter()
result = await router.add_provider("materials-project")

The built-in profile resolves the provider into its known access methods and only registers methods that are safe and usable in the current process. Missing credentials or optional dependencies are reported rather than guessed or installed.

The default is intentionally best-effort: usable methods are registered even when another method needs credentials, an optional dependency, or a manual binding. The returned ProviderRegistrationResult.status is complete, partial, or failed, so callers do not need to infer the aggregate outcome from each method.

When the requested methods form one required fallback/topology unit, use all-or-nothing registration:

result = await router.add_provider(
    "my-provider",
    methods={"openapi", "mcp"},
    require_all=True,
    replace=True,
)
assert result.status == "complete"

With require_all=True, SchemaRouter first prepares every requested declarative method without publishing it. Any missing credential, unavailable dependency/source, manual-only method, duplicate/collision, or preparation failure leaves the requested provider topology unchanged. If preparation succeeds, contracts are published through one version-guarded registry batch and trusted bindings are attached. A binding failure restores the previous contracts and bindings; concurrent registry mutation fails closed rather than overwriting the concurrent writer.

Unknown or ambiguous provider names

Known profiles still resolve locally and deterministically. Unknown names do not trigger implicit network discovery or execution authority.

Use discover_provider() to obtain an inspectable proposal first:

proposal = router.discover_provider("google")

for candidate in proposal.candidates:
    print(candidate.candidate_id, candidate.display_name, candidate.registrable)

A broad organization name such as google is intentionally treated as ambiguous. The built-in service-family hint currently proposes concrete services such as Google Drive, Calendar, Maps, Gemini, and BigQuery rather than guessing one.

Hosts may attach a trusted discovery backend backed by an internal service catalog, documentation search, or another reviewed source:

proposal = router.discover_provider("catalog", backend=my_discovery_backend)
candidate = proposal.candidates[0]

router.approve_provider_candidate(
    candidate,
    expected_digest=candidate.approval_digest,
)

The backend can propose a ProviderProfile, but the proposal is inert. Registration requires an explicit approval call with the exact profile digest, and normal adapter/binding/credential rules still apply afterward. Discovery therefore follows discover → inspect → approve → register, never search → execute.

Inspect before registering

Provider resolution is local and does not make network calls:

resolution = router.resolve_provider("materials-project")

for method in resolution.methods:
    print(method.method_id, method.status, method.credential_names)

A typical environment may report:

optimade    available
openapi     available    credential: X-API-KEY
python-sdk  dependency_missing

Credential requirements are declarations only. Secret values are never stored in provider profiles, ToolSpec documents, snapshots, or inspection output.

Built-in acceptance providers

The built-in acceptance profiles deliberately cover different access shapes:

  • Materials Project — public OPTIMADE, authenticated OpenAPI, and optional mp-api SDK.
  • Crossref — public declarative HTTP/JSON REST access.
  • Tavily — authenticated declarative HTTP/JSON search plus an optional Python SDK path.
  • APIs.guru — public OpenAPI discovery and read-only metrics execution.
  • OData.org V4 reference service — public OData metadata discovery and read-only query execution.

For authenticated methods, supply trusted process-local headers:

result = await router.add_provider(
    "tavily",
    methods={"rest"},
    trusted_headers_by_method={
        "rest": {"Authorization": f"Bearer {TAVILY_API_KEY}"}
    },
)

SchemaRouter does not install SDKs automatically. An installed SDK method still requires an explicit trusted binding unless a provider plugin supplies one.

Multiple access methods remain distinct

Provider-first registration is a convenience layer over the existing adapters:

provider identity
    |
    v
ProviderProfile
    |
    +-- OpenAPI
    +-- OPTIMADE
    +-- GraphQL
    +-- OData
    +-- OpenRPC
    +-- MCP (URL-backed)
    +-- HTTP/JSON
    +-- Python / plugin binding
    |
    v
existing ToolSpec / EndpointSpec / FieldSpec pipeline

All methods keep a shared provider identity and a distinct access_mode. Sharing a provider ID does not make two methods interchangeable. Existing semantic, datatype, unit, qualifier, health, policy, and fallback rules still apply.

Extending the catalog

Applications can register a local ProviderProfile, and installed packages can expose provider profiles through the schemarouter.providers entry-point group. Plugin loading is explicit and allowlisted because importing a Python entry point executes trusted local code.

Method support

Provider profiles can target every URL-backed adapter in the default registry. The provider-first layer forwards the declared method into the existing adapter instead of implementing protocol-specific registration logic.

Profile method kind Provider-first behavior
openapi automatic URL registration
optimade automatic URL registration
graphql automatic URL registration
odata automatic URL registration
openrpc automatic URL registration
mcp automatic URL registration for URL-backed MCP transports
http_json automatic registration from an explicitly trusted declarative ToolSpec
python / SDK explicit trusted binding required; package presence alone never grants execution authority

Caller-owned transports, Python clients, and framework objects remain explicit trust boundaries. Applications can still describe them in a provider profile, but core SchemaRouter does not infer executable authority from an installed package or importable object.

Provider profiles are not an execution planner. They describe how a named provider can be ingested; routing and execution continue through the normal SchemaRouter contracts.