This is the front-end for ToolHive, an Electron application built with React, TypeScript, and Vite.
This project uses pnpm as the package manager.
It is recommended to use a Node.js version manager like nvm or fnm. The
required Node.js version is specified in the .nvmrc file (which points to the
latest LTS version).
[!IMPORTANT] Make sure the Docker daemon is running before you start, as it is required by ToolHive.
To get started, follow these steps:
-
Install dependencies:
pnpm install
-
Make sure thv is downloaded and runs properly:
pnpm thv
This command needs to be run once to build the application before starting the development server.
-
Start the development server:
pnpm run start
This will start the Electron application with hot reload for the renderer process.
Here are the most common scripts you will use during development:
pnpm run start: Starts the development server with hot reload.pnpm run lint: Lints the code using ESLint.pnpm run format: Formats the code with Prettier.pnpm run type-check: Runs TypeScript type checking.pnpm run test: Runs tests using Vitest.pnpm run test:coverage: Runs tests with coverage.pnpm run thv: Run the samethvbinary that the dev server uses
E2E tests use Playwright to test the packaged Electron application.
pnpm run e2e: Packages the app and runs e2e tests. Use this for a full test run from scratch.pnpm run e2e:prebuilt: Runs e2e tests against an already packaged app inout/. Use this when iterating on tests without rebuilding.- Detailed E2E guide:
docs/e2e-testing.md
See the deep link design document for background and rationale.
The app registers the toolhive-gui:// protocol for deep linking. Deep links
follow the format:
toolhive-gui://<version>/<intent>?<params>
Example deep links:
toolhive-gui://v1/open-registry-server-detail?serverName=fetch
toolhive-gui://v1/open-registry-server-detail?serverName=time
Register the protocol handler (builds a .deb, extracts and installs the
.desktop file system-wide):
pnpm run deeplink:registerTestLinuxProtocolHandlerOnce registered, you can open deep links from the browser or with xdg-open:
xdg-open "toolhive-gui://v1/open-registry-server-detail?serverName=fetch"You can check whether the protocol handler is registered with:
xdg-mime query default x-scheme-handler/toolhive-guipnpm run package: Packages the application for the current platform.pnpm run make: Creates distributable packages for the application.
pnpm run generate-client: Fetches the latest OpenAPI specification and generates the API client.pnpm run generate-client:nofetch: Generates the API client from the existing local OpenAPI specification.
The project is structured as a typical Electron application:
main/: Contains the code for the Electron main process.preload/: Contains the preload scripts for the Electron renderer process.renderer/: Contains the React application for the renderer process. This is where the UI components live.
Important
Electron applications can be decompiled, so do not store sensitive information in runtime environment variables. Use secure methods to handle sensitive data.
The project uses environment variables for configuration.
You can set these in a .env file in the root directory when developing
locally. The .env.example file provides a template for the required variables.
For building and deploying the application, these should be configured in Github actions secrets/variables (as appropriate).
To expose environment variables at run time, you need to prefix them with
VITE_. This will make them available on import.meta.env (not
process.env))
For example, if you want to expose a variable named API_URL, you should define
it as VITE_API_URL in the .env file (locally) or in the CI environment.
| Variable | Required | Build-time | Run-time | Description |
|---|---|---|---|---|
VITE_SENTRY_DSN |
false |
true |
true |
Sentry DSN. The URL that events are posted to. |
VITE_ENABLE_AUTO_DEVTOOLS |
false |
false |
true |
Enable automatic opening of DevTools in development mode. Set to true to enable. |
SENTRY_AUTH_TOKEN |
false |
true |
false |
Sentry authentication token. Used for sourcemap uploads at build-time to enable readable stacktraces. |
SENTRY_ORG |
false |
true |
false |
Sentry organization. Used for sourcemap uploads at build-time to enable readable stacktraces. |
SENTRY_PROJECT |
false |
true |
false |
Sentry project name. Used for sourcemap uploads at build-time to enable readable stacktraces. |
The app renders different window chrome depending on the platform — macOS uses the system traffic-light buttons and extra left padding, while Windows/Linux renders custom min/max/close controls.
To test either layout without switching machines, use the OsDesign helper
exposed on window in the renderer DevTools console:
OsDesign.setMac() // macOS layout (traffic-light padding, no custom controls)
OsDesign.setWindows() // Windows/Linux layout (custom min/max/close buttons)
OsDesign.reset() // restore the real platform detection
OsDesign.current() // log the currently active variantEach set* call writes to sessionStorage and reloads the page. The override
is scoped to the current session and does not affect any behavioural logic (file
paths, networking flags, etc.) — only the visual layout.
Tip: Open DevTools with
Ctrl+Shift+I(orCmd+Option+Ion macOS), then run the commands in the Console tab.
During development, you can test the UI with a custom thv binary by running it
manually:
-
Start your custom
thvbinary with the serve command:thv serve \ --openapi \ --host=127.0.0.1 --port=50000 \ --experimental-mcp \ --experimental-mcp-host=127.0.0.1 \ --experimental-mcp-port=50001
-
Set the
THV_PORTandTHV_MCP_PORTenvironment variables and start the dev server.THV_PORT=50000 THV_MCP_PORT=50001 pnpm start
The UI displays a banner with the HTTP address when using a custom port. This works in development mode only; packaged builds use the embedded binary.
Note on MCP Optimizer If you plan to use the MCP Optimizer with an external
thv, ensureTHV_PORTis within the range50000-50100. The app starts its embedded server in this range, and the optimizer expects the ToolHive API to be reachable there.
Supports both macOS and Windows code signing. macOS uses Apple certificates, Windows uses DigiCert KeyLocker for EV certificates.
Optional: Set MAC_DEVELOPER_IDENTITY in .env to use a specific certificate:
MAC_DEVELOPER_IDENTITY="Developer ID Application: Your Name (TEAM123)"
Local signing is not required for development.
Requires these GitHub secrets:
APPLE_CERTIFICATE- Base64 encoded .p12 certificateAPPLE_CERTIFICATE_PASSWORD- Certificate passwordKEYCHAIN_PASSWORD- Temporary keychain passwordAPPLE_API_KEY- Base64 encoded .p8 API keyAPPLE_ISSUER_ID- Apple API Issuer IDAPPLE_KEY_ID- Apple API Key ID
Requires these GitHub secrets for EV certificate signing:
SM_HOST- DigiCert KeyLocker host URLSM_API_KEY- DigiCert KeyLocker API keySM_CLIENT_CERT_FILE_B64- Base64 encoded client certificate (.p12)SM_CLIENT_CERT_PASSWORD- Client certificate passwordSM_CODE_SIGNING_CERT_SHA1_HASH- SHA1 fingerprint of the code signing certificate
CI auto-detects the certificates. Apps are signed automatically during the build process.
The project uses ESLint with typescript-eslint for linting TypeScript code.
The configuration is in the eslint.config.mjs file. It includes rules for
React hooks and React Refresh.
This project adheres to the Contributor Covenant code of conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to code-of-conduct@stacklok.dev.
We welcome contributions and feedback from the community!
If you have ideas, suggestions, or want to get involved, check out our contributing guide or open an issue. Join us in making ToolHive even better!
This project is licensed under the Apache 2.0 License.