├── .cursorrules
├── .eslintrc.json
├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── LICENSE
├── README.md
├── biome.json
├── bun.lockb
├── electron
├── main.ts
├── preload.ts
├── tsconfig.json
└── tsconfig.tsbuildinfo
├── eslint.config.js
├── index.html
├── package.json
├── postcss.config.js
├── public
├── app.png
├── claude-logo.svg
├── logo_zue.svg
├── mcp-favicon.ico
└── mcp-logo.svg
├── src
├── App.tsx
├── assets
│ ├── tiempos-text-web-regular.woff2
│ └── tiempos-text-web-semibold.woff2
├── components
│ ├── applying-instructions.tsx
│ ├── loading-instructions.tsx
│ ├── mcp-server-card.tsx
│ ├── mcp-servers.tsx
│ ├── server-configs
│ │ ├── env-config.tsx
│ │ ├── filesystem-config.tsx
│ │ ├── obsidian-config.tsx
│ │ ├── postgres-config.tsx
│ │ ├── sentry-config.tsx
│ │ └── sqlite-config.tsx
│ └── terminal-command.tsx
├── index.css
├── main.tsx
├── server-configs.ts
├── types
│ └── electron.d.ts
├── utils.ts
└── vite-env.d.ts
├── tailwind.config.ts
├── tsconfig.json
└── vite.config.ts
/.cursorrules:
--------------------------------------------------------------------------------
1 | this is a simple react and vite project that allows users to manage a JSON file on their computer with a nice UI.
2 |
3 | the file path is " ~/Library/Application\ Support/Claude/claude_desktop_config.json"
4 |
5 | styling is done with tailwindcss and daisyui whenever possible
6 |
7 | never use "any" as a type
8 |
9 | always add the type=button property to buttons
10 |
11 | always use bun over npm for this project
12 |
13 | JSX elements without children should be marked as self-closing
14 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "@typescript-eslint/no-require-imports": "off"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build mac app
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 | branches:
8 | - main
9 | - test-build-action
10 |
11 | permissions: write-all
12 |
13 | jobs:
14 | release:
15 | runs-on: ${{ matrix.os }}
16 |
17 | strategy:
18 | matrix:
19 | os: [macos-latest]
20 |
21 | steps:
22 | - name: Check out Git repository
23 | uses: actions/checkout@v4
24 |
25 | - name: Install Node.js, NPM and Yarn
26 | uses: actions/setup-node@v4
27 | with:
28 | node-version: 20
29 |
30 | - name: Build/release Electron app
31 | uses: samuelmeuli/action-electron-builder@v1
32 | with:
33 | # GitHub token, automatically provided to the action
34 | # (No need to define this secret in the repo settings)
35 | github_token: ${{ secrets.github_token }}
36 |
37 | # Always release the app after building
38 | release: true
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | dist-electron
14 | electron/dist/
15 | *.local
16 |
17 | # Editor directories and files
18 | .vscode/*
19 | !.vscode/extensions.json
20 | .idea
21 | .DS_Store
22 | **/.DS_Store
23 | *.suo
24 | *.ntvs*
25 | *.njsproj
26 | *.sln
27 | *.sw?
28 |
29 | # Config files with sensitive data
30 | **/claude_desktop_config.json.bak
31 | **/claude_desktop_config.json
32 |
33 | # Build directories
34 | .tmp
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Zue AI
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
MCP Manager for Claude Desktop
2 |
3 | A desktop application to manage Model Context Protocol (MCP) servers for the Claude Desktop app on MacOS. Just follow the instructions and paste a few commands to give your Claude app instant superpowers.
4 |
5 | 
6 |
7 | ## What is MCP?
8 |
9 | The 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:
10 |
11 | - [modelcontextprotocol.io](https://modelcontextprotocol.io)
12 | - [Anthropic's MCP Announcement](https://www.anthropic.com/news/model-context-protocol)
13 |
14 | ## Features
15 |
16 | - 🚀 Easy-to-use desktop interface for managing MCP servers
17 | - 🔒 Runs locally - your data never leaves your computer
18 | - ⚡️ Quick setup for popular MCP servers:
19 | - Apple Notes - Access and search your Apple Notes
20 | - AWS Knowledge Base - Access and query AWS Knowledge Base for information retrieval
21 | - Brave Search - Search the web with Brave Search API
22 | - Browserbase - Let Claude explore the web with Browserbase
23 | - Cloudflare - Manage your Cloudflare workers and account resources
24 | - Everart - Interface with Everart API for digital art and design tools
25 | - Exa - Search the web with Exa
26 | - Filesystem - Access and manage local filesystem
27 | - GitHub - Access your GitHub repositories
28 | - GitLab - Manage GitLab repositories and resources
29 | - Google Drive - Access and search files in your Google Drive
30 | - Google Maps - Access Google Maps API for location services
31 | - Memory - Give Claude memory of previous conversations
32 | - Obsidian - Read and search files in your Obsidian vault
33 | - Perplexity - Search the web with Perplexity API
34 | - PostgreSQL - Connect and interact with PostgreSQL databases
35 | - Puppeteer - Automate browser interactions
36 | - Sequential Thinking - Enable step-by-step reasoning
37 | - Slack - Access your Slack workspace
38 | - SQLite - Manage SQLite databases
39 | - Todoist - Access and search your Todoist tasks
40 | - YouTube Transcript - Access and search YouTube transcripts
41 | - 🛠 Simple configuration of environment variables and server settings
42 | - 📋 One-click copying of terminal commands for installation
43 |
44 | ## Tech Stack
45 |
46 | - **Desktop Framework**:
47 | - Electron 29.1.4 with React 18.3.1
48 | - TypeScript 5.6.2
49 | - **Build Tool**:
50 | - Vite 6.0.1
51 | - Electron Builder 25.1.8
52 | - **UI Components**:
53 | - TailwindCSS 3.4.16
54 | - DaisyUI 4.12.14
55 | - Lucide React 0.468.0 for icons
56 | - Tiempos Font
57 | - **Code Quality**:
58 | - Biome 1.9.4
59 | - ESLint 9.15.0
60 | - **Package Manager**: Bun
61 |
62 | ## Project Structure
63 |
64 | ```plaintext
65 | src/
66 | ├── components/ # React components
67 | │ ├── server-configs/ # Server-specific configuration components
68 | │ └── ...
69 | ├── assets/ # Static assets and fonts
70 | ├── App.tsx # Main application component
71 | ├── server-configs.ts # MCP server configurations
72 | └── utils.ts # Utility functions
73 | electron/
74 | ├── main.ts # Electron main process
75 | └── tsconfig.json # TypeScript config for Electron
76 | ```
77 |
78 | ## Development
79 |
80 | 1. Install dependencies:
81 | ```bash
82 | bun install
83 | ```
84 |
85 | 2. Start development:
86 | ```bash
87 | bun electron:dev
88 | ```
89 |
90 | 3. Build for MacOS:
91 | ```bash
92 | rm -rf dist dist-electron # When rebuilding
93 | bun electron:build # Creates .dmg installer
94 | ```
95 |
96 | 4. Additional commands:
97 | ```bash
98 | bun check # Run TypeScript checks and Biome formatting
99 | bun lint # Run ESLint
100 | ```
101 |
102 | ## Work to be done
103 |
104 | Add preset MCPs:
105 | - Fetch
106 | - Time-related
107 | - Sentry
108 |
109 | Contributions to resolve these are welcome!
110 |
111 | ## Contributing
112 |
113 | Contributions are extremely welcome! Please open a PR with new MCP servers or any other improvements to the codebase.
114 |
115 | ## Disclaimer
116 |
117 | This project is not affiliated with Anthropic. All logos are trademarks of their respective owners.
118 |
119 | ## License
120 |
121 | MIT
122 |
123 | ---
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | Contact us for custom AI automation solutions and product development.
137 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": {
3 | "ignore": [
4 | "dist/**/*",
5 | "node_modules/**/*",
6 | "public/**/*",
7 | "**/*.css",
8 | ".github/**/*"
9 | ]
10 | },
11 | "linter": {
12 | "enabled": true,
13 | "rules": {
14 | "recommended": true,
15 | "suspicious": {
16 | "noArrayIndexKey": "off",
17 | "noAssignInExpressions": "off"
18 | },
19 | "style": {
20 | "noCommaOperator": "off"
21 | }
22 | },
23 | "ignore": ["**/*.md", "**/*.css", ".github/**/*"]
24 | },
25 | "formatter": {
26 | "enabled": true,
27 | "indentStyle": "tab",
28 | "indentWidth": 4
29 | },
30 | "javascript": {
31 | "formatter": {
32 | "semicolons": "asNeeded",
33 | "trailingCommas": "none"
34 | }
35 | },
36 | "json": {
37 | "parser": {
38 | "allowComments": true
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zueai/mcp-manager/1140651e836cabe746f444bdc449cdd2429c4bca/bun.lockb
--------------------------------------------------------------------------------
/electron/main.ts:
--------------------------------------------------------------------------------
1 | import { exec } from "node:child_process"
2 | import { existsSync, readFileSync } from "node:fs"
3 | import * as fs from "node:fs/promises"
4 | import * as os from "node:os"
5 | import * as path from "node:path"
6 | import { promisify } from "node:util"
7 | import { BrowserWindow, type IpcMainInvokeEvent, app, ipcMain } from "electron"
8 | import { protocol } from "electron"
9 |
10 | let mainWindow: BrowserWindow | null = null
11 |
12 | async function createWindow() {
13 | if (mainWindow) return
14 |
15 | mainWindow = new BrowserWindow({
16 | width: 1200,
17 | height: 800,
18 | fullscreen: true,
19 | webPreferences: {
20 | nodeIntegration: false,
21 | contextIsolation: true,
22 | preload: path.join(__dirname, "preload.js"),
23 | webSecurity: false
24 | }
25 | })
26 |
27 | if (process.platform === "darwin") {
28 | app.dock.show()
29 | }
30 |
31 | if (process.env.VITE_DEV_SERVER_URL) {
32 | await mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL)
33 | } else {
34 | mainWindow.loadFile("dist/index.html")
35 | }
36 | }
37 |
38 | app.whenReady().then(() => {
39 | protocol.registerFileProtocol("app", (request, callback) => {
40 | const url = request.url.slice("app://".length)
41 | callback({ path: path.join(__dirname, url) })
42 | })
43 | createWindow()
44 | })
45 |
46 | app.on("activate", () => {
47 | if (mainWindow === null) {
48 | createWindow()
49 | }
50 | })
51 |
52 | app.on("window-all-closed", () => {
53 | if (process.platform !== "darwin") {
54 | app.quit()
55 | }
56 | })
57 |
58 | ipcMain.handle("read-config", async () => {
59 | try {
60 | const configPath = path.join(
61 | os.homedir(),
62 | "Library",
63 | "Application Support",
64 | "Claude",
65 | "claude_desktop_config.json"
66 | )
67 | console.log("Reading config from:", configPath)
68 |
69 | const exists = existsSync(configPath)
70 | console.log("Config file exists:", exists)
71 |
72 | const data = await fs.readFile(configPath, "utf8")
73 | console.log("Config data:", data)
74 |
75 | const parsedData = JSON.parse(data)
76 | console.log("Parsed config:", parsedData)
77 | return parsedData
78 | } catch (error) {
79 | console.error("Error reading config:", error)
80 | return { success: false, error }
81 | }
82 | })
83 |
84 | ipcMain.handle(
85 | "execute-command",
86 | async (_event: IpcMainInvokeEvent, command: string) => {
87 | const execAsync = promisify(exec)
88 | try {
89 | const { stdout, stderr } = await execAsync(command)
90 | console.log("Command output:", stdout)
91 | if (stderr) {
92 | console.error("Command stderr:", stderr)
93 | }
94 | return { success: true, output: stdout }
95 | } catch (error) {
96 | console.error("Command error:", error)
97 | return { success: false, error }
98 | }
99 | }
100 | )
101 |
--------------------------------------------------------------------------------
/electron/preload.ts:
--------------------------------------------------------------------------------
1 | const { contextBridge, ipcRenderer } = require("electron")
2 |
3 | contextBridge.exposeInMainWorld("electron", {
4 | readConfig: () => ipcRenderer.invoke("read-config"),
5 | executeCommand: (command: string) =>
6 | ipcRenderer.invoke("execute-command", command)
7 | })
8 |
9 | export {}
10 |
--------------------------------------------------------------------------------
/electron/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "composite": true,
5 | "module": "CommonJS",
6 | "moduleResolution": "node",
7 | "types": ["electron", "node"]
8 | },
9 | "include": ["."]
10 | }
11 |
--------------------------------------------------------------------------------
/electron/tsconfig.tsbuildinfo:
--------------------------------------------------------------------------------
1 | {"fileNames":["../node_modules/typescript/lib/lib.es5.d.ts","../node_modules/typescript/lib/lib.es2015.d.ts","../node_modules/typescript/lib/lib.es2016.d.ts","../node_modules/typescript/lib/lib.es2017.d.ts","../node_modules/typescript/lib/lib.es2018.d.ts","../node_modules/typescript/lib/lib.es2019.d.ts","../node_modules/typescript/lib/lib.es2020.d.ts","../node_modules/typescript/lib/lib.es2021.d.ts","../node_modules/typescript/lib/lib.es2022.d.ts","../node_modules/typescript/lib/lib.es2023.d.ts","../node_modules/typescript/lib/lib.dom.d.ts","../node_modules/typescript/lib/lib.dom.iterable.d.ts","../node_modules/typescript/lib/lib.es2015.core.d.ts","../node_modules/typescript/lib/lib.es2015.collection.d.ts","../node_modules/typescript/lib/lib.es2015.generator.d.ts","../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../node_modules/typescript/lib/lib.es2015.promise.d.ts","../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../node_modules/typescript/lib/lib.es2016.intl.d.ts","../node_modules/typescript/lib/lib.es2017.date.d.ts","../node_modules/typescript/lib/lib.es2017.object.d.ts","../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../node_modules/typescript/lib/lib.es2017.string.d.ts","../node_modules/typescript/lib/lib.es2017.intl.d.ts","../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../node_modules/typescript/lib/lib.es2018.intl.d.ts","../node_modules/typescript/lib/lib.es2018.promise.d.ts","../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../node_modules/typescript/lib/lib.es2019.array.d.ts","../node_modules/typescript/lib/lib.es2019.object.d.ts","../node_modules/typescript/lib/lib.es2019.string.d.ts","../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../node_modules/typescript/lib/lib.es2019.intl.d.ts","../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../node_modules/typescript/lib/lib.es2020.date.d.ts","../node_modules/typescript/lib/lib.es2020.promise.d.ts","../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../node_modules/typescript/lib/lib.es2020.string.d.ts","../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../node_modules/typescript/lib/lib.es2020.intl.d.ts","../node_modules/typescript/lib/lib.es2020.number.d.ts","../node_modules/typescript/lib/lib.es2021.promise.d.ts","../node_modules/typescript/lib/lib.es2021.string.d.ts","../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../node_modules/typescript/lib/lib.es2021.intl.d.ts","../node_modules/typescript/lib/lib.es2022.array.d.ts","../node_modules/typescript/lib/lib.es2022.error.d.ts","../node_modules/typescript/lib/lib.es2022.intl.d.ts","../node_modules/typescript/lib/lib.es2022.object.d.ts","../node_modules/typescript/lib/lib.es2022.sharedmemory.d.ts","../node_modules/typescript/lib/lib.es2022.string.d.ts","../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../node_modules/typescript/lib/lib.es2023.array.d.ts","../node_modules/typescript/lib/lib.es2023.collection.d.ts","../node_modules/typescript/lib/lib.es2023.intl.d.ts","../node_modules/typescript/lib/lib.decorators.d.ts","../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../node_modules/@types/react/global.d.ts","../node_modules/csstype/index.d.ts","../node_modules/@types/prop-types/index.d.ts","../node_modules/@types/react/index.d.ts","../node_modules/@types/react/jsx-runtime.d.ts","../node_modules/@types/node/compatibility/disposable.d.ts","../node_modules/@types/node/compatibility/indexable.d.ts","../node_modules/@types/node/compatibility/iterators.d.ts","../node_modules/@types/node/compatibility/index.d.ts","../node_modules/@types/node/ts5.6/globals.typedarray.d.ts","../node_modules/@types/node/ts5.6/buffer.buffer.d.ts","../node_modules/buffer/index.d.ts","../node_modules/undici-types/header.d.ts","../node_modules/undici-types/readable.d.ts","../node_modules/undici-types/file.d.ts","../node_modules/undici-types/fetch.d.ts","../node_modules/undici-types/formdata.d.ts","../node_modules/undici-types/connector.d.ts","../node_modules/undici-types/client.d.ts","../node_modules/undici-types/errors.d.ts","../node_modules/undici-types/dispatcher.d.ts","../node_modules/undici-types/global-dispatcher.d.ts","../node_modules/undici-types/global-origin.d.ts","../node_modules/undici-types/pool-stats.d.ts","../node_modules/undici-types/pool.d.ts","../node_modules/undici-types/handlers.d.ts","../node_modules/undici-types/balanced-pool.d.ts","../node_modules/undici-types/agent.d.ts","../node_modules/undici-types/mock-interceptor.d.ts","../node_modules/undici-types/mock-agent.d.ts","../node_modules/undici-types/mock-client.d.ts","../node_modules/undici-types/mock-pool.d.ts","../node_modules/undici-types/mock-errors.d.ts","../node_modules/undici-types/proxy-agent.d.ts","../node_modules/undici-types/env-http-proxy-agent.d.ts","../node_modules/undici-types/retry-handler.d.ts","../node_modules/undici-types/retry-agent.d.ts","../node_modules/undici-types/api.d.ts","../node_modules/undici-types/interceptors.d.ts","../node_modules/undici-types/util.d.ts","../node_modules/undici-types/cookies.d.ts","../node_modules/undici-types/patch.d.ts","../node_modules/undici-types/websocket.d.ts","../node_modules/undici-types/eventsource.d.ts","../node_modules/undici-types/filereader.d.ts","../node_modules/undici-types/diagnostics-channel.d.ts","../node_modules/undici-types/content-type.d.ts","../node_modules/undici-types/cache.d.ts","../node_modules/undici-types/index.d.ts","../node_modules/@types/node/globals.d.ts","../node_modules/@types/node/assert.d.ts","../node_modules/@types/node/assert/strict.d.ts","../node_modules/@types/node/async_hooks.d.ts","../node_modules/@types/node/buffer.d.ts","../node_modules/@types/node/child_process.d.ts","../node_modules/@types/node/cluster.d.ts","../node_modules/@types/node/console.d.ts","../node_modules/@types/node/constants.d.ts","../node_modules/@types/node/crypto.d.ts","../node_modules/@types/node/dgram.d.ts","../node_modules/@types/node/diagnostics_channel.d.ts","../node_modules/@types/node/dns.d.ts","../node_modules/@types/node/dns/promises.d.ts","../node_modules/@types/node/domain.d.ts","../node_modules/@types/node/dom-events.d.ts","../node_modules/@types/node/events.d.ts","../node_modules/@types/node/fs.d.ts","../node_modules/@types/node/fs/promises.d.ts","../node_modules/@types/node/http.d.ts","../node_modules/@types/node/http2.d.ts","../node_modules/@types/node/https.d.ts","../node_modules/@types/node/inspector.d.ts","../node_modules/@types/node/module.d.ts","../node_modules/@types/node/net.d.ts","../node_modules/@types/node/os.d.ts","../node_modules/@types/node/path.d.ts","../node_modules/@types/node/perf_hooks.d.ts","../node_modules/@types/node/process.d.ts","../node_modules/@types/node/punycode.d.ts","../node_modules/@types/node/querystring.d.ts","../node_modules/@types/node/readline.d.ts","../node_modules/@types/node/readline/promises.d.ts","../node_modules/@types/node/repl.d.ts","../node_modules/@types/node/sea.d.ts","../node_modules/@types/node/sqlite.d.ts","../node_modules/@types/node/stream.d.ts","../node_modules/@types/node/stream/promises.d.ts","../node_modules/@types/node/stream/consumers.d.ts","../node_modules/@types/node/stream/web.d.ts","../node_modules/@types/node/string_decoder.d.ts","../node_modules/@types/node/test.d.ts","../node_modules/@types/node/timers.d.ts","../node_modules/@types/node/timers/promises.d.ts","../node_modules/@types/node/tls.d.ts","../node_modules/@types/node/trace_events.d.ts","../node_modules/@types/node/tty.d.ts","../node_modules/@types/node/url.d.ts","../node_modules/@types/node/util.d.ts","../node_modules/@types/node/v8.d.ts","../node_modules/@types/node/vm.d.ts","../node_modules/@types/node/wasi.d.ts","../node_modules/@types/node/worker_threads.d.ts","../node_modules/@types/node/zlib.d.ts","../node_modules/@types/node/ts5.6/index.d.ts","../node_modules/electron/electron.d.ts","./main.ts","./preload.ts"],"fileIdsList":[[68,74,117,118,130,131,138,139,161,168],[68,74,117],[74,114,117],[74,116,117],[74,117,122,152],[74,117,118,123,129,130,137,149,160],[74,117,118,119,129,137],[74,117],[69,70,71,74,117],[74,117,120,161],[74,117,121,122,130,138],[74,117,122,149,157],[74,117,123,125,129,137],[74,116,117,124],[74,117,125,126],[74,117,129],[74,117,127,129],[74,116,117,129],[74,117,129,130,131,149,160],[74,117,129,130,131,144,149,152],[74,112,117,165],[74,112,117,125,129,132,137,149,160],[74,117,129,130,132,133,137,149,157,160],[74,117,132,134,149,157,160],[74,117,129,135],[74,117,136,160,165],[74,117,125,129,137,149],[74,117,138],[74,117,139],[74,116,117,140],[74,114,115,116,117,118,119,120,121,122,123,124,125,126,127,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166],[74,117,142],[74,117,143],[74,117,129,144,145],[74,117,144,146,161,163],[74,117,129,149,150,151,152],[74,117,149,151],[74,117,149,150],[74,117,152],[74,117,153],[74,114,117,149],[74,117,129,155,156],[74,117,155,156],[74,117,122,137,149,157],[74,117,158],[117],[72,73,74,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166],[74,117,137,159],[74,117,132,143,160],[74,117,122,161],[74,117,149,162],[74,117,136,163],[74,117,164],[74,117,122,129,131,140,149,160,163,165],[74,117,149,166],[64,65,66,74,117],[67,74,117],[74,117,129,130,167],[74,84,88,117,160],[74,84,117,149,160],[74,79,117],[74,81,84,117,157,160],[74,117,137,157],[74,117,167],[74,79,117,167],[74,81,84,117,137,160],[74,76,77,80,83,117,129,149,160],[74,84,91,117],[74,76,82,117],[74,84,105,106,117],[74,80,84,117,152,160,167],[74,105,117,167],[74,78,79,117,167],[74,84,117],[74,78,79,80,81,82,83,84,85,86,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,106,107,108,109,110,111,117],[74,84,99,117],[74,84,91,92,117],[74,82,84,92,93,117],[74,83,117],[74,76,79,84,117],[74,84,88,92,93,117],[74,88,117],[74,82,84,87,117,160],[74,76,81,84,91,117],[74,117,149],[74,79,84,105,117,165,167]],"fileInfos":[{"version":"44e584d4f6444f58791784f1d530875970993129442a847597db702a073ca68c","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"9a68c0c07ae2fa71b44384a839b7b8d81662a236d4b9ac30916718f7510b1b2d","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"5514e54f17d6d74ecefedc73c504eadffdeda79c7ea205cf9febead32d45c4bc","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"9e8ca8ed051c2697578c023d9c29d6df689a083561feba5c14aedee895853999","affectsGlobalScope":true,"impliedFormat":1},{"version":"69e65d976bf166ce4a9e6f6c18f94d2424bf116e90837ace179610dbccad9b42","affectsGlobalScope":true,"impliedFormat":1},{"version":"6920e1448680767498a0b77c6a00a8e77d14d62c3da8967b171f1ddffa3c18e4","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"45d8ccb3dfd57355eb29749919142d4321a0aa4df6acdfc54e30433d7176600a","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"93495ff27b8746f55d19fcbcdbaccc99fd95f19d057aed1bd2c0cafe1335fbf0","affectsGlobalScope":true,"impliedFormat":1},{"version":"6fc23bb8c3965964be8c597310a2878b53a0306edb71d4b5a4dfe760186bcc01","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea011c76963fb15ef1cdd7ce6a6808b46322c527de2077b6cfdf23ae6f5f9ec7","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"4738f2420687fd85629c9efb470793bb753709c2379e5f85bc1815d875ceadcd","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"9fc46429fbe091ac5ad2608c657201eb68b6f1b8341bd6d670047d32ed0a88fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"1a94697425a99354df73d9c8291e2ecd4dddd370aed4023c2d6dee6cccb32666","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"bf14a426dbbf1022d11bd08d6b8e709a2e9d246f0c6c1032f3b2edb9a902adbe","affectsGlobalScope":true,"impliedFormat":1},{"version":"e3f9fc0ec0b96a9e642f11eda09c0be83a61c7b336977f8b9fdb1e9788e925fe","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"479553e3779be7d4f68e9f40cdb82d038e5ef7592010100410723ceced22a0f7","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"d3d7b04b45033f57351c8434f60b6be1ea71a2dfec2d0a0c3c83badbb0e3e693","affectsGlobalScope":true,"impliedFormat":1},{"version":"956d27abdea9652e8368ce029bb1e0b9174e9678a273529f426df4b3d90abd60","affectsGlobalScope":true,"impliedFormat":1},{"version":"4fa6ed14e98aa80b91f61b9805c653ee82af3502dc21c9da5268d3857772ca05","affectsGlobalScope":true,"impliedFormat":1},{"version":"e6633e05da3ff36e6da2ec170d0d03ccf33de50ca4dc6f5aeecb572cedd162fb","affectsGlobalScope":true,"impliedFormat":1},{"version":"15c1c3d7b2e46e0025417ed6d5f03f419e57e6751f87925ca19dc88297053fe6","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"caccc56c72713969e1cfe5c3d44e5bab151544d9d2b373d7dbe5a1e4166652be","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"9d540251809289a05349b70ab5f4b7b99f922af66ab3c39ba56a475dcf95d5ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"33358442698bb565130f52ba79bfd3d4d484ac85fe33f3cb1759c54d18201393","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"36a2e4c9a67439aca5f91bb304611d5ae6e20d420503e96c230cf8fcdc948d94","affectsGlobalScope":true,"impliedFormat":1},{"version":"8a8eb4ebffd85e589a1cc7c178e291626c359543403d58c9cd22b81fab5b1fb9","impliedFormat":1},{"version":"65ff5a0aefd7817a03c1ad04fee85c9cdd3ec415cc3c9efec85d8008d4d5e4ee","impliedFormat":1},{"version":"aa17748c522bd586f8712b1a308ea23af59c309b2fd278f6d4f406647c72e659","affectsGlobalScope":true,"impliedFormat":1},{"version":"42c169fb8c2d42f4f668c624a9a11e719d5d07dacbebb63cbcf7ef365b0a75b3","impliedFormat":1},{"version":"70521b6ab0dcba37539e5303104f29b721bfb2940b2776da4cc818c07e1fefc1","affectsGlobalScope":true,"impliedFormat":1},{"version":"030e350db2525514580ed054f712ffb22d273e6bc7eddc1bb7eda1e0ba5d395e","affectsGlobalScope":true,"impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"613b21ccdf3be6329d56e6caa13b258c842edf8377be7bc9f014ed14cdcfc308","affectsGlobalScope":true,"impliedFormat":1},{"version":"2d1319e6b5d0efd8c5eae07eb864a00102151e8b9afddd2d45db52e9aae002c4","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e9c23ba78aabc2e0a27033f18737a6df754067731e69dc5f52823957d60a4b6","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"24bd580b5743dc56402c440dc7f9a4f5d592ad7a419f25414d37a7bfe11e342b","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"6bdc71028db658243775263e93a7db2fd2abfce3ca569c3cca5aee6ed5eb186d","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"81184fe8e67d78ac4e5374650f0892d547d665d77da2b2f544b5d84729c4a15d","affectsGlobalScope":true,"impliedFormat":1},{"version":"f52e8dacc97d71dcc96af29e49584353f9c54cb916d132e3e768d8b8129c928d","impliedFormat":1},{"version":"7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419","impliedFormat":1},{"version":"76103716ba397bbb61f9fa9c9090dca59f39f9047cb1352b2179c5d8e7f4e8d0","impliedFormat":1},{"version":"53eac70430b30089a3a1959d8306b0f9cfaf0de75224b68ef25243e0b5ad1ca3","affectsGlobalScope":true,"impliedFormat":1},{"version":"4314c7a11517e221f7296b46547dbc4df047115b182f544d072bdccffa57fc72","impliedFormat":1},{"version":"115971d64632ea4742b5b115fb64ed04bcaae2c3c342f13d9ba7e3f9ee39c4e7","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"a40826e8476694e90da94aa008283a7de50d1dafd37beada623863f1901cb7fb","impliedFormat":1},{"version":"86956cc2eb9dd371d6fab493d326a574afedebf76eef3fa7833b8e0d9b52d6f1","affectsGlobalScope":true,"impliedFormat":1},{"version":"24642567d3729bcc545bacb65ee7c0db423400c7f1ef757cab25d05650064f98","impliedFormat":1},{"version":"e6f5a38687bebe43a4cef426b69d34373ef68be9a6b1538ec0a371e69f309354","impliedFormat":1},{"version":"a6bf63d17324010ca1fbf0389cab83f93389bb0b9a01dc8a346d092f65b3605f","impliedFormat":1},{"version":"e009777bef4b023a999b2e5b9a136ff2cde37dc3f77c744a02840f05b18be8ff","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"ee1ee365d88c4c6c0c0a5a5701d66ebc27ccd0bcfcfaa482c6e2e7fe7b98edf7","affectsGlobalScope":true,"impliedFormat":1},{"version":"875928df2f3e9a3aed4019539a15d04ff6140a06df6cd1b2feb836d22a81eaca","affectsGlobalScope":true,"impliedFormat":1},{"version":"e9ad08a376ac84948fcca0013d6f1d4ae4f9522e26b91f87945b97c99d7cc30b","impliedFormat":1},{"version":"eaf9ee1d90a35d56264f0bf39842282c58b9219e112ac7d0c1bce98c6c5da672","impliedFormat":1},{"version":"c15c4427ae7fd1dcd7f312a8a447ac93581b0d4664ddf151ecd07de4bf2bb9d7","impliedFormat":1},{"version":"5135bdd72cc05a8192bd2e92f0914d7fc43ee077d1293dc622a049b7035a0afb","impliedFormat":1},{"version":"4f80de3a11c0d2f1329a72e92c7416b2f7eab14f67e92cac63bb4e8d01c6edc8","impliedFormat":1},{"version":"6d386bc0d7f3afa1d401afc3e00ed6b09205a354a9795196caed937494a713e6","impliedFormat":1},{"version":"75c3400359d59fae5aed4c4a59fcd8a9760cf451e25dc2174cb5e08b9d4803e2","affectsGlobalScope":true,"impliedFormat":1},{"version":"94c4187083503a74f4544503b5a30e2bd7af0032dc739b0c9a7ce87f8bddc7b9","impliedFormat":1},{"version":"b1b6ee0d012aeebe11d776a155d8979730440082797695fc8e2a5c326285678f","impliedFormat":1},{"version":"45875bcae57270aeb3ebc73a5e3fb4c7b9d91d6b045f107c1d8513c28ece71c0","impliedFormat":1},{"version":"3eb62baae4df08c9173e6903d3ca45942ccec8c3659b0565684a75f3292cffbb","affectsGlobalScope":true,"impliedFormat":1},{"version":"a85683ef86875f4ad4c6b7301bbcc63fb379a8d80d3d3fd735ee57f48ef8a47e","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"c6b4e0a02545304935ecbf7de7a8e056a31bb50939b5b321c9d50a405b5a0bba","impliedFormat":1},{"version":"fab29e6d649aa074a6b91e3bdf2bff484934a46067f6ee97a30fcd9762ae2213","impliedFormat":1},{"version":"8145e07aad6da5f23f2fcd8c8e4c5c13fb26ee986a79d03b0829b8fce152d8b2","impliedFormat":1},{"version":"e1120271ebbc9952fdc7b2dd3e145560e52e06956345e6fdf91d70ca4886464f","impliedFormat":1},{"version":"15c5e91b5f08be34a78e3d976179bf5b7a9cc28dc0ef1ffebffeb3c7812a2dca","impliedFormat":1},{"version":"a8f06c2382a30b7cb89ad2dfc48fc3b2b490f3dafcd839dadc008e4e5d57031d","impliedFormat":1},{"version":"553870e516f8c772b89f3820576152ebc70181d7994d96917bb943e37da7f8a7","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"93452d394fdd1dc551ec62f5042366f011a00d342d36d50793b3529bfc9bd633","impliedFormat":1},{"version":"745c4240220559bd340c8aeb6e3c5270a709d3565e934dc22a69c304703956bc","affectsGlobalScope":true,"impliedFormat":1},{"version":"2754d8221d77c7b382096651925eb476f1066b3348da4b73fe71ced7801edada","impliedFormat":1},{"version":"9212c6e9d80cb45441a3614e95afd7235a55a18584c2ed32d6c1aca5a0c53d93","affectsGlobalScope":true,"impliedFormat":1},{"version":"bef91efa0baea5d0e0f0f27b574a8bc100ce62a6d7e70220a0d58af6acab5e89","affectsGlobalScope":true,"impliedFormat":1},{"version":"282fd2a1268a25345b830497b4b7bf5037a5e04f6a9c44c840cb605e19fea841","impliedFormat":1},{"version":"5360a27d3ebca11b224d7d3e38e3e2c63f8290cb1fcf6c3610401898f8e68bc3","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"7d6ff413e198d25639f9f01f16673e7df4e4bd2875a42455afd4ecc02ef156da","affectsGlobalScope":true,"impliedFormat":1},{"version":"6bd91a2a356600dee28eb0438082d0799a18a974a6537c4410a796bab749813c","affectsGlobalScope":true,"impliedFormat":1},{"version":"f689c4237b70ae6be5f0e4180e8833f34ace40529d1acc0676ab8fb8f70457d7","impliedFormat":1},{"version":"ae25afbbf1ed5df63a177d67b9048bf7481067f1b8dc9c39212e59db94fc9fc6","impliedFormat":1},{"version":"ac5ed35e649cdd8143131964336ab9076937fa91802ec760b3ea63b59175c10a","impliedFormat":1},{"version":"52a8e7e8a1454b6d1b5ad428efae3870ffc56f2c02d923467f2940c454aa9aec","affectsGlobalScope":true,"impliedFormat":1},{"version":"78dc0513cc4f1642906b74dda42146bcbd9df7401717d6e89ea6d72d12ecb539","impliedFormat":1},{"version":"171fd8807643c46a9d17e843959abdf10480d57d60d38d061fb44a4c8d4a8cc4","impliedFormat":1},{"version":"f1b4268529636fec8722efb9ed630629eea91aac4a3b93b527d8227bcc3c7cc3","affectsGlobalScope":true,"impliedFormat":1},{"version":"b1e707a76ece9b66e51e39bb66ecaf8241ab212b6d3327bab09e426fa29e40d6","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"9e65fb0fb545f58301f16e5996e49e9b94f65bd9c60465ecbdd3285e9b734e98","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"}],"root":[169,170],"options":{"allowImportingTsExtensions":true,"composite":true,"jsx":4,"module":1,"noFallthroughCasesInSwitch":true,"noUncheckedSideEffectImports":true,"noUnusedLocals":false,"noUnusedParameters":true,"skipLibCheck":true,"strict":true,"target":9,"useDefineForClassFields":true},"referencedMap":[[169,1],[170,2],[114,3],[115,3],[116,4],[117,5],[118,6],[119,7],[69,8],[72,9],[70,8],[71,8],[120,10],[121,11],[122,12],[123,13],[124,14],[125,15],[126,15],[128,16],[127,17],[129,18],[130,19],[131,20],[113,21],[132,22],[133,23],[134,24],[135,25],[136,26],[137,27],[138,28],[139,29],[140,30],[141,31],[142,32],[143,33],[144,34],[145,34],[146,35],[147,8],[148,8],[149,36],[151,37],[150,38],[152,39],[153,40],[154,41],[155,42],[156,43],[157,44],[158,45],[74,46],[73,8],[167,47],[159,48],[160,49],[161,50],[162,51],[163,52],[164,53],[165,54],[166,55],[66,8],[64,8],[67,56],[68,57],[75,8],[65,8],[168,58],[62,8],[63,8],[11,8],[12,8],[14,8],[13,8],[2,8],[15,8],[16,8],[17,8],[18,8],[19,8],[20,8],[21,8],[22,8],[3,8],[23,8],[4,8],[24,8],[28,8],[25,8],[26,8],[27,8],[29,8],[30,8],[31,8],[5,8],[32,8],[33,8],[34,8],[35,8],[6,8],[39,8],[36,8],[37,8],[38,8],[40,8],[7,8],[41,8],[46,8],[47,8],[42,8],[43,8],[44,8],[45,8],[8,8],[51,8],[48,8],[49,8],[50,8],[52,8],[9,8],[53,8],[54,8],[55,8],[58,8],[56,8],[57,8],[59,8],[60,8],[10,8],[61,8],[1,8],[91,59],[101,60],[90,59],[111,61],[82,62],[81,63],[110,64],[104,65],[109,66],[84,67],[98,68],[83,69],[107,70],[79,71],[78,64],[108,72],[80,73],[85,74],[86,8],[89,74],[76,8],[112,75],[102,76],[93,77],[94,78],[96,79],[92,80],[95,81],[105,64],[87,82],[88,83],[97,84],[77,85],[100,76],[99,74],[103,8],[106,86]],"affectedFilesPendingEmit":[[169,17],[170,17]],"emitSignatures":[169,170],"version":"5.6.3"}
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js"
2 | import reactHooks from "eslint-plugin-react-hooks"
3 | import reactRefresh from "eslint-plugin-react-refresh"
4 | import globals from "globals"
5 | import tseslint from "typescript-eslint"
6 |
7 | export default tseslint.config(
8 | {
9 | ignores: ["dist/**", "electron/**", "dist-electron/**"]
10 | },
11 | {
12 | extends: [js.configs.recommended, ...tseslint.configs.recommended],
13 | files: ["**/*.{ts,tsx}"],
14 | languageOptions: {
15 | ecmaVersion: 2020,
16 | globals: globals.browser
17 | },
18 | plugins: {
19 | "react-hooks": reactHooks,
20 | "react-refresh": reactRefresh
21 | },
22 | rules: {
23 | ...reactHooks.configs.recommended.rules,
24 | "react-refresh/only-export-components": [
25 | "warn",
26 | { allowConstantExport: true }
27 | ],
28 | "@typescript-eslint/no-unused-vars": "off"
29 | }
30 | }
31 | )
32 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
10 | MCP Manager for Claude Desktop
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mcp-manager",
3 | "private": true,
4 | "version": "0.1.9",
5 | "main": "./dist-electron/main.js",
6 | "scripts": {
7 | "dev": "npm run check && vite --port 7777",
8 | "build": "npm run check && vite build",
9 | "lint": "eslint .",
10 | "preview": "vite preview",
11 | "check": "tsc --noEmit && biome check --write .",
12 | "electron:dev": "tsc -p electron/tsconfig.json && vite",
13 | "electron:build": "vite build && tsc -p electron/tsconfig.json && electron-builder --mac",
14 | "app:dir": "electron-builder --dir",
15 | "app:dist": "electron-builder"
16 | },
17 | "dependencies": {
18 | "lucide-react": "^0.468.0",
19 | "react": "^18.3.1",
20 | "react-dom": "^18.3.1"
21 | },
22 | "devDependencies": {
23 | "@biomejs/biome": "^1.9.4",
24 | "@eslint/js": "^9.15.0",
25 | "@types/node": "^22.10.1",
26 | "@types/react": "^18.3.12",
27 | "@types/react-dom": "^18.3.1",
28 | "@vitejs/plugin-react": "^4.3.4",
29 | "autoprefixer": "^10.4.20",
30 | "concurrently": "^8.2.2",
31 | "daisyui": "^4.12.14",
32 | "electron": "^29.1.4",
33 | "electron-builder": "^25.1.8",
34 | "eslint": "^9.15.0",
35 | "eslint-plugin-react-hooks": "^5.0.0",
36 | "eslint-plugin-react-refresh": "^0.4.14",
37 | "globals": "^15.12.0",
38 | "postcss": "^8.4.49",
39 | "tailwindcss": "^3.4.16",
40 | "typescript": "~5.6.2",
41 | "typescript-eslint": "^8.15.0",
42 | "vite": "^6.0.1",
43 | "vite-plugin-electron": "^0.28.3",
44 | "vite-plugin-electron-renderer": "^0.14.5",
45 | "wait-on": "^7.2.0"
46 | },
47 | "build": {
48 | "appId": "com.mcp-manager",
49 | "productName": "MCP Manager",
50 | "files": ["dist/**/*", "dist-electron/**/*", "electron/**/*"],
51 | "mac": {
52 | "icon": "public/app.png",
53 | "target": [
54 | {
55 | "target": "dmg",
56 | "arch": ["arm64"]
57 | }
58 | ],
59 | "artifactName": "${productName}-${version}-${arch}.${ext}"
60 | },
61 | "dmg": {
62 | "sign": false
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/public/app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zueai/mcp-manager/1140651e836cabe746f444bdc449cdd2429c4bca/public/app.png
--------------------------------------------------------------------------------
/public/claude-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
15 |
34 |
36 |
50 |
51 |
54 |
59 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/public/logo_zue.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/mcp-favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zueai/mcp-manager/1140651e836cabe746f444bdc449cdd2429c4bca/public/mcp-favicon.ico
--------------------------------------------------------------------------------
/public/mcp-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { ApplyingInstructions } from "@/components/applying-instructions"
2 | import { LoadingInstructions } from "@/components/loading-instructions"
3 | import { MCPServers } from "@/components/mcp-servers"
4 | import { SERVER_CONFIGS } from "@/server-configs"
5 | import {
6 | type MCPConfig,
7 | checkForConfigFile,
8 | validateServerConfig
9 | } from "@/utils"
10 | import type React from "react"
11 | import { useEffect, useState } from "react"
12 |
13 | function App() {
14 | const [jsonContent, setJsonContent] = useState({
15 | mcpServers: {}
16 | })
17 | const [uploadStatus, setUploadStatus] = useState<
18 | "idle" | "success" | "error"
19 | >("idle")
20 | const [isInstructionsOpen, setIsInstructionsOpen] = useState(false)
21 | const [isLoading, setIsLoading] = useState(true)
22 |
23 | // Check for config file on component mount
24 | useEffect(() => {
25 | const loadConfig = async () => {
26 | console.log("Starting loadConfig...")
27 | try {
28 | const config = await checkForConfigFile()
29 | console.log("Config loaded:", config)
30 |
31 | if (config && validateServerConfig(config)) {
32 | console.log("Config is valid, setting state...")
33 | setJsonContent(config)
34 | setUploadStatus("success")
35 | setIsInstructionsOpen(false)
36 | console.log("State updated")
37 | } else {
38 | console.log(
39 | "Config is invalid or missing, showing instructions"
40 | )
41 | setIsInstructionsOpen(true)
42 | }
43 | } catch (err) {
44 | console.error("Error loading config:", err)
45 | setIsInstructionsOpen(true)
46 | } finally {
47 | console.log("Setting loading to false")
48 | setIsLoading(false)
49 | }
50 | }
51 | loadConfig()
52 | }, [])
53 |
54 | useEffect(() => {
55 | console.log("Current state:", {
56 | jsonContent,
57 | uploadStatus,
58 | isInstructionsOpen,
59 | isLoading,
60 | serverCount: Object.keys(jsonContent.mcpServers).length
61 | })
62 | }, [jsonContent, uploadStatus, isInstructionsOpen, isLoading])
63 |
64 | const handleJsonInput = (e: React.ChangeEvent) => {
65 | try {
66 | const content = JSON.parse(e.target.value)
67 | if (validateServerConfig(content)) {
68 | setJsonContent(content)
69 | setUploadStatus("success")
70 | setIsInstructionsOpen(false)
71 | } else {
72 | throw new Error("Invalid server configuration")
73 | }
74 | } catch (error) {
75 | console.error("Error parsing JSON:", error)
76 | setUploadStatus("error")
77 | }
78 | }
79 |
80 | const handleServerAdd = (serverType: keyof typeof SERVER_CONFIGS) => {
81 | console.log("Adding server:", serverType)
82 | const serverConfig = SERVER_CONFIGS[serverType]
83 |
84 | if (!serverConfig.command || !serverConfig.args) {
85 | console.error("Invalid server configuration")
86 | return
87 | }
88 |
89 | const newServer = {
90 | command: serverConfig.command,
91 | args: serverConfig.args,
92 | ...(serverConfig.env && { env: serverConfig.env })
93 | }
94 |
95 | setJsonContent({
96 | ...jsonContent,
97 | mcpServers: {
98 | ...jsonContent.mcpServers,
99 | [serverType]: newServer
100 | }
101 | })
102 | }
103 |
104 | const handleServerRemove = (serverType: string) => {
105 | console.log("Removing server:", serverType)
106 | if (jsonContent.mcpServers[serverType]) {
107 | const { [serverType]: _, ...rest } = jsonContent.mcpServers
108 | setJsonContent({
109 | ...jsonContent,
110 | mcpServers: rest
111 | })
112 | }
113 | }
114 |
115 | if (isLoading) {
116 | return (
117 |
118 | Loading configuration...
119 |
120 | )
121 | }
122 |
123 | // Debug render conditions
124 | console.log("Render conditions:", {
125 | hasServers: Object.keys(jsonContent.mcpServers).length > 0,
126 | uploadStatus,
127 | shouldShowServers:
128 | Object.keys(jsonContent.mcpServers).length > 0 &&
129 | uploadStatus === "success"
130 | })
131 |
132 | return (
133 |
134 |
135 |
136 |
137 |
142 |
143 |
144 |
145 |
150 |
151 |
152 |
153 | MCP Manager for Claude Desktop
154 |
155 |
156 |
157 |
158 | Give Claude access to private data, APIs, and other
159 | services using the Model Context Protocol so it can
160 | answer questions and perform actions on your behalf.{" "}
161 |
162 |
163 | In a nutshell, MCP servers are like plugins that give
164 | Claude (the "client") prompts, resources, and tools to
165 | perform actions on your behalf. Read the{" "}
166 |
172 | MCP docs
173 | {" "}
174 | or check out{" "}
175 |
181 | Anthropic's announcement
182 | {" "}
183 | to learn more.
184 |
185 |
186 | This is a simple GUI to help you install and manage MCP
187 | servers in your Claude App.
188 | This runs client-side on your machine only, so your data
189 | will never leave your computer.
190 |
191 |
192 |
193 |
194 | {isInstructionsOpen && (
195 |
201 | )}
202 |
203 | {Object.keys(jsonContent.mcpServers).length > 0 &&
204 | uploadStatus === "success" && (
205 |
217 | )}
218 |
219 |
230 |
231 |
232 | Made with ❤️ by zue and some incredible{" "}
233 |
239 | contributors
240 |
241 | .
242 |
243 | You can view the source code on{" "}
244 |
250 | GitHub
251 |
252 | .
253 |
254 |
255 |
261 | Contact us
262 | {" "}
263 | for custom AI automation solutions and product
264 | development.
265 |
266 |
267 |
268 |
269 | This project is not affiliated with Anthropic. All logos
270 | are trademarks of their respective owners.
271 |
272 |
273 |
274 |
275 | )
276 | }
277 |
278 | export default App
279 |
--------------------------------------------------------------------------------
/src/assets/tiempos-text-web-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zueai/mcp-manager/1140651e836cabe746f444bdc449cdd2429c4bca/src/assets/tiempos-text-web-regular.woff2
--------------------------------------------------------------------------------
/src/assets/tiempos-text-web-semibold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zueai/mcp-manager/1140651e836cabe746f444bdc449cdd2429c4bca/src/assets/tiempos-text-web-semibold.woff2
--------------------------------------------------------------------------------
/src/components/applying-instructions.tsx:
--------------------------------------------------------------------------------
1 | import { TerminalCommand } from "@/components/terminal-command"
2 | import { SERVER_CONFIGS } from "@/server-configs"
3 | import { Play } from "lucide-react"
4 | import { useState } from "react"
5 |
6 | type RuntimeServerConfig = {
7 | command: string
8 | args: string[]
9 | env?: Record
10 | }
11 |
12 | type ApplyingInstructionsProps = {
13 | jsonContent: {
14 | mcpServers: Record
15 | cloudflare?: unknown
16 | }
17 | }
18 |
19 | export function ApplyingInstructions({
20 | jsonContent
21 | }: ApplyingInstructionsProps) {
22 | const [autoApplyStatus, setAutoApplyStatus] = useState<
23 | "idle" | "running" | "success" | "error"
24 | >("idle")
25 | const [errorMessage, setErrorMessage] = useState("")
26 |
27 | const serversNeedingSetup = Object.keys(jsonContent.mcpServers).filter(
28 | (serverType) =>
29 | SERVER_CONFIGS[serverType as keyof typeof SERVER_CONFIGS]
30 | ?.setupCommands
31 | )
32 |
33 | // Helper function to modify the JSON content with absolute paths
34 | const getJsonWithAbsolutePaths = () => {
35 | const { cloudflare: _, ...modifiedContent } = jsonContent
36 |
37 | for (const [serverType, config] of Object.entries(
38 | modifiedContent.mcpServers
39 | )) {
40 | const serverConfig =
41 | SERVER_CONFIGS[serverType as keyof typeof SERVER_CONFIGS]
42 | if (serverConfig?.setupCommands) {
43 | // Update the args to use the shell variable expansion syntax
44 | config.args = config.args?.map((arg) => {
45 | if (arg.includes("index.js")) {
46 | switch (serverType) {
47 | case "exa":
48 | return "$HOME_DIR/mcp-servers/exa-mcp-server-main/build/index.js"
49 | case "browserbase":
50 | return "$HOME_DIR/mcp-servers/mcp-server-browserbase-main/browserbase/dist/index.js"
51 | default:
52 | return arg
53 | }
54 | }
55 | return arg
56 | })
57 | }
58 | }
59 |
60 | return modifiedContent
61 | }
62 |
63 | const handleAutoApply = async () => {
64 | try {
65 | setAutoApplyStatus("running")
66 | setErrorMessage("")
67 |
68 | // Create a new process to execute the command
69 | const command = `HOME_DIR=$(echo $HOME) && echo '${JSON.stringify(
70 | getJsonWithAbsolutePaths(),
71 | null,
72 | 2
73 | ).replace(
74 | /\$HOME_DIR/g,
75 | "'\"$HOME_DIR\"'"
76 | )}' > "$HOME_DIR/Library/Application Support/Claude/claude_desktop_config.json"`
77 |
78 | // Use the child_process module through window.electron
79 | const result = await window.electron.executeCommand(command)
80 |
81 | if (!result.success) {
82 | setAutoApplyStatus("error")
83 | setErrorMessage(result.output)
84 | } else {
85 | setAutoApplyStatus("success")
86 | }
87 | } catch (error) {
88 | setAutoApplyStatus("error")
89 | setErrorMessage(
90 | error instanceof Error
91 | ? error.message
92 | : "Unknown error occurred"
93 | )
94 | }
95 | }
96 |
97 | return (
98 |
99 |
100 |
101 |
102 | Apply your changes
103 |
104 |
105 |
106 |
107 | Option 1: Auto Apply (Recommended)
108 |
109 |
110 |
111 | Click the button below to automatically apply
112 | your changes. This will update your Claude
113 | configuration file directly.
114 |
115 |
121 |
122 | {autoApplyStatus === "running"
123 | ? "Applying..."
124 | : "Apply Changes"}
125 |
126 | {autoApplyStatus === "success" && (
127 |
128 |
129 | Changes applied successfully! Please
130 | restart Claude.app
131 |
132 |
133 | )}
134 | {autoApplyStatus === "error" && (
135 |
136 |
137 | Error applying changes: {errorMessage}
138 |
139 |
140 | )}
141 |
142 |
143 |
144 |
OR
145 |
146 |
147 |
148 | Option 2: Manual Setup
149 |
150 |
151 |
152 | Step 1: Install Node.js and uv by running these
153 | commands (if not already installed)
154 |
155 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 | Step 2: Save your MCP servers to Claude by
185 | running:
186 |
187 | "$HOME_DIR/Library/Application Support/Claude/claude_desktop_config.json"`}
196 | />
197 |
198 |
199 |
200 |
201 | {serversNeedingSetup.length > 0 && (
202 |
203 |
204 | Step 3: Some servers require additional setup.
205 | Run the following commands:
206 |
207 | {serversNeedingSetup.map((serverType) => (
208 |
209 |
210 | {serverType.charAt(0).toUpperCase() +
211 | serverType.slice(1)}
212 | :
213 |
214 |
221 |
222 | ))}
223 |
224 | )}
225 |
226 |
227 |
228 | Step {serversNeedingSetup.length > 0 ? "4" : "3"}:
229 | Restart Claude.app
230 |
231 |
232 |
233 |
234 |
235 | )
236 | }
237 |
--------------------------------------------------------------------------------
/src/components/loading-instructions.tsx:
--------------------------------------------------------------------------------
1 | import { TerminalCommand } from "@/components/terminal-command"
2 | import { Check, XCircle } from "lucide-react"
3 | import type { ChangeEvent } from "react"
4 |
5 | interface LoadingInstructionsProps {
6 | isOpen: boolean
7 | onOpenChange: (isOpen: boolean) => void
8 | onJsonInput: (e: ChangeEvent) => void
9 | uploadStatus: "idle" | "success" | "error"
10 | }
11 |
12 | export function LoadingInstructions({
13 | isOpen,
14 | onOpenChange,
15 | onJsonInput,
16 | uploadStatus
17 | }: LoadingInstructionsProps) {
18 | const command =
19 | "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)"
20 |
21 | return (
22 |
23 |
24 |
onOpenChange(e.target.checked)}
28 | aria-label="MacOS Instructions"
29 | />
30 |
31 | MacOS Instructions
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | Step 1: Run this terminal command to
40 | copy your current MCP config to the
41 | clipboard.
42 |
43 |
44 | If you've never used MCP before, this
45 | will create a blank config file and copy
46 | its contents.
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | Step 2: Paste your config below.
57 |
58 |
63 | {uploadStatus === "success" && (
64 |
65 |
66 |
67 | Valid MCP Configuration
68 |
69 |
70 | )}
71 | {uploadStatus === "error" && (
72 |
73 |
74 |
75 | Error: Please ensure the content
76 | is valid JSON.
77 |
78 |
79 | )}
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | )
88 | }
89 |
--------------------------------------------------------------------------------
/src/components/mcp-server-card.tsx:
--------------------------------------------------------------------------------
1 | import { EnvConfig } from "@/components/server-configs/env-config"
2 | import { FilesystemConfig } from "@/components/server-configs/filesystem-config"
3 | import { ObsidianConfig } from "@/components/server-configs/obsidian-config"
4 | import { PostgresConfig } from "@/components/server-configs/postgres-config"
5 | import { SentryConfig } from "@/components/server-configs/sentry-config"
6 | import { SQLiteConfig } from "@/components/server-configs/sqlite-config"
7 | import { TerminalCommand } from "@/components/terminal-command"
8 | import { SERVER_CONFIGS } from "@/server-configs"
9 | import { ArrowUpRight, Trash2 } from "lucide-react"
10 |
11 | type MCPServerConfig = {
12 | command: string
13 | args: string[]
14 | env?: Record
15 | }
16 |
17 | type MCPServerCardProps = {
18 | serverName: string
19 | config: MCPServerConfig
20 | icon?: string
21 | onUpdate: (name: string, newConfig: MCPServerConfig) => void
22 | onDelete: (name: string) => void
23 | }
24 |
25 | export function MCPServerCard({
26 | serverName,
27 | config,
28 | icon,
29 | onUpdate,
30 | onDelete
31 | }: MCPServerCardProps) {
32 | const handleFilesystemUpdate = (paths: string[]) => {
33 | const newConfig = {
34 | ...config,
35 | args: [...config.args.slice(0, 2), ...paths]
36 | }
37 | onUpdate(serverName, newConfig)
38 | }
39 |
40 | const handlePostgresUpdate = (url: string) => {
41 | const newConfig = {
42 | ...config,
43 | args: [...config.args.slice(0, 2), url]
44 | }
45 | onUpdate(serverName, newConfig)
46 | }
47 |
48 | const handleEnvUpdate = (key: string, value: string) => {
49 | const newConfig = {
50 | ...config,
51 | env: {
52 | ...(config.env || {}),
53 | [key]: value
54 | }
55 | }
56 | onUpdate(serverName, newConfig)
57 | }
58 |
59 | const handleSqliteUpdate = (dbPath: string) => {
60 | const newConfig = {
61 | ...config,
62 | args: [
63 | "--directory",
64 | "parent_of_servers_repo/servers/src/sqlite",
65 | "run",
66 | "mcp-server-sqlite",
67 | "--db-path",
68 | dbPath
69 | ]
70 | }
71 | onUpdate(serverName, newConfig)
72 | }
73 |
74 | const handleObsidianUpdate = (path: string) => {
75 | const newConfig = {
76 | ...config,
77 | args: [...config.args.slice(0, 2), path]
78 | }
79 | onUpdate(serverName, newConfig)
80 | }
81 |
82 | const handleSentryUpdate = (token: string) => {
83 | const newConfig = {
84 | ...config,
85 | args: [...config.args.slice(0, 2), token]
86 | }
87 | onUpdate(serverName, newConfig)
88 | }
89 |
90 | const handleDelete = (e: React.MouseEvent) => {
91 | e.stopPropagation()
92 | onDelete(serverName)
93 | }
94 |
95 | const serverConfig =
96 | SERVER_CONFIGS[serverName as keyof typeof SERVER_CONFIGS]
97 | const isFilesystemServer = serverName === "filesystem"
98 | const isPostgresServer = serverName === "postgres"
99 | const isSqliteServer = serverName === "sqlite"
100 | const isObsidianServer = serverName === "obsidian"
101 | const isSentryServer = serverName === "sentry"
102 | const iconUrl = icon || serverConfig?.icon
103 |
104 | return (
105 |
106 |
107 |
108 |
109 |
110 |
111 | {iconUrl && (
112 |
{
117 | e.currentTarget.style.display = "none"
118 | }}
119 | />
120 | )}
121 |
{serverName}
122 |
123 |
124 |
125 |
126 | {isFilesystemServer ? (
127 |
131 | ) : isPostgresServer ? (
132 |
136 | ) : isSqliteServer ? (
137 |
141 | ) : isObsidianServer ? (
142 |
146 | ) : isSentryServer ? (
147 |
151 | ) : serverConfig?.env &&
152 | Object.keys(serverConfig.env).length > 0 ? (
153 |
158 | ) : null}
159 |
160 | {serverConfig?.docsUrl ===
161 | "https://github.com/cloudflare/mcp-server-cloudflare" && (
162 |
163 |
164 | MCP Manager can't update this server directly,
165 | please run this terminal command to modify this
166 | server.
167 |
168 |
173 |
174 | )}
175 |
176 |
177 |
178 |
181 | window.open(serverConfig?.docsUrl, "_blank")
182 | }
183 | className="btn btn-sm btn-secondary"
184 | >
185 |
186 | Docs
187 |
188 |
189 |
190 |
195 |
196 | Delete
197 |
198 |
199 |
200 |
201 |
202 | )
203 | }
204 |
--------------------------------------------------------------------------------
/src/components/mcp-servers.tsx:
--------------------------------------------------------------------------------
1 | import { MCPServerCard } from "@/components/mcp-server-card"
2 | import { SERVER_CONFIGS } from "@/server-configs"
3 | import { capitalizeFirstLetter } from "@/utils"
4 | import { Plus } from "lucide-react"
5 | import { useState } from "react"
6 |
7 | type MCPServer = {
8 | command: string
9 | args: string[]
10 | env?: Record
11 | }
12 |
13 | type MCPServers = {
14 | [key: string]: MCPServer
15 | }
16 |
17 | type MCPConfig = {
18 | mcpServers: MCPServers
19 | }
20 |
21 | type MCPServersProps = {
22 | jsonContent: MCPConfig
23 | onUpdate: (newContent: MCPConfig) => void
24 | onServerAdd: (serverType: keyof typeof SERVER_CONFIGS) => void
25 | onServerRemove: (serverType: string) => void
26 | }
27 |
28 | export function MCPServers({
29 | jsonContent,
30 | onUpdate,
31 | onServerAdd,
32 | onServerRemove
33 | }: MCPServersProps) {
34 | const [customJson, setCustomJson] = useState("")
35 | const [customServerName, setCustomServerName] = useState("")
36 | const [jsonError, setJsonError] = useState("")
37 |
38 | const handleServerUpdate = (name: string, newConfig: MCPServer) => {
39 | console.log("Updating server:", name, newConfig)
40 | const updatedContent = {
41 | ...jsonContent,
42 | mcpServers: {
43 | ...jsonContent.mcpServers,
44 | [name]: newConfig
45 | }
46 | }
47 | onUpdate(updatedContent)
48 | }
49 |
50 | const handleServerDelete = (name: string) => {
51 | console.log("Deleting server:", name)
52 | onServerRemove(name)
53 | }
54 |
55 | const handleCustomServerAdd = () => {
56 | if (!customServerName.trim()) {
57 | setJsonError("Please enter a server name")
58 | return
59 | }
60 |
61 | try {
62 | const parsedConfig = JSON.parse(customJson)
63 | if (!parsedConfig.command || !Array.isArray(parsedConfig.args)) {
64 | setJsonError(
65 | "Invalid server configuration. Must include 'command' and 'args' fields"
66 | )
67 | return
68 | }
69 |
70 | const updatedContent = {
71 | ...jsonContent,
72 | mcpServers: {
73 | ...jsonContent.mcpServers,
74 | [customServerName]: parsedConfig
75 | }
76 | }
77 | onUpdate(updatedContent)
78 |
79 | // Reset form and close modal
80 | setCustomJson("")
81 | setCustomServerName("")
82 | setJsonError("")
83 | const modal = document.getElementById(
84 | "add_custom_server_modal"
85 | ) as HTMLDialogElement
86 | if (modal) {
87 | modal.close()
88 | }
89 | } catch (error) {
90 | setJsonError("Invalid JSON format")
91 | }
92 | }
93 |
94 | const hasServers = Object.keys(jsonContent.mcpServers).length > 0
95 |
96 | return (
97 |
98 |
99 |
100 |
Your MCP Servers
101 |
102 |
{
106 | const modal = document.getElementById(
107 | "add_server_modal"
108 | ) as HTMLDialogElement
109 | if (modal) {
110 | modal.showModal()
111 | }
112 | }}
113 | >
114 |
115 | Add Preset
116 |
117 |
{
121 | const modal = document.getElementById(
122 | "add_custom_server_modal"
123 | ) as HTMLDialogElement
124 | if (modal) {
125 | modal.showModal()
126 | }
127 | }}
128 | >
129 |
130 | Add Custom
131 |
132 |
133 |
134 |
135 |
136 | {/* Preset Server Modal */}
137 |
138 |
139 |
140 |
Add Preset Server
141 | {
145 | const modal = document.getElementById(
146 | "add_server_modal"
147 | ) as HTMLDialogElement
148 | if (modal) {
149 | modal.close()
150 | }
151 | }}
152 | >
153 | ✕
154 |
155 |
156 |
157 |
158 | {Object.entries(SERVER_CONFIGS).map(
159 | ([serverType, config]) => (
160 |
{
165 | onServerAdd(
166 | serverType as keyof typeof SERVER_CONFIGS
167 | )
168 | const modal = document.getElementById(
169 | "add_server_modal"
170 | ) as HTMLDialogElement
171 | if (modal) {
172 | modal.close()
173 | }
174 | }}
175 | >
176 |
177 |
182 |
183 |
184 |
185 | {capitalizeFirstLetter(serverType)}
186 |
187 |
188 | {config.description}
189 |
190 |
191 |
192 | )
193 | )}
194 |
195 |
196 |
197 |
198 | {/* Custom Server Modal */}
199 |
203 |
204 |
205 |
Add Custom Server
206 | {
210 | const modal = document.getElementById(
211 | "add_custom_server_modal"
212 | ) as HTMLDialogElement
213 | if (modal) {
214 | modal.close()
215 | }
216 | }}
217 | >
218 | ✕
219 |
220 |
221 |
222 |
223 |
224 |
225 | Server Name
226 |
227 |
233 | setCustomServerName(e.target.value)
234 | }
235 | placeholder="Enter server name"
236 | />
237 |
238 |
239 |
240 |
241 | Server Configuration (JSON)
242 |
243 |
244 |
252 | {jsonError && (
253 |
254 | {jsonError}
255 |
256 | )}
257 |
262 | Add Server
263 |
264 |
265 |
266 |
267 |
268 |
269 | {hasServers ? (
270 | Object.entries(jsonContent.mcpServers).map(
271 | ([name, config]) => {
272 | console.log("Rendering server:", name, config)
273 | const serverConfig =
274 | SERVER_CONFIGS[
275 | name as keyof typeof SERVER_CONFIGS
276 | ]
277 |
278 | return (
279 |
287 | )
288 | }
289 | )
290 | ) : (
291 |
292 | You currently have no MCP servers configured. Add one by
293 | clicking the "Add Preset" or "Add Custom" button.
294 |
295 | )}
296 |
297 |
298 | )
299 | }
300 |
--------------------------------------------------------------------------------
/src/components/server-configs/env-config.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 |
3 | type EnvConfigProps = {
4 | env: Record
5 | initialValues: Record
6 | onUpdate: (key: string, value: string) => void
7 | }
8 |
9 | export function EnvConfig({ env, initialValues, onUpdate }: EnvConfigProps) {
10 | const [envValues, setEnvValues] =
11 | useState>(initialValues)
12 |
13 | const handleEnvChange = (key: string, value: string) => {
14 | setEnvValues((prev) => ({ ...prev, [key]: value }))
15 | onUpdate(key, value)
16 | }
17 |
18 | return (
19 |
20 |
21 | {Object.entries(env).map(([key]) => (
22 |
23 |
24 | {key}
25 |
26 |
33 | handleEnvChange(key, e.target.value)
34 | }
35 | />
36 |
37 | ))}
38 |
39 |
40 | )
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/server-configs/filesystem-config.tsx:
--------------------------------------------------------------------------------
1 | import { Plus, X } from "lucide-react"
2 | import { useState } from "react"
3 |
4 | type FilesystemConfigProps = {
5 | initialPaths: string[]
6 | onUpdate: (paths: string[]) => void
7 | }
8 |
9 | export function FilesystemConfig({
10 | initialPaths,
11 | onUpdate
12 | }: FilesystemConfigProps) {
13 | const [filesystemPaths, setFilesystemPaths] =
14 | useState(initialPaths)
15 |
16 | const handleFilesystemPathChange = (value: string, index: number) => {
17 | const newPaths = [...filesystemPaths]
18 | newPaths[index] = value
19 | setFilesystemPaths(newPaths)
20 | }
21 |
22 | const handleFilesystemPathBlur = () => {
23 | onUpdate(filesystemPaths)
24 | }
25 |
26 | const handleAddPath = () => {
27 | const newPaths = [...filesystemPaths, ""]
28 | setFilesystemPaths(newPaths)
29 | onUpdate(newPaths)
30 | }
31 |
32 | const handleRemovePath = (index: number) => {
33 | const newPaths = filesystemPaths.filter((_, i) => i !== index)
34 | setFilesystemPaths(newPaths)
35 | onUpdate(newPaths)
36 | }
37 |
38 | return (
39 |
40 |
41 | {filesystemPaths.map((path, index) => (
42 |
43 |
44 |
48 |
49 | Allowed Directory Path{" "}
50 | {filesystemPaths.length > 1
51 | ? index + 1
52 | : ""}
53 |
54 |
55 |
62 | handleFilesystemPathChange(
63 | e.target.value,
64 | index
65 | )
66 | }
67 | onBlur={handleFilesystemPathBlur}
68 | />
69 |
70 | {filesystemPaths.length > 1 && (
71 |
handleRemovePath(index)}
75 | >
76 |
77 |
78 | )}
79 |
80 | ))}
81 |
86 |
87 | Add Another Path
88 |
89 |
90 |
91 | )
92 | }
93 |
--------------------------------------------------------------------------------
/src/components/server-configs/obsidian-config.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 |
3 | type ObsidianConfigProps = {
4 | initialPath: string
5 | onUpdate: (path: string) => void
6 | }
7 |
8 | export function ObsidianConfig({ initialPath, onUpdate }: ObsidianConfigProps) {
9 | const [vaultPath, setVaultPath] = useState(initialPath)
10 |
11 | const handleVaultPathChange = (value: string) => {
12 | setVaultPath(value)
13 | }
14 |
15 | const handleVaultPathBlur = () => {
16 | onUpdate(vaultPath)
17 | }
18 |
19 | return (
20 |
21 |
22 |
23 | Obsidian Vault Path
24 |
25 | handleVaultPathChange(e.target.value)}
32 | onBlur={handleVaultPathBlur}
33 | />
34 |
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/server-configs/postgres-config.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 |
3 | type PostgresConfigProps = {
4 | initialUrl: string
5 | onUpdate: (url: string) => void
6 | }
7 |
8 | export function PostgresConfig({ initialUrl, onUpdate }: PostgresConfigProps) {
9 | const [postgresUrl, setPostgresUrl] = useState(initialUrl)
10 |
11 | const handlePostgresUrlChange = (value: string) => {
12 | setPostgresUrl(value)
13 | }
14 |
15 | const handlePostgresUrlBlur = () => {
16 | onUpdate(postgresUrl)
17 | }
18 |
19 | return (
20 |
21 |
22 |
23 |
24 | PostgreSQL Connection URL
25 |
26 |
27 | handlePostgresUrlChange(e.target.value)}
34 | onBlur={handlePostgresUrlBlur}
35 | />
36 |
37 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/server-configs/sentry-config.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 |
3 | type SentryConfigProps = {
4 | initialToken: string
5 | onUpdate: (token: string) => void
6 | }
7 |
8 | export function SentryConfig({ initialToken, onUpdate }: SentryConfigProps) {
9 | const [authToken, setAuthToken] = useState(initialToken)
10 |
11 | const handleAuthTokenChange = (value: string) => {
12 | setAuthToken(value)
13 | }
14 |
15 | const handleAuthTokenBlur = () => {
16 | onUpdate(authToken)
17 | }
18 |
19 | return (
20 |
21 |
22 |
23 |
24 | Sentry Authentication Token
25 |
26 |
27 | handleAuthTokenChange(e.target.value)}
34 | onBlur={handleAuthTokenBlur}
35 | />
36 |
37 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/server-configs/sqlite-config.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 |
3 | type SQLiteConfigProps = {
4 | initialPath: string
5 | onUpdate: (dbPath: string) => void
6 | }
7 |
8 | export function SQLiteConfig({ initialPath, onUpdate }: SQLiteConfigProps) {
9 | const [dbPath, setDbPath] = useState(initialPath)
10 |
11 | const handleDbPathChange = (value: string) => {
12 | setDbPath(value)
13 | }
14 |
15 | const handleDbPathBlur = () => {
16 | onUpdate(dbPath)
17 | }
18 |
19 | return (
20 |
21 |
22 |
23 |
24 | SQLite Database Path
25 |
26 |
27 | handleDbPathChange(e.target.value)}
34 | onBlur={handleDbPathBlur}
35 | />
36 |
37 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/terminal-command.tsx:
--------------------------------------------------------------------------------
1 | import { Check, Copy } from "lucide-react"
2 | import { useState } from "react"
3 |
4 | interface TerminalCommandProps {
5 | command: string
6 | className?: string
7 | }
8 |
9 | export function TerminalCommand({
10 | command,
11 | className = ""
12 | }: TerminalCommandProps) {
13 | const [hasCopied, setHasCopied] = useState(false)
14 |
15 | const handleCopy = () => {
16 | navigator.clipboard.writeText(command)
17 | setHasCopied(true)
18 | setTimeout(() => setHasCopied(false), 2000)
19 | }
20 |
21 | return (
22 |
23 |
{command}
24 |
29 | {hasCopied ? (
30 |
31 | ) : (
32 |
33 | )}
34 |
35 | {hasCopied ? "Copied!" : "Copy Command"}
36 |
37 |
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @font-face {
6 | font-family: "Tiempos Text";
7 | src: url("./assets/tiempos-text-web-semibold.woff2") format("woff2");
8 | font-weight: 600;
9 | font-display: swap;
10 | }
11 |
12 | @font-face {
13 | font-family: "Tiempos Text";
14 | src: url("./assets/tiempos-text-web-regular.woff2") format("woff2");
15 | font-weight: 400;
16 | font-display: swap;
17 | }
18 |
19 | @layer base {
20 | h1 {
21 | @apply font-tiempos-semibold;
22 | }
23 | h2 {
24 | @apply font-tiempos-semibold;
25 | }
26 | h3 {
27 | @apply font-tiempos-regular;
28 | }
29 | p {
30 | @apply font-tiempos-regular;
31 | }
32 | span {
33 | @apply font-tiempos-regular;
34 | font-weight: 400;
35 | }
36 | }
37 |
38 | .collapse {
39 | border-radius: 1.5rem;
40 | box-shadow: 0 10px 10px 0 rgba(0, 0, 0, 0.05);
41 | }
42 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react"
2 | import { createRoot } from "react-dom/client"
3 | import "@/index.css"
4 | import App from "@/App.tsx"
5 |
6 | const rootElement = document.getElementById("root")
7 | if (!rootElement) throw new Error("Root element not found")
8 |
9 | createRoot(rootElement).render(
10 |
11 |
12 |
13 | )
14 |
--------------------------------------------------------------------------------
/src/server-configs.ts:
--------------------------------------------------------------------------------
1 | export type ServerConfig = {
2 | command?: string
3 | args?: string[]
4 | env?: Record
5 | icon: string
6 | description: string
7 | docsUrl: string
8 | setupCommands?: {
9 | installPath: string
10 | command: string
11 | }
12 | }
13 |
14 | export const SERVER_CONFIGS: Record = {
15 | "brave-search": {
16 | icon: "https://www.svgrepo.com/show/305818/brave.svg",
17 | description: "Search the web with Brave Search API",
18 |
19 | docsUrl:
20 | "https://github.com/modelcontextprotocol/servers/tree/main/src/brave-search/README.md",
21 | command: "npx",
22 | args: ["-y", "@modelcontextprotocol/server-brave-search"],
23 | env: { BRAVE_API_KEY: "" }
24 | },
25 | filesystem: {
26 | icon: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWZvbGRlci1jbG9zZWQiPjxwYXRoIGQ9Ik0yMCAyMGEyIDIgMCAwIDAgMi0yVjhhMiAyIDAgMCAwLTItMmgtNy45YTIgMiAwIDAgMS0xLjY5LS45TDkuNiAzLjlBMiAyIDAgMCAwIDcuOTMgM0g0YTIgMiAwIDAgMC0yIDJ2MTNhMiAyIDAgMCAwIDIgMloiLz48cGF0aCBkPSJNMiAxMGgyMCIvPjwvc3ZnPg==",
27 | description:
28 | "Access and manage local filesystem with specified allowed directories",
29 | command: "npx",
30 | args: ["-y", "@modelcontextprotocol/server-filesystem", "/Users/"],
31 |
32 | docsUrl:
33 | "https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem/README.md"
34 | },
35 | memory: {
36 | icon: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWJyYWluIj48cGF0aCBkPSJNMTIgNWEzIDMgMCAxIDAtNS45OTcuMTI1IDQgNCAwIDAgMC0yLjUyNiA1Ljc3IDQgNCAwIDAgMCAuNTU2IDYuNTg4QTQgNCAwIDEgMCAxMiAxOFoiLz48cGF0aCBkPSJNMTIgNWEzIDMgMCAxIDEgNS45OTcuMTI1IDQgNCAwIDAgMSAyLjUyNiA1Ljc3IDQgNCAwIDAgMS0uNTU2IDYuNTg4QTQgNCAwIDEgMSAxMiAxOFoiLz48cGF0aCBkPSJNMTUgMTNhNC41IDQuNSAwIDAgMS0zLTQgNC41IDQuNSAwIDAgMS0zIDQiLz48cGF0aCBkPSJNMTcuNTk5IDYuNWEzIDMgMCAwIDAgLjM5OS0xLjM3NSIvPjxwYXRoIGQ9Ik02LjAwMyA1LjEyNUEzIDMgMCAwIDAgNi40MDEgNi41Ii8+PHBhdGggZD0iTTMuNDc3IDEwLjg5NmE0IDQgMCAwIDEgLjU4NS0uMzk2Ii8+PHBhdGggZD0iTTE5LjkzOCAxMC41YTQgNCAwIDAgMSAuNTg1LjM5NiIvPjxwYXRoIGQ9Ik02IDE4YTQgNCAwIDAgMS0xLjk2Ny0uNTE2Ii8+PHBhdGggZD0iTTE5Ljk2NyAxNy40ODRBNCA0IDAgMCAxIDE4IDE4Ii8+PC9zdmc+",
37 | description: "Give Claude memory of previous conversations",
38 | command: "npx",
39 | args: ["-y", "@modelcontextprotocol/server-memory"],
40 |
41 | docsUrl:
42 | "https://github.com/modelcontextprotocol/servers/tree/main/src/memory/README.md"
43 | },
44 | "sequential-thinking": {
45 | icon: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXNwYXJrbGUiPjxwYXRoIGQ9Ik05LjkzNyAxNS41QTIgMiAwIDAgMCA4LjUgMTQuMDYzbC02LjEzNS0xLjU4MmEuNS41IDAgMCAxIDAtLjk2Mkw4LjUgOS45MzZBMiAyIDAgMCAwIDkuOTM3IDguNWwxLjU4Mi02LjEzNWEuNS41IDAgMCAxIC45NjMgMEwxNC4wNjMgOC41QTIgMiAwIDAgMCAxNS41IDkuOTM3bDYuMTM1IDEuNTgxYS41LjUgMCAwIDEgMCAuOTY0TDE1LjUgMTQuMDYzYTIgMiAwIDAgMC0xLjQzNyAxLjQzN2wtMS41ODIgNi4xMzVhLjUuNSAwIDAgMS0uOTYzIDB6Ii8+PC9zdmc+",
46 | description:
47 | "Enable step-by-step reasoning and sequential problem-solving",
48 | command: "npx",
49 | args: ["-y", "@modelcontextprotocol/server-sequential-thinking"],
50 |
51 | docsUrl:
52 | "https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking/README.md"
53 | },
54 | slack: {
55 | icon: "https://icon.icepanel.io/Technology/svg/Slack.svg",
56 | description: "Let Claude access your Slack workspace",
57 | command: "npx",
58 | args: ["-y", "@modelcontextprotocol/server-slack"],
59 | env: {
60 | SLACK_BOT_TOKEN: "",
61 | SLACK_TEAM_ID: ""
62 | },
63 |
64 | docsUrl:
65 | "https://github.com/modelcontextprotocol/servers/tree/main/src/slack/README.md"
66 | },
67 | "google-drive": {
68 | icon: "https://upload.wikimedia.org/wikipedia/commons/1/12/Google_Drive_icon_%282020%29.svg",
69 | description: "Access and search files in your Google Drive",
70 | command: "npx",
71 | args: ["-y", "@modelcontextprotocol/server-gdrive"],
72 |
73 | docsUrl:
74 | "https://github.com/modelcontextprotocol/servers/tree/main/src/gdrive/README.md"
75 | },
76 | // time: {
77 | // icon: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWNsb2NrIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCIvPjxwb2x5bGluZSBwb2ludHM9IjEyIDYgMTIgMTIgMTYgMTQiLz48L3N2Zz4=",
78 | // description: "Current time / time zone conversion utilities",
79 | // command: "uvx",
80 | // args: ["mcp-server-time"],
81 | // docsUrl:
82 | // "https://github.com/modelcontextprotocol/servers/tree/main/src/time/README.md"
83 | // },
84 | "google-maps": {
85 | icon: "https://upload.wikimedia.org/wikipedia/commons/b/bd/Google_Maps_Logo_2020.svg",
86 | description: "Access Google Maps API for location and mapping services",
87 | command: "npx",
88 | args: ["-y", "@modelcontextprotocol/server-google-maps"],
89 | env: { GOOGLE_MAPS_API_KEY: "" },
90 |
91 | docsUrl:
92 | "https://github.com/modelcontextprotocol/servers/tree/main/src/google-maps/README.md"
93 | },
94 | "youtube-transcript": {
95 | icon: "https://www.svgrepo.com/show/13671/youtube.svg",
96 | description: "Access and search YouTube transcripts",
97 | command: "npx",
98 | args: ["-y", "@kimtaeyoon83/mcp-server-youtube-transcript"],
99 |
100 | docsUrl: "https://github.com/kimtaeyoon83/mcp-server-youtube-transcript"
101 | },
102 | perplexity: {
103 | icon: "https://seeklogo.com/images/P/perplexity-ai-logo-13120A0AAE-seeklogo.com.png",
104 | description: "Search the web with Perplexity API",
105 | command: "uvx",
106 | args: ["mcp-server-perplexity"],
107 | env: {
108 | PERPLEXITY_API_KEY: "your-perplexity-api-key"
109 | },
110 |
111 | docsUrl: "https://github.com/tanigami/mcp-server-perplexity"
112 | },
113 | // fetch: {
114 | // icon: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWdsb2JlIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCIvPjxwYXRoIGQ9Ik0xMiAyYTE0LjUgMTQuNSAwIDAgMCAwIDIwIDE0LjUgMTQuNSAwIDAgMCAwLTIwIi8+PHBhdGggZD0iTTIgMTJoMjAiLz48L3N2Zz4=",
115 | // description: "Let Claude fetch and read a website",
116 | // command: "uvx",
117 | // args: ["mcp-server-fetch"],
118 |
119 | // docsUrl:
120 | // "https://github.com/modelcontextprotocol/servers/tree/main/src/fetch/README.md"
121 | // },
122 | "apple-notes": {
123 | icon: "https://upload.wikimedia.org/wikipedia/commons/f/fa/Apple_Notes_icon.svg",
124 | description: "Access and search your Apple Notes",
125 | command: "uvx",
126 | args: ["apple-notes-mcp"],
127 |
128 | docsUrl: "https://github.com/sirmews/apple-notes-mcp"
129 | },
130 | exa: {
131 | icon: "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",
132 | description: "Search the web with Exa",
133 | command: "npx",
134 | args: ["exa-mcp-server/build/index.js"],
135 | env: {
136 | EXA_API_KEY: "your-api-key-here"
137 | },
138 | setupCommands: {
139 | installPath: "~/mcp-servers",
140 | command:
141 | "mkdir -p $(echo $HOME)/mcp-servers && cd $(echo $HOME)/mcp-servers && \
142 | curl -L https://github.com/exa-labs/exa-mcp-server/archive/refs/heads/main.zip -o exa-mcp-server.zip && \
143 | unzip -o exa-mcp-server.zip && \
144 | rm exa-mcp-server.zip && \
145 | cd exa-mcp-server-main && \
146 | npm install --save axios dotenv && \
147 | npm run build && \
148 | sudo npm link"
149 | },
150 |
151 | docsUrl: "https://github.com/exa-labs/exa-mcp-server"
152 | },
153 | browserbase: {
154 | icon: "https://opensourcepledge.com/images/members/browserbase/logo.webp",
155 | description: "Let Claude explore the web with Browserbase",
156 | command: "node",
157 | args: ["mcp-server-browserbase-main/browserbase/dist/index.js"],
158 | env: {
159 | BROWSERBASE_API_KEY: "your-api-key-here",
160 | BROWSERBASE_PROJECT_ID: "your-project-id-here"
161 | },
162 | setupCommands: {
163 | installPath: "~/mcp-servers",
164 | command:
165 | "mkdir -p $(echo $HOME)/mcp-servers && cd $(echo $HOME)/mcp-servers && \
166 | curl -L https://github.com/browserbase/mcp-server-browserbase/archive/refs/heads/main.zip -o browserbase-mcp-server.zip && \
167 | unzip -o browserbase-mcp-server.zip && \
168 | rm browserbase-mcp-server.zip && \
169 | cd mcp-server-browserbase-main/browserbase && \
170 | npm install && \
171 | npm run build"
172 | },
173 |
174 | docsUrl:
175 | "https://github.com/browserbase/mcp-server-browserbase/tree/main/browserbase"
176 | },
177 | obsidian: {
178 | icon: "https://upload.wikimedia.org/wikipedia/commons/1/10/2023_Obsidian_logo.svg",
179 | description: "Read and search files in your Obsidian vault",
180 | command: "npx",
181 | args: ["-y", "mcp-obsidian", ""],
182 |
183 | docsUrl: "https://github.com/calclavia/mcp-obsidian"
184 | },
185 | todoist: {
186 | icon: "https://www.svgrepo.com/show/354452/todoist-icon.svg",
187 | description: "Access and search your Todoist tasks",
188 | command: "npx",
189 | args: ["-y", "@abhiz123/todoist-mcp-server"],
190 | env: {
191 | TODOIST_API_TOKEN: "your_api_token_here"
192 | },
193 |
194 | docsUrl: "https://github.com/abhiz123/todoist-mcp-server"
195 | },
196 | cloudflare: {
197 | icon: "https://icon.icepanel.io/Technology/svg/Cloudflare.svg",
198 | description: "Manage your Cloudflare workers and account resources",
199 | docsUrl: "https://github.com/cloudflare/mcp-server-cloudflare",
200 | setupCommands: {
201 | installPath: "~/mcp-servers",
202 | command: "npx @cloudflare/mcp-server-cloudflare init"
203 | }
204 | },
205 | "aws-kb-retrieval": {
206 | icon: "https://icon.icepanel.io/Technology/svg/AWS.svg",
207 | description:
208 | "Access and query AWS Knowledge Base for information retrieval",
209 | command: "npx",
210 | args: ["-y", "@modelcontextprotocol/server-aws-kb-retrieval"],
211 | env: {
212 | AWS_ACCESS_KEY_ID: "",
213 | AWS_SECRET_ACCESS_KEY: "",
214 | AWS_REGION: ""
215 | },
216 |
217 | docsUrl:
218 | "https://github.com/modelcontextprotocol/servers/tree/main/src/aws-kb-retrieval/README.md"
219 | },
220 | everart: {
221 | icon: "https://pbs.twimg.com/profile_images/1717719314369789952/AmXarABn_400x400.png",
222 | description:
223 | "Interface with Everart API for digital art and design tools",
224 | command: "npx",
225 | args: ["-y", "@modelcontextprotocol/server-everart"],
226 | env: { EVERART_API_KEY: "" },
227 |
228 | docsUrl:
229 | "https://github.com/modelcontextprotocol/servers/tree/main/src/everart/README.md"
230 | },
231 | github: {
232 | icon: "https://icon.icepanel.io/Technology/svg/GitHub.svg",
233 | description: "Let Claude access your GitHub repositories",
234 | command: "npx",
235 | args: ["-y", "@modelcontextprotocol/server-github"],
236 | env: { GITHUB_PERSONAL_ACCESS_TOKEN: "" },
237 |
238 | docsUrl:
239 | "https://github.com/modelcontextprotocol/servers/blob/main/src/github/README.md"
240 | },
241 | gitlab: {
242 | icon: "https://icon.icepanel.io/Technology/svg/GitLab.svg",
243 | description: "Manage GitLab repositories and resources",
244 | command: "npx",
245 | args: ["-y", "@modelcontextprotocol/server-gitlab"],
246 | env: {
247 | GITLAB_PERSONAL_ACCESS_TOKEN: "",
248 | GITLAB_API_URL: "https://gitlab.com/api/v4"
249 | },
250 |
251 | docsUrl:
252 | "https://github.com/modelcontextprotocol/servers/blob/main/src/gitlab/README.md"
253 | },
254 | postgres: {
255 | icon: "https://www.svgrepo.com/show/303301/postgresql-logo.svg",
256 | description: "Connect and interact with PostgreSQL databases",
257 | command: "npx",
258 | args: [
259 | "-y",
260 | "@modelcontextprotocol/server-postgres",
261 | "postgresql://localhost/mydb"
262 | ],
263 |
264 | docsUrl:
265 | "https://github.com/modelcontextprotocol/servers/tree/main/src/postgres/README.md"
266 | },
267 | puppeteer: {
268 | icon: "https://www.svgrepo.com/show/354228/puppeteer.svg",
269 | description: "Automate browser interactions with Puppeteer",
270 | command: "npx",
271 | args: ["-y", "@modelcontextprotocol/server-puppeteer"],
272 |
273 | docsUrl:
274 | "https://github.com/modelcontextprotocol/servers/tree/main/src/puppeteer/README.md"
275 | },
276 | sqlite: {
277 | icon: "https://icon.icepanel.io/Technology/svg/SQLite.svg",
278 | description: "Manage SQLite databases in local file storage",
279 | command: "uv",
280 | args: [
281 | "--directory",
282 | "parent_of_servers_repo/servers/src/sqlite",
283 | "run",
284 | "mcp-server-sqlite",
285 | "--db-path",
286 | "~/test.db"
287 | ],
288 |
289 | docsUrl:
290 | "https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite/README.md"
291 | }
292 | // sentry: {
293 | // icon: "https://www.svgrepo.com/show/306716/sentry.svg",
294 | // description: "Retrieve and analyze issues from Sentry for debugging",
295 | // command: "uvx",
296 | // args: ["mcp-server-sentry", "--auth-token", ""],
297 |
298 | // docsUrl:
299 | // "https://github.com/modelcontextprotocol/servers/tree/main/src/sentry"
300 | // }
301 | }
302 |
--------------------------------------------------------------------------------
/src/types/electron.d.ts:
--------------------------------------------------------------------------------
1 | interface ConfigData {
2 | mcpServers: {
3 | [key: string]: {
4 | command: string
5 | args: string[]
6 | env?: Record
7 | }
8 | }
9 | }
10 |
11 | export interface IElectronAPI {
12 | executeCommand: (
13 | command: string
14 | ) => Promise<{ success: boolean; output: string }>
15 | readConfig: () => Promise
16 | }
17 |
18 | declare global {
19 | interface Window {
20 | electron: IElectronAPI
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | export type MCPConfig = {
2 | mcpServers: Record<
3 | string,
4 | { command: string; args: string[]; env?: Record }
5 | >
6 | cloudflare?: unknown
7 | }
8 |
9 | export function capitalizeFirstLetter(str: string): string {
10 | return str.charAt(0).toUpperCase() + str.slice(1)
11 | }
12 |
13 | export function isValidJsonString(str: string): boolean {
14 | try {
15 | JSON.parse(str)
16 | return true
17 | } catch (e) {
18 | return false
19 | }
20 | }
21 |
22 | export const isRunningInElectron = () => {
23 | if (
24 | typeof window !== "undefined" &&
25 | typeof window.process === "object" &&
26 | (window.process as NodeJS.Process)?.type === "renderer"
27 | ) {
28 | return true
29 | }
30 | return false
31 | }
32 |
33 | export function formatCommand(command: string) {
34 | return command
35 | .replace(/\s+/g, " ")
36 | .replace(/^\s+|\s+$/g, "")
37 | .replace(/\\n/g, "\n")
38 | }
39 |
40 | export async function readDefaultConfig() {
41 | console.log("Reading default config...")
42 | console.log("Window electron API:", window.electron)
43 |
44 | try {
45 | if (!window.electron?.readConfig) {
46 | console.error("No electron API available")
47 | return null
48 | }
49 |
50 | const config = await window.electron.readConfig()
51 | console.log("Got config from electron:", config)
52 | console.log("Config structure:", JSON.stringify(config, null, 2))
53 |
54 | if (config && typeof config === "object" && "mcpServers" in config) {
55 | console.log("Config is valid, mcpServers:", config.mcpServers)
56 | return config
57 | }
58 |
59 | console.log("Invalid config format, received:", typeof config)
60 | return null
61 | } catch (error) {
62 | console.error("Error reading config file:", error)
63 | return null
64 | }
65 | }
66 |
67 | export async function checkForConfigFile(): Promise {
68 | console.log("Checking for config file...")
69 | const config = await readDefaultConfig()
70 | if (!config) {
71 | console.log("No config found")
72 | return null
73 | }
74 | console.log("Config validation result:", validateServerConfig(config))
75 | console.log("Returning config:", config)
76 | return config as MCPConfig
77 | }
78 |
79 | export function validateServerConfig(config: unknown): config is MCPConfig {
80 | console.log("Validating config:", config)
81 | if (!config || typeof config !== "object") {
82 | console.log("Config is not an object")
83 | return false
84 | }
85 |
86 | const { mcpServers } = config as MCPConfig
87 |
88 | if (!mcpServers || typeof mcpServers !== "object") {
89 | console.log("mcpServers is missing or not an object")
90 | return false
91 | }
92 |
93 | console.log("Validating servers:", Object.keys(mcpServers))
94 |
95 | for (const [serverName, server] of Object.entries(mcpServers)) {
96 | console.log(`Validating server ${serverName}:`, server)
97 | if (!server || typeof server !== "object") {
98 | console.log(`Server ${serverName} is not an object`)
99 | return false
100 | }
101 |
102 | if (
103 | typeof server.command !== "string" ||
104 | !Array.isArray(server.args) ||
105 | !server.args.every((arg) => typeof arg === "string")
106 | ) {
107 | console.log(`Server ${serverName} has invalid command or args`)
108 | return false
109 | }
110 |
111 | if (
112 | server.env !== undefined &&
113 | (typeof server.env !== "object" ||
114 | !Object.values(server.env).every(
115 | (value) => typeof value === "string"
116 | ))
117 | ) {
118 | console.log(`Server ${serverName} has invalid env`)
119 | return false
120 | }
121 | }
122 |
123 | console.log("Config validation passed!")
124 | return true
125 | }
126 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import daisyui from "daisyui"
2 | import { light } from "daisyui/src/theming/themes"
3 | import type { Config } from "tailwindcss"
4 |
5 | export default {
6 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
7 | theme: {
8 | extend: {
9 | fontFamily: {
10 | "tiempos-semibold": ["Tiempos Text", "serif"],
11 | "tiempos-regular": ["Tiempos Text", "serif"]
12 | }
13 | }
14 | },
15 | plugins: [daisyui],
16 | daisyui: {
17 | themes: [
18 | {
19 | light: {
20 | ...light,
21 | primary: "#da7756",
22 | "primary-content": "#ffffff",
23 | secondary: "#f2f1e9",
24 | "secondary-content": "#000000"
25 | }
26 | }
27 | ]
28 | }
29 | } satisfies Config
30 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2023", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "moduleDetection": "force",
15 | "noEmit": true,
16 | "jsx": "react-jsx",
17 |
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": false,
21 | "noUnusedParameters": true,
22 | "noFallthroughCasesInSwitch": true,
23 | "noUncheckedSideEffectImports": true,
24 |
25 | /* Path Aliases */
26 | "baseUrl": ".",
27 | "paths": {
28 | "@/*": ["src/*"]
29 | }
30 | },
31 | "include": ["src", "electron", "vite.config.ts"]
32 | }
33 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path"
2 | import react from "@vitejs/plugin-react"
3 | import { defineConfig } from "vite"
4 | import electron from "vite-plugin-electron"
5 | import renderer from "vite-plugin-electron-renderer"
6 |
7 | export default defineConfig({
8 | plugins: [
9 | react(),
10 | electron([
11 | {
12 | entry: "electron/main.ts",
13 | vite: {
14 | build: {
15 | outDir: "dist-electron",
16 | lib: {
17 | entry: "electron/main.ts",
18 | formats: ["cjs"]
19 | },
20 | rollupOptions: {
21 | external: ["electron"]
22 | }
23 | }
24 | }
25 | },
26 | {
27 | entry: "electron/preload.ts",
28 | onstart(options) {
29 | options.reload()
30 | },
31 | vite: {
32 | build: {
33 | outDir: "dist-electron",
34 | lib: {
35 | entry: "electron/preload.ts",
36 | formats: ["cjs"]
37 | },
38 | rollupOptions: {
39 | external: ["electron"]
40 | }
41 | }
42 | }
43 | }
44 | ]),
45 | renderer()
46 | ],
47 | resolve: {
48 | alias: {
49 | "@": path.resolve(__dirname, "./src")
50 | }
51 | },
52 | publicDir: "public",
53 | base: process.env.ELECTRON_RENDERER_URL ? "/" : "./",
54 | build: {
55 | assetsDir: ".",
56 | rollupOptions: {
57 | output: {
58 | assetFileNames: (assetInfo) => {
59 | const name = assetInfo.name || ""
60 | return name.endsWith(".svg") ? name : `assets/${name}`
61 | }
62 | }
63 | }
64 | },
65 | server: {
66 | port: 7777
67 | }
68 | })
69 |
--------------------------------------------------------------------------------