[
  {
    "path": ".cursorrules",
    "content": "this is a simple react and vite project that allows users to manage a JSON file on their computer with a nice UI.\n\nthe file path is \" ~/Library/Application\\ Support/Claude/claude_desktop_config.json\"\n\nstyling is done with tailwindcss and daisyui whenever possible\n\nnever use \"any\" as a type\n\nalways add the type=button property to buttons\n\nalways use bun over npm for this project\n\nJSX elements without children should be marked as self-closing\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-electron\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 Zue AI\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">MCP Manager for Claude Desktop</h1>\n\n<p align=\"center\">A simple web GUI to manage Model Context Protocol (MCP) servers for the Claude Desktop app on MacOS easily. Just follow the instructions and paste a few commands to give your Claude app instant superpowers. </p>\n\n![MCP Manager for Claude Desktop](https://assets.zue.ai/mcp-manager-hero.png)\n\n## What is MCP?\n\nThe Model Context Protocol (MCP) enables Claude to access private data, APIs, and other services to answer questions and perform actions on your behalf. Learn more about MCP at:\n\n- [modelcontextprotocol.io](https://modelcontextprotocol.io)\n- [Anthropic's MCP Announcement](https://www.anthropic.com/news/model-context-protocol)\n\n## Features\n\n- 🚀 Easy-to-use interface for managing MCP servers\n- 🔒 Runs entirely client-side - your data never leaves your computer\n- ⚡️ Quick setup for popular MCP servers:\n  - Apple Notes - Access and search your Apple Notes\n  - AWS Knowledge Base - Access and query AWS Knowledge Base for information retrieval\n  - Brave Search - Search the web with Brave Search API\n  - Browserbase - Let Claude explore the web with Browserbase\n  - Cloudflare - Manage your Cloudflare workers and account resources\n  - Everart - Interface with Everart API for digital art and design tools\n  - Exa - Search the web with Exa\n  - Filesystem - Access and manage local filesystem\n  - GitHub - Access your GitHub repositories\n  - GitLab - Manage GitLab repositories and resources\n  - Google Drive - Access and search files in your Google Drive\n  - Google Maps - Access Google Maps API for location services\n  - Memory - Give Claude memory of previous conversations\n  - Obsidian - Read and search files in your Obsidian vault\n  - Perplexity - Search the web with Perplexity API\n  - PostgreSQL - Connect and interact with PostgreSQL databases\n  - Puppeteer - Automate browser interactions\n  - Sequential Thinking - Enable step-by-step reasoning\n  - Slack - Access your Slack workspace\n  - SQLite - Manage SQLite databases\n  - Todoist - Access and search your Todoist tasks\n  - YouTube Transcript - Access and search YouTube transcripts\n- 🛠 Simple configuration of environment variables and server settings\n- 📋 One-click copying of terminal commands for installation\n\n## Tech Stack\n\n- **Frontend Framework**: React 18 with TypeScript\n- **Build Tool**: Vite\n- **Styling**:\n  - TailwindCSS for utility-first CSS\n  - DaisyUI for component styling\n  - Tiempos Font to match the Anthropic Design Language\n- **Package Manager**: Bun\n- **Deployment**: Cloudflare Pages for <60s build times\n\n## Project Structure\n\n```plaintext\nsrc/\n├── components/ # React components\n│ ├── server-configs/ # Server-specific configuration components\n│ └── ...\n├── assets/ # Static assets and fonts\n├── App.tsx # Main application component\n├── server-configs.ts # MCP server configurations\n└── utils.ts # Utility functions\n```\n\n## Development\n\n1. Install dependencies:\n\n   ```bash\n   bun install\n   ```\n\n2. Start the dev server:\n\n   ```bash\n   bun dev\n   ```\n\n3. Build for production:\n\n   ```bash\n   bun run build\n   ```\n\n## Contributing\n\nContributions are extremely welcome! Please open a PR with new MCP servers or any other improvements to the codebase.\nPS. I wasnt able to get fetch, time, and sentry working, if you can help me out, that would be great!\n\n## Disclaimer\n\nThis project is not affiliated with Anthropic. All logos are trademarks of their respective owners.\n\n## License\n\nMIT\n\n---\n<br/>\n<br/>\n<p align=\"center\">\n<a href=\"https://zue.ai#gh-light-mode-only\">\n  <img src=\"https://assets.zue.ai/logo_zue_purple.svg\" alt=\"zue logo\" width=\"200\" height=\"auto\" style=\"display: block; margin: 0 auto;\" />\n</a>\n<a href=\"https://zue.ai#gh-dark-mode-only\">\n  <img src=\"https://assets.zue.ai/logo_zue_yellow.svg\" alt=\"zue logo\" width=\"200\" height=\"auto\" style=\"display: block; margin: 0 auto;\" />\n</a>\n</p>\n\n<p align=\"center\">\n<a href=\"https://zue.ai/talk-to-us\">Contact us</a> for custom AI automation solutions and product development.\n</p>\n"
  },
  {
    "path": "biome.json",
    "content": "{\n\t\"files\": {\n\t\t\"ignore\": [\"dist/**/*\", \"node_modules/**/*\", \"public/**/*\", \"**/*.css\"]\n\t},\n\t\"linter\": {\n\t\t\"enabled\": true,\n\t\t\"rules\": {\n\t\t\t\"recommended\": true,\n\t\t\t\"suspicious\": {\n\t\t\t\t\"noArrayIndexKey\": \"off\"\n\t\t\t}\n\t\t},\n\t\t\"ignore\": [\"**/*.md\", \"**/*.css\"]\n\t},\n\t\"formatter\": {\n\t\t\"enabled\": true,\n\t\t\"indentStyle\": \"tab\",\n\t\t\"indentWidth\": 4\n\t},\n\t\"javascript\": {\n\t\t\"formatter\": {\n\t\t\t\"semicolons\": \"asNeeded\",\n\t\t\t\"trailingCommas\": \"none\"\n\t\t}\n\t},\n\t\"json\": {\n\t\t\"parser\": {\n\t\t\t\"allowComments\": true\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "eslint.config.js",
    "content": "import js from \"@eslint/js\"\nimport reactHooks from \"eslint-plugin-react-hooks\"\nimport reactRefresh from \"eslint-plugin-react-refresh\"\nimport globals from \"globals\"\nimport tseslint from \"typescript-eslint\"\n\nexport default tseslint.config(\n\t{ ignores: [\"dist\"] },\n\t{\n\t\textends: [js.configs.recommended, ...tseslint.configs.recommended],\n\t\tfiles: [\"**/*.{ts,tsx}\"],\n\t\tlanguageOptions: {\n\t\t\tecmaVersion: 2020,\n\t\t\tglobals: globals.browser\n\t\t},\n\t\tplugins: {\n\t\t\t\"react-hooks\": reactHooks,\n\t\t\t\"react-refresh\": reactRefresh\n\t\t},\n\t\trules: {\n\t\t\t...reactHooks.configs.recommended.rules,\n\t\t\t\"react-refresh/only-export-components\": [\n\t\t\t\t\"warn\",\n\t\t\t\t{ allowConstantExport: true }\n\t\t\t],\n\t\t\t\"@typescript-eslint/no-unused-vars\": \"off\"\n\t\t}\n\t}\n)\n"
  },
  {
    "path": "index.html",
    "content": "<!doctype html>\n<html lang=\"en\" data-theme=\"light\" class=\"min-h-screen bg-[#f2f1e9]\">\n\n<head>\n  <meta charset=\"UTF-8\" />\n  <link rel=\"icon\" type=\"image/ico\" href=\"/mcp-favicon.ico\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <meta name=\"description\"\n    content=\"MCP Manager for Claude Desktop - A simple web GUI to manage Model Context Protocol (MCP) servers. Configure and manage your Claude.app's superpowers with ease.\" />\n  <title>MCP Manager for Claude Desktop</title>\n</head>\n\n<body class=\"min-h-screen h-full\">\n  <div id=\"root\"></div>\n  <script type=\"module\" src=\"/src/main.tsx\"></script>\n</body>\n\n</html>"
  },
  {
    "path": "package.json",
    "content": "{\n\t\"name\": \"mcp-manager\",\n\t\"private\": true,\n\t\"version\": \"0.0.0\",\n\t\"type\": \"module\",\n\t\"scripts\": {\n\t\t\"dev\": \"bun check && vite --port 7777\",\n\t\t\"build\": \"bun check && vite build\",\n\t\t\"lint\": \"eslint .\",\n\t\t\"preview\": \"vite preview\",\n\t\t\"check\": \"tsc --noEmit && biome check --write .\"\n\t},\n\t\"dependencies\": {\n\t\t\"lucide-react\": \"^0.468.0\",\n\t\t\"react\": \"^18.3.1\",\n\t\t\"react-dom\": \"^18.3.1\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@biomejs/biome\": \"^1.9.4\",\n\t\t\"@eslint/js\": \"^9.15.0\",\n\t\t\"@types/node\": \"^22.10.1\",\n\t\t\"@types/react\": \"^18.3.12\",\n\t\t\"@types/react-dom\": \"^18.3.1\",\n\t\t\"@vitejs/plugin-react\": \"^4.3.4\",\n\t\t\"autoprefixer\": \"^10.4.20\",\n\t\t\"daisyui\": \"^4.12.14\",\n\t\t\"eslint\": \"^9.15.0\",\n\t\t\"eslint-plugin-react-hooks\": \"^5.0.0\",\n\t\t\"eslint-plugin-react-refresh\": \"^0.4.14\",\n\t\t\"globals\": \"^15.12.0\",\n\t\t\"postcss\": \"^8.4.49\",\n\t\t\"tailwindcss\": \"^3.4.16\",\n\t\t\"typescript\": \"~5.6.2\",\n\t\t\"typescript-eslint\": \"^8.15.0\",\n\t\t\"vite\": \"^6.0.1\"\n\t}\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "export default {\n\tplugins: {\n\t\ttailwindcss: {},\n\t\tautoprefixer: {}\n\t}\n}\n"
  },
  {
    "path": "public/robots.txt",
    "content": "User-agent: *\nAllow: /"
  },
  {
    "path": "src/App.tsx",
    "content": "import { ApplyingInstructions } from \"@/components/applying-instructions\"\nimport { LoadingInstructions } from \"@/components/loading-instructions\"\nimport { MCPServers } from \"@/components/mcp-servers\"\nimport { SERVER_CONFIGS } from \"@/server-configs\"\nimport type React from \"react\"\nimport { useState } from \"react\"\n\nfunction App() {\n\tconst [jsonContent, setJsonContent] = useState<{\n\t\tmcpServers: Record<\n\t\t\tstring,\n\t\t\t{ command: string; args: string[]; env?: Record<string, string> }\n\t\t>\n\t}>({\n\t\tmcpServers: {}\n\t})\n\tconst [uploadStatus, setUploadStatus] = useState<\n\t\t\"idle\" | \"success\" | \"error\"\n\t>(\"idle\")\n\tconst [isInstructionsOpen, setIsInstructionsOpen] = useState(true)\n\n\tconst handleJsonInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n\t\ttry {\n\t\t\tconst content = JSON.parse(e.target.value)\n\t\t\tsetJsonContent(content)\n\t\t\tsetUploadStatus(\"success\")\n\t\t\tsetIsInstructionsOpen(false) // Close accordion on successful upload\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Error parsing JSON:\", error)\n\t\t\tsetUploadStatus(\"error\")\n\t\t}\n\t}\n\n\tconst handleServerAdd = (serverType: keyof typeof SERVER_CONFIGS) => {\n\t\tconst serverConfig = SERVER_CONFIGS[serverType]\n\n\t\t// Ensure we only add servers with required properties\n\t\tconst newServer = {\n\t\t\tcommand: serverConfig.command as string,\n\t\t\targs: serverConfig.args as string[],\n\t\t\t...(serverConfig.env && { env: serverConfig.env })\n\t\t}\n\n\t\tsetJsonContent({\n\t\t\t...jsonContent,\n\t\t\tmcpServers: {\n\t\t\t\t...jsonContent.mcpServers,\n\t\t\t\t[serverType]: newServer\n\t\t\t}\n\t\t})\n\t}\n\n\tconst handleServerRemove = (serverType: string) => {\n\t\t// Remove from mcpServers if present\n\t\tif (jsonContent.mcpServers[serverType]) {\n\t\t\tconst { [serverType]: _, ...rest } = jsonContent.mcpServers\n\t\t\tsetJsonContent({\n\t\t\t\t...jsonContent,\n\t\t\t\tmcpServers: rest\n\t\t\t})\n\t\t}\n\t}\n\n\treturn (\n\t\t<main className=\"max-h-screen p-16\">\n\t\t\t<div className=\"container mx-auto p-4 max-w-4xl\">\n\t\t\t\t<div className=\"flex justify-center items-center gap-8 mb-16\">\n\t\t\t\t\t<div className=\"flex items-center justify-center rounded-2xl h-16 p-8 border-2 border-black/20\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tsrc=\"/mcp-logo.svg\"\n\t\t\t\t\t\t\talt=\"MCP Manager\"\n\t\t\t\t\t\t\tclassName=\"h-8\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div className=\"flex items-center justify-center rounded-2xl p-8 h-16 border-2 border-primary/30\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tsrc=\"/claude-logo.svg\"\n\t\t\t\t\t\t\talt=\"Claude\"\n\t\t\t\t\t\t\tclassName=\"h-6\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<h1 className=\"text-5xl font-light text-center my-8\">\n\t\t\t\t\tMCP Manager for Claude Desktop\n\t\t\t\t</h1>\n\n\t\t\t\t<div className=\"flex justify-center\">\n\t\t\t\t\t<span className=\"text-md text-center mb-8\">\n\t\t\t\t\t\tGive Claude access to private data, APIs, and other\n\t\t\t\t\t\tservices using the Model Context Protocol so it can\n\t\t\t\t\t\tanswer questions and perform actions on your behalf.{\" \"}\n\t\t\t\t\t\t<br />\n\t\t\t\t\t\t<br />\n\t\t\t\t\t\tIn a nutshell, MCP servers are like plugins that give\n\t\t\t\t\t\tClaude (the \"client\") prompts, resources, and tools to\n\t\t\t\t\t\tperform actions on your behalf. Read the{\" \"}\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\thref=\"https://modelcontextprotocol.io\"\n\t\t\t\t\t\t\tclassName=\"link\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noreferrer\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tMCP docs\n\t\t\t\t\t\t</a>{\" \"}\n\t\t\t\t\t\tor check out{\" \"}\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\thref=\"https://www.anthropic.com/news/model-context-protocol\"\n\t\t\t\t\t\t\tclassName=\"link\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noreferrer\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tAnthropic's announcement\n\t\t\t\t\t\t</a>{\" \"}\n\t\t\t\t\t\tto learn more.\n\t\t\t\t\t\t<br />\n\t\t\t\t\t\t<br />\n\t\t\t\t\t\tThis is a simple, web-based GUI to help you install and\n\t\t\t\t\t\tmanage MCP servers in your Claude App. <br />\n\t\t\t\t\t\tThis runs client-side in your browser so your data will\n\t\t\t\t\t\tnever leave your computer.\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\n\t\t\t\t<div className=\"space-y-6\">\n\t\t\t\t\t<LoadingInstructions\n\t\t\t\t\t\tisOpen={isInstructionsOpen}\n\t\t\t\t\t\tonOpenChange={setIsInstructionsOpen}\n\t\t\t\t\t\tonJsonInput={handleJsonInput}\n\t\t\t\t\t\tuploadStatus={uploadStatus}\n\t\t\t\t\t/>\n\n\t\t\t\t\t{Object.keys(jsonContent).length > 0 &&\n\t\t\t\t\t\tuploadStatus === \"success\" && (\n\t\t\t\t\t\t\t<div className=\"space-y-6\">\n\t\t\t\t\t\t\t\t<MCPServers\n\t\t\t\t\t\t\t\t\tjsonContent={{\n\t\t\t\t\t\t\t\t\t\tmcpServers:\n\t\t\t\t\t\t\t\t\t\t\tjsonContent.mcpServers as Record<\n\t\t\t\t\t\t\t\t\t\t\t\tstring,\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tcommand: string\n\t\t\t\t\t\t\t\t\t\t\t\t\targs: string[]\n\t\t\t\t\t\t\t\t\t\t\t\t\tenv?: Record<string, string>\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonUpdate={setJsonContent}\n\t\t\t\t\t\t\t\t\tonServerAdd={handleServerAdd}\n\t\t\t\t\t\t\t\t\tonServerRemove={handleServerRemove}\n\t\t\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t\t\t{Object.keys(jsonContent.mcpServers).length >\n\t\t\t\t\t\t\t\t\t0 && (\n\t\t\t\t\t\t\t\t\t<ApplyingInstructions\n\t\t\t\t\t\t\t\t\t\tjsonContent={jsonContent}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t\t<div className=\"flex justify-center my-16\">\n\t\t\t\t\t<span className=\"text-sm text-center text-black/50\">\n\t\t\t\t\t\tThis project is not affiliated with Anthropic. All logos\n\t\t\t\t\t\tare trademarks of their respective owners.\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</main>\n\t)\n}\n\nexport default App\n"
  },
  {
    "path": "src/components/applying-instructions.tsx",
    "content": "import { TerminalCommand } from \"@/components/terminal-command\"\nimport { SERVER_CONFIGS } from \"@/server-configs\"\nimport type { ServerConfig } from \"@/server-configs\"\n\ntype RuntimeServerConfig = {\n\tcommand: string\n\targs: string[]\n\tenv?: Record<string, string>\n}\n\ntype ApplyingInstructionsProps = {\n\tjsonContent: {\n\t\tmcpServers: Record<string, RuntimeServerConfig>\n\t\tcloudflare?: unknown\n\t}\n}\n\nexport function ApplyingInstructions({\n\tjsonContent\n}: ApplyingInstructionsProps) {\n\tconst serversNeedingSetup = Object.keys(jsonContent.mcpServers).filter(\n\t\t(serverType) =>\n\t\t\tSERVER_CONFIGS[serverType as keyof typeof SERVER_CONFIGS]\n\t\t\t\t?.setupCommands\n\t)\n\n\t// Helper function to modify the JSON content with absolute paths\n\tconst getJsonWithAbsolutePaths = () => {\n\t\tconst { cloudflare: _, ...modifiedContent } = jsonContent\n\n\t\tfor (const [serverType, config] of Object.entries(\n\t\t\tmodifiedContent.mcpServers\n\t\t)) {\n\t\t\tconst serverConfig =\n\t\t\t\tSERVER_CONFIGS[serverType as keyof typeof SERVER_CONFIGS]\n\t\t\tif (serverConfig?.setupCommands) {\n\t\t\t\t// Update the args to use the shell variable expansion syntax\n\t\t\t\tconfig.args = config.args?.map((arg) => {\n\t\t\t\t\tif (arg.includes(\"index.js\")) {\n\t\t\t\t\t\tswitch (serverType) {\n\t\t\t\t\t\t\tcase \"exa\":\n\t\t\t\t\t\t\t\treturn \"$HOME_DIR/mcp-servers/exa-mcp-server-main/build/index.js\"\n\t\t\t\t\t\t\tcase \"browserbase\":\n\t\t\t\t\t\t\t\treturn \"$HOME_DIR/mcp-servers/mcp-server-browserbase-main/browserbase/dist/index.js\"\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\treturn arg\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn arg\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\treturn modifiedContent\n\t}\n\n\treturn (\n\t\t<div className=\"join join-vertical w-full\">\n\t\t\t<div className=\"collapse collapse-arrow join-item border border-base-300 bg-white mb-16 p-4\">\n\t\t\t\t<input type=\"checkbox\" />\n\t\t\t\t<h2 className=\"collapse-title text-xl font-tiempos-regular my-4\">\n\t\t\t\t\tApply your changes\n\t\t\t\t</h2>\n\t\t\t\t<div className=\"collapse-content space-y-4\">\n\t\t\t\t\t<div className=\"bg-base-200 rounded-xl p-4\">\n\t\t\t\t\t\t<h3 className=\"text-lg font-tiempos-regular\">\n\t\t\t\t\t\t\tStep 1: Install Node.js and uv by running these\n\t\t\t\t\t\t\tcommands (if not already installed)\n\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t<div className=\"space-y-4 mt-4\">\n\t\t\t\t\t\t\t<TerminalCommand\n\t\t\t\t\t\t\t\tcommand={\n\t\t\t\t\t\t\t\t\t'curl -fsSL https://fnm.vercel.app/install | bash && source ~/.zshrc && eval \"$(fnm env --use-on-cd --shell zsh)\" >> ~/.zshrc && source ~/.zshrc && fnm use --install-if-missing 22 && node -v'\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\tIf the command above fails, install Node.js by\n\t\t\t\t\t\t\tdownloading the installer from{\" \"}\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\thref=\"https://nodejs.org/en/download/prebuilt-installer\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\tclassName=\"link link-primary\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\thttps://nodejs.org/en/download/prebuilt-installer\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t<TerminalCommand\n\t\t\t\t\t\t\t\tcommand={\n\t\t\t\t\t\t\t\t\t\"curl -LsSf https://astral.sh/uv/install.sh | sh && source $HOME/.cargo/env && uv python install\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div className=\"bg-base-200 rounded-xl p-4\">\n\t\t\t\t\t\t<div className=\"space-y-4\">\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t<h3 className=\"text-lg font-tiempos-regular mb-4\">\n\t\t\t\t\t\t\t\t\tStep 2: Save your MCP servers to Claude by\n\t\t\t\t\t\t\t\t\trunning:\n\t\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t\t<TerminalCommand\n\t\t\t\t\t\t\t\t\tcommand={`HOME_DIR=$(echo $HOME) && echo '${JSON.stringify(\n\t\t\t\t\t\t\t\t\t\tgetJsonWithAbsolutePaths(),\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\t2\n\t\t\t\t\t\t\t\t\t).replace(\n\t\t\t\t\t\t\t\t\t\t/\\$HOME_DIR/g,\n\t\t\t\t\t\t\t\t\t\t\"'\\\"$HOME_DIR\\\"'\"\n\t\t\t\t\t\t\t\t\t)}' > \"$HOME_DIR/Library/Application Support/Claude/claude_desktop_config.json\"`}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{serversNeedingSetup.length > 0 && (\n\t\t\t\t\t\t<div className=\"bg-base-200 rounded-xl p-4\">\n\t\t\t\t\t\t\t<h3 className=\"text-lg font-tiempos-regular mb-4\">\n\t\t\t\t\t\t\t\tStep 3: Some servers require additional setup.\n\t\t\t\t\t\t\t\tRun the following commands:\n\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t{serversNeedingSetup.map((serverType) => (\n\t\t\t\t\t\t\t\t<div key={serverType} className=\"mb-4\">\n\t\t\t\t\t\t\t\t\t<p className=\"text-md mb-2\">\n\t\t\t\t\t\t\t\t\t\t{serverType.charAt(0).toUpperCase() +\n\t\t\t\t\t\t\t\t\t\t\tserverType.slice(1)}\n\t\t\t\t\t\t\t\t\t\t:\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t<TerminalCommand\n\t\t\t\t\t\t\t\t\t\tcommand={\n\t\t\t\t\t\t\t\t\t\t\tSERVER_CONFIGS[\n\t\t\t\t\t\t\t\t\t\t\t\tserverType as keyof typeof SERVER_CONFIGS\n\t\t\t\t\t\t\t\t\t\t\t]?.setupCommands?.command || \"\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\n\t\t\t\t\t<div className=\"bg-base-200 rounded-xl p-4 mt-4\">\n\t\t\t\t\t\t<h3 className=\"text-lg font-tiempos-regular\">\n\t\t\t\t\t\t\tStep {serversNeedingSetup.length > 0 ? \"4\" : \"3\"}:\n\t\t\t\t\t\t\tRestart Claude.app\n\t\t\t\t\t\t</h3>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"
  },
  {
    "path": "src/components/loading-instructions.tsx",
    "content": "import { TerminalCommand } from \"@/components/terminal-command\"\nimport { Check, XCircle } from \"lucide-react\"\nimport type { ChangeEvent } from \"react\"\n\ninterface LoadingInstructionsProps {\n\tisOpen: boolean\n\tonOpenChange: (isOpen: boolean) => void\n\tonJsonInput: (e: ChangeEvent<HTMLTextAreaElement>) => void\n\tuploadStatus: \"idle\" | \"success\" | \"error\"\n}\n\nexport function LoadingInstructions({\n\tisOpen,\n\tonOpenChange,\n\tonJsonInput,\n\tuploadStatus\n}: LoadingInstructionsProps) {\n\tconst command =\n\t\t\"test -f ~/Library/Application\\\\ Support/Claude/claude_desktop_config.json && pbcopy < ~/Library/Application\\\\ Support/Claude/claude_desktop_config.json || (echo '{\\\\n  \\\"mcpServers\\\": {}\\\\n}' | tee ~/Library/Application\\\\ Support/Claude/claude_desktop_config.json | pbcopy)\"\n\n\treturn (\n\t\t<div className=\"join join-vertical w-full\">\n\t\t\t<div className=\"collapse collapse-arrow join-item border border-base-300 bg-white p-4\">\n\t\t\t\t<input\n\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\tchecked={isOpen}\n\t\t\t\t\tonChange={(e) => onOpenChange(e.target.checked)}\n\t\t\t\t\taria-label=\"MacOS Instructions\"\n\t\t\t\t/>\n\t\t\t\t<h2 className=\"collapse-title text-xl my-4\">\n\t\t\t\t\tMacOS Instructions\n\t\t\t\t</h2>\n\t\t\t\t<div className=\"collapse-content\">\n\t\t\t\t\t<div className=\"prose\">\n\t\t\t\t\t\t<div className=\"space-y-6\">\n\t\t\t\t\t\t\t<div className=\"bg-base-200 rounded-xl p-4\">\n\t\t\t\t\t\t\t\t<div className=\"space-y-4\">\n\t\t\t\t\t\t\t\t\t<h3 className=\"text-lg font-tiempos-regular\">\n\t\t\t\t\t\t\t\t\t\tStep 1: Run this terminal command to\n\t\t\t\t\t\t\t\t\t\tcopy your current MCP config to the\n\t\t\t\t\t\t\t\t\t\tclipboard.\n\t\t\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t\t\t<p className=\"text-sm opacity-80\">\n\t\t\t\t\t\t\t\t\t\tIf you've never used MCP before, this\n\t\t\t\t\t\t\t\t\t\twill create a blank config file and copy\n\t\t\t\t\t\t\t\t\t\tits contents.\n\t\t\t\t\t\t\t\t\t</p>\n\n\t\t\t\t\t\t\t\t\t<TerminalCommand command={command} />\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t<div className=\"bg-base-200 rounded-xl p-4\">\n\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t<h3 className=\"text-lg font-tiempos-regular mb-4\">\n\t\t\t\t\t\t\t\t\t\tStep 2: Paste your config below.\n\t\t\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t\t\t<textarea\n\t\t\t\t\t\t\t\t\t\tclassName=\"textarea textarea-bordered w-full h-16 font-mono\"\n\t\t\t\t\t\t\t\t\t\tplaceholder=\"Paste the copied JSON content here...\"\n\t\t\t\t\t\t\t\t\t\tonChange={onJsonInput}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t{uploadStatus === \"success\" && (\n\t\t\t\t\t\t\t\t\t\t<div className=\"mt-2 flex items-center text-primary\">\n\t\t\t\t\t\t\t\t\t\t\t<Check className=\"w-5 h-5\" />\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"ml-2\">\n\t\t\t\t\t\t\t\t\t\t\t\tValid MCP Configuration\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t{uploadStatus === \"error\" && (\n\t\t\t\t\t\t\t\t\t\t<div className=\"mt-2 flex items-center text-error\">\n\t\t\t\t\t\t\t\t\t\t\t<XCircle className=\"w-5 h-5\" />\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"ml-2\">\n\t\t\t\t\t\t\t\t\t\t\t\tError: Please ensure the content\n\t\t\t\t\t\t\t\t\t\t\t\tis valid JSON.\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"
  },
  {
    "path": "src/components/mcp-server-card.tsx",
    "content": "import { EnvConfig } from \"@/components/server-configs/env-config\"\nimport { FilesystemConfig } from \"@/components/server-configs/filesystem-config\"\nimport { ObsidianConfig } from \"@/components/server-configs/obsidian-config\"\nimport { PostgresConfig } from \"@/components/server-configs/postgres-config\"\nimport { SentryConfig } from \"@/components/server-configs/sentry-config\"\nimport { SQLiteConfig } from \"@/components/server-configs/sqlite-config\"\nimport { TerminalCommand } from \"@/components/terminal-command\"\nimport { SERVER_CONFIGS } from \"@/server-configs\"\nimport { ArrowUpRight, Trash2 } from \"lucide-react\"\n\ntype MCPServerConfig = {\n\tcommand: string\n\targs: string[]\n\tenv?: Record<string, string>\n}\n\ntype MCPServerCardProps = {\n\tserverName: string\n\tconfig: MCPServerConfig\n\ticon?: string\n\tonUpdate: (name: string, newConfig: MCPServerConfig) => void\n\tonDelete: (name: string) => void\n}\n\nexport function MCPServerCard({\n\tserverName,\n\tconfig,\n\ticon,\n\tonUpdate,\n\tonDelete\n}: MCPServerCardProps) {\n\tconst handleFilesystemUpdate = (paths: string[]) => {\n\t\tconst newConfig = {\n\t\t\t...config,\n\t\t\targs: [...config.args.slice(0, 2), ...paths]\n\t\t}\n\t\tonUpdate(serverName, newConfig)\n\t}\n\n\tconst handlePostgresUpdate = (url: string) => {\n\t\tconst newConfig = {\n\t\t\t...config,\n\t\t\targs: [...config.args.slice(0, 2), url]\n\t\t}\n\t\tonUpdate(serverName, newConfig)\n\t}\n\n\tconst handleEnvUpdate = (key: string, value: string) => {\n\t\tconst newConfig = {\n\t\t\t...config,\n\t\t\tenv: {\n\t\t\t\t...(config.env || {}),\n\t\t\t\t[key]: value\n\t\t\t}\n\t\t}\n\t\tonUpdate(serverName, newConfig)\n\t}\n\n\tconst handleSqliteUpdate = (dbPath: string) => {\n\t\tconst newConfig = {\n\t\t\t...config,\n\t\t\targs: [\n\t\t\t\t\"--directory\",\n\t\t\t\t\"parent_of_servers_repo/servers/src/sqlite\",\n\t\t\t\t\"run\",\n\t\t\t\t\"mcp-server-sqlite\",\n\t\t\t\t\"--db-path\",\n\t\t\t\tdbPath\n\t\t\t]\n\t\t}\n\t\tonUpdate(serverName, newConfig)\n\t}\n\n\tconst handleObsidianUpdate = (path: string) => {\n\t\tconst newConfig = {\n\t\t\t...config,\n\t\t\targs: [...config.args.slice(0, 2), path]\n\t\t}\n\t\tonUpdate(serverName, newConfig)\n\t}\n\n\tconst handleSentryUpdate = (token: string) => {\n\t\tconst newConfig = {\n\t\t\t...config,\n\t\t\targs: [...config.args.slice(0, 2), token]\n\t\t}\n\t\tonUpdate(serverName, newConfig)\n\t}\n\n\tconst handleDelete = (e: React.MouseEvent) => {\n\t\te.stopPropagation()\n\t\tonDelete(serverName)\n\t}\n\n\tconst serverConfig =\n\t\tSERVER_CONFIGS[serverName as keyof typeof SERVER_CONFIGS]\n\tconst isFilesystemServer = serverName === \"filesystem\"\n\tconst isPostgresServer = serverName === \"postgres\"\n\tconst isSqliteServer = serverName === \"sqlite\"\n\tconst isObsidianServer = serverName === \"obsidian\"\n\tconst isSentryServer = serverName === \"sentry\"\n\tconst iconUrl = icon || serverConfig?.icon\n\n\treturn (\n\t\t<div className=\"join join-vertical w-full\">\n\t\t\t<div className=\"collapse collapse-arrow join-item border border-base-300 bg-white p-4\">\n\t\t\t\t<input type=\"checkbox\" defaultChecked />\n\t\t\t\t<div className=\"collapse-title\">\n\t\t\t\t\t<div className=\"flex items-center\">\n\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t{iconUrl && (\n\t\t\t\t\t\t\t\t<img\n\t\t\t\t\t\t\t\t\tsrc={iconUrl}\n\t\t\t\t\t\t\t\t\talt={`${serverName} icon`}\n\t\t\t\t\t\t\t\t\tclassName=\"w-20 h-12 object-contain\"\n\t\t\t\t\t\t\t\t\tonError={(e) => {\n\t\t\t\t\t\t\t\t\t\te.currentTarget.style.display = \"none\"\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t<h3 className=\"text-lg capitalize\">{serverName}</h3>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"collapse-content\">\n\t\t\t\t\t{isFilesystemServer ? (\n\t\t\t\t\t\t<FilesystemConfig\n\t\t\t\t\t\t\tinitialPaths={[config.args[2] || \"/Users/\"]}\n\t\t\t\t\t\t\tonUpdate={handleFilesystemUpdate}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : isPostgresServer ? (\n\t\t\t\t\t\t<PostgresConfig\n\t\t\t\t\t\t\tinitialUrl={config.args[2]}\n\t\t\t\t\t\t\tonUpdate={handlePostgresUpdate}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : isSqliteServer ? (\n\t\t\t\t\t\t<SQLiteConfig\n\t\t\t\t\t\t\tinitialPath={config.args[5]}\n\t\t\t\t\t\t\tonUpdate={handleSqliteUpdate}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : isObsidianServer ? (\n\t\t\t\t\t\t<ObsidianConfig\n\t\t\t\t\t\t\tinitialPath={config.args[2]}\n\t\t\t\t\t\t\tonUpdate={handleObsidianUpdate}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : isSentryServer ? (\n\t\t\t\t\t\t<SentryConfig\n\t\t\t\t\t\t\tinitialToken={config.args[2]}\n\t\t\t\t\t\t\tonUpdate={handleSentryUpdate}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : serverConfig?.env &&\n\t\t\t\t\t\tObject.keys(serverConfig.env).length > 0 ? (\n\t\t\t\t\t\t<EnvConfig\n\t\t\t\t\t\t\tenv={serverConfig.env}\n\t\t\t\t\t\t\tinitialValues={config.env || {}}\n\t\t\t\t\t\t\tonUpdate={handleEnvUpdate}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : null}\n\n\t\t\t\t\t{serverConfig?.docsUrl ===\n\t\t\t\t\t\t\"https://github.com/cloudflare/mcp-server-cloudflare\" && (\n\t\t\t\t\t\t<div className=\"bg-base-200 rounded-xl p-4 space-y-4\">\n\t\t\t\t\t\t\t<p className=\"text-sm text-gray-600\">\n\t\t\t\t\t\t\t\tMCP Manager can't update this server directly,\n\t\t\t\t\t\t\t\tplease run this terminal command to modify this\n\t\t\t\t\t\t\t\tserver.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<TerminalCommand\n\t\t\t\t\t\t\t\tcommand={\n\t\t\t\t\t\t\t\t\tserverConfig?.setupCommands?.command ?? \"\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t\t<div className=\"flex justify-end\">\n\t\t\t\t\t<div className=\"flex gap-2 mb-4 mr-2\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\t\twindow.open(serverConfig?.docsUrl, \"_blank\")\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tclassName=\"btn btn-sm btn-secondary\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<ArrowUpRight className=\"w-4 h-4\" />\n\t\t\t\t\t\t\t<span>Docs</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"flex gap-2 mb-4 mr-4 justify-end\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={handleDelete}\n\t\t\t\t\t\t\tclassName=\"btn btn-sm bg-red-50 hover:bg-red-100\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<Trash2 className=\"w-4 h-4\" />\n\t\t\t\t\t\t\t<span>Delete</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"
  },
  {
    "path": "src/components/mcp-servers.tsx",
    "content": "import { MCPServerCard } from \"@/components/mcp-server-card\"\nimport { SERVER_CONFIGS } from \"@/server-configs\"\nimport { capitalizeFirstLetter } from \"@/utils\"\nimport { Plus, Save, X } from \"lucide-react\"\n\ntype MCPServer = {\n\tcommand: string\n\targs: string[]\n}\n\ntype MCPServers = {\n\t[key: string]: MCPServer\n}\n\ntype MCPConfig = {\n\tmcpServers: MCPServers\n}\n\ntype MCPServersProps = {\n\tjsonContent: MCPConfig\n\tonUpdate: (newContent: MCPConfig) => void\n\tonServerAdd: (serverType: keyof typeof SERVER_CONFIGS) => void\n\tonServerRemove: (serverType: string) => void\n}\n\nexport function MCPServers({\n\tjsonContent,\n\tonUpdate,\n\tonServerAdd,\n\tonServerRemove\n}: MCPServersProps) {\n\tconst handleServerUpdate = (name: string, newConfig: MCPServer) => {\n\t\tconst updatedContent = {\n\t\t\t...jsonContent,\n\t\t\tmcpServers: {\n\t\t\t\t...jsonContent.mcpServers,\n\t\t\t\t[name]: newConfig\n\t\t\t}\n\t\t}\n\t\tonUpdate(updatedContent)\n\t}\n\n\tconst handleServerDelete = (name: string) => {\n\t\tonServerRemove(name)\n\t}\n\n\tconst hasServers = Object.keys(jsonContent.mcpServers).length > 0\n\n\treturn (\n\t\t<div className=\"space-y-4 my-32\">\n\t\t\t<div className=\"flex justify-between items-center mb-8\">\n\t\t\t\t<div className=\"flex items-center gap-4\">\n\t\t\t\t\t<h2 className=\"text-2xl text-center\">Your MCP Servers</h2>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"btn btn-primary btn-sm\"\n\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\tdocument.getElementById(\n\t\t\t\t\t\t\t\t\t\"add_server_modal\"\n\t\t\t\t\t\t\t\t) as HTMLDialogElement\n\t\t\t\t\t\t\t)?.showModal()\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\t<Plus className=\"w-4 h-4\" />\n\t\t\t\t\t\t<span>Add Server</span>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<dialog id=\"add_server_modal\" className=\"modal backdrop-blur-sm\">\n\t\t\t\t<div className=\"modal-box rounded-3xl\">\n\t\t\t\t\t<div className=\"flex justify-between items-center mb-4 sticky top-0 py-4 -mt-4 -mx-6 px-6\">\n\t\t\t\t\t\t<h3 className=\"text-xl ml-4\">Add New Server</h3>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclassName=\"btn btn-square btn-ghost\"\n\t\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\t\tdocument.getElementById(\n\t\t\t\t\t\t\t\t\t\t\"add_server_modal\"\n\t\t\t\t\t\t\t\t\t) as HTMLDialogElement\n\t\t\t\t\t\t\t\t)?.close()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<X className=\"w-4 h-4\" />\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div className=\"grid gap-4 py-4 max-h-[70vh] overflow-y-auto px-4\">\n\t\t\t\t\t\t{Object.keys(SERVER_CONFIGS).map((serverType) => (\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tkey={serverType}\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclassName=\"w-full bg-base-200 hover:bg-base-300 rounded-3xl p-4 flex items-center gap-6 h-24\"\n\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\tonServerAdd(\n\t\t\t\t\t\t\t\t\t\tserverType as keyof typeof SERVER_CONFIGS\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t;(\n\t\t\t\t\t\t\t\t\t\tdocument.getElementById(\n\t\t\t\t\t\t\t\t\t\t\t\"add_server_modal\"\n\t\t\t\t\t\t\t\t\t\t) as HTMLDialogElement\n\t\t\t\t\t\t\t\t\t)?.close()\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<div className=\"my-auto mx-2\">\n\t\t\t\t\t\t\t\t\t<img\n\t\t\t\t\t\t\t\t\t\tsrc={\n\t\t\t\t\t\t\t\t\t\t\tSERVER_CONFIGS[\n\t\t\t\t\t\t\t\t\t\t\t\tserverType as keyof typeof SERVER_CONFIGS\n\t\t\t\t\t\t\t\t\t\t\t].icon\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\talt={`${serverType} icon`}\n\t\t\t\t\t\t\t\t\t\tclassName=\"w-10 h-10 object-contain\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div className=\"flex flex-col text-left w-full\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-xl font-normal mb-1\">\n\t\t\t\t\t\t\t\t\t\t{capitalizeFirstLetter(serverType)}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<p className=\"text-sm opacity-80\">\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tSERVER_CONFIGS[\n\t\t\t\t\t\t\t\t\t\t\t\tserverType as keyof typeof SERVER_CONFIGS\n\t\t\t\t\t\t\t\t\t\t\t].description\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<form method=\"dialog\" className=\"modal-backdrop\">\n\t\t\t\t\t<button type=\"button\">close</button>\n\t\t\t\t</form>\n\t\t\t</dialog>\n\n\t\t\t<div className=\"space-y-4\">\n\t\t\t\t{hasServers ? (\n\t\t\t\t\tObject.entries(jsonContent.mcpServers).map(\n\t\t\t\t\t\t([name, config]) => {\n\t\t\t\t\t\t\tconst serverConfig =\n\t\t\t\t\t\t\t\tSERVER_CONFIGS[\n\t\t\t\t\t\t\t\t\tname as keyof typeof SERVER_CONFIGS\n\t\t\t\t\t\t\t\t]\n\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<MCPServerCard\n\t\t\t\t\t\t\t\t\tkey={name}\n\t\t\t\t\t\t\t\t\tserverName={name}\n\t\t\t\t\t\t\t\t\tconfig={config}\n\t\t\t\t\t\t\t\t\ticon={serverConfig?.icon}\n\t\t\t\t\t\t\t\t\tonUpdate={handleServerUpdate}\n\t\t\t\t\t\t\t\t\tonDelete={handleServerDelete}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t)\n\t\t\t\t) : (\n\t\t\t\t\t<p className=\" text-gray-500 text-center\">\n\t\t\t\t\t\tYou currently have no MCP servers configured. Add one by\n\t\t\t\t\t\tclicking the \"Add Server\" button.\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"
  },
  {
    "path": "src/components/server-configs/env-config.tsx",
    "content": "import { useState } from \"react\"\n\ntype EnvConfigProps = {\n\tenv: Record<string, string>\n\tinitialValues: Record<string, string>\n\tonUpdate: (key: string, value: string) => void\n}\n\nexport function EnvConfig({ env, initialValues, onUpdate }: EnvConfigProps) {\n\tconst [envValues, setEnvValues] =\n\t\tuseState<Record<string, string>>(initialValues)\n\n\tconst handleEnvChange = (key: string, value: string) => {\n\t\tsetEnvValues((prev) => ({ ...prev, [key]: value }))\n\t\tonUpdate(key, value)\n\t}\n\n\treturn (\n\t\t<div className=\"bg-base-200 rounded-xl p-4 mb-4 space-y-4\">\n\t\t\t<div className=\"space-y-2\">\n\t\t\t\t{Object.entries(env).map(([key]) => (\n\t\t\t\t\t<div key={key} className=\"form-control\">\n\t\t\t\t\t\t<label htmlFor={`env-${key}`} className=\"label\">\n\t\t\t\t\t\t\t<span className=\"label-text mb-2\">{key}</span>\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tid={`env-${key}`}\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder={`Paste your ${key} here`}\n\t\t\t\t\t\t\tclassName=\"input input-bordered w-full\"\n\t\t\t\t\t\t\tvalue={envValues[key] || \"\"}\n\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\thandleEnvChange(key, e.target.value)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"
  },
  {
    "path": "src/components/server-configs/filesystem-config.tsx",
    "content": "import { Plus, X } from \"lucide-react\"\nimport { useState } from \"react\"\n\ntype FilesystemConfigProps = {\n\tinitialPaths: string[]\n\tonUpdate: (paths: string[]) => void\n}\n\nexport function FilesystemConfig({\n\tinitialPaths,\n\tonUpdate\n}: FilesystemConfigProps) {\n\tconst [filesystemPaths, setFilesystemPaths] =\n\t\tuseState<string[]>(initialPaths)\n\n\tconst handleFilesystemPathChange = (value: string, index: number) => {\n\t\tconst newPaths = [...filesystemPaths]\n\t\tnewPaths[index] = value\n\t\tsetFilesystemPaths(newPaths)\n\t}\n\n\tconst handleFilesystemPathBlur = () => {\n\t\tonUpdate(filesystemPaths)\n\t}\n\n\tconst handleAddPath = () => {\n\t\tconst newPaths = [...filesystemPaths, \"\"]\n\t\tsetFilesystemPaths(newPaths)\n\t\tonUpdate(newPaths)\n\t}\n\n\tconst handleRemovePath = (index: number) => {\n\t\tconst newPaths = filesystemPaths.filter((_, i) => i !== index)\n\t\tsetFilesystemPaths(newPaths)\n\t\tonUpdate(newPaths)\n\t}\n\n\treturn (\n\t\t<div className=\"bg-base-200 rounded-xl p-4 mb-4 space-y-4\">\n\t\t\t<div className=\"space-y-4\">\n\t\t\t\t{filesystemPaths.map((path, index) => (\n\t\t\t\t\t<div key={index} className=\"flex items-center gap-2\">\n\t\t\t\t\t\t<div className=\"form-control flex-1\">\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\thtmlFor={`filesystem-path-${index}`}\n\t\t\t\t\t\t\t\tclassName=\"label\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<span className=\"label-text mb-2\">\n\t\t\t\t\t\t\t\t\tAllowed Directory Path{\" \"}\n\t\t\t\t\t\t\t\t\t{filesystemPaths.length > 1\n\t\t\t\t\t\t\t\t\t\t? index + 1\n\t\t\t\t\t\t\t\t\t\t: \"\"}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\tid={`filesystem-path-${index}`}\n\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\tplaceholder=\"Enter the directory path (e.g., /Users/username/Documents)\"\n\t\t\t\t\t\t\t\tclassName=\"input input-bordered w-full\"\n\t\t\t\t\t\t\t\tvalue={path}\n\t\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\t\thandleFilesystemPathChange(\n\t\t\t\t\t\t\t\t\t\te.target.value,\n\t\t\t\t\t\t\t\t\t\tindex\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tonBlur={handleFilesystemPathBlur}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{filesystemPaths.length > 1 && (\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclassName=\"btn btn-ghost btn-sm mt-8\"\n\t\t\t\t\t\t\t\tonClick={() => handleRemovePath(index)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<X className=\"w-4 h-4\" />\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t))}\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"btn btn-ghost btn-sm\"\n\t\t\t\t\tonClick={handleAddPath}\n\t\t\t\t>\n\t\t\t\t\t<Plus className=\"w-4 h-4\" />\n\t\t\t\t\t<span>Add Another Path</span>\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"
  },
  {
    "path": "src/components/server-configs/obsidian-config.tsx",
    "content": "import { useState } from \"react\"\n\ntype ObsidianConfigProps = {\n\tinitialPath: string\n\tonUpdate: (path: string) => void\n}\n\nexport function ObsidianConfig({ initialPath, onUpdate }: ObsidianConfigProps) {\n\tconst [vaultPath, setVaultPath] = useState(initialPath)\n\n\tconst handleVaultPathChange = (value: string) => {\n\t\tsetVaultPath(value)\n\t}\n\n\tconst handleVaultPathBlur = () => {\n\t\tonUpdate(vaultPath)\n\t}\n\n\treturn (\n\t\t<div className=\"bg-base-200 rounded-xl p-4 mb-4 space-y-4\">\n\t\t\t<div className=\"form-control\">\n\t\t\t\t<label htmlFor=\"vault-path\" className=\"label\">\n\t\t\t\t\t<span className=\"label-text mb-2\">Obsidian Vault Path</span>\n\t\t\t\t</label>\n\t\t\t\t<input\n\t\t\t\t\tid=\"vault-path\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tplaceholder=\"Path to your Obsidian vault, e.g. /Users/yourname/Documents/MyVault\"\n\t\t\t\t\tclassName=\"input input-bordered w-full\"\n\t\t\t\t\tvalue={vaultPath}\n\t\t\t\t\tonChange={(e) => handleVaultPathChange(e.target.value)}\n\t\t\t\t\tonBlur={handleVaultPathBlur}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"
  },
  {
    "path": "src/components/server-configs/postgres-config.tsx",
    "content": "import { useState } from \"react\"\n\ntype PostgresConfigProps = {\n\tinitialUrl: string\n\tonUpdate: (url: string) => void\n}\n\nexport function PostgresConfig({ initialUrl, onUpdate }: PostgresConfigProps) {\n\tconst [postgresUrl, setPostgresUrl] = useState(initialUrl)\n\n\tconst handlePostgresUrlChange = (value: string) => {\n\t\tsetPostgresUrl(value)\n\t}\n\n\tconst handlePostgresUrlBlur = () => {\n\t\tonUpdate(postgresUrl)\n\t}\n\n\treturn (\n\t\t<div className=\"bg-base-200 rounded-xl p-4 mb-4 space-y-4\">\n\t\t\t<div className=\"form-control\">\n\t\t\t\t<label htmlFor=\"postgres-url\" className=\"label\">\n\t\t\t\t\t<span className=\"label-text mb-2\">\n\t\t\t\t\t\tPostgreSQL Connection URL\n\t\t\t\t\t</span>\n\t\t\t\t</label>\n\t\t\t\t<input\n\t\t\t\t\tid=\"postgres-url\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tplaceholder=\"postgresql://localhost/mydb\"\n\t\t\t\t\tclassName=\"input input-bordered w-full\"\n\t\t\t\t\tvalue={postgresUrl}\n\t\t\t\t\tonChange={(e) => handlePostgresUrlChange(e.target.value)}\n\t\t\t\t\tonBlur={handlePostgresUrlBlur}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"
  },
  {
    "path": "src/components/server-configs/sentry-config.tsx",
    "content": "import { useState } from \"react\"\n\ntype SentryConfigProps = {\n\tinitialToken: string\n\tonUpdate: (token: string) => void\n}\n\nexport function SentryConfig({ initialToken, onUpdate }: SentryConfigProps) {\n\tconst [authToken, setAuthToken] = useState(initialToken)\n\n\tconst handleAuthTokenChange = (value: string) => {\n\t\tsetAuthToken(value)\n\t}\n\n\tconst handleAuthTokenBlur = () => {\n\t\tonUpdate(authToken)\n\t}\n\n\treturn (\n\t\t<div className=\"bg-base-200 rounded-xl p-4 mb-4 space-y-4\">\n\t\t\t<div className=\"form-control\">\n\t\t\t\t<label htmlFor=\"sentry-token\" className=\"label\">\n\t\t\t\t\t<span className=\"label-text mb-2\">\n\t\t\t\t\t\tSentry Authentication Token\n\t\t\t\t\t</span>\n\t\t\t\t</label>\n\t\t\t\t<input\n\t\t\t\t\tid=\"sentry-token\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tplaceholder=\"your-auth-token\"\n\t\t\t\t\tclassName=\"input input-bordered w-full\"\n\t\t\t\t\tvalue={authToken}\n\t\t\t\t\tonChange={(e) => handleAuthTokenChange(e.target.value)}\n\t\t\t\t\tonBlur={handleAuthTokenBlur}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"
  },
  {
    "path": "src/components/server-configs/sqlite-config.tsx",
    "content": "import { useState } from \"react\"\n\ntype SQLiteConfigProps = {\n\tinitialPath: string\n\tonUpdate: (dbPath: string) => void\n}\n\nexport function SQLiteConfig({ initialPath, onUpdate }: SQLiteConfigProps) {\n\tconst [dbPath, setDbPath] = useState(initialPath)\n\n\tconst handleDbPathChange = (value: string) => {\n\t\tsetDbPath(value)\n\t}\n\n\tconst handleDbPathBlur = () => {\n\t\tonUpdate(dbPath)\n\t}\n\n\treturn (\n\t\t<div className=\"bg-base-200 rounded-xl p-4 mb-4 space-y-4\">\n\t\t\t<div className=\"form-control\">\n\t\t\t\t<label htmlFor=\"sqlite-path\" className=\"label\">\n\t\t\t\t\t<span className=\"label-text mb-2\">\n\t\t\t\t\t\tSQLite Database Path\n\t\t\t\t\t</span>\n\t\t\t\t</label>\n\t\t\t\t<input\n\t\t\t\t\tid=\"sqlite-path\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tplaceholder=\"~/my-database.db\"\n\t\t\t\t\tclassName=\"input input-bordered w-full\"\n\t\t\t\t\tvalue={dbPath}\n\t\t\t\t\tonChange={(e) => handleDbPathChange(e.target.value)}\n\t\t\t\t\tonBlur={handleDbPathBlur}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"
  },
  {
    "path": "src/components/terminal-command.tsx",
    "content": "import { Check, Copy } from \"lucide-react\"\nimport { useState } from \"react\"\n\ninterface TerminalCommandProps {\n\tcommand: string\n\tclassName?: string\n}\n\nexport function TerminalCommand({\n\tcommand,\n\tclassName = \"\"\n}: TerminalCommandProps) {\n\tconst [hasCopied, setHasCopied] = useState(false)\n\n\tconst handleCopy = () => {\n\t\tnavigator.clipboard.writeText(command)\n\t\tsetHasCopied(true)\n\t\tsetTimeout(() => setHasCopied(false), 2000)\n\t}\n\n\treturn (\n\t\t<div className={`bg-base-300 rounded-xl p-4 mb-4 ${className}`}>\n\t\t\t<p className=\"font-mono text-sm mb-4 break-all\">{command}</p>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={handleCopy}\n\t\t\t\tclassName=\"btn btn-primary btn-sm\"\n\t\t\t>\n\t\t\t\t{hasCopied ? (\n\t\t\t\t\t<Check className=\"w-4 h-4\" />\n\t\t\t\t) : (\n\t\t\t\t\t<Copy className=\"w-4 h-4\" />\n\t\t\t\t)}\n\t\t\t\t<span className=\"ml-2\">\n\t\t\t\t\t{hasCopied ? \"Copied!\" : \"Copy Command\"}\n\t\t\t\t</span>\n\t\t\t</button>\n\t\t</div>\n\t)\n}\n"
  },
  {
    "path": "src/index.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@font-face {\n  font-family: \"Tiempos Text\";\n  src: url(\"./assets/tiempos-text-web-semibold.woff2\") format(\"woff2\");\n  font-weight: 600;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: \"Tiempos Text\";\n  src: url(\"./assets/tiempos-text-web-regular.woff2\") format(\"woff2\");\n  font-weight: 400;\n  font-display: swap;\n}\n\n@layer base {\n  h1 {\n    @apply font-tiempos-semibold;\n  }\n  h2 {\n    @apply font-tiempos-semibold;\n  }\n  h3 {\n    @apply font-tiempos-regular;\n  }\n  p {\n    @apply font-tiempos-regular;\n  }\n  span {\n    @apply font-tiempos-regular;\n    font-weight: 400;\n  }\n}\n\n.collapse {\n  border-radius: 1.5rem;\n  box-shadow: 0 10px 10px 0 rgba(0, 0, 0, 0.05);\n}\n"
  },
  {
    "path": "src/main.tsx",
    "content": "import { StrictMode } from \"react\"\nimport { createRoot } from \"react-dom/client\"\nimport \"@/index.css\"\nimport App from \"@/App.tsx\"\n\nconst rootElement = document.getElementById(\"root\")\nif (!rootElement) throw new Error(\"Root element not found\")\n\ncreateRoot(rootElement).render(\n\t<StrictMode>\n\t\t<App />\n\t</StrictMode>\n)\n"
  },
  {
    "path": "src/server-configs.ts",
    "content": "export type ServerConfig = {\n\tcommand?: string\n\targs?: string[]\n\tenv?: Record<string, string>\n\ticon: string\n\tdescription: string\n\tdocsUrl: string\n\tsetupCommands?: {\n\t\tinstallPath: string\n\t\tcommand: string\n\t}\n}\n\nexport const SERVER_CONFIGS: Record<string, ServerConfig> = {\n\t\"brave-search\": {\n\t\ticon: \"https://www.svgrepo.com/show/305818/brave.svg\",\n\t\tdescription: \"Search the web with Brave Search API\",\n\n\t\tdocsUrl:\n\t\t\t\"https://github.com/modelcontextprotocol/servers/tree/main/src/brave-search/README.md\",\n\t\tcommand: \"npx\",\n\t\targs: [\"-y\", \"@modelcontextprotocol/server-brave-search\"],\n\t\tenv: { BRAVE_API_KEY: \"\" }\n\t},\n\tfilesystem: {\n\t\ticon: \"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWZvbGRlci1jbG9zZWQiPjxwYXRoIGQ9Ik0yMCAyMGEyIDIgMCAwIDAgMi0yVjhhMiAyIDAgMCAwLTItMmgtNy45YTIgMiAwIDAgMS0xLjY5LS45TDkuNiAzLjlBMiAyIDAgMCAwIDcuOTMgM0g0YTIgMiAwIDAgMC0yIDJ2MTNhMiAyIDAgMCAwIDIgMloiLz48cGF0aCBkPSJNMiAxMGgyMCIvPjwvc3ZnPg==\",\n\t\tdescription:\n\t\t\t\"Access and manage local filesystem with specified allowed directories\",\n\t\tcommand: \"npx\",\n\t\targs: [\"-y\", \"@modelcontextprotocol/server-filesystem\", \"/Users/\"],\n\n\t\tdocsUrl:\n\t\t\t\"https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem/README.md\"\n\t},\n\tmemory: {\n\t\ticon: \"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWJyYWluIj48cGF0aCBkPSJNMTIgNWEzIDMgMCAxIDAtNS45OTcuMTI1IDQgNCAwIDAgMC0yLjUyNiA1Ljc3IDQgNCAwIDAgMCAuNTU2IDYuNTg4QTQgNCAwIDEgMCAxMiAxOFoiLz48cGF0aCBkPSJNMTIgNWEzIDMgMCAxIDEgNS45OTcuMTI1IDQgNCAwIDAgMSAyLjUyNiA1Ljc3IDQgNCAwIDAgMS0uNTU2IDYuNTg4QTQgNCAwIDEgMSAxMiAxOFoiLz48cGF0aCBkPSJNMTUgMTNhNC41IDQuNSAwIDAgMS0zLTQgNC41IDQuNSAwIDAgMS0zIDQiLz48cGF0aCBkPSJNMTcuNTk5IDYuNWEzIDMgMCAwIDAgLjM5OS0xLjM3NSIvPjxwYXRoIGQ9Ik02LjAwMyA1LjEyNUEzIDMgMCAwIDAgNi40MDEgNi41Ii8+PHBhdGggZD0iTTMuNDc3IDEwLjg5NmE0IDQgMCAwIDEgLjU4NS0uMzk2Ii8+PHBhdGggZD0iTTE5LjkzOCAxMC41YTQgNCAwIDAgMSAuNTg1LjM5NiIvPjxwYXRoIGQ9Ik02IDE4YTQgNCAwIDAgMS0xLjk2Ny0uNTE2Ii8+PHBhdGggZD0iTTE5Ljk2NyAxNy40ODRBNCA0IDAgMCAxIDE4IDE4Ii8+PC9zdmc+\",\n\t\tdescription: \"Give Claude memory of previous conversations\",\n\t\tcommand: \"npx\",\n\t\targs: [\"-y\", \"@modelcontextprotocol/server-memory\"],\n\n\t\tdocsUrl:\n\t\t\t\"https://github.com/modelcontextprotocol/servers/tree/main/src/memory/README.md\"\n\t},\n\t\"sequential-thinking\": {\n\t\ticon: \"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXNwYXJrbGUiPjxwYXRoIGQ9Ik05LjkzNyAxNS41QTIgMiAwIDAgMCA4LjUgMTQuMDYzbC02LjEzNS0xLjU4MmEuNS41IDAgMCAxIDAtLjk2Mkw4LjUgOS45MzZBMiAyIDAgMCAwIDkuOTM3IDguNWwxLjU4Mi02LjEzNWEuNS41IDAgMCAxIC45NjMgMEwxNC4wNjMgOC41QTIgMiAwIDAgMCAxNS41IDkuOTM3bDYuMTM1IDEuNTgxYS41LjUgMCAwIDEgMCAuOTY0TDE1LjUgMTQuMDYzYTIgMiAwIDAgMC0xLjQzNyAxLjQzN2wtMS41ODIgNi4xMzVhLjUuNSAwIDAgMS0uOTYzIDB6Ii8+PC9zdmc+\",\n\t\tdescription:\n\t\t\t\"Enable step-by-step reasoning and sequential problem-solving\",\n\t\tcommand: \"npx\",\n\t\targs: [\"-y\", \"@modelcontextprotocol/server-sequential-thinking\"],\n\n\t\tdocsUrl:\n\t\t\t\"https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking/README.md\"\n\t},\n\tslack: {\n\t\ticon: \"https://icon.icepanel.io/Technology/svg/Slack.svg\",\n\t\tdescription: \"Let Claude access your Slack workspace\",\n\t\tcommand: \"npx\",\n\t\targs: [\"-y\", \"@modelcontextprotocol/server-slack\"],\n\t\tenv: {\n\t\t\tSLACK_BOT_TOKEN: \"\",\n\t\t\tSLACK_TEAM_ID: \"\"\n\t\t},\n\n\t\tdocsUrl:\n\t\t\t\"https://github.com/modelcontextprotocol/servers/tree/main/src/slack/README.md\"\n\t},\n\t\"google-drive\": {\n\t\ticon: \"https://upload.wikimedia.org/wikipedia/commons/1/12/Google_Drive_icon_%282020%29.svg\",\n\t\tdescription: \"Access and search files in your Google Drive\",\n\t\tcommand: \"npx\",\n\t\targs: [\"-y\", \"@modelcontextprotocol/server-gdrive\"],\n\n\t\tdocsUrl:\n\t\t\t\"https://github.com/modelcontextprotocol/servers/tree/main/src/gdrive/README.md\"\n\t},\n\t// time: {\n\t// \ticon: \"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWNsb2NrIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCIvPjxwb2x5bGluZSBwb2ludHM9IjEyIDYgMTIgMTIgMTYgMTQiLz48L3N2Zz4=\",\n\t// \tdescription: \"Current time / time zone conversion utilities\",\n\t// \tcommand: \"uvx\",\n\t// \targs: [\"mcp-server-time\"],\n\t// \tdocsUrl:\n\t// \t\t\"https://github.com/modelcontextprotocol/servers/tree/main/src/time/README.md\"\n\t// },\n\t\"google-maps\": {\n\t\ticon: \"https://upload.wikimedia.org/wikipedia/commons/b/bd/Google_Maps_Logo_2020.svg\",\n\t\tdescription: \"Access Google Maps API for location and mapping services\",\n\t\tcommand: \"npx\",\n\t\targs: [\"-y\", \"@modelcontextprotocol/server-google-maps\"],\n\t\tenv: { GOOGLE_MAPS_API_KEY: \"\" },\n\n\t\tdocsUrl:\n\t\t\t\"https://github.com/modelcontextprotocol/servers/tree/main/src/google-maps/README.md\"\n\t},\n\t\"youtube-transcript\": {\n\t\ticon: \"https://www.svgrepo.com/show/13671/youtube.svg\",\n\t\tdescription: \"Access and search YouTube transcripts\",\n\t\tcommand: \"npx\",\n\t\targs: [\"-y\", \"@kimtaeyoon83/mcp-server-youtube-transcript\"],\n\n\t\tdocsUrl: \"https://github.com/kimtaeyoon83/mcp-server-youtube-transcript\"\n\t},\n\tperplexity: {\n\t\ticon: \"https://seeklogo.com/images/P/perplexity-ai-logo-13120A0AAE-seeklogo.com.png\",\n\t\tdescription: \"Search the web with Perplexity API\",\n\t\tcommand: \"uvx\",\n\t\targs: [\"mcp-server-perplexity\"],\n\t\tenv: {\n\t\t\tPERPLEXITY_API_KEY: \"your-perplexity-api-key\"\n\t\t},\n\n\t\tdocsUrl: \"https://github.com/tanigami/mcp-server-perplexity\"\n\t},\n\t// fetch: {\n\t// \ticon: \"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWdsb2JlIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCIvPjxwYXRoIGQ9Ik0xMiAyYTE0LjUgMTQuNSAwIDAgMCAwIDIwIDE0LjUgMTQuNSAwIDAgMCAwLTIwIi8+PHBhdGggZD0iTTIgMTJoMjAiLz48L3N2Zz4=\",\n\t// \tdescription: \"Let Claude fetch and read a website\",\n\t// \tcommand: \"uvx\",\n\t// \targs: [\"mcp-server-fetch\"],\n\n\t// \tdocsUrl:\n\t// \t\t\"https://github.com/modelcontextprotocol/servers/tree/main/src/fetch/README.md\"\n\t// },\n\t\"apple-notes\": {\n\t\ticon: \"https://upload.wikimedia.org/wikipedia/commons/f/fa/Apple_Notes_icon.svg\",\n\t\tdescription: \"Access and search your Apple Notes\",\n\t\tcommand: \"uvx\",\n\t\targs: [\"apple-notes-mcp\"],\n\n\t\tdocsUrl: \"https://github.com/sirmews/apple-notes-mcp\"\n\t},\n\texa: {\n\t\ticon: \"https://media.licdn.com/dms/image/v2/D4D0BAQEGEKPKKLiNvA/company-logo_200_200/company-logo_200_200/0/1721090778302/exa_ai_logo?e=2147483647&v=beta&t=bNJAmBL2v359QkVTUgGTEbdBOqsnYSMaOuCtMDuG920\",\n\t\tdescription: \"Search the web with Exa\",\n\t\tcommand: \"npx\",\n\t\targs: [\"exa-mcp-server/build/index.js\"],\n\t\tenv: {\n\t\t\tEXA_API_KEY: \"your-api-key-here\"\n\t\t},\n\t\tsetupCommands: {\n\t\t\tinstallPath: \"~/mcp-servers\",\n\t\t\tcommand:\n\t\t\t\t\"mkdir -p $(echo $HOME)/mcp-servers && cd $(echo $HOME)/mcp-servers && \\\n\t\t\t\tcurl -L https://github.com/exa-labs/exa-mcp-server/archive/refs/heads/main.zip -o exa-mcp-server.zip && \\\n\t\t\t\tunzip -o exa-mcp-server.zip && \\\n\t\t\t\trm exa-mcp-server.zip && \\\n\t\t\t\tcd exa-mcp-server-main && \\\n\t\t\t\tnpm install --save axios dotenv && \\\n\t\t\t\tnpm run build && \\\n\t\t\t\tsudo npm link\"\n\t\t},\n\n\t\tdocsUrl: \"https://github.com/exa-labs/exa-mcp-server\"\n\t},\n\tbrowserbase: {\n\t\ticon: \"https://opensourcepledge.com/images/members/browserbase/logo.webp\",\n\t\tdescription: \"Let Claude explore the web with Browserbase\",\n\t\tcommand: \"node\",\n\t\targs: [\"mcp-server-browserbase-main/browserbase/dist/index.js\"],\n\t\tenv: {\n\t\t\tBROWSERBASE_API_KEY: \"your-api-key-here\",\n\t\t\tBROWSERBASE_PROJECT_ID: \"your-project-id-here\"\n\t\t},\n\t\tsetupCommands: {\n\t\t\tinstallPath: \"~/mcp-servers\",\n\t\t\tcommand:\n\t\t\t\t\"mkdir -p $(echo $HOME)/mcp-servers && cd $(echo $HOME)/mcp-servers && \\\n\t\t\t\tcurl -L https://github.com/browserbase/mcp-server-browserbase/archive/refs/heads/main.zip -o browserbase-mcp-server.zip && \\\n\t\t\t\tunzip -o browserbase-mcp-server.zip && \\\n\t\t\t\trm browserbase-mcp-server.zip && \\\n\t\t\t\tcd mcp-server-browserbase-main/browserbase && \\\n\t\t\t\tnpm install && \\\n\t\t\t\tnpm run build\"\n\t\t},\n\n\t\tdocsUrl:\n\t\t\t\"https://github.com/browserbase/mcp-server-browserbase/tree/main/browserbase\"\n\t},\n\tobsidian: {\n\t\ticon: \"https://upload.wikimedia.org/wikipedia/commons/1/10/2023_Obsidian_logo.svg\",\n\t\tdescription: \"Read and search files in your Obsidian vault\",\n\t\tcommand: \"npx\",\n\t\targs: [\"-y\", \"mcp-obsidian\", \"\"],\n\n\t\tdocsUrl: \"https://github.com/calclavia/mcp-obsidian\"\n\t},\n\ttodoist: {\n\t\ticon: \"https://www.svgrepo.com/show/354452/todoist-icon.svg\",\n\t\tdescription: \"Access and search your Todoist tasks\",\n\t\tcommand: \"npx\",\n\t\targs: [\"-y\", \"@abhiz123/todoist-mcp-server\"],\n\t\tenv: {\n\t\t\tTODOIST_API_TOKEN: \"your_api_token_here\"\n\t\t},\n\n\t\tdocsUrl: \"https://github.com/abhiz123/todoist-mcp-server\"\n\t},\n\tcloudflare: {\n\t\ticon: \"https://icon.icepanel.io/Technology/svg/Cloudflare.svg\",\n\t\tdescription: \"Manage your Cloudflare workers and account resources\",\n\t\tdocsUrl: \"https://github.com/cloudflare/mcp-server-cloudflare\",\n\t\tsetupCommands: {\n\t\t\tinstallPath: \"~/mcp-servers\",\n\t\t\tcommand: \"npx @cloudflare/mcp-server-cloudflare init\"\n\t\t}\n\t},\n\t\"aws-kb-retrieval\": {\n\t\ticon: \"https://icon.icepanel.io/Technology/svg/AWS.svg\",\n\t\tdescription:\n\t\t\t\"Access and query AWS Knowledge Base for information retrieval\",\n\t\tcommand: \"npx\",\n\t\targs: [\"-y\", \"@modelcontextprotocol/server-aws-kb-retrieval\"],\n\t\tenv: {\n\t\t\tAWS_ACCESS_KEY_ID: \"\",\n\t\t\tAWS_SECRET_ACCESS_KEY: \"\",\n\t\t\tAWS_REGION: \"\"\n\t\t},\n\n\t\tdocsUrl:\n\t\t\t\"https://github.com/modelcontextprotocol/servers/tree/main/src/aws-kb-retrieval/README.md\"\n\t},\n\teverart: {\n\t\ticon: \"https://pbs.twimg.com/profile_images/1717719314369789952/AmXarABn_400x400.png\",\n\t\tdescription:\n\t\t\t\"Interface with Everart API for digital art and design tools\",\n\t\tcommand: \"npx\",\n\t\targs: [\"-y\", \"@modelcontextprotocol/server-everart\"],\n\t\tenv: { EVERART_API_KEY: \"\" },\n\n\t\tdocsUrl:\n\t\t\t\"https://github.com/modelcontextprotocol/servers/tree/main/src/everart/README.md\"\n\t},\n\tgithub: {\n\t\ticon: \"https://icon.icepanel.io/Technology/svg/GitHub.svg\",\n\t\tdescription: \"Let Claude access your GitHub repositories\",\n\t\tcommand: \"npx\",\n\t\targs: [\"-y\", \"@modelcontextprotocol/server-github\"],\n\t\tenv: { GITHUB_PERSONAL_ACCESS_TOKEN: \"\" },\n\n\t\tdocsUrl:\n\t\t\t\"https://github.com/modelcontextprotocol/servers/blob/main/src/github/README.md\"\n\t},\n\tgitlab: {\n\t\ticon: \"https://icon.icepanel.io/Technology/svg/GitLab.svg\",\n\t\tdescription: \"Manage GitLab repositories and resources\",\n\t\tcommand: \"npx\",\n\t\targs: [\"-y\", \"@modelcontextprotocol/server-gitlab\"],\n\t\tenv: {\n\t\t\tGITLAB_PERSONAL_ACCESS_TOKEN: \"\",\n\t\t\tGITLAB_API_URL: \"https://gitlab.com/api/v4\"\n\t\t},\n\n\t\tdocsUrl:\n\t\t\t\"https://github.com/modelcontextprotocol/servers/blob/main/src/gitlab/README.md\"\n\t},\n\tpostgres: {\n\t\ticon: \"https://www.svgrepo.com/show/303301/postgresql-logo.svg\",\n\t\tdescription: \"Connect and interact with PostgreSQL databases\",\n\t\tcommand: \"npx\",\n\t\targs: [\n\t\t\t\"-y\",\n\t\t\t\"@modelcontextprotocol/server-postgres\",\n\t\t\t\"postgresql://localhost/mydb\"\n\t\t],\n\n\t\tdocsUrl:\n\t\t\t\"https://github.com/modelcontextprotocol/servers/tree/main/src/postgres/README.md\"\n\t},\n\tpuppeteer: {\n\t\ticon: \"https://www.svgrepo.com/show/354228/puppeteer.svg\",\n\t\tdescription: \"Automate browser interactions with Puppeteer\",\n\t\tcommand: \"npx\",\n\t\targs: [\"-y\", \"@modelcontextprotocol/server-puppeteer\"],\n\n\t\tdocsUrl:\n\t\t\t\"https://github.com/modelcontextprotocol/servers/tree/main/src/puppeteer/README.md\"\n\t},\n\tsqlite: {\n\t\ticon: \"https://icon.icepanel.io/Technology/svg/SQLite.svg\",\n\t\tdescription: \"Manage SQLite databases in local file storage\",\n\t\tcommand: \"uv\",\n\t\targs: [\n\t\t\t\"--directory\",\n\t\t\t\"parent_of_servers_repo/servers/src/sqlite\",\n\t\t\t\"run\",\n\t\t\t\"mcp-server-sqlite\",\n\t\t\t\"--db-path\",\n\t\t\t\"~/test.db\"\n\t\t],\n\n\t\tdocsUrl:\n\t\t\t\"https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite/README.md\"\n\t}\n\t// sentry: {\n\t// \ticon: \"https://www.svgrepo.com/show/306716/sentry.svg\",\n\t// \tdescription: \"Retrieve and analyze issues from Sentry for debugging\",\n\t// \tcommand: \"uvx\",\n\t// \targs: [\"mcp-server-sentry\", \"--auth-token\", \"\"],\n\n\t// \tdocsUrl:\n\t// \t\t\"https://github.com/modelcontextprotocol/servers/tree/main/src/sentry\"\n\t// }\n}\n"
  },
  {
    "path": "src/utils.ts",
    "content": "export const capitalizeFirstLetter = (str: string): string => {\n\treturn str.charAt(0).toUpperCase() + str.slice(1)\n}\n"
  },
  {
    "path": "src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "tailwind.config.ts",
    "content": "import daisyui from \"daisyui\"\nimport { light } from \"daisyui/src/theming/themes\"\nimport type { Config } from \"tailwindcss\"\n\nexport default {\n\tcontent: [\"./index.html\", \"./src/**/*.{js,ts,jsx,tsx}\"],\n\ttheme: {\n\t\textend: {\n\t\t\tfontFamily: {\n\t\t\t\t\"tiempos-semibold\": [\"Tiempos Text\", \"serif\"],\n\t\t\t\t\"tiempos-regular\": [\"Tiempos Text\", \"serif\"]\n\t\t\t}\n\t\t}\n\t},\n\tplugins: [daisyui],\n\tdaisyui: {\n\t\tthemes: [\n\t\t\t{\n\t\t\t\tlight: {\n\t\t\t\t\t...light,\n\t\t\t\t\tprimary: \"#da7756\",\n\t\t\t\t\t\"primary-content\": \"#ffffff\",\n\t\t\t\t\tsecondary: \"#f2f1e9\",\n\t\t\t\t\t\"secondary-content\": \"#000000\"\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t}\n} satisfies Config\n"
  },
  {
    "path": "tsconfig.app.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.app.tsbuildinfo\",\n\t\t\"target\": \"ES2020\",\n\t\t\"useDefineForClassFields\": true,\n\t\t\"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n\t\t\"module\": \"ESNext\",\n\t\t\"skipLibCheck\": true,\n\n\t\t/* Bundler mode */\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"allowImportingTsExtensions\": true,\n\t\t\"isolatedModules\": true,\n\t\t\"moduleDetection\": \"force\",\n\t\t\"noEmit\": true,\n\t\t\"jsx\": \"react-jsx\",\n\n\t\t/* Linting */\n\t\t\"strict\": true,\n\t\t\"noUnusedLocals\": false,\n\t\t\"noUnusedParameters\": true,\n\t\t\"noFallthroughCasesInSwitch\": true,\n\t\t\"noUncheckedSideEffectImports\": true,\n\n\t\t/* Path Aliases */\n\t\t\"baseUrl\": \".\",\n\t\t\"paths\": {\n\t\t\t\"@/*\": [\"src/*\"]\n\t\t}\n\t},\n\t\"include\": [\"src\"]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n\t\"files\": [],\n\t\"references\": [\n\t\t{ \"path\": \"./tsconfig.app.json\" },\n\t\t{ \"path\": \"./tsconfig.node.json\" }\n\t]\n}\n"
  },
  {
    "path": "tsconfig.node.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n\t\t\"target\": \"ES2022\",\n\t\t\"lib\": [\"ES2023\"],\n\t\t\"module\": \"ESNext\",\n\t\t\"skipLibCheck\": true,\n\n\t\t/* Bundler mode */\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"allowImportingTsExtensions\": true,\n\t\t\"isolatedModules\": true,\n\t\t\"moduleDetection\": \"force\",\n\t\t\"noEmit\": true,\n\n\t\t/* Linting */\n\t\t\"strict\": true,\n\t\t\"noUnusedLocals\": false,\n\t\t\"noUnusedParameters\": true,\n\t\t\"noFallthroughCasesInSwitch\": true,\n\t\t\"noUncheckedSideEffectImports\": true,\n\n\t\t/* Path Aliases */\n\t\t\"baseUrl\": \".\",\n\t\t\"paths\": {\n\t\t\t\"@/*\": [\"src/*\"]\n\t\t}\n\t},\n\t\"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import { dirname } from \"node:path\"\nimport path from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\nimport react from \"@vitejs/plugin-react\"\nimport { defineConfig } from \"vite\"\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\n// https://vite.dev/config/\nexport default defineConfig({\n\tplugins: [react()],\n\tresolve: {\n\t\talias: {\n\t\t\t\"@\": path.resolve(__dirname, \"./src\")\n\t\t}\n\t}\n})\n"
  }
]