Conversation
|
@copilot for notion there is not real oauth configuration, just the /mcp endpoint. how would that be handled here? |
Great point. Notion has two modes: Option A — Internal integration token (most common for personal use): you get a static token from https://www.notion.so/profile/integrations and pass it directly as a header. No "notion": {
"transport": "streamable-http",
"url": "https://mcp.notion.com/mcp",
"headers": {
"Authorization": "******"
}
}Option B — Public OAuth app: only needed when you've registered a public Notion OAuth application (has a Updated |
|
@copilot no there is also no token. could it be some dynamic client registration thing? |
Yes, exactly — Notion's remote MCP server uses OAuth 2.1 with Dynamic Client Registration (RFC 7591 / RFC 8414). There's no Implemented in commit
The minimal config for Notion is now just: "notion": {
"transport": "streamable-http",
"url": "https://mcp.notion.com/mcp",
"auth": { "type": "oauth2" }
}Then run |
acadfa0 to
ecbc7d4
Compare
Agent-Logs-Url: https://github.com/convidera/omlx/sessions/16fe44b2-7940-4be4-8cc4-0fee521779b1 Co-authored-by: ChristianPraiss <6369555+ChristianPraiss@users.noreply.github.com>
…flow Agent-Logs-Url: https://github.com/convidera/omlx/sessions/e7f02143-b251-4cf7-bdfb-429b7f97e6f2 Co-authored-by: ChristianPraiss <6369555+ChristianPraiss@users.noreply.github.com>
…e Notion Agent-Logs-Url: https://github.com/convidera/omlx/sessions/5ffcc387-cbcc-485b-96ca-81e467724689 Co-authored-by: ChristianPraiss <6369555+ChristianPraiss@users.noreply.github.com>
When a request includes `execute_mcp_tools: true` (sent by the built-in
chat UI), the server now executes MCP tool calls in a loop and streams
the final model response — the client never sees intermediate tool_calls.
All other clients (API scripts, OpenClaw, etc.) get the standard OpenAI
behaviour: tool_calls are returned for the client to handle.
Also fixes two bugs in the tool message construction:
- Use "" instead of None for assistant content to avoid Jinja2 TypeError
- Guard json.loads() against returning None ("null") which broke the
Harmony chat template's `in` containment checks
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
`omlx mcp login/logout/status` now respect the configured MCP config path from settings when --mcp-config is not explicitly provided. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Emit tool_call_event chunks from server during MCP execution loops, containing tool name, arguments, and result - Add tooluse-container CSS and renderToolUseBlock() JS for collapsible tool call display (matching the thinking block style) - Rewrite renderMarkdown/renderStreamingMarkdown to process <think> and <tooluse> tags in document order via unified _renderSequential() - Replace full-innerHTML streaming updates with incremental DOM appends: completed blocks become permanent .streaming-stable nodes, only the active tail is updated in-place, preventing flicker between tool calls Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- New /admin/api/mcp/* routes: list servers with status/auth info, reconnect, authenticate (OAuth PKCE), logout, and get/save config - OAuth PKCE flow runs entirely in-browser: server returns auth_url, frontend opens a popup, callback page posts message on completion - New MCP tab in admin navbar and _mcp.html template showing server cards with transport/state/auth badges, action buttons, and a collapsible raw config JSON editor - DASHBOARD_MAIN_TABS updated to include 'mcp'; tab loads servers automatically on activation Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…erError - Fix Lucide icon processor in base.html to skip Alpine.js attributes (@, x- prefixes) when copying attributes to replacement SVG elements, preventing InvalidCharacterError: Invalid qualified name: '@mouseenter' - Replace all :class/:show bindings on <i data-lucide> elements with inline SVG spinners on wrappers — Lucide's setInterval poller replaces <i> elements and severs Alpine reactivity, making spinners and disabled states invisible; now all loading states use text changes + inline SVGs - Add tools list to GET /admin/api/mcp/servers response (name, description, param_count per tool from connected clients) - Show tools per server in an expandable section (click tool count badge) - Add mcpExpandedServer state to track which server is expanded Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
After auth completes, the server-side reconnect is an asyncio.create_task so the server is still 'connecting' when the first loadMcpServers() fires. Now poll every 1.5s (up to 12 attempts / 18s) until the server leaves the 'connecting' state, updating the message to 'Connected successfully' or 'Authenticated' accordingly. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- POST /admin/api/mcp/reload — stops current MCP manager, loads config from disk, and restarts with new config (no server restart needed) - 'Reload Config' button in the MCP tab header with spinner feedback - Banner message shows reload result (server/tool count on success, error detail on failure), auto-dismisses after 5s Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Each transport (stdio, sse, streamable-http) is now run inside its own asyncio.Task via _run_transport_in_task / _start_transport_task. The context manager's __aenter__ and __aexit__ are called within that single Task, satisfying anyio's invariant that cancel scopes must be exited from the same task that entered them. Disconnect is signalled via asyncio.Event; _cleanup_resources sets the event and awaits the task (5 s timeout then cancels) instead of calling __aexit__ directly from the caller's task. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Tool cards show 3-line clamp with pointer cursor and hover highlight - Clicking a card opens a modal overlay with the full markdown description - Modal dismisses on backdrop click, X button, or Escape key Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ind class max-h-[80vh] is not in the precompiled Tailwind CSS, so maxHeight resolved to 'none' and the modal expanded to full content height. Inline style max-height: 80vh is always applied regardless of the CSS build. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Persist discovered token_url in TokenData so token refresh works for servers using Dynamic Client Registration (fixes empty URL error) - Reuse existing registered_client_id in OAuth start flow to prevent redundant DCR on repeated authentication attempts - Only unpack tool result JSON content back to dict (not list/scalar) for gpt_oss, preventing Jinja2 |items filter failure on non-mappings Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ckaging fixes - Add omlx/mcp/builtins.py with BuiltinToolProvider exposing builtin__fetch and builtin__web_search (DuckDuckGo) — available without any MCP server - Integrate builtins into MCPClientManager (get_all_tools, execute, _find_server) - Extract inject_tool_calling() into omlx/utils/tokenizer so both VLM and batched engines share the same logic; patch gemma4 regex to match hyphenated tool names; handle mlx-lm private-attr API vs plain instance attrs - Switch packaging/build.py from pipx run to uvx for venvstacks commands - Add ddgs and truststore dependencies to pyproject.toml Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ecbc7d4 to
6ce853f
Compare
OAuth-protected MCP servers (e.g. Notion MCP at
https://mcp.notion.com/mcp) return401 Unauthorized, causing zero tools to be discovered. The client had no OAuth lifecycle: no token acquisition, no refresh, no retry. Notion's remote MCP endpoint uses OAuth 2.1 with Dynamic Client Registration (DCR) — noclient_idneeds to be pre-configured.New modules
omlx/mcp/token_store.py—TokenData+TokenStore: persists tokens per server name using the OS keychain (keyring) with a~/.config/omlx/mcp_tokens.json(mode0o600) fallback.is_expiredincludes a 30s safety margin.TokenDatanow also storesregistered_client_idfor DCR-based servers.omlx/mcp/oauth.py—MCPOAuthManagerorchestrating two flows:flow="pkce") — opens browser, captures redirect on an ephemeral local HTTP serverflow="device") — prints user code, polls token endpointclient_idis configured, the manager auto-discovers the OAuth Authorization Server metadata (/.well-known/oauth-authorization-server) and registers the client on the fly to obtain aclient_id. The dynamically registeredclient_idis persisted inTokenDataand reused for token refresh.get_token_info()returns metadata only (raw tokens never exposed)Modified files
omlx/mcp/types.py— newMCPAuthConfigdataclass (type,client_id,auth_url,token_url,scopes,audience,device_auth_url,token_store);client_id,auth_url, andtoken_urlare optional — when omitted, DCR auto-discovery is used.MCPServerConfiggains optionalauthfield;MCPServerStatusgainsauth_state.omlx/mcp/config.py— parses theauthdict block intoMCPAuthConfig(backward-compatible; servers withoutauthare unaffected). Keys prefixed with_(e.g._comment) are stripped before parsing so documentation annotations in JSON configs don't cause errors.omlx/mcp/client.py—connect()injectsAuthorization: Bearerheader before connecting, and retries once on any 401 with a force-refreshed token._connect_sse()now passes headers to the MCP SDK'ssse_client.get_status()reportsauth_state. Server URL is forwarded to the OAuth manager to enable DCR discovery.omlx/cli.py— newomlx mcpcommand:Notion configuration
Notion's remote MCP server uses OAuth 2.1 with DCR — no developer portal registration or manual
client_idis required. The minimal config is:Then run
omlx mcp login notion. The client discovershttps://mcp.notion.com/.well-known/oauth-authorization-server, registers itself, opens a browser for consent, and stores the resulting tokens. For developers with a registered public OAuth application, explicitclient_id/auth_url/token_urlfields are still supported.