mirror of
https://github.com/open-webui/mcpo.git
synced 2026-07-01 21:04:00 -04:00
issue: OAuth authentication with static client metadata #126
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @bpellens on GitHub (Nov 21, 2025).
Check Existing Issues
mcpo Version
0.0.19
Open WebUI Version (if applicable)
No response
Operating System
macOS Sequoia
Browser (if applicable)
No response
Confirmation
README.md.Expected Behavior
A web browser would open initiating the OAuth flow.
Actual Behavior
Still a dynamic client registration seems to be performed. No OAuth flow is started.
Steps to Reproduce
{ "mcpServers": { "toolbox-mcp": { "type": "streamable-http", "url": "http://127.0.0.1:5000/mcp", "oauth": { "server_url": "https://accounts.google.com", "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth", "token_endpoint": "https://oauth2.googleapis.com/token", "storage_type": "file", "use_loopback": true, "callback_port": 3030, "client_metadata": { "client_name": "MySampleApp", "client_id": "101******.apps.googleusercontent.com", "client_secret": "GO******", "redirect_uris": ["http://localhost:3030/callback"], "scope": "openid email profile" } } } } }Launch the mcpo procy, using the above configuration, via: uvx mcpo@0.0.19 --config mcpoconfig.json
Add the tool to Open WebUI
Logs & Screenshots
Starting MCP OpenAPI Proxy with config file: mcpoconfig.json
2025-11-21 09:42:51,101 - INFO - Starting MCPO Server...
2025-11-21 09:42:51,101 - INFO - Name: MCP OpenAPI Proxy
2025-11-21 09:42:51,101 - INFO - Version: 1.0
2025-11-21 09:42:51,101 - INFO - Description: Automatically generated API from MCP Tool Schemas
2025-11-21 09:42:51,101 - INFO - Hostname: ******.local
2025-11-21 09:42:51,101 - INFO - Port: 8000
2025-11-21 09:42:51,101 - INFO - API Key: Not Provided
2025-11-21 09:42:51,101 - INFO - CORS Allowed Origins: ['']
2025-11-21 09:42:51,102 - INFO - Path Prefix: /
2025-11-21 09:42:51,102 - INFO - Root Path:
2025-11-21 09:42:51,102 - INFO - Loading MCP server configurations from: mcpoconfig.json
2025-11-21 09:42:51,102 - INFO - Configuring MCP Servers:
2025-11-21 09:42:51,102 - INFO - Uvicorn server starting...
INFO: Started server process [22387]
INFO: Waiting for application startup.
2025-11-21 09:42:51,109 - INFO - Initiating connection for server: 'toolbox-mcp'...
2025-11-21 09:42:51,109 - INFO - OAuth configuration detected for server: toolbox-mcp
2025-11-21 09:42:51,112 - INFO - OAuth provider created for server: toolbox-mcp
2025-11-21 09:42:51,129 - INFO - HTTP Request: POST http://127.0.0.1:5000/mcp "HTTP/1.1 200 OK"
2025-11-21 09:42:51,129 - INFO - Negotiated protocol version: 2025-06-18
2025-11-21 09:42:51,130 - INFO - HTTP Request: POST http://127.0.0.1:5000/mcp "HTTP/1.1 202 Accepted"
2025-11-21 09:42:51,131 - INFO - HTTP Request: POST http://127.0.0.1:5000/mcp "HTTP/1.1 200 OK"
2025-11-21 09:42:51,132 - INFO - Successfully connected to 'toolbox-mcp'.
2025-11-21 09:42:51,132 - INFO -
--- Server Startup Summary ---
2025-11-21 09:42:51,132 - INFO - Successfully connected to:
2025-11-21 09:42:51,132 - INFO - - toolbox-mcp
2025-11-21 09:42:51,132 - INFO - --------------------------
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: 127.0.0.1:59644 - "OPTIONS /toolbox-mcp/openapi.json HTTP/1.1" 200 OK
INFO: 127.0.0.1:59644 - "GET /toolbox-mcp/openapi.json HTTP/1.1" 200 OK
INFO: 127.0.0.1:59644 - "GET /toolbox-mcp/openapi.json HTTP/1.1" 200 OK
INFO: 127.0.0.1:59644 - "GET /toolbox-mcp/openapi.json HTTP/1.1" 200 OK
INFO: 127.0.0.1:59696 - "OPTIONS /toolbox-mcp/find-profiles-by-skill HTTP/1.1" 200 OK
2025-11-21 09:51:35,787 - INFO - Calling endpoint: find-profiles-by-skill, with args: {'skillName': 'C#'}
2025-11-21 09:51:35,794 - INFO - HTTP Request: POST http://127.0.0.1:5000/mcp "HTTP/1.1 401 Unauthorized"
2025-11-21 09:51:35,909 - INFO - HTTP Request: GET https://accounts.google.com/.well-known/oauth-protected-resource "HTTP/1.1 404 Not Found"
2025-11-21 09:51:35,931 - INFO - HTTP Request: GET https://accounts.google.com/.well-known/oauth-authorization-server "HTTP/1.1 200 OK"
2025-11-21 09:51:35,961 - INFO - HTTP Request: POST https://accounts.google.com/register "HTTP/1.1 400 Bad Request"
2025-11-21 09:51:35,962 - ERROR - OAuth flow error
Traceback (most recent call last):
File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/mcp/client/auth/oauth2.py", line 522, in async_auth_flow
client_information = await handle_registration_response(registration_response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/mcp/client/auth/utils.py", line 234, in handle_registration_response
raise OAuthRegistrationError(f"Registration failed: {response.status_code} {response.text}")
mcp.client.auth.exceptions.OAuthRegistrationError: Registration failed: 400 <html lang="nl" dir=ltr><style nonce="jrmkwMbw87vCPldJ4EGIvQ">{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{color:#222;text-align:unset;margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px;} > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}pre{white-space:pre-wrap;}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}</style>
400. That’s an error.
The server cannot process the request because it is malformed. It should not be retried. That’s all we know.
2025-11-21 09:51:35,969 - ERROR - Failed to connect to MCP server 'Toolbox': ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/mcpo/main.py", line 567, in lifespan
| async with client_context as (reader, writer, _):
| ^^^^^^^^^^^^^^
| File "/opt/homebrew/Cellar/python@3.13/3.13.9_1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py", line 235, in aexit
| await self.gen.athrow(value)
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/mcp/client/streamable_http.py", line 480, in streamablehttp_client
| async with anyio.create_task_group() as tg:
| ~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/anyio/_backends/_asyncio.py", line 781, in aexit
| raise BaseExceptionGroup(
| "unhandled errors in a TaskGroup", self._exceptions
| ) from None
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/mcp/client/streamable_http.py", line 411, in handle_request_async
| await self._handle_post_request(ctx)
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/mcp/client/streamable_http.py", line 262, in _handle_post_request
| async with ctx.client.stream(
| ~~~~~~~~~~~~~~~~~^
| "POST",
| ^^^^^^^
| ...<2 lines>...
| headers=headers,
| ^^^^^^^^^^^^^^^^
| ) as response:
| ^
| File "/opt/homebrew/Cellar/python@3.13/3.13.9_1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py", line 214, in aenter
| return await anext(self.gen)
| ^^^^^^^^^^^^^^^^^^^^^
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/httpx/_client.py", line 1583, in stream
| response = await self.send(
| ^^^^^^^^^^^^^^^^
| ...<4 lines>...
| )
| ^
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/httpx/_client.py", line 1629, in send
| response = await self._send_handling_auth(
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ...<4 lines>...
| )
| ^
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/httpx/_client.py", line 1675, in _send_handling_auth
| raise exc
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/httpx/_client.py", line 1664, in _send_handling_auth
| next_request = await auth_flow.asend(response)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/mcp/client/auth/oauth2.py", line 522, in async_auth_flow
| client_information = await handle_registration_response(registration_response)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/mcp/client/auth/utils.py", line 234, in handle_registration_response
| raise OAuthRegistrationError(f"Registration failed: {response.status_code} {response.text}")
| mcp.client.auth.exceptions.OAuthRegistrationError: Registration failed: 400 <html lang="nl" dir=ltr><style nonce="jrmkwMbw87vCPldJ4EGIvQ">{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{color:#222;text-align:unset;margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px;} > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}pre{white-space:pre-wrap;}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}</style>
400. That’s an error.
The server cannot process the request because it is malformed. It should not be retried. That’s all we know.
+------------------------------------
ERROR: + Exception Group Traceback (most recent call last):
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/starlette/routing.py", line 694, in lifespan
| async with self.lifespan_context(app) as maybe_state:
| ~~~~~~~~~~~~~~~~~~~~~^^^^^
| File "/opt/homebrew/Cellar/python@3.13/3.13.9_1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py", line 235, in aexit
| await self.gen.athrow(value)
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/mcpo/main.py", line 438, in lifespan
| async with AsyncExitStack() as stack:
| ~~~~~~~~~~~~~~^^
| File "/opt/homebrew/Cellar/python@3.13/3.13.9_1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py", line 768, in aexit
| raise exc
| File "/opt/homebrew/Cellar/python@3.13/3.13.9_1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py", line 751, in aexit
| cb_suppress = await cb(exc_details)
| ^^^^^^^^^^^^^^^^^^^^^^
| File "/opt/homebrew/Cellar/python@3.13/3.13.9_1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py", line 235, in aexit
| await self.gen.athrow(value)
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/mcpo/main.py", line 567, in lifespan
| async with client_context as (reader, writer, _):
| ^^^^^^^^^^^^^^
| File "/opt/homebrew/Cellar/python@3.13/3.13.9_1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py", line 235, in aexit
| await self.gen.athrow(value)
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/mcp/client/streamable_http.py", line 480, in streamablehttp_client
| async with anyio.create_task_group() as tg:
| ~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/anyio/_backends/_asyncio.py", line 781, in aexit
| raise BaseExceptionGroup(
| "unhandled errors in a TaskGroup", self._exceptions
| ) from None
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/mcp/client/streamable_http.py", line 411, in handle_request_async
| await self._handle_post_request(ctx)
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/mcp/client/streamable_http.py", line 262, in _handle_post_request
| async with ctx.client.stream(
| ~~~~~~~~~~~~~~~~~^
| "POST",
| ^^^^^^^
| ...<2 lines>...
| headers=headers,
| ^^^^^^^^^^^^^^^^
| ) as response:
| ^
| File "/opt/homebrew/Cellar/python@3.13/3.13.9_1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py", line 214, in aenter
| return await anext(self.gen)
| ^^^^^^^^^^^^^^^^^^^^^
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/httpx/_client.py", line 1583, in stream
| response = await self.send(
| ^^^^^^^^^^^^^^^^
| ...<4 lines>...
| )
| ^
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/httpx/_client.py", line 1629, in send
| response = await self._send_handling_auth(
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ...<4 lines>...
| )
| ^
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/httpx/_client.py", line 1675, in _send_handling_auth
| raise exc
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/httpx/_client.py", line 1664, in _send_handling_auth
| next_request = await auth_flow.asend(response)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/Users//.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/mcp/client/auth/oauth2.py", line 522, in async_auth_flow
| client_information = await handle_registration_response(registration_response)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/Users/*/.cache/uv/archive-v0/LCGc1p2kkXKfMqNpTMiyL/lib/python3.13/site-packages/mcp/client/auth/utils.py", line 234, in handle_registration_response
| raise OAuthRegistrationError(f"Registration failed: {response.status_code} {response.text}")
| mcp.client.auth.exceptions.OAuthRegistrationError: Registration failed: 400 <html lang="nl" dir=ltr><style nonce="jrmkwMbw87vCPldJ4EGIvQ">{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{color:#222;text-align:unset;margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px;} > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}pre{white-space:pre-wrap;}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}</style>
400. That’s an error.
The server cannot process the request because it is malformed. It should not be retried. That’s all we know.
+------------------------------------
Additional Information
My objective is to expose my MCP server, which is created with the MCP Toolbox for Databases (https://googleapis.github.io/genai-toolbox), as a tool in my Open WebUI via the mcpo proxy. The MCP server is configured with a BigQuery source and tool. I would like to pass the user context to the tool, executing the query on my BigQuery dataset.
As Google does not seem to support dynamic OAuth client registration, I would like to configure the mcpo proxy using static client metadata. I expected with this configuration that a browser based OAuth flow would be initiated. However, if I understand correctly, the mcpo still tries to initiate a dynamic client registration and consequently fails.
Is it possible to setup the mcpo proxy in such a way that a user-based OAuth flow is triggered and the access token resulting from the OAuth flow is passed to the MCP server?
(My apologies if this question, or similar has been raised elsewhere).
@sonmaximum commented on GitHub (Jan 20, 2026):
Hi, I'm not involved in this project directly, but I was seemingly able to get the static registration to work locally at least by putting registration info in the file
~/.mcpo/tokens/<hash>_client.jsonwhere {hash} is the first eight hex digits of the MD5 of the connection name (so seemingly8b7dc71cfromtoolbox-mcpin your case)my {hash}_client.json file looks like
Dunno if this will help or not but maybe it will at least give you a place to follow up on?