Skip to content

Commit 6b204b5

Browse files
committed
feat: add active tab system
1 parent 29678dd commit 6b204b5

File tree

2 files changed

+116
-42
lines changed

2 files changed

+116
-42
lines changed

client/src/App.tsx

Lines changed: 115 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,9 @@ const App = () => {
138138
>([]);
139139
const [isAuthDebuggerVisible, setIsAuthDebuggerVisible] = useState(false);
140140

141-
// Auth debugger state
142141
const [authState, setAuthState] =
143142
useState<AuthDebuggerState>(EMPTY_DEBUGGER_STATE);
144143

145-
// Helper function to update specific auth state properties
146144
const updateAuthState = (updates: Partial<AuthDebuggerState>) => {
147145
setAuthState((prev) => ({ ...prev, ...updates }));
148146
};
@@ -170,6 +168,19 @@ const App = () => {
170168
const [nextToolCursor, setNextToolCursor] = useState<string | undefined>();
171169
const progressTokenRef = useRef(0);
172170

171+
const [activeTab, setActiveTab] = useState<string>(() => {
172+
const hash = window.location.hash.slice(1);
173+
const initialTab = hash || "resources";
174+
return initialTab;
175+
});
176+
177+
const currentTabRef = useRef<string>(activeTab);
178+
const lastToolCallOriginTabRef = useRef<string>(activeTab);
179+
180+
useEffect(() => {
181+
currentTabRef.current = activeTab;
182+
}, [activeTab]);
183+
173184
const { height: historyPaneHeight, handleDragStart } = useDraggablePane(300);
174185
const {
175186
width: sidebarWidth,
@@ -213,6 +224,8 @@ const App = () => {
213224
]);
214225
},
215226
onElicitationRequest: (request, resolve) => {
227+
const currentTab = lastToolCallOriginTabRef.current;
228+
216229
setPendingElicitationRequests((prev) => [
217230
...prev,
218231
{
@@ -222,16 +235,52 @@ const App = () => {
222235
message: request.params.message,
223236
requestedSchema: request.params.requestedSchema,
224237
},
238+
originatingTab: currentTab,
225239
resolve,
226240
decline: (error: Error) => {
227241
console.error("Elicitation request rejected:", error);
228242
},
229243
},
230244
]);
245+
246+
setActiveTab("elicitations");
247+
window.location.hash = "elicitations";
231248
},
232249
getRoots: () => rootsRef.current,
233250
});
234251

252+
useEffect(() => {
253+
if (serverCapabilities) {
254+
const hash = window.location.hash.slice(1);
255+
256+
const validTabs = [
257+
...(serverCapabilities?.resources ? ["resources"] : []),
258+
...(serverCapabilities?.prompts ? ["prompts"] : []),
259+
...(serverCapabilities?.tools ? ["tools"] : []),
260+
"ping",
261+
"sampling",
262+
"elicitations",
263+
"roots",
264+
"auth",
265+
];
266+
267+
const isValidTab = validTabs.includes(hash);
268+
269+
if (!isValidTab) {
270+
const defaultTab = serverCapabilities?.resources
271+
? "resources"
272+
: serverCapabilities?.prompts
273+
? "prompts"
274+
: serverCapabilities?.tools
275+
? "tools"
276+
: "ping";
277+
278+
setActiveTab(defaultTab);
279+
window.location.hash = defaultTab;
280+
}
281+
}
282+
}, [serverCapabilities]);
283+
235284
useEffect(() => {
236285
localStorage.setItem("lastCommand", command);
237286
}, [command]);
@@ -260,7 +309,6 @@ const App = () => {
260309
saveInspectorConfig(CONFIG_LOCAL_STORAGE_KEY, config);
261310
}, [config]);
262311

263-
// Auto-connect to previously saved serverURL after OAuth callback
264312
const onOAuthConnect = useCallback(
265313
(serverUrl: string) => {
266314
setSseUrl(serverUrl);
@@ -270,7 +318,6 @@ const App = () => {
270318
[connectMcpServer],
271319
);
272320

273-
// Update OAuth debug state during debug callback
274321
const onOAuthDebugConnect = useCallback(
275322
async ({
276323
authorizationCode,
@@ -291,7 +338,6 @@ const App = () => {
291338
}
292339

293340
if (restoredState && authorizationCode) {
294-
// Restore the previous auth state and continue the OAuth flow
295341
let currentState: AuthDebuggerState = {
296342
...restoredState,
297343
authorizationCode,
@@ -302,12 +348,10 @@ const App = () => {
302348
};
303349

304350
try {
305-
// Create a new state machine instance to continue the flow
306351
const stateMachine = new OAuthStateMachine(sseUrl, (updates) => {
307352
currentState = { ...currentState, ...updates };
308353
});
309354

310-
// Continue stepping through the OAuth flow from where we left off
311355
while (
312356
currentState.oauthStep !== "complete" &&
313357
currentState.oauthStep !== "authorization_code"
@@ -316,7 +360,6 @@ const App = () => {
316360
}
317361

318362
if (currentState.oauthStep === "complete") {
319-
// After the flow completes or reaches a user-input step, update the app state
320363
updateAuthState({
321364
...currentState,
322365
statusMessage: {
@@ -339,7 +382,6 @@ const App = () => {
339382
});
340383
}
341384
} else if (authorizationCode) {
342-
// Fallback to the original behavior if no state was restored
343385
updateAuthState({
344386
authorizationCode,
345387
oauthStep: "token_request",
@@ -349,7 +391,6 @@ const App = () => {
349391
[sseUrl],
350392
);
351393

352-
// Load OAuth tokens when sseUrl changes
353394
useEffect(() => {
354395
const loadOAuthTokens = async () => {
355396
try {
@@ -408,6 +449,18 @@ const App = () => {
408449
}
409450
}, []);
410451

452+
useEffect(() => {
453+
const handleHashChange = () => {
454+
const hash = window.location.hash.slice(1);
455+
if (hash && hash !== activeTab) {
456+
setActiveTab(hash);
457+
}
458+
};
459+
460+
window.addEventListener("hashchange", handleHashChange);
461+
return () => window.removeEventListener("hashchange", handleHashChange);
462+
}, [activeTab]);
463+
411464
const handleApproveSampling = (id: number, result: CreateMessageResult) => {
412465
setPendingSampleRequests((prev) => {
413466
const request = prev.find((r) => r.id === id);
@@ -430,7 +483,34 @@ const App = () => {
430483
) => {
431484
setPendingElicitationRequests((prev) => {
432485
const request = prev.find((r) => r.id === id);
433-
request?.resolve(response);
486+
if (request) {
487+
request.resolve(response);
488+
489+
if (request.originatingTab) {
490+
const originatingTab = request.originatingTab;
491+
492+
const validTabs = [
493+
...(serverCapabilities?.resources ? ["resources"] : []),
494+
...(serverCapabilities?.prompts ? ["prompts"] : []),
495+
...(serverCapabilities?.tools ? ["tools"] : []),
496+
"ping",
497+
"sampling",
498+
"elicitations",
499+
"roots",
500+
"auth",
501+
];
502+
503+
if (validTabs.includes(originatingTab)) {
504+
setActiveTab(originatingTab);
505+
window.location.hash = originatingTab;
506+
507+
setTimeout(() => {
508+
setActiveTab(originatingTab);
509+
window.location.hash = originatingTab;
510+
}, 100);
511+
}
512+
}
513+
}
434514
return prev.filter((r) => r.id !== id);
435515
});
436516
};
@@ -492,7 +572,23 @@ const App = () => {
492572
setNextResourceTemplateCursor(response.nextCursor);
493573
};
494574

575+
const getPrompt = async (name: string, args: Record<string, string> = {}) => {
576+
lastToolCallOriginTabRef.current = currentTabRef.current;
577+
578+
const response = await sendMCPRequest(
579+
{
580+
method: "prompts/get" as const,
581+
params: { name, arguments: args },
582+
},
583+
GetPromptResultSchema,
584+
"prompts",
585+
);
586+
setPromptContent(JSON.stringify(response, null, 2));
587+
};
588+
495589
const readResource = async (uri: string) => {
590+
lastToolCallOriginTabRef.current = currentTabRef.current;
591+
496592
const response = await sendMCPRequest(
497593
{
498594
method: "resources/read" as const,
@@ -549,18 +645,6 @@ const App = () => {
549645
setNextPromptCursor(response.nextCursor);
550646
};
551647

552-
const getPrompt = async (name: string, args: Record<string, string> = {}) => {
553-
const response = await sendMCPRequest(
554-
{
555-
method: "prompts/get" as const,
556-
params: { name, arguments: args },
557-
},
558-
GetPromptResultSchema,
559-
"prompts",
560-
);
561-
setPromptContent(JSON.stringify(response, null, 2));
562-
};
563-
564648
const listTools = async () => {
565649
const response = await sendMCPRequest(
566650
{
@@ -572,11 +656,12 @@ const App = () => {
572656
);
573657
setTools(response.tools);
574658
setNextToolCursor(response.nextCursor);
575-
// Cache output schemas for validation
576659
cacheToolOutputSchemas(response.tools);
577660
};
578661

579662
const callTool = async (name: string, params: Record<string, unknown>) => {
663+
lastToolCallOriginTabRef.current = currentTabRef.current;
664+
580665
try {
581666
const response = await sendMCPRequest(
582667
{
@@ -592,6 +677,7 @@ const App = () => {
592677
CompatibilityCallToolResultSchema,
593678
"tools",
594679
);
680+
595681
setToolResult(response);
596682
} catch (e) {
597683
const toolResult: CompatibilityCallToolResult = {
@@ -626,7 +712,6 @@ const App = () => {
626712
setStdErrNotifications([]);
627713
};
628714

629-
// Helper component for rendering the AuthDebugger
630715
const AuthDebuggerWrapper = () => (
631716
<TabsContent value="auth">
632717
<AuthDebugger
@@ -638,7 +723,6 @@ const App = () => {
638723
</TabsContent>
639724
);
640725

641-
// Helper function to render OAuth callback components
642726
if (window.location.pathname === "/oauth/callback") {
643727
const OAuthCallback = React.lazy(
644728
() => import("./components/OAuthCallback"),
@@ -698,7 +782,6 @@ const App = () => {
698782
loggingSupported={!!serverCapabilities?.logging || false}
699783
clearStdErrNotifications={clearStdErrNotifications}
700784
/>
701-
{/* Drag handle for resizing sidebar */}
702785
<div
703786
onMouseDown={handleSidebarDragStart}
704787
style={{
@@ -719,21 +802,12 @@ const App = () => {
719802
<div className="flex-1 overflow-auto">
720803
{mcpClient ? (
721804
<Tabs
722-
defaultValue={
723-
Object.keys(serverCapabilities ?? {}).includes(
724-
window.location.hash.slice(1),
725-
)
726-
? window.location.hash.slice(1)
727-
: serverCapabilities?.resources
728-
? "resources"
729-
: serverCapabilities?.prompts
730-
? "prompts"
731-
: serverCapabilities?.tools
732-
? "tools"
733-
: "ping"
734-
}
805+
value={activeTab}
735806
className="w-full p-4"
736-
onValueChange={(value) => (window.location.hash = value)}
807+
onValueChange={(value) => {
808+
setActiveTab(value);
809+
window.location.hash = value;
810+
}}
737811
>
738812
<TabsList className="mb-4 py-0">
739813
<TabsTrigger
@@ -895,7 +969,6 @@ const App = () => {
895969
clearTools={() => {
896970
setTools([]);
897971
setNextToolCursor(undefined);
898-
// Clear cached output schemas
899972
cacheToolOutputSchemas([]);
900973
}}
901974
callTool={async (name, params) => {

client/src/components/ElicitationTab.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface ElicitationResponse {
1717
export type PendingElicitationRequest = {
1818
id: number;
1919
request: ElicitationRequestData;
20+
originatingTab?: string;
2021
};
2122

2223
export type Props = {

0 commit comments

Comments
 (0)