RFC: Structured Auth Ownership Metadata for UTCP (with a Non-Normative Runtime State Pattern)
- Status: Draft — seeking community feedback before formal submission
- Author: @rnbokade
- Target repo:
utcp-specification (schema extension only — see Appendix A for why the runtime pattern is not a repo target)
1. Summary
UTCP's auth_type enum (api_key, basic, oauth2) describes how a credential is transmitted, but not enough for a client to determine, before dispatch, what kind of credential lifecycle a tool needs. It can't distinguish a credential the connector developer provisions once and shares across every caller from one that must be provisioned per end user, and it can't distinguish which OAuth grant produced a given token. Every non-trivial implementer that needs this — multi-tenant agent hosts, anything doing per-user OAuth — ends up hand-rolling a classification on top of the spec, differently each time.
This RFC proposes a small, additive extension to the auth block — ownership and grant_type, added alongside the existing auth_type field — so a manual can describe enough about a credential's provisioning model for a client to set up the right authentication flow before attempting a call. It does not propose that UTCP track runtime credential status or session state; that remains an application concern, consistent with UTCP's stateless design. Appendix A sketches a reference pattern for that runtime layer, for implementers who want a shared starting point, but it is explicitly non-normative and not part of the schema proposal.
2. Problem Statement
UTCP's auth_type enum only describes where a credential goes on the wire. It doesn't capture:
- Who owns the credential (connector dev vs. end user) — which determines who provisions/rotates it, and what a client should do when it's missing.
- Which OAuth grant applies — the schema has no field for this today, so a manual can't distinguish a shared client-credentials integration from a per-user authorization-code or device-code flow.
Because neither is describable in a manual, every implementer that needs them ends up hand-rolling a classification on top of UTCP — informally, and inconsistently between projects. This is a metadata gap at the manual level; whether a specific tool call is currently usable for a given user or session is a separate, downstream question that an application layer decides using this metadata (see Appendix A) — it isn't something a manual can or should state on its own.
3. Proposal
3.1 Schema addition — two new optional fields, no changes to auth_type
auth_type already mixes different kinds of concepts under one enum — oauth2 is an authorization framework, basic is an HTTP scheme. This RFC doesn't attempt to normalize that taxonomy; it treats auth_type as an established compatibility surface and adds metadata around it, rather than redefining what it means or changing its enum.
"auth": {
"auth_type": "oauth2",
"ownership": "user",
"grant_type": "authorization_code",
"token_url": "...",
"scopes": ["..."]
}
-
ownership (new, optional): the principal a given credential instance is provisioned for and whose lifecycle governs it. This is narrower than it might sound — it does not describe who owns an underlying OAuth client application, and it does not describe the OAuth resource owner. It's specifically about the credential instance's provisioning and lifecycle. Two values for v1:
static: one credential, provisioned by the connector/tool developer, shared across every caller.
user: a separate credential provisioned per end user.
Finer-grained models — organization-owned, per-tenant, delegated or impersonated credentials — aren't covered by this two-value split. That's a deliberate v1 scope limit, not an oversight; see Open Questions.
-
grant_type (new, optional, applies when auth_type is oauth2): client_credentials | authorization_code | device_code | jwt_bearer, nested under oauth2 rather than promoted to a top-level auth_type value, so new flows don't require new top-level enum values. grant_type identifies which authorization mechanism produced the credential — it's a provisioning-model label, not a complete OAuth flow description. It doesn't carry everything needed to execute that flow: authorization endpoints, PKCE, redirect handling, and client registration are out of scope and remain resolved elsewhere (e.g. token_url plus provider docs, as today).
| Field |
Required |
Applies to |
Default when omitted |
Meaning |
auth_type |
existing, required |
all auth |
— |
Authentication mechanism (existing field, unchanged) |
ownership |
no |
all auth |
static |
Principal the credential is provisioned for and lifecycle-bound to |
grant_type |
no |
auth_type: oauth2 only |
client_credentials |
OAuth flow that produced the credential |
Both fields are optional and additive, and neither introduces a new auth_type value. A client that doesn't act on them can ignore them like any other unrecognized key, and nothing changes in how any client currently reads or validates auth_type.
4. Compatibility
Fully backward compatible. ownership and grant_type are new optional fields, not new values on the auth_type enum, so this proposal introduces no risk even for implementations that validate auth_type as a closed enum — that enum doesn't change. A client that ignores unrecognized keys, which is the baseline UTCP already assumes, behaves identically whether or not a manual includes these fields.
5. Alternatives Considered
- Do nothing, leave it application-specific. The status quo, and precisely what produces the fragmentation this RFC responds to — every implementer handling per-user OAuth converges on some version of this split anyway, just inconsistently.
- Put the full manager interfaces into the spec as required behavior. Rejected — this would turn UTCP into a stateful platform spec rather than a calling-convention spec, undermining the design goal that differentiates it.
- Skip the schema too, keep everything external. Viable, but forgoes the low-cost, high-leverage part of the fix —
ownership/grant_type are cheap to standardize and immediately useful to any client building the Appendix A pattern instead of guessing at conventions.
- Rename
auth_type to credential_shape and demote it to a pure wire-mechanics field. The conceptually cleanest version of a two-axes split. Rejected because it breaks every existing reader of auth_type — contradicting the additive, backward-compatible goal this RFC is built around. Adding sibling fields gets most of the same descriptive power without the breakage.
- Keep ownership/grant metadata entirely outside the manual, in a policy overlay, with a resolver wrapping existing UTCP calls. A policy file keyed by provider/tool ID carries
ownership/grant_type; a resolver reads manual and overlay together, running the Appendix A cascade before delegating to an unmodified UTCP client. Zero spec changes, works against any manual today — a fast way to prototype and validate the idea. Set aside as the main proposal because policy and manual can drift out of sync with nothing enforcing they match, every deployment maintains its own overlay copy, and the ecosystem never converges on shared vocabulary. Still a reasonable way to validate ownership/grant_type before or instead of a schema change; the resolver/overlay pattern is reusable regardless of whether §3.1 is adopted.
- Nest credential metadata under
auth_type — e.g. {"auth_type": "oauth2", "credential": {"ownership": "user", "grant_type": "authorization_code"}}. Separates wire mechanics from credential metadata more cleanly than flat sibling fields, and is arguably the better long-term shape given this RFC's own case that ownership and wire mechanics are different concerns. Not proposed here because it restructures the existing auth object for every consumer rather than just adding keys — a larger compatibility footprint for a comparable descriptive gain. Worth revisiting if the ecosystem wants a deeper auth-schema normalization later.
- Extend
auth_type itself (e.g. mtls, signature) alongside the ownership/grant metadata. Considered, since a broader wire-mechanics taxonomy was part of earlier framing of this proposal. Deferred here: it doesn't serve the ownership/grant-type problem this RFC targets, and bundling an enum extension with an additive metadata proposal adds review surface — and a closed-enum compatibility question — for no benefit to the core ask. Left as a separate, future proposal if there's appetite for it.
6. Open Questions for Discussion
- Does
ownership: static | user cover real cases, or are there ownership models this misses (e.g. org-owned-but-not-shared, delegated/impersonation credentials)?
- Is defaulting omitted
ownership to static right, or should omission mean "unspecified" and force explicitness once user-owned flows are in play?
- Should
ownership generalize to non-OAuth mechanisms UTCP might describe in the future (e.g. per-user vs. shared client certificates under a hypothetical mtls auth type)?
- Is there appetite to converge on the Appendix A pattern even informally, or does each project's internal version differ enough that a shared pattern wouldn't reduce duplicated effort?
Appendix A: Reference Pattern for Runtime State (Non-Normative)
Not part of the proposal in §3 and not intended for inclusion in utcp-specification. This documents one way ownership and grant_type can be consumed at runtime — a shared starting point for implementers, not a requirement, since the fragmentation in §2 already exists in practice regardless of whether this RFC is adopted.
-
CredentialManager — account-level, persists across sessions, keyed by (provider_id, user_id). Exposes get, status (Valid | Missing | Expired | Revoked | Error), refresh, revoke. Error means the manager attempted to determine validity and couldn't reach a reliable conclusion — e.g. the introspection or refresh call itself failed — distinct from Missing (no credential on record) or Revoked (a definite negative answer).
-
ProviderStateManager — session-level, ephemeral, keyed by (provider_id, session_id, tool_id?). Tool-level granularity so partial-scope providers (e.g. Gmail-read enabled, Gmail-send disabled) aren't forced into one rollup state. Where provider- and tool-level settings both exist, the tool-level setting wins for enabling a tool; a provider disabled at the provider level disables all its tools regardless of individual settings.
-
Effective tool state, derived rather than stored directly:
resolve(provider, tool, session, user):
if CredentialManager.status(provider, user) != Valid:
return AuthRequired(reason=status) # Missing / Expired / Revoked / Error, distinct UX for each
if not ProviderStateManager.get_enabled(provider, session, tool):
return Disabled
return Enabled
This keeps "never connected," "token expired," "revoked," and "toggled off for this session" distinguishable — a flat Enabled/Disabled/AuthRequired result collapses all of them.
This pattern directly consumes the fields proposed in §3.1: knowing a credential is user-owned via authorization_code is what tells a CredentialManager which provisioning flow to trigger when status comes back Missing.
RFC: Structured Auth Ownership Metadata for UTCP (with a Non-Normative Runtime State Pattern)
utcp-specification(schema extension only — see Appendix A for why the runtime pattern is not a repo target)1. Summary
UTCP's
auth_typeenum (api_key,basic,oauth2) describes how a credential is transmitted, but not enough for a client to determine, before dispatch, what kind of credential lifecycle a tool needs. It can't distinguish a credential the connector developer provisions once and shares across every caller from one that must be provisioned per end user, and it can't distinguish which OAuth grant produced a given token. Every non-trivial implementer that needs this — multi-tenant agent hosts, anything doing per-user OAuth — ends up hand-rolling a classification on top of the spec, differently each time.This RFC proposes a small, additive extension to the auth block —
ownershipandgrant_type, added alongside the existingauth_typefield — so a manual can describe enough about a credential's provisioning model for a client to set up the right authentication flow before attempting a call. It does not propose that UTCP track runtime credential status or session state; that remains an application concern, consistent with UTCP's stateless design. Appendix A sketches a reference pattern for that runtime layer, for implementers who want a shared starting point, but it is explicitly non-normative and not part of the schema proposal.2. Problem Statement
UTCP's
auth_typeenum only describes where a credential goes on the wire. It doesn't capture:Because neither is describable in a manual, every implementer that needs them ends up hand-rolling a classification on top of UTCP — informally, and inconsistently between projects. This is a metadata gap at the manual level; whether a specific tool call is currently usable for a given user or session is a separate, downstream question that an application layer decides using this metadata (see Appendix A) — it isn't something a manual can or should state on its own.
3. Proposal
3.1 Schema addition — two new optional fields, no changes to
auth_typeauth_typealready mixes different kinds of concepts under one enum —oauth2is an authorization framework,basicis an HTTP scheme. This RFC doesn't attempt to normalize that taxonomy; it treatsauth_typeas an established compatibility surface and adds metadata around it, rather than redefining what it means or changing its enum.ownership(new, optional): the principal a given credential instance is provisioned for and whose lifecycle governs it. This is narrower than it might sound — it does not describe who owns an underlying OAuth client application, and it does not describe the OAuth resource owner. It's specifically about the credential instance's provisioning and lifecycle. Two values for v1:static: one credential, provisioned by the connector/tool developer, shared across every caller.user: a separate credential provisioned per end user.Finer-grained models — organization-owned, per-tenant, delegated or impersonated credentials — aren't covered by this two-value split. That's a deliberate v1 scope limit, not an oversight; see Open Questions.
grant_type(new, optional, applies whenauth_typeisoauth2):client_credentials|authorization_code|device_code|jwt_bearer, nested underoauth2rather than promoted to a top-levelauth_typevalue, so new flows don't require new top-level enum values.grant_typeidentifies which authorization mechanism produced the credential — it's a provisioning-model label, not a complete OAuth flow description. It doesn't carry everything needed to execute that flow: authorization endpoints, PKCE, redirect handling, and client registration are out of scope and remain resolved elsewhere (e.g.token_urlplus provider docs, as today).auth_typeownershipstaticgrant_typeauth_type: oauth2onlyclient_credentialsBoth fields are optional and additive, and neither introduces a new
auth_typevalue. A client that doesn't act on them can ignore them like any other unrecognized key, and nothing changes in how any client currently reads or validatesauth_type.4. Compatibility
Fully backward compatible.
ownershipandgrant_typeare new optional fields, not new values on theauth_typeenum, so this proposal introduces no risk even for implementations that validateauth_typeas a closed enum — that enum doesn't change. A client that ignores unrecognized keys, which is the baseline UTCP already assumes, behaves identically whether or not a manual includes these fields.5. Alternatives Considered
ownership/grant_typeare cheap to standardize and immediately useful to any client building the Appendix A pattern instead of guessing at conventions.auth_typetocredential_shapeand demote it to a pure wire-mechanics field. The conceptually cleanest version of a two-axes split. Rejected because it breaks every existing reader ofauth_type— contradicting the additive, backward-compatible goal this RFC is built around. Adding sibling fields gets most of the same descriptive power without the breakage.ownership/grant_type; a resolver reads manual and overlay together, running the Appendix A cascade before delegating to an unmodified UTCP client. Zero spec changes, works against any manual today — a fast way to prototype and validate the idea. Set aside as the main proposal because policy and manual can drift out of sync with nothing enforcing they match, every deployment maintains its own overlay copy, and the ecosystem never converges on shared vocabulary. Still a reasonable way to validateownership/grant_typebefore or instead of a schema change; the resolver/overlay pattern is reusable regardless of whether §3.1 is adopted.auth_type— e.g.{"auth_type": "oauth2", "credential": {"ownership": "user", "grant_type": "authorization_code"}}. Separates wire mechanics from credential metadata more cleanly than flat sibling fields, and is arguably the better long-term shape given this RFC's own case that ownership and wire mechanics are different concerns. Not proposed here because it restructures the existingauthobject for every consumer rather than just adding keys — a larger compatibility footprint for a comparable descriptive gain. Worth revisiting if the ecosystem wants a deeper auth-schema normalization later.auth_typeitself (e.g.mtls,signature) alongside the ownership/grant metadata. Considered, since a broader wire-mechanics taxonomy was part of earlier framing of this proposal. Deferred here: it doesn't serve the ownership/grant-type problem this RFC targets, and bundling an enum extension with an additive metadata proposal adds review surface — and a closed-enum compatibility question — for no benefit to the core ask. Left as a separate, future proposal if there's appetite for it.6. Open Questions for Discussion
ownership: static | usercover real cases, or are there ownership models this misses (e.g. org-owned-but-not-shared, delegated/impersonation credentials)?ownershiptostaticright, or should omission mean "unspecified" and force explicitness onceuser-owned flows are in play?ownershipgeneralize to non-OAuth mechanisms UTCP might describe in the future (e.g. per-user vs. shared client certificates under a hypotheticalmtlsauth type)?Appendix A: Reference Pattern for Runtime State (Non-Normative)
Not part of the proposal in §3 and not intended for inclusion in
utcp-specification. This documents one wayownershipandgrant_typecan be consumed at runtime — a shared starting point for implementers, not a requirement, since the fragmentation in §2 already exists in practice regardless of whether this RFC is adopted.CredentialManager— account-level, persists across sessions, keyed by(provider_id, user_id). Exposesget,status(Valid | Missing | Expired | Revoked | Error),refresh,revoke.Errormeans the manager attempted to determine validity and couldn't reach a reliable conclusion — e.g. the introspection or refresh call itself failed — distinct fromMissing(no credential on record) orRevoked(a definite negative answer).ProviderStateManager— session-level, ephemeral, keyed by(provider_id, session_id, tool_id?). Tool-level granularity so partial-scope providers (e.g. Gmail-read enabled, Gmail-send disabled) aren't forced into one rollup state. Where provider- and tool-level settings both exist, the tool-level setting wins for enabling a tool; a provider disabled at the provider level disables all its tools regardless of individual settings.Effective tool state, derived rather than stored directly:
This keeps "never connected," "token expired," "revoked," and "toggled off for this session" distinguishable — a flat
Enabled/Disabled/AuthRequiredresult collapses all of them.This pattern directly consumes the fields proposed in §3.1: knowing a credential is
user-owned viaauthorization_codeis what tells aCredentialManagerwhich provisioning flow to trigger when status comes backMissing.