Add server-side proxy endpoints for OAuth discovery, client registration,
and token exchange to avoid CORS issues when using proxy connection mode.
Server proxy endpoints:
- GET /oauth/resource-metadata - proxies OAuth protected resource metadata
- GET /oauth/metadata - proxies OAuth authorization server metadata
- POST /oauth/register - proxies dynamic client registration
- POST /oauth/token - proxies token exchange
Client changes:
- Construct correct RFC 9728/8414 path-aware well-known URLs client-side
- Route all OAuth discovery through proxy when in proxy connection mode
- Eliminate direct browser fetches that fail with CORS in proxy mode
* ### client/src/App.tsx
- Imports:
- Added: `Task` and `GetTaskResultSchema` to the same import block.
- UI Icons: Added `ListTodo` from `lucide-react` to the icon imports.
- Components: Added `TasksTab` import beside `ToolsTab`.
- Config utils: Added `getMCPTaskTtl` to the config utils import block.
- State additions:
- `const [tasks, setTasks] = useState<Task[]>([]);`
- Extended `errors` state to include a `tasks` key: `tasks: null,`
- `const [selectedTask, setSelectedTask] = useState<Task | null>(null);`
- `const [isPollingTask, setIsPollingTask] = useState(false);`
- `const [nextTaskCursor, setNextTaskCursor] = useState<string | undefined>();`
- Hook: `useConnection({...})` return value usage extended
- Added destructured functions: `cancelTask: cancelMcpTask` and `listTasks: listMcpTasks` from the custom hook.
- Notification handling:
- In `onNotification`, added:
- If `method === "notifications/tasks/list_changed"`, call `listTasks()` (voided).
- If `method === "notifications/tasks/status"`, treat `notification.params` as a `Task` and update `tasks` state: replace if exists by `taskId`, otherwise prepend. Also update `selectedTask` if it’s the same `taskId`.
- Tab routing:
- When computing valid tabs, added `tasks` when server declares capability: `...(serverCapabilities?.tasks ? ["tasks"] : []),`
- When choosing a default tab, added a branch to fall back to `"tasks"` if neither resources/prompts/tools are present but tasks are.
- Effect for Tasks tab:
- When `mcpClient` is connected and `activeTab === "tasks"`, invoke `listTasks()`.
- Tools → task-augmented calls integration in `callTool`:
- Parameter signature supports `runAsTask?: boolean` (already present in this file), but now:
- If `runAsTask` is true, augment the `tools/call` request’s `params` with a `task` object: `{ task: { ttl: getMCPTaskTtl(config) } }`.
- Use a permissive result schema for tool call: `sendMCPRequest(request, z.any(), "tools")` to avoid version-mismatch schema issues.
- Task reference detection introduced:
- `isTaskResult` helper checks for a nested `task` object with `taskId` (i.e., `response.task.taskId`).
- When task is detected:
- Set `isPollingTask(true)`.
- Immediately set a temporary `toolResult` that includes text content “Task created: … Polling for status…” and `_meta` with `"io.modelcontextprotocol/related-task": { taskId }`.
- Start a polling loop:
- Delay 1s between polls.
- Call `tasks/get` with `GetTaskResultSchema` for status.
- If status is `completed`: call `tasks/result` with `z.any()` to retrieve the final result and set it as `toolResult`; call `listTasks()`.
- If status is `failed` or `cancelled`: set an error `toolResult` content that includes the status + `statusMessage`; call `listTasks()`.
- Else (still running): update `toolResult` content with current `status`/`statusMessage` and preserve `_meta` related-task.
- After loop, set `isPollingTask(false)`.
- When not a task response, set `toolResult` directly from response (cast to `CompatibilityCallToolResult`).
- Tasks list + cancel helpers in App:
- `listTasks`: uses `listMcpTasks(nextTaskCursor)` from the hook, updates `tasks`, `nextTaskCursor`, and clears `errors.tasks`.
- `cancelTask`: calls `cancelMcpTask(taskId)`, updates `tasks` array by `taskId`, updates `selectedTask` if it matches, and clears `errors.tasks`.
- UI integration:
- Added a `TabsTrigger` for “Tasks” with `<ListTodo />` icon, disabled unless server supports tasks.
- Added `<TasksTab />` to the main `TabsContent` block, passing: `tasks`, `listTasks`, `clearTasks`, `cancelTask`, `selectedTask`, `setSelectedTask`, `error={errors.tasks}`, `nextCursor={nextTaskCursor}`.
- Passed `isPollingTask={isPollingTask}` and `toolResult` into `ToolsTab` so the Tools tab can show the live “Polling Task…” state and block reruns while polling.
Note: The raw diff is long; the key hunks align with the above bullet points (imports, state, notifications, tab wiring, request augmentation, polling loop, UI additions).
---
### client/src/components/ToolsTab.tsx
- Props shape changed:
- Added `isPollingTask?: boolean` prop in the destructured props and in the prop types.
- The `callTool` callback signature is now `(name, params, metadata?, runAsTask?) => Promise<void>` (runAsTask added earlier; test updates elsewhere reflect this).
- Local state additions:
- `const [runAsTask, setRunAsTask] = useState(false);`
- Reset behavior:
- When switching tools (`useEffect` on `selectedTool`), reset `runAsTask(false)`.
- When clearing the list in `ListPane.clearItems`, also call `setRunAsTask(false)`.
- UI additions:
- New checkbox control block to toggle “Run as task”:
- Checkbox `id="run-as-task"`, bound to `runAsTask`, with `onCheckedChange` → `setRunAsTask(checked)`.
- Label “Run as task”.
- Run button disabling conditions expanded to include `isPollingTask`.
- Run button text shows spinner with conditional label:
- If `isToolRunning || isPollingTask` → show spinner and text `isPollingTask ? "Polling Task..." : "Running..."`.
- Call invocation change:
- When clicking “Run Tool”, the `callTool` is invoked with `(selectedTool.name, params, metadata?, runAsTask)`.
- ToolResults relay:
- Passes `isPollingTask` to `<ToolResults />`.
---
### client/src/components/ToolResults.tsx
- Props shape changed:
- Added optional prop: `isPollingTask?: boolean`.
- Task-running banner logic:
- Extracts related task from the tool result’s `_meta["io.modelcontextprotocol/related-task"]` if present.
- Computes `isTaskRunning` as `isPollingTask ||` a text-heuristic against `structuredResult.content` entries that contain text like “Polling” or “Task status”.
- Header “Tool Result:” now conditionally shows:
- `Error` (red) if `isError` is true, else
- `Task Running` (yellow) if `isTaskRunning`, else
- `Success` (green).
No other changes to validation or rendering of content blocks.
---
### client/src/components/TasksTab.tsx (new file)
- A brand new tab to list and inspect tasks.
- Key elements:
- Imports `Task` type and multiple status icons.
- `TaskStatusIcon` component maps task `status` to an icon and color.
- Main `TasksTab` props: `tasks`, `listTasks`, `clearTasks`, `cancelTask`, `selectedTask`, `setSelectedTask`, `error`, `nextCursor`.
- Left column (`ListPane`): lists tasks, shows status icon, `taskId`, `status`, and last update time; button text changes to “List More Tasks” if `nextCursor` present; disables button if no cursor and list non-empty.
- Right column:
- Shows error `Alert` if `error` prop provided.
- If a task is selected: header with `Task Details`, a Cancel button when `status === "working"` (shows a spinner while cancelling), and a grid of task fields: Status (with colored label and icon), Last Updated, Created At, TTL (shows “Infinite” if `ttl === null`, otherwise shows numeric with `s` suffix), optional Status Message, and full task JSON via `JsonView`.
- If no task is selected: centered empty state with a “Refresh Tasks” button.
---
### client/src/lib/hooks/useConnection.ts
- Imports added from `@modelcontextprotocol/sdk/types.js`:
- `ListTasksResultSchema`, `CancelTaskResultSchema`, `TaskStatusNotificationSchema`.
- Client capabilities on `connect`:
- Added `tasks: { list: {}, cancel: {} }` into the `clientCapabilities` passed to `new Client(...)`.
- Notification handling setup:
- The hook’s notification schema registration now includes the `TaskStatusNotificationSchema` in the `setNotificationHandler` list so the app receives `notifications/tasks/status`.
- New hook functions:
- `cancelTask(taskId: string)` sends `tasks/cancel` with `CancelTaskResultSchema`.
- `listTasks(cursor?: string)` sends `tasks/list` with `ListTasksResultSchema`.
- Exports:
- Returned object now includes `cancelTask` and `listTasks`.
---
### client/src/utils/configUtils.ts
- Added a new getter:
- `export const getMCPTaskTtl = (config: InspectorConfig): number => { return config.MCP_TASK_TTL.value as number; };`
---
### client/src/lib/configurationTypes.ts
- `InspectorConfig` type extended with a new item:
- `MCP_TASK_TTL: ConfigItem;`
- Includes descriptive JSDoc about default TTL in milliseconds for newly created tasks.
---
### client/src/lib/constants.ts
- `DEFAULT_INSPECTOR_CONFIG` extended with a default for task TTL:
- Key: `MCP_TASK_TTL`
- Label: `"Task TTL"`
- Description: `"Default Time-to-Live (TTL) in milliseconds for newly created tasks"`
- Default `value: 60000`
- `is_session_item: false`
---
### client/src/components/__tests__/ToolsTab.test.tsx
- Expectations updated due to new `callTool` signature (4th arg `runAsTask`). Everywhere the test asserts a `callTool` invocation, an additional trailing `false` argument was added to reflect the default state when the box isn’t checked.
- Examples of added trailing `false` at various assertion points (line offsets from diff): after calls around prior lines 132, 157, 193, 236, 257, 279, 297, 818, 1082 (now passing 4 arguments: name, params, metadata-or-undefined, false).
---
### Additional notes
- The Tasks feature is wired end-to-end:
- Client capability declaration (list/cancel)
- Notification handler for `notifications/tasks/status`
- Tools call augmentation with `task` `{ ttl }`
- Polling loop using `tasks/get` and `tasks/result`
- UI feedback in Tools and dedicated Tasks tab
- Configurable TTL via new config item and getter
* Fix issue where displayed task on task tab was not updated when task completes, leaving the cancel task button available.
* Handle button the same when input_requred or working state
* ### What changed
- Added **receiver-side (client-side) Tasks support** to the Inspector client for task-augmented **server → client** requests.
- When the server sends `sampling/createMessage` or `elicitation/create` with `params.task`, the client now:
- Creates a local task and immediately returns a `CreateTaskResult` (`{ task: ... }`).
- Runs the underlying user-driven flow asynchronously.
- Updates task state to `completed` / `failed` and makes the final payload available via `tasks/result`.
- Implemented server polling handlers backed by the local task store:
- `tasks/get`
- `tasks/result` (blocks until payload is ready)
- `tasks/list`
- `tasks/cancel`
- Advertises task-augmented request capabilities via `capabilities.tasks.requests.sampling.createMessage` and `capabilities.tasks.requests.elicitation.create` when the corresponding callbacks are provided.
- Emits best-effort `notifications/tasks/status` updates as local task status changes.
* Fix review requests.
* In the ToolsTab, when a tool call results in an elicitation request, the app automatically switches to the elicitation tab, and once submitted, switches back to the ToolsTab to view the result of the tool call.
However, when a tool call results in a sampling request, it does not switch automatically to the SamplingTab and when the sampling request form is submitted it does not automatically switch back to the ToolsTab to view the result of the tool call.
These changes make the sampling use case work like the elicitation one. Tests were added.
fix: OAuth DCR scope field compliance
Omits the scope field entirely when undefined, per RFC 7591. This allows OAuth servers to assign default scopes instead of registering clients with no scopes.
Tested with Keycloak DCR.
When failing to connect to an MCP server, its very hard to understand
why. There is only an opaque "Connection Error" message with no
additional info. This also doesn't change at all on repeated attempts,
so there is no indication we attempted again and failed.
This change introduces a `toast` to visualize the error, and adds the
`cause` into the error message for further debugging
- Add state for connectionType
- Fetch connectionType from localstorage if present
* In Sidebar.tsx
- Add dropdown for connectionType, only visible if transport type is not stdio
* In Sidebar.test.tsx
- set connectionType to "proxy"
* In useConnection.ts
- add connectionType param, defaulting to "proxy"
- add state for mcpSessionId
- Only check proxy health if the connection type is proxy
- create a serverUrl variable which will either be the proxy url OR the server url depending upon the connection type
- added captureResponseHeaders function to extract the protocol-related headers from a response.
- when creating transport options and connectionType is "direct" and transportType is not "stdio",
- add the appropriate `Accept` and `Content-Type` headers
- capture the protocol related response headers and store them in the custom fetch handler
- set serverUrl to be the server url, not the proxy url
- if connection type is proxy set serverUrl to the mcpProxyUrl
- use serverUrl instead of mcpProxyUrl for the transport instantiation
* In useConnection.test.tsx
- replaced test "sends X-MCP-Proxy-Auth header when proxy auth token is configured" with two tests:
- "sends X-MCP-Proxy-Auth header when proxy auth token is configured for 'proxy' connectionType"
- "does NOT send X-MCP-Proxy-Auth header when proxy auth token is configured for 'direct' connectionType"
- added Connection URL Verification section with 2 tests
- uses server URL directly when connectionType is 'direct'"
- "uses proxy server URL when connectionType is 'proxy'"
- Introduced CustomHeaders component for managing custom headers.
- Updated Sidebar to include CustomHeaders component.
- Enhanced useConnection hook to handle custom headers and legacy auth migration.
- Added tests for custom headers functionality.
- Updated package.json and package-lock.json for new dependencies.
- set TS SDK to 1.17.5, which has the automatic log level handling
* In useConnection.ts
- push the logging/setLevel message onto the message history stack so it is clear in the Inspector that it has been sent.
Make both clients use name from package.json for consistency and protocol clarity
- web client changes `mcp-inspector` -> `inspector-client` (package.json)
- cli client remains `inspector-cli` (package.json)
The STDIO and SSE transports use SSE between the client and the proxy
server. SSE requires the client to initiate the MCP conversation
(event-stream) at the endpoint provided by the server. The endpoint
provided by the server must obey where the client sees the server,
rather than being assumed to be server-root `/message`.
The Streamable HTTP endpoint does not need the full proxy address
because the response is an event-stream directly, and does not use SSE.
* Inspect the stderr output of STDIO servers and attempt to assign an appropriate RFC 5424 Syslog Protocol level before sending a leveled logging message to the client
- In auth.ts
- in InspectorOAuthClientProvider constructor
- add optional scope param
- set instance var this.scope to scope param
- in clientMetadata getter
- add scope param set to this.scope or ""
- in useConnection.ts
- in handleAuthError handler
- move instantiation of serverAuthProvider until after scope has been determined
- pass scope to InspectorOAuthClientProvider constructor
- Add shared discoverScopes function in auth.ts with resource metadata preference
- Update automatic flow (handleAuthError) to use dynamic scope discovery
- Update manual flow (OAuth state machine) to use shared scope discovery logic
- Add comprehensive test suite for discoverScopes function (11 test cases)
- Fix empty array handling to return undefined instead of empty string
- Maintain backward compatibility with existing oauthScope parameter
- All quality gates pass: TypeScript, linting, formatting, and tests
Resolves OAuth scope discovery inconsistency where automatic flow used static
scopes while manual flow used metadata scopes. Both flows now use identical
shared logic with proper resource metadata preference.
* In useConnection.ts
- During initialization, if there is an error immediately following the client sending 'logging/setLevel', pops up a toast with the message `Server declares logging capability but doesn't implement method "logging/setLevel"`