Skip to content

add URL elicitation design doc for per-user credentials#800

Open
maleck13 wants to merge 10 commits intoKuadrant:mainfrom
maleck13:URL-Elicitation-User-Credentials
Open

add URL elicitation design doc for per-user credentials#800
maleck13 wants to merge 10 commits intoKuadrant:mainfrom
maleck13:URL-Elicitation-User-Credentials

Conversation

@maleck13
Copy link
Copy Markdown
Contributor

@maleck13 maleck13 commented Apr 22, 2026

Summary

  • Design doc for gateway-initiated URL elicitation — a per-user credential strategy that keeps
    credentials server-side, out of the MCP client/LLM context
  • Introduces elicitation object on MCPServerRegistration CRD, extensible to OAuth flows
  • Credentials stored encrypted (AES-GCM) on the existing session cache hash, invalidated on upstream
    401

Related #775

Summary by CodeRabbit

  • Documentation
    • Added design specification for URL-based credential collection, enabling secure per-user credential management with encryption and caching capabilities across gateway sessions.

Signed-off-by: craig <cbrookes@redhat.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

📝 Walkthrough

Walkthrough

Introduces a design specification for per-user credential collection via MCP URL mode elicitation. Describes router detection of missing credentials, emission of URLElicitationRequiredError (-32042), credential caching with AES-GCM encryption, and invalidation on upstream 401 responses.

Changes

Cohort / File(s) Summary
Design Specification
docs/design/gateway-initiated-url-elicitation.md
New design spec documenting credential collection via MCP URL mode elicitation, including router flow, credential caching strategy with encryption, cache invalidation behavior, security controls, API/CRD additions, and implementation checklist.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding a design document for URL elicitation to enable per-user credentials.
Linked Issues check ✅ Passed The design doc addresses all coding requirements from issue #775: URL elicitation error flow, per-user credential caching with encryption, session-based storage, upstream 401 invalidation, and API/CRD additions.
Out of Scope Changes check ✅ Passed All changes are scoped to the design document and directly support the per-user credential elicitation feature defined in issue #775; no unrelated modifications present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Signed-off-by: craig <cbrookes@redhat.com>
@maleck13 maleck13 force-pushed the URL-Elicitation-User-Credentials branch from ca05c78 to 61690b8 Compare April 22, 2026 15:09
Signed-off-by: craig <cbrookes@redhat.com>
Signed-off-by: craig <cbrookes@redhat.com>
@maleck13
Copy link
Copy Markdown
Contributor Author

TODO call out how an MCP could handle both bot (SA) and User (elicitation)

Signed-off-by: craig <cbrookes@redhat.com>
Signed-off-by: craig <cbrookes@redhat.com>
Signed-off-by: craig <cbrookes@redhat.com>
Signed-off-by: craig <cbrookes@redhat.com>
Signed-off-by: craig <cbrookes@redhat.com>
@maleck13 maleck13 marked this pull request as ready for review April 24, 2026 14:06
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (5)
docs/design/gateway-initiated-url-elicitation.md (5)

8-8: Hyphenate "third-party".

📝 Grammar fix
-2. **Token exchange via OAuth provider** — requires the OAuth provider to support token exchange and be configured per upstream. Requires third party identity federation.
+2. **Token exchange via OAuth provider** — requires the OAuth provider to support token exchange and be configured per upstream. Requires third-party identity federation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/gateway-initiated-url-elicitation.md` at line 8, In the sentence
under "Token exchange via OAuth provider" update the phrase "third party
identity federation" to use the hyphenated form "third-party identity
federation" so it reads "...Requires third-party identity federation."; locate
the string "Token exchange via OAuth provider — requires the OAuth provider to
support token exchange and be configured per upstream. Requires third party
identity federation." and replace the unhyphenated instance with the hyphenated
one.

112-112: Consider clarifying Authorization header precedence.

The current description compresses the decision tree. For implementation clarity, consider making the order explicit:

  1. If Authorization header exists → use as-is (don't inject)
  2. Else if cached credential exists → inject into Authorization header
  3. Else if client supports elicitation → return -32042
  4. Else → standard error

Based on learnings, the router must only set the Authorization header when not already present to allow AuthPolicy to validate OAuth tokens first.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/gateway-initiated-url-elicitation.md` at line 112, Update the
Router decision logic to make Authorization header precedence explicit: in the
Router handler (the code that checks request headers, client elicitation
capability, cache, and returns -32042) implement the exact sequence: 1) if
request.headers.Authorization (or equivalent header lookup) exists, leave it
untouched and proceed; 2) else if a cached credential exists for the client,
inject it into Authorization; 3) else if client.capabilities.elicitation.url is
true, return error -32042; 4) otherwise return the standard error path; ensure
AuthPolicy validation runs against an existing Authorization header and that
cached injection only occurs when no header is present.

156-156: Use American English: "In the future".

📝 Grammar fix
-When no `url` is set, the router generates a URL pointing to the broker's built-in credential page. In future, if OAuth fields are added (client ID, authorize endpoint, etc.), their presence on the object will imply an OAuth flow.
+When no `url` is set, the router generates a URL pointing to the broker's built-in credential page. In the future, if OAuth fields are added (client ID, authorize endpoint, etc.), their presence on the object will imply an OAuth flow.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/gateway-initiated-url-elicitation.md` at line 156, The phrase "In
future" in the sentence starting "When no `url` is set, the router generates a
URL..." should be changed to American English "In the future"; update that
sentence so it reads "In the future, if OAuth fields are added (client ID,
authorize endpoint, etc.), their presence on the object will imply an OAuth
flow." to fix the grammar in the gateway-initiated-url-elicitation document.

177-177: Add language specifier to fenced code blocks.

Markdown linters require language identifiers on fenced blocks. Use text for flow diagrams.

🔧 Markdown lint fix

Line 177:

-```
+```text
 Router → -32042 (broker URL) → User enters PAT → Broker stores in cache → Router reads cache → sets header

Line 187:
```diff
-```
+```text
 Router → -32042 (external URL) → User stores PAT in Vault → AuthPolicy reads from Vault → sets header
</details>


Also applies to: 187-187

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/design/gateway-initiated-url-elicitation.md at line 177, The two fenced
code blocks containing the flow diagrams (the blocks starting with "Router →
-32042 (broker URL) → User enters PAT → Broker stores in cache → Router reads
cache → sets header" and "Router → -32042 (external URL) → User stores PAT in
Vault → AuthPolicy reads from Vault → sets header") need a language specifier;
update each triple-backtick fence from totext so Markdown linters accept
them.


</details>

---

`212-212`: **Simplify: "to" instead of "in order to".**


<details>
<summary>📝 Conciseness improvement</summary>

```diff
-Encryption is only applied when using an external cache store as the storage backend — it protects credentials in an external store that may be shared or persisted to disk. For the in-memory backend, encryption adds no value since a process memory dump would reveal the encryption key alongside the ciphertext and in order to be used to call a backend the token credential has to be in plain text in memory.
+Encryption is only applied when using an external cache store as the storage backend — it protects credentials in an external store that may be shared or persisted to disk. For the in-memory backend, encryption adds no value since a process memory dump would reveal the encryption key alongside the ciphertext and to be used to call a backend the token credential has to be in plain text in memory.
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@docs/design/gateway-initiated-url-elicitation.md` at line 212, Replace the
phrase "in order to be used to call a backend the token credential has to be in
plain text in memory" with the concise version using "to": "to be used to call a
backend the token credential has to be in plain text in memory" in the sentence
about in-memory backend encryption: "For the in-memory backend, encryption adds
no value since a process memory dump would reveal the encryption key alongside
the ciphertext and to be used to call a backend the token credential has to be
in plain text in memory." Ensure the only change is removing "in order" to keep
meaning and flow.
```

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @docs/design/gateway-initiated-url-elicitation.md:

  • Line 8: In the sentence under "Token exchange via OAuth provider" update the
    phrase "third party identity federation" to use the hyphenated form "third-party
    identity federation" so it reads "...Requires third-party identity federation.";
    locate the string "Token exchange via OAuth provider — requires the OAuth
    provider to support token exchange and be configured per upstream. Requires
    third party identity federation." and replace the unhyphenated instance with the
    hyphenated one.
  • Line 112: Update the Router decision logic to make Authorization header
    precedence explicit: in the Router handler (the code that checks request
    headers, client elicitation capability, cache, and returns -32042) implement the
    exact sequence: 1) if request.headers.Authorization (or equivalent header
    lookup) exists, leave it untouched and proceed; 2) else if a cached credential
    exists for the client, inject it into Authorization; 3) else if
    client.capabilities.elicitation.url is true, return error -32042; 4) otherwise
    return the standard error path; ensure AuthPolicy validation runs against an
    existing Authorization header and that cached injection only occurs when no
    header is present.
  • Line 156: The phrase "In future" in the sentence starting "When no url is
    set, the router generates a URL..." should be changed to American English "In
    the future"; update that sentence so it reads "In the future, if OAuth fields
    are added (client ID, authorize endpoint, etc.), their presence on the object
    will imply an OAuth flow." to fix the grammar in the
    gateway-initiated-url-elicitation document.
  • Line 177: The two fenced code blocks containing the flow diagrams (the blocks
    starting with "Router → -32042 (broker URL) → User enters PAT → Broker stores in
    cache → Router reads cache → sets header" and "Router → -32042 (external URL) →
    User stores PAT in Vault → AuthPolicy reads from Vault → sets header") need a
    language specifier; update each triple-backtick fence from totext so
    Markdown linters accept them.
  • Line 212: Replace the phrase "in order to be used to call a backend the token
    credential has to be in plain text in memory" with the concise version using
    "to": "to be used to call a backend the token credential has to be in plain text
    in memory" in the sentence about in-memory backend encryption: "For the
    in-memory backend, encryption adds no value since a process memory dump would
    reveal the encryption key alongside the ciphertext and to be used to call a
    backend the token credential has to be in plain text in memory." Ensure the only
    change is removing "in order" to keep meaning and flow.

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: Path: .coderabbit.yaml

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `81828a6e-e685-42cd-acff-3db3edb40f2d`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ee72d691ec82d8ee34e6afc2d7e67535fb2655ec and 0a408ac485a8ded04d11b223e9daec3f2792aad7.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `docs/design/gateway-initiated-url-elicitation.md`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Signed-off-by: craig <cbrookes@redhat.com>
@maleck13 maleck13 self-assigned this Apr 30, 2026
Copy link
Copy Markdown
Member

@david-martin david-martin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for this.
A few comments worth discussing, but shouldn't stop this merging or progressing at this stage.

Router → -32042 (external URL) → User stores PAT in Vault → AuthPolicy reads from Vault → sets header
```

AuthPolicy handles credential injection. The router's role simplifies to:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For an experimental feature, having this capability split between MCPServerRegistration and AuthPolicy is reasonable.
It does tie the gateway closer to AuthPolicy capability for pulling in an external credential and swapping a header. I think this is worth further discussion later from a couple of viewpoints:

  • do we tie in closer to the rest of Kuadrant policies e.g. ship with them out of the box
  • is there a resource (a policy?) that would make this particular flow easier overall to configure, given there is a pattern, or perhaps authorino is configured based just on what's in the MCPServerRegistration.credentialURLElicitation stanza?


| Backend | Description |
|---------|-------------|
| **Session cache** (Redis / in-memory) | Initial implementation. Credentials are session-scoped and lost on session expiry or cache eviction. |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In memory might be sufficient for initial implementation (easier for people to try out if they don't have a redis store configured). It would also remove the need for the encryption piece in this version?


The MCP specification [warns about phishing attacks](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation#phishing) where an attacker could trick another user into completing an elicitation on their behalf. The credential page must verify that the user opening the URL is the same user who triggered the elicitation.

Identity verification is handled by the AuthPolicy attached to the gateway route where the credential page is served. The AuthPolicy validates the OIDC token from the request, ensuring the user opening the URL is the same user who triggered the elicitation.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

highlighting, another dependency on AuthPolicy for the e2e to work.
Not against this, but highlighting the coupling

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants