From bc04e291d2c2c005ea562758b2f58b67e1a58651 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Wed, 21 Oct 2026 18:22:12 +0200 Subject: [PATCH 1/3] docs: Document MCP OAuth Sign-In on First Tool Use --- .../object_structure/mcp_servers.mdx | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/content/docs/configuration/librechat_yaml/object_structure/mcp_servers.mdx b/content/docs/configuration/librechat_yaml/object_structure/mcp_servers.mdx index c1846bb09..f1681e968 100644 --- a/content/docs/configuration/librechat_yaml/object_structure/mcp_servers.mdx +++ b/content/docs/configuration/librechat_yaml/object_structure/mcp_servers.mdx @@ -227,6 +227,18 @@ mcpServers: '(Optional) Map of header names and values used only for OAuth flow requests, such as dynamic client registration or token exchange.', 'oauth_headers:\n Authorization: "Bearer ${DCR_API_KEY}"\n X-Custom-Header: "custom_value"', ], + [ + 'oauthPrompt', + 'String', + "(Optional) When an OAuth server asks a user to sign in. `on_start` (default) asks when the conversation turn starts. `on_use` waits until the agent first calls one of the server's tools, when every tool the agent selected from the server is deferred and `oauthToolCatalog` is declared.", + 'oauthPrompt: on_use', + ], + [ + 'oauthToolCatalog', + 'Array of Objects', + '(Optional) Tools an `oauthPrompt: on_use` server offers to tool search before the user signs in. Each entry has a `name` and an optional `description` and `inputSchema`.', + 'oauthToolCatalog:\n - name: read_sheet\n description: Read the values in a spreadsheet range', + ], [ 'obo', 'Object', @@ -641,6 +653,67 @@ Server-side OAuth discovery, registration, token, and revocation HTTP requests d X-Custom-Header: "custom_value" ``` +#### `oauthPrompt` + +- **Type:** String (Optional) +- **Description:** Controls when a user is asked to sign in to an OAuth-protected server. + - `on_start`: the sign-in prompt appears when the conversation turn starts, before the agent runs. + - `on_use`: the turn starts without a prompt, and the agent finds the server's tools through tool search. The prompt appears when the agent first calls one of the server's tools, and the run waits for the user to sign in. +- **Default Value:** `on_start` +- **Notes:** + - `on_use` applies only when every tool the agent selected from the server is [deferred](/docs/features/agents#deferred-tools) ("Defer Loading" on). If any selected tool loads up front, or the agent selects the whole server rather than individual tools, the prompt stays at turn start. + - The `deferred_tools` [agent capability](/docs/configuration/librechat_yaml/object_structure/agents#capabilities) must be enabled. When the `run_in_background` capability is enabled, a selected tool that opts into background execution keeps the prompt at turn start, since a detached call cannot wait for a sign-in. + - `on_use` requires a declared [`oauthToolCatalog`](#oauthtoolcatalog). Without one, the server keeps the turn-start prompt, even for a user who has connected before. + - The tools offered before sign-in must include every selected tool, or the prompt stays at turn start. That is the declared catalog, or the user's own tool list when an earlier connection left one. + - Only servers defined in `librechat.yaml` qualify. The prompt stays at turn start for a server that uses `obo`, `customUserVars`, `requestHeaders`, or per-user or per-request placeholders (`{{LIBRECHAT_USER_*}}`, `{{LIBRECHAT_OPENID_*}}`, `{{LIBRECHAT_GRAPH_*}}`, `{{LIBRECHAT_BODY_*}}`) in its connection settings. + - Applies whether OAuth is configured up front (`oauth`, `requiresOAuth: true`) or detected when the server refuses the connection. + - Not available on servers users create in the UI. +- **Example:** + ```yaml + mcpServers: + google-sheets: + type: streamable-http + url: https://sheets.example.com/mcp + requiresOAuth: true + oauthPrompt: on_use + oauthToolCatalog: + - name: read_sheet + description: Read the values in a spreadsheet range + ``` + +#### `oauthToolCatalog` + +- **Type:** Array of Objects (Optional) +- **Description:** The tools an `oauthPrompt: on_use` server offers to tool search before a user signs in. LibreChat cannot list a protected server's tools for a user who has not signed in, so the operator declares them here. The catalog is the same for every user; no user's connection decides what other users' agents see. Once a user signs in, the server's own tool definitions are used, and the server validates every call. +- **Entry fields:** + - `name`: String (Required) - The tool's name on the server, as the agent selects it. + - `description`: String (Optional) - What the tool does, shown to tool search and the model. + - `inputSchema`: Object (Optional) - The tool's parameters: + - `type`: must be `object` + - `properties`: map of parameter name to a property schema + - `required`: array of required parameter names +- **Property schema:** a bounded subset of JSON Schema with these keys only: + - `type`: one of `string`, `number`, `integer`, `boolean`, `object`, `array`, `null`, or a non-empty list of them + - `description`: String + - `enum`: non-empty array of strings, numbers, booleans, or `null` + - `default`: a string, number, boolean, or `null` + - `items`: for arrays, an object with `type`, `description`, and `enum` +- **Notes:** + - Validation is strict. A misspelled entry key (such as `input_schema`) or a keyword outside the bounded schema (such as `minLength`, `pattern`, or `$ref`) fails config validation instead of being ignored. + - A user whose earlier connection left a tool list is offered that list instead; either way it must include every tool the agent selected from the server, or the prompt stays at turn start. + - Not available on servers users create in the UI. +- **Example:** + ```yaml + oauthToolCatalog: + - name: read_sheet + description: Read the values in a spreadsheet range + inputSchema: + type: object + properties: + range: { type: string } + required: [range] + ``` + #### `startup` - **Type:** Boolean (Optional) @@ -804,6 +877,27 @@ oauth-api-server: X-Custom-Header: 'custom_value' ``` +### MCP Server with OAuth Sign-In on First Tool Use + +```yaml filename="MCP Server with oauthPrompt: on_use" +google-sheets: + type: streamable-http + url: https://sheets.example.com/mcp + requiresOAuth: true + # Ask users to sign in when the agent first calls one of this server's tools. + # Every tool the agent selects from this server must be deferred. + oauthPrompt: on_use + # Offered to tool search until a user signs in; without it the turn-start prompt is kept. + oauthToolCatalog: + - name: read_sheet + description: Read the values in a spreadsheet range + inputSchema: + type: object + properties: + range: { type: string } + required: [range] +``` + ### MCP Server with Server Instructions ```yaml filename="MCP Server with Instructions" From 15845763ca48af873b10e0126efed2d3d403eeb4 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Wed, 21 Oct 2026 20:48:06 +0200 Subject: [PATCH 2/3] test: Verify the MCP OAuth Sign-In Timing Docs Render --- e2e/mcp-oauth-prompt-verify.spec.ts | 54 +++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 e2e/mcp-oauth-prompt-verify.spec.ts diff --git a/e2e/mcp-oauth-prompt-verify.spec.ts b/e2e/mcp-oauth-prompt-verify.spec.ts new file mode 100644 index 000000000..b10ec0433 --- /dev/null +++ b/e2e/mcp-oauth-prompt-verify.spec.ts @@ -0,0 +1,54 @@ +import { test, expect } from '@playwright/test' + +const PAGE = '/docs/configuration/librechat_yaml/object_structure/mcp_servers' + +test.describe('MCP OAuth sign-in timing docs', () => { + test('@scenario:mcp-oauth-prompt-keys-documented both keys have sections that state when each mode prompts', async ({ + page, + }) => { + await page.goto(PAGE) + const main = page.locator('main') + await expect(main.locator('[id="oauthprompt"]')).toHaveCount(1) + await expect(main.locator('[id="oauthtoolcatalog"]')).toHaveCount(1) + await expect(main).toContainText('the sign-in prompt appears when the conversation turn starts') + await expect(main).toContainText('The prompt appears when the agent first calls one of the') + await expect(main).toContainText('Without one, the server keeps the turn-start prompt') + await expect(main).toContainText('fails config validation instead of being ignored') + }) + + test('@scenario:mcp-oauth-prompt-links-resolve the deferred tools, capability and catalog links reach their targets', async ({ + page, + }) => { + await page.goto(PAGE) + const section = page.locator('main') + const catalogLink = section.getByRole('link', { name: 'oauthToolCatalog', exact: true }).first() + await expect(catalogLink).toHaveAttribute('href', /#oauthtoolcatalog$/) + + const deferred = section.getByRole('link', { name: 'deferred', exact: true }).first() + await expect(deferred).toHaveAttribute('href', /\/docs\/features\/agents#deferred-tools$/) + await deferred.click() + await expect(page.locator('[id="deferred-tools"]')).toHaveCount(1) + + await page.goto(PAGE) + const capability = page + .locator('main') + .getByRole('link', { name: 'agent capability', exact: true }) + .first() + await expect(capability).toHaveAttribute('href', /object_structure\/agents#capabilities$/) + await capability.click() + await expect(page.locator('[id="capabilities"]')).toHaveCount(1) + }) + + test('@scenario:mcp-oauth-prompt-example-shown the on_use example with a declared catalog is on the page', async ({ + page, + }) => { + await page.goto(PAGE) + const main = page.locator('main') + await expect( + main.getByRole('heading', { name: 'MCP Server with OAuth Sign-In on First Tool Use' }), + ).toBeVisible() + const example = main.locator('figure, pre').filter({ hasText: 'oauthPrompt: on_use' }).last() + await expect(example).toContainText('oauthToolCatalog:') + await expect(example).toContainText('required: [range]') + }) +}) From f9533e68fdeb0106b2c09c3a09037978576bc88c Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Wed, 21 Oct 2026 21:02:26 +0200 Subject: [PATCH 3/3] docs: Include Admin-Configured Servers in the on_use Eligibility Note --- .../librechat_yaml/object_structure/mcp_servers.mdx | 2 +- e2e/mcp-oauth-prompt-verify.spec.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/content/docs/configuration/librechat_yaml/object_structure/mcp_servers.mdx b/content/docs/configuration/librechat_yaml/object_structure/mcp_servers.mdx index f1681e968..cd0f9843f 100644 --- a/content/docs/configuration/librechat_yaml/object_structure/mcp_servers.mdx +++ b/content/docs/configuration/librechat_yaml/object_structure/mcp_servers.mdx @@ -665,7 +665,7 @@ Server-side OAuth discovery, registration, token, and revocation HTTP requests d - The `deferred_tools` [agent capability](/docs/configuration/librechat_yaml/object_structure/agents#capabilities) must be enabled. When the `run_in_background` capability is enabled, a selected tool that opts into background execution keeps the prompt at turn start, since a detached call cannot wait for a sign-in. - `on_use` requires a declared [`oauthToolCatalog`](#oauthtoolcatalog). Without one, the server keeps the turn-start prompt, even for a user who has connected before. - The tools offered before sign-in must include every selected tool, or the prompt stays at turn start. That is the declared catalog, or the user's own tool list when an earlier connection left one. - - Only servers defined in `librechat.yaml` qualify. The prompt stays at turn start for a server that uses `obo`, `customUserVars`, `requestHeaders`, or per-user or per-request placeholders (`{{LIBRECHAT_USER_*}}`, `{{LIBRECHAT_OPENID_*}}`, `{{LIBRECHAT_GRAPH_*}}`, `{{LIBRECHAT_BODY_*}}`) in its connection settings. + - Only operator-configured servers qualify: those defined in `librechat.yaml` or added through [admin configuration overrides](/docs/features/admin_panel#configuration-management). Servers users create in the UI keep the turn-start prompt. The prompt stays at turn start for a server that uses `obo`, `customUserVars`, `requestHeaders`, or per-user or per-request placeholders (`{{LIBRECHAT_USER_*}}`, `{{LIBRECHAT_OPENID_*}}`, `{{LIBRECHAT_GRAPH_*}}`, `{{LIBRECHAT_BODY_*}}`) in its connection settings. - Applies whether OAuth is configured up front (`oauth`, `requiresOAuth: true`) or detected when the server refuses the connection. - Not available on servers users create in the UI. - **Example:** diff --git a/e2e/mcp-oauth-prompt-verify.spec.ts b/e2e/mcp-oauth-prompt-verify.spec.ts index b10ec0433..bbf102602 100644 --- a/e2e/mcp-oauth-prompt-verify.spec.ts +++ b/e2e/mcp-oauth-prompt-verify.spec.ts @@ -14,6 +14,11 @@ test.describe('MCP OAuth sign-in timing docs', () => { await expect(main).toContainText('The prompt appears when the agent first calls one of the') await expect(main).toContainText('Without one, the server keeps the turn-start prompt') await expect(main).toContainText('fails config validation instead of being ignored') + const admin = main.getByRole('link', { name: 'admin configuration overrides', exact: true }) + await expect(admin).toHaveAttribute( + 'href', + /\/docs\/features\/admin_panel#configuration-management$/, + ) }) test('@scenario:mcp-oauth-prompt-links-resolve the deferred tools, capability and catalog links reach their targets', async ({