diff --git a/cli/package.json b/cli/package.json index ebd5d229..a5f2c703 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-cli", - "version": "0.20.0", + "version": "0.21.0", "description": "CLI for the Model Context Protocol inspector", "license": "SEE LICENSE IN LICENSE", "author": "Model Context Protocol a Series of LF Projects, LLC.", diff --git a/client/package.json b/client/package.json index 30fadb51..a9cb374d 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-client", - "version": "0.20.0", + "version": "0.21.0", "description": "Client-side application for the Model Context Protocol inspector", "license": "SEE LICENSE IN LICENSE", "author": "Model Context Protocol a Series of LF Projects, LLC.", diff --git a/client/src/App.tsx b/client/src/App.tsx index 184f04d9..12e9a7bd 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1526,6 +1526,9 @@ const App = () => { error={errors.prompts} /> { clearError("tools"); diff --git a/client/src/components/OAuthFlowProgress.tsx b/client/src/components/OAuthFlowProgress.tsx index 6e0fd695..58d4e99f 100644 --- a/client/src/components/OAuthFlowProgress.tsx +++ b/client/src/components/OAuthFlowProgress.tsx @@ -6,6 +6,7 @@ import { useEffect, useMemo, useState } from "react"; import { OAuthClientInformation } from "@modelcontextprotocol/sdk/shared/auth.js"; import { validateRedirectUrl } from "@/utils/urlValidation"; import { useToast } from "@/lib/hooks/useToast"; +import { getAuthorizationServerMetadataDiscoveryUrl } from "@/utils/oauthUtils"; interface OAuthStepProps { label: string; @@ -81,6 +82,13 @@ export const OAuthFlowProgress = ({ const [clientInfo, setClientInfo] = useState( null, ); + const authorizationServerMetadataDiscoveryUrl = useMemo(() => { + if (!authState.authServerUrl) { + return null; + } + + return getAuthorizationServerMetadataDiscoveryUrl(authState.authServerUrl); + }, [authState.authServerUrl]); const currentStepIdx = steps.findIndex((s) => s === authState.oauthStep); @@ -197,13 +205,7 @@ export const OAuthFlowProgress = ({

Authorization Server Metadata:

{authState.authServerUrl && (

- From{" "} - { - new URL( - "/.well-known/oauth-authorization-server", - authState.authServerUrl, - ).href - } + From {authorizationServerMetadataDiscoveryUrl}

)}
diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx
index 339ba7c6..febea1d8 100644
--- a/client/src/components/ToolsTab.tsx
+++ b/client/src/components/ToolsTab.tsx
@@ -53,9 +53,38 @@ import {
   isReservedMetaKey,
 } from "@/utils/metaUtils";
 
+/**
+ * Extended Tool type that includes optional fields used by the inspector.
+ */
+export interface ExtendedTool extends Tool, WithIcons {
+  _meta?: Record;
+  execution?: {
+    taskSupport?: "forbidden" | "required" | "optional";
+  };
+}
+
 // Type guard to safely detect the optional _meta field without using `any`
-const hasMeta = (tool: Tool): tool is Tool & { _meta: unknown } =>
-  typeof (tool as { _meta?: unknown })._meta !== "undefined";
+const hasMeta = (
+  tool: Tool,
+): tool is ExtendedTool & { _meta: Record } =>
+  typeof (tool as ExtendedTool)._meta !== "undefined";
+
+// Returns the execution.taskSupport value for a tool, defaulting to "forbidden" per MCP spec
+const getTaskSupport = (
+  tool: Tool | null,
+): "forbidden" | "required" | "optional" => {
+  if (!tool) return "forbidden";
+  const extendedTool = tool as ExtendedTool;
+  const taskSupport = extendedTool.execution?.taskSupport;
+  if (
+    taskSupport === "forbidden" ||
+    taskSupport === "required" ||
+    taskSupport === "optional"
+  ) {
+    return taskSupport;
+  }
+  return "forbidden";
+};
 
 // Type guard to safely detect the optional annotations field
 const hasAnnotations = (
@@ -148,6 +177,7 @@ const ToolsTab = ({
   error,
   resourceContent,
   onReadResource,
+  serverSupportsTaskRequests,
 }: {
   tools: Tool[];
   listTools: () => void;
@@ -166,6 +196,7 @@ const ToolsTab = ({
   error: string | null;
   resourceContent: Record;
   onReadResource?: (uri: string) => void;
+  serverSupportsTaskRequests: boolean;
 }) => {
   const [params, setParams] = useState>({});
   const [runAsTask, setRunAsTask] = useState(false);
@@ -210,14 +241,17 @@ const ToolsTab = ({
       ];
     });
     setParams(Object.fromEntries(params));
-    setRunAsTask(false);
+    const toolTaskSupport = serverSupportsTaskRequests
+      ? getTaskSupport(selectedTool)
+      : "forbidden";
+    setRunAsTask(toolTaskSupport === "required");
 
     // Reset validation errors when switching tools
     setHasValidationErrors(false);
 
     // Clear form refs for the previous tool
     formRefs.current = {};
-  }, [selectedTool]);
+  }, [selectedTool, serverSupportsTaskRequests]);
 
   const hasReservedMetadataEntry = metadataEntries.some(({ key }) => {
     const trimmedKey = key.trim();
@@ -234,6 +268,10 @@ const ToolsTab = ({
     return trimmedKey !== "" && !hasValidMetaName(trimmedKey);
   });
 
+  const taskSupport = serverSupportsTaskRequests
+    ? getTaskSupport(selectedTool)
+    : "forbidden";
+
   return (
     
       
@@ -249,7 +287,7 @@ const ToolsTab = ({ renderItem={(tool) => (
- +
{tool.title || tool.name} @@ -270,7 +308,7 @@ const ToolsTab = ({
{selectedTool && ( )} @@ -747,21 +785,24 @@ const ToolsTab = ({
)} -
- - setRunAsTask(checked) - } - /> - -
+ {taskSupport !== "forbidden" && ( +
+ + setRunAsTask(checked) + } + disabled={taskSupport === "required"} + /> + +
+ )}