Skip to content
MCP Servers
Skill

/deep-links

Deep links in ToolHive Studio. Use when implementing, debugging, or asking about deep link features (toolhive-gui:// protocol), adding new deep link intents, understanding the deep link architecture, IPC model, or platform/packaging support.

From plugin
toolhive-studio
1639 skills
Install
$ npx -y skills add stacklok/toolhive-studio --skill deep-links --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/deep-links

Context preview

The summary Claude sees to decide when to auto-load this skill.

Deep links in ToolHive Studio. Use when implementing, debugging, or asking about deep link features (toolhive-gui:// protocol), adding new deep link intents, understanding the deep link architecture, IPC model, or platform/packaging support.

SKILL.md

deep-links.SKILL.md
name: deep-links
description: Deep links in ToolHive Studio. Use when implementing, debugging, or asking about deep link features (toolhive-gui:// protocol), adding new deep link intents, understanding the deep link architecture, IPC model, or platform/packaging support.
allowed-tools: Read, Grep, Glob, Bash

Deep Links in ToolHive Studio

Deep links allow external systems (browsers, terminals, other apps) to trigger navigation inside ToolHive Desktop via the `toolhive-gui://` custom protocol.

> **About this document**: Much of the content in the reference docs is the result of research into how other Electron apps implement deep links. Some design decisions are implemented; others describe the intended direction but are not yet in the codebase. The base skill reflects the current implementation. The reference docs reflect the research and design intent — read them with that in mind, and update them when relevant implementation decisions change.

---

URL Schema

toolhive-gui://v1/<intent>[?<query>]

Examples:

  • `toolhive-gui://v1/open-registry-server-detail?serverName=fetch` — open a registry server detail page
  • `toolhive-gui://v1/open-registry-server-install?serverName=fetch` — open the registry server detail page and auto-open the install dialog
  • `toolhive-gui://v1/open-registry-skill-detail?namespace=io.github.stacklok&skillName=skill-creator` — open a registry skill detail page
  • `toolhive-gui://v1/open-registry-skill-install?namespace=io.github.stacklok&skillName=skill-creator&version=v1.0.0` — open the skill detail page and auto-open the install dialog with the reference (and optional `?version` tag) prefilled. Tag-only — OCI digests (`sha256:…`) are intentionally not supported because `safeIdentifier` rejects colons; users wanting digest pinning can paste it into the dialog directly.

The `v1` segment is the version. The intent is a kebab-case action name. Query params carry intent-specific data.

---

Current Implementation

Key Files

| File | Role | | --------------------------------- | -------------------------------------------------------------------------------------------------------- | | `common/deep-links.ts` | **Single source of truth.** All deep link definitions: intent name, Zod param schema, navigation target. | | `main/src/deep-links/parse.ts` | Parses and validates a raw URL string using the schemas from `common/deep-links.ts`. | | `main/src/deep-links/index.ts` | Entry point: extracts URL from argv (Windows/Linux), waits for window ready, dispatches via IPC. | | `main/src/deep-links/squirrel.ts` | Squirrel.Windows-specific protocol registration. |

IPC Channel

`deep-link-navigation` — sent **main → renderer** as a `NavigateTarget` (`{ to: string; params?: Record<string, string> }`).

The renderer receives this and calls the TanStack Router `navigate()` directly.

How It Works (Current)

1. **Protocol registration**: On app start, `app.setAsDefaultProtocolClient('toolhive-gui')` registers the protocol. On Windows with Squirrel, `registerProtocolWithSquirrel()` is called instead (see `squirrel.ts`). 2. **URL extraction**: On Windows/Linux, the URL arrives in `process.argv`. `extractDeepLinkFromArgs()` scans for the first `toolhive-gui://` argument (safe against argv injection — see [patterns doc](references/patterns.md#url-parsing)). 3. **Parse + validate**: `parseDeepLinkUrl()` parses the URL and runs it through the Zod discriminated union schema defined in `common/deep-links.ts`. Invalid links resolve to `showNotFound`. 4. **Window ready**: `waitForMainWindowReady()` polls until the window is visible and not loading before dispatching. 5. **Dispatch**: `resolveDeepLinkTarget()` converts the validated intent to a `NavigateTarget`, which is sent to the renderer via the `deep-link-navigation` IPC channel. 6. **Renderer**: The renderer listens for `deep-link-navigation` and calls `navigate(target)`.

Current Limitations vs. Design Intent

The current implementation only supports **read (navigate) operations**. The design doc proposes a confirmation flow for write/destructive operations (C/U/D), but this is not yet implemented. The IPC sends a pre-resolved `NavigateTarget` rather than a raw parsed intent — this simplified the initial implementation. See [design doc](references/design.md) for the full intended model.

---

How to Add a New Deep Link

All changes happen in **`common/deep-links.ts`**:

// 1. Define the new intent using v1DeepLink()
export const myNewIntent = v1DeepLink({
  intent: 'my-new-intent', // kebab-case, matches URL path segment
  params: z.object({
    someParam: safeIdentifier, // use safeIdentifier for user-supplied strings
  }),
  navigate: (params) => ({
    to: '/some-route/$id', // TanStack Router route
    params: { id: params.someParam },
  }),
})

// 2. Add to allDeepLinks array
const allDeepLinks = [
  openRegistryServerDetail,
  showNotFound,
  myNewIntent,
] as const

// 3. Add to deepLinkSchema discriminated union
export const deepLinkSchema = z.discriminatedUnion('intent', [
  openRegistryServerDetail.schema,
  showNotFound.schema,
  myNewIntent.schema, // ← add here
])

**Test manually:**

./node_modules/.bin/electron . "toolhive-gui://v1/my-new-intent?someParam=value"

> `safeIdentifier` is defined as `z.string().regex(/^[a-zA-Z0-9_.-]+$/)` — use it for any param that could be user-supplied to prevent injection.

---

Reference Documents

For deeper background, see:

  • **[OS & Packaging Support](references/os-and-packaging.md)** — Platform-specific registration requirements (Windows, Linux, macOS) and packaging format considerations (Squirrel, Flatpak, .deb, .rpm, .dmg, AppImage, MSIX, etc.). Largely research/prior art.
  • **[Observe
Read more
Ships withtoolhive-studio

Run any Model Context Protocol (MCP) server — securely, instantly, anywhere. ToolHive is the easiest way to discover, deploy, and manage MCP servers. Launch any MCP server in a locked-down container with just a few clicks.

Get the whole plugin
Stats
163
Stars
22
Forks
Active
Maintenance
TypeScript
Language
Apache-2.0
License
6h ago
Last commit
1y ago
Created

Repo: stacklok/toolhive-studio