-
-
Notifications
You must be signed in to change notification settings - Fork 173
Export servers to json #1153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Export servers to json #1153
Changes from all commits
ca9826d
e7071fc
9d0953e
983245a
b1ca0d2
4e6d1c1
ddcf20e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ import { Button } from "./ui/button"; | |
| import { | ||
| Plus, | ||
| FileText, | ||
| FileSymlink, | ||
| Cable, | ||
| Link, | ||
| Loader2, | ||
|
|
@@ -47,6 +48,7 @@ import { | |
| import { CollapsedPanelStrip } from "./ui/collapsed-panel-strip"; | ||
| import { LoggerView } from "./logger-view"; | ||
| import { useJsonRpcPanelVisibility } from "@/hooks/use-json-rpc-panel"; | ||
| import { formatJsonConfig } from "@/lib/json-config-parser"; | ||
| import { Skeleton } from "./ui/skeleton"; | ||
|
|
||
| interface ServersTabProps { | ||
|
|
@@ -155,6 +157,35 @@ export function ServersTab({ | |
| setIsActionMenuOpen(false); | ||
| }; | ||
|
|
||
| const downloadJson = (filename: string, data: any) => { | ||
| const blob = new Blob([JSON.stringify(data, null, 2)], { | ||
| type: "application/json;charset=utf-8", | ||
| }); | ||
| const url = URL.createObjectURL(blob); | ||
| const a = document.createElement("a"); | ||
| a.href = url; | ||
| a.download = filename; | ||
| document.body.appendChild(a); | ||
| a.click(); | ||
| document.body.removeChild(a); | ||
| URL.revokeObjectURL(url); | ||
| }; | ||
|
|
||
| const handleExportAsJsonClick = () => { | ||
| posthog.capture("export_servers_to_json_button_clicked", { | ||
| location: "servers_tab", | ||
| platform: detectPlatform(), | ||
| environment: detectEnvironment(), | ||
| }); | ||
| const formattedJson = formatJsonConfig(connectedServerConfigs); | ||
| const timestamp = new Date() | ||
| .toISOString() | ||
| .split(".")[0] | ||
| .replace(/[T:]/g, "-"); | ||
| const fileName = `mcp-servers-config-${timestamp}.json`; | ||
| downloadJson(fileName, formattedJson); | ||
| }; | ||
|
|
||
| const handleCreateTunnel = () => { | ||
| posthog.capture("create_tunnel_button_clicked", { | ||
| location: "servers_tab", | ||
|
|
@@ -309,43 +340,56 @@ export function ServersTab({ | |
| }; | ||
|
|
||
| const renderServerActionsMenu = () => ( | ||
| <HoverCard | ||
| open={isActionMenuOpen} | ||
| onOpenChange={setIsActionMenuOpen} | ||
| openDelay={150} | ||
| closeDelay={100} | ||
| > | ||
| <HoverCardTrigger asChild> | ||
| <> | ||
| {Object.keys(connectedServerConfigs ?? {}).length > 0 && ( | ||
| <Button | ||
| variant="outline" | ||
| size="sm" | ||
| onClick={handleAddServerClick} | ||
| className="cursor-pointer" | ||
| className="justify-start" | ||
| onClick={handleExportAsJsonClick} | ||
| > | ||
| <Plus className="h-4 w-4 mr-2" /> | ||
| Add Server | ||
| <FileSymlink className="h-4 w-4 mr-2" /> | ||
| Export Servers | ||
| </Button> | ||
| </HoverCardTrigger> | ||
| <HoverCardContent align="end" sideOffset={8} className="w-56 p-3"> | ||
| <div className="flex flex-col gap-2"> | ||
| )} | ||
| <HoverCard | ||
| open={isActionMenuOpen} | ||
| onOpenChange={setIsActionMenuOpen} | ||
| openDelay={150} | ||
| closeDelay={100} | ||
| > | ||
| <HoverCardTrigger asChild> | ||
| <Button | ||
| variant="ghost" | ||
| className="justify-start" | ||
| size="sm" | ||
| onClick={handleAddServerClick} | ||
| className="cursor-pointer" | ||
| > | ||
| <Plus className="h-4 w-4 mr-2" /> | ||
| Add manually | ||
| </Button> | ||
| <Button | ||
| variant="ghost" | ||
| className="justify-start" | ||
| onClick={handleImportJsonClick} | ||
| > | ||
| <FileText className="h-4 w-4 mr-2" /> | ||
| Import JSON | ||
| Add Server | ||
| </Button> | ||
| </div> | ||
| </HoverCardContent> | ||
| </HoverCard> | ||
| </HoverCardTrigger> | ||
| <HoverCardContent align="end" sideOffset={8} className="w-56 p-3"> | ||
| <div className="flex flex-col gap-2"> | ||
| <Button | ||
| variant="ghost" | ||
| className="justify-start" | ||
| onClick={handleAddServerClick} | ||
| > | ||
| <Plus className="h-4 w-4 mr-2" /> | ||
| Add manually | ||
| </Button> | ||
| <Button | ||
| variant="ghost" | ||
| className="justify-start" | ||
| onClick={handleImportJsonClick} | ||
| > | ||
| <FileText className="h-4 w-4 mr-2" /> | ||
| Import JSON | ||
| </Button> | ||
| </div> | ||
| </HoverCardContent> | ||
| </HoverCard> | ||
| </> | ||
| ); | ||
|
|
||
| const renderConnectedContent = () => ( | ||
|
|
@@ -472,6 +516,7 @@ export function ServersTab({ | |
| : connectedCount > 0 | ||
| ? renderConnectedContent() | ||
| : renderEmptyContent()} | ||
| {connectedCount > 0 ? renderConnectedContent() : renderEmptyContent()} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: rg -n "isLoadingWorkspaces" --type=ts --type=tsxRepository: MCPJam/inspector Length of output: 87 🏁 Script executed: # Get more context on ServersTab component definition
rg -A 15 "interface ServersTabProps" --type=ts --type=tsxRepository: MCPJam/inspector Length of output: 87 🏁 Script executed: # Check for ServersTab usage/imports to find callers
rg -n "ServersTab" --type=ts --type=tsx | head -30Repository: MCPJam/inspector Length of output: 87 🏁 Script executed: # Find all TypeScript/TSX files first
fd -e tsx -e ts | head -20Repository: MCPJam/inspector Length of output: 1171 🏁 Script executed: # Search for isLoadingWorkspaces without type restrictions
rg "isLoadingWorkspaces"Repository: MCPJam/inspector Length of output: 813 🏁 Script executed: # Look for ServersTab.tsx directly
fd -n "ServersTab.tsx"Repository: MCPJam/inspector Length of output: 290 🏁 Script executed: # Find ServersTab.tsx without -n flag
fd "ServersTab.tsx"Repository: MCPJam/inspector Length of output: 113 🏁 Script executed: # Check App.tsx to see how ServersTab is used
rg -A 2 -B 2 "ServersTab" mcpjam-inspector/client/src/App.tsxRepository: MCPJam/inspector Length of output: 516 🏁 Script executed: # Read ServersTab.tsx to check the props interface and line 502
cat -n mcpjam-inspector/client/src/components/ServersTab.tsx | grep -A 30 "interface ServersTabProps"Repository: MCPJam/inspector Length of output: 1220 🏁 Script executed: # Check around line 502 specifically
sed -n '495,510p' mcpjam-inspector/client/src/components/ServersTab.tsxRepository: MCPJam/inspector Length of output: 428 🏁 Script executed: # Check the full App.tsx ServersTab usage to see if isLoadingWorkspaces is still passed
rg -A 10 "<ServersTab" mcpjam-inspector/client/src/App.tsxRepository: MCPJam/inspector Length of output: 479 Update App.tsx to remove the unused The 🤖 Prompt for AI Agents |
||
|
|
||
| {/* Add Server Modal */} | ||
| <AddServerModal | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defer URL revocation to avoid flaky downloads.
Revoking immediately after
a.click()can cancel the download in some browsers. A short defer makes the flow more reliable.🧩 Proposed fix
a.click(); document.body.removeChild(a); - URL.revokeObjectURL(url); + // Allow the browser to start the download before revoking + setTimeout(() => URL.revokeObjectURL(url), 0);🤖 Prompt for AI Agents