diff --git a/docs/theme-preview.mdx b/docs/theme-preview.mdx
index ad5a2cc3..3d47abc0 100644
--- a/docs/theme-preview.mdx
+++ b/docs/theme-preview.mdx
@@ -30,10 +30,10 @@ By default level 2-3 headings generate the TOC on the top right
### Level 3 heading
-Some text within a section. [Here is a link](/theme-preview.mdx).
+Some text within a section. [Here is a link](./theme-preview.mdx).
And here is some `inline code` to show how it looks, even some
-[`inline code with a link`](/theme-preview.mdx).
+[`inline code with a link`](./theme-preview.mdx).
#### Level 4 heading
@@ -190,7 +190,7 @@ Stacklok Enterprise includes turnkey integrations for common identity providers.
Instead of manually configuring OIDC, use the built-in Okta or Entra ID
integration to map IdP groups directly to ToolHive roles and policy sets.
-[Learn more about Stacklok Enterprise](/toolhive/enterprise).
+[Learn more about Stacklok Enterprise](./toolhive/enterprise.mdx).
:::
diff --git a/docs/toolhive/index.mdx b/docs/toolhive/index.mdx
index 1c4d74b3..3e809aa6 100644
--- a/docs/toolhive/index.mdx
+++ b/docs/toolhive/index.mdx
@@ -17,6 +17,7 @@ import ThemedImage from '@theme/ThemedImage';
}}
title='ToolHive logo'
height={'120'}
+ style={{ 'margin-top': '1rem' }}
/>
diff --git a/docs/toolhive/reference/crds/index.mdx b/docs/toolhive/reference/crds/index.mdx
index e6058455..827e6a85 100644
--- a/docs/toolhive/reference/crds/index.mdx
+++ b/docs/toolhive/reference/crds/index.mdx
@@ -16,10 +16,12 @@ references.
+{/* Use relative hrefs so cards resolve to the correct version when this page is snapshotted into a versioned build. */}
+
+{/* Use relative hrefs so cards resolve to the correct version when this page is snapshotted into a versioned build. */}
+
` on `main`.
+4. Add the version-specific Docusaurus config.
+5. Prune versions older than N-2.
+6. Commit and deploy via PR.
+
+**Fallback - release branch:** if a component is rolled back to an
+older version (e.g., a late regression in v0.23.0 forces a pin to
+v0.22.0 after v0.23.0 docs have already landed), a release branch
+from the right point in `main`'s history is needed. See the
+process doc for the branch-based approach.
+
+This is a quality forcing function, not a guarantee of perfection.
+The goal is "accurate enough" at cut time, with the understanding
+that narrative docs may have minor drift.
+
+**Upstream requirement:** this process depends on each pinned
+component version being fully documented before the cut. The
+release-triggered docs PR for each component version is the
+completeness gate - see the process doc for details.
+
+### Automated docs pipeline integration
+
+A parallel initiative is introducing automated, release-triggered docs
+PRs: when an OSS component release is cut, a docs PR is generated
+with an AI-drafted changelog and review required from contributors
+on that release.
+
+This creates a natural per-component checkpoint. When an Enterprise
+release is cut (comprising specific versions of each OSS component),
+the docs will have already been reviewed in the context of each
+constituent release. This significantly reduces the accuracy gap at
+Enterprise version cut time.
+
+The release-triggered docs PR also serves as a potential automation
+point for the version snapshot itself - the Enterprise release
+pipeline could trigger the `docs:version` command after all component
+docs PRs are merged.
+
+## Release cadence impact
+
+| Cadence | Active versions | Max drift (N-2 to latest) | Maintenance load |
+| --------- | --------------- | ------------------------- | ------------------------------------------------------ |
+| Monthly | 3 | ~2 months | Low - minimal divergence between versions |
+| Quarterly | 3 | ~6 months | Moderate - guides and UI docs may diverge meaningfully |
+
+Starting monthly and moving to quarterly as the product stabilizes.
+The quarterly cadence is where cross-version maintenance becomes a
+real concern - at that point, the automated docs pipeline and
+per-release review process will be critical for keeping versions
+accurate.
+
+## Risks and mitigations
+
+**Maintenance burden of versioned snapshots.** Each snapshot is a full
+copy of every doc page. A typo fix or security correction must be
+patched in up to 4 places (current + 3 versions).
+
+- _Mitigation:_ Prune at N-2 to limit active versions. Automate
+ cherry-picking of critical fixes to active versions. Accept that
+ non-critical fixes only go into current.
+
+**Docs not matching Enterprise release at cut time.** The Enterprise
+release pins specific upstream versions, but docs may describe
+features not yet in that release.
+
+- _Mitigation:_ Release-triggered docs PRs reduce the gap. A
+ lightweight review checklist at cut time catches obvious issues.
+ Perfect accuracy is a non-goal - "useful and mostly correct" is the
+ bar.
+
+**Search engine indexing of versioned pages.** Duplicate content
+across versions can dilute SEO.
+
+- _Mitigation:_ Set `noIndex: true` in the version config (validated
+ in spike). Docusaurus also sets canonical URLs to the latest
+ version by default. Verify both are working correctly after the
+ first production version cut.
+
+**Build time and repo size growth.** Each version snapshot adds a full
+copy of the docs directory.
+
+- _Mitigation:_ Prune at N-2. Monitor build times after first few
+ cuts. If repo size becomes an issue, consider moving versioned docs
+ to a separate branch or git subtree.
+
+## Implementation phases
+
+### Phase 1: Foundation
+
+- Add the custom `:::enterprise` admonition component.
+- Create separate CRD spec pages per API version
+ (`crd-spec-v1alpha1.md`, `crd-spec-v1beta1.md`).
+- Add initial enterprise-specific inline content to existing pages.
+
+### Phase 2: First version cut
+
+- Follow the release branch process to cut the first Enterprise
+ version snapshot (see
+ [enterprise-version-cut-process.md](enterprise-version-cut-process.md)).
+- Add the `lastVersion`, `versions`, and `docsVersionDropdown`
+ config to `docusaurus.config.ts` (config validated in spike -
+ see Docusaurus configuration above).
+- Suppress the default version banner with `banner: 'none'`.
+- Verify URL structure (`/enterprise//toolhive/...`),
+ `noIndex` on versioned pages, and version dropdown navigation.
+
+### Phase 3: Automation
+
+- Integrate version cut into Enterprise release pipeline.
+- Automate pruning of versions older than N-2.
+- Connect release-triggered docs PRs to version snapshot process.
+
+## Open questions
+
+- **Version label format:** `1.0`, `v1.0`, or `2026.04`? Should align
+ with the Enterprise product versioning scheme.
+- **Version switcher UX:** ~~Should Enterprise users see a version
+ dropdown in the navbar, or is URL-based navigation sufficient?~~
+ **Decision: navbar version picker.** The dropdown serves double duty
+ as a wayfinding indicator for Enterprise users on pinned versions and
+ as a discovery/upsell moment for OSS users who see Enterprise
+ versions listed. Docusaurus supports this natively via the
+ `docsVersionDropdown` navbar item.
+- **Backport policy:** Which types of doc fixes warrant backporting to
+ older versions? Suggest limiting to factual errors and security
+ content.
+- **Enterprise content gating:** ~~Is the inline enterprise content
+ purely informational (visible to all, with CTA), or should any of
+ it be gated behind authentication?~~ **Decision: no gating.** All
+ documentation, including Enterprise-specific content, is public.
+ No plans for gated material on the docs site.
+- **Version persistence across non-docs pages:** Docusaurus version
+ context is purely URL-based. When an Enterprise user navigates to
+ a non-docs page (blog, external link) and returns, their version
+ resets to Latest (OSS). A localStorage or cookie-based solution
+ could remember the last-viewed version and restore it on return.
+ The version dropdown is hidden on non-docs pages (e.g., blog) via
+ CSS to avoid confusion, but the context loss on return is still a
+ UX papercut.
diff --git a/proposals/enterprise-version-cut-process.md b/proposals/enterprise-version-cut-process.md
new file mode 100644
index 00000000..4b2b87fe
--- /dev/null
+++ b/proposals/enterprise-version-cut-process.md
@@ -0,0 +1,455 @@
+# Enterprise version cut process
+
+Companion to
+[docs-versioning-strategy.md](docs-versioning-strategy.md) -
+walks through the concrete process of cutting a versioned docs
+snapshot when an Enterprise release is declared.
+
+## Status
+
+Draft - April 2026
+
+## Upstream components and their docs PRs
+
+Each independently-versioned upstream component has a
+release-triggered docs PR workflow. When a component cuts a release,
+a docs PR is generated containing updated reference content and an
+AI-drafted changelog for human review.
+
+| Component repo | What it covers | Example release |
+| -------------------------- | ----------------------------- | --------------- |
+| `toolhive` | CLI, API, Kubernetes Operator | v0.21.0 |
+| `toolhive-studio` | Desktop UI app | v0.32.1 |
+| `toolhive-registry-server` | Registry Server | v1.3.2 |
+| `toolhive-cloud-ui` | Cloud portal | v0.7.2 |
+
+Each of these repos produces a docs PR against this docs-website
+repo on release. Those PRs update:
+
+- Auto-generated CLI reference pages (toolhive)
+- API and CRD specs (toolhive)
+- Feature documentation and guides (all components)
+- Changelog/release notes content (all components)
+
+## The Enterprise release
+
+The `stacklok-enterprise` repo consolidates the upstream components
+at pinned versions and adds enterprise overlays:
+
+- Enterprise Connectors (qualified MCP servers)
+- Configuration server (Desktop UI lockdown controls)
+- Turnkey IdP integrations (Okta, Entra ID)
+- Canonical policy packs
+- Enterprise Cloud UI administration
+
+An Enterprise release declares a specific version of each upstream
+component. For this walkthrough, we're cutting **Enterprise v1.1**.
+
+## How Enterprise pins upstream versions
+
+Enterprise development continuously syncs and pins the latest
+upstream releases. The process is not "declare versions, then
+build" - it's more like:
+
+1. Enterprise features are developed iteratively.
+2. During that process, Enterprise continuously syncs and pins
+ upstream releases.
+3. When an Enterprise release is cut, the pinned upstream versions
+ are the latest (or very recent) available.
+
+This means that at Enterprise release time, the docs on `main`
+should already reflect the pinned component versions because
+they're the latest. The happy path is to snapshot `main` HEAD
+directly.
+
+### When does forward drift happen?
+
+In rare cases, a component may be rolled back after its docs have
+already landed. For example, a late-breaking regression in
+toolhive v0.23.0 might force the Enterprise release to pin
+v0.22.0 instead, but the v0.23.0 docs PR has already merged to
+`main`. In this scenario, `main` documents features the Enterprise
+customer doesn't have.
+
+When this happens, a release branch from the right point in
+`main`'s history is needed (see the fallback process below).
+
+## Upstream requirement: docs completeness
+
+Whether the snapshot is taken from `main` HEAD (happy path) or
+from a release branch (fallback), the version cut captures whatever
+docs exist at that point. If a pinned component's docs PR was
+merged but incomplete - for example, a new CLI command was added
+in toolhive v0.21.0 but the docs PR only updated the
+auto-generated reference and skipped the narrative guide - that gap
+is baked into the Enterprise snapshot.
+
+This means the release-triggered docs PR for each component
+version must be treated as a completeness gate, not just a
+reference-update mechanism. Reviewers on each docs PR need to
+verify that:
+
+- Auto-generated content (CLI reference, API specs, CRD specs) is
+ present and correct.
+- New features, changed behaviors, and deprecations are reflected
+ in the relevant guides and concept pages.
+- Breaking changes have migration guidance.
+
+The quality bar for these docs PRs directly determines the quality
+of the Enterprise snapshot. If the upstream docs PR process is
+treated as a "we'll fix it later" checkpoint, the Enterprise
+version cut inherits that debt with no easy way to pay it down
+(since the snapshot is a frozen copy).
+
+For a monthly Enterprise release cadence, this is manageable: the
+window between component releases and the Enterprise cut is short,
+and docs PRs are small. For a quarterly cadence, the risk
+increases: more features accumulate per component release, docs PRs
+are larger and take longer to review, and the pressure to cut the
+Enterprise release on schedule may conflict with docs completeness.
+
+## Worked example: cutting Enterprise v1.1
+
+### Enterprise v1.1 manifest
+
+```text
+stacklok-enterprise v1.1
+├── toolhive v0.21.0
+├── toolhive-studio v0.32.1
+├── toolhive-registry-server v1.3.2
+└── toolhive-cloud-ui v0.7.2
+```
+
+### Happy path: snapshot from main HEAD
+
+Since Enterprise pins the latest upstream at cut time, `main`
+should already reflect all four pinned versions. The typical state
+at cut time:
+
+```text
+main branch (HEAD)
+──────────────────────────────────────────────────
+
+ merge: toolhive v0.21.0 docs PR ◄── pinned (latest)
+ merge: toolhive-studio v0.32.1 docs PR ◄── pinned (latest)
+ merge: registry-server v1.3.2 docs PR ◄── pinned (latest)
+ merge: cloud-ui v0.7.2 docs PR ◄── pinned (latest)
+ merge: misc unrelated docs fixes
+ ← HEAD
+```
+
+`main` HEAD matches the Enterprise manifest. Snapshot directly.
+
+#### 1. Verify all pinned-version docs PRs are merged
+
+Confirm that the release-triggered docs PR for each component
+version in the manifest has been merged into `main`.
+
+Checklist:
+
+- [ ] `toolhive` v0.21.0 docs PR - merged
+- [ ] `toolhive-studio` v0.32.1 docs PR - merged
+- [ ] `toolhive-registry-server` v1.3.2 docs PR - merged
+- [ ] `toolhive-cloud-ui` v0.7.2 docs PR - merged
+
+If any pinned-version docs PR is still open or hasn't been
+generated yet, that's a blocker.
+
+#### 2. Add enterprise overlay content
+
+Commit enterprise-specific content to `main`:
+
+- Enterprise-only pages (Configuration server guides, IdP
+ integration guides, Connectors catalog, policy pack docs, Cloud
+ UI administration).
+- `:::enterprise` inline admonitions in existing OSS pages where
+ Enterprise v1.1 introduces new differentiation.
+- Updates to the Enterprise landing page at `/toolhive/enterprise`
+ if the feature comparison or pricing has changed.
+
+This content serves double duty: it appears in the Enterprise
+snapshot and stays in the OSS rolling latest as upsell content.
+
+#### 3. Sanity-check
+
+Quick review of the docs on `main` against the Enterprise v1.1
+manifest. Look for:
+
+- Features documented in latest that aren't in any of the pinned
+ component versions.
+- Broken links or references to content that doesn't exist yet.
+- Enterprise content that references capabilities not in this
+ release.
+
+#### 4. Cut the version snapshot
+
+```bash
+npx docusaurus docs:version 1.1
+```
+
+This creates:
+
+- `versioned_docs/version-1.1/` - a full copy of the `docs/`
+ directory
+- `versioned_sidebars/version-1.1-sidebars.json` - a snapshot of
+ the sidebar configuration
+- An entry in `versions.json` for version `1.1`
+
+#### 5. Update Docusaurus config
+
+After cutting the version, add the version-specific config to
+`docusaurus.config.ts`:
+
+```ts title="docusaurus.config.ts (docs plugin options)"
+lastVersion: 'current',
+versions: {
+ current: {
+ label: 'Latest (OSS)',
+ },
+ '1.1': {
+ label: 'Enterprise 1.1',
+ path: 'enterprise/1.1',
+ banner: 'none',
+ noIndex: true,
+ },
+},
+```
+
+Key settings:
+
+- `lastVersion: 'current'` keeps the `docs/` folder (OSS rolling
+ latest) as the default at the root path.
+- `path: 'enterprise/1.1'` puts the versioned docs under
+ `/enterprise/1.1/toolhive/...` instead of the default
+ `/1.1/toolhive/...`.
+- `banner: 'none'` suppresses the default "this is an older
+ version" banner, which doesn't make sense for an Enterprise
+ pinned version.
+- `noIndex: true` prevents search engines from indexing versioned
+ pages, avoiding duplicate content.
+
+Also add the version dropdown to the navbar (first version cut
+only):
+
+```ts title="docusaurus.config.ts (navbar items)"
+{
+ type: 'docsVersionDropdown',
+ position: 'right',
+},
+```
+
+**Important:** the `versions` config block must not reference a
+version until after `docs:version` has been run and `versions.json`
+contains it. Docusaurus validates at startup and errors on unknown
+versions.
+
+#### 6. Prune old versions if needed
+
+Check whether the N-2 threshold is exceeded. With Enterprise v1.1,
+the active versions should be:
+
+| Version | Status |
+| -------------------- | ----------------------------- |
+| current (OSS latest) | Always active |
+| 1.1 | Current Enterprise release |
+| 1.0 | N-1 (supported) |
+| 0.9 | N-2 (supported, if it exists) |
+
+If a version older than N-2 exists, remove it:
+
+```bash
+# Remove the versioned docs directory
+rm -rf versioned_docs/version-0.8
+
+# Remove the versioned sidebar
+rm versioned_sidebars/version-0.8-sidebars.json
+
+# Remove the entry from versions.json
+# (edit manually or script it)
+```
+
+#### 7. Commit and deploy
+
+```bash
+git add versioned_docs/version-1.1 \
+ versioned_sidebars/version-1.1-sidebars.json \
+ versions.json \
+ docusaurus.config.ts
+git commit -m "Cut Enterprise v1.1 docs snapshot"
+```
+
+Open a PR, review, merge, and deploy through the normal pipeline.
+
+### Fallback: release branch for component rollbacks
+
+If a component is rolled back to an older version after its
+successor's docs have already landed on `main`, the happy path
+doesn't work. For example, a late regression in toolhive v0.22.0
+forces a pin to v0.21.0, but the v0.22.0 docs PR has already
+merged.
+
+```text
+main branch (HEAD)
+──────────────────────────────────────────────────
+
+ merge: toolhive v0.21.0 docs PR ◄── pinned
+ merge: toolhive v0.22.0 docs PR ◄── AHEAD of pin
+ ← HEAD
+```
+
+In this case:
+
+1. Identify the base commit - the merge of the last pinned-version
+ docs PR before the newer version landed.
+2. Create a release branch from that commit:
+ `git checkout -b enterprise/v1.1 `
+3. Add enterprise overlay content on the branch.
+4. Cut the version and update config on the branch.
+5. Copy the snapshot artifacts back to `main` via PR:
+
+ ```bash
+ git checkout main
+ git checkout enterprise/v1.1 -- \
+ versioned_docs/version-1.1 \
+ versioned_sidebars/version-1.1-sidebars.json
+ ```
+
+6. Update `versions.json` and `docusaurus.config.ts` on `main`.
+7. Prune, commit, and deploy as normal.
+
+## Resulting URL structure
+
+After cutting Enterprise v1.1, the site serves:
+
+| URL path | Content |
+| ------------------------------ | ------------------------------------------------------- |
+| `/toolhive/...` | OSS rolling latest (default, indexed by search engines) |
+| `/enterprise/1.1/toolhive/...` | Enterprise v1.1 snapshot |
+| `/enterprise/1.0/toolhive/...` | Enterprise v1.0 snapshot (N-1) |
+
+The version dropdown in the navbar shows all active versions,
+with the OSS latest as the default selection.
+
+## Validated behavior (spike)
+
+The following was validated on a local dev server using the
+Docusaurus config changes described above:
+
+- **OSS latest stays at `/toolhive/...`** - `lastVersion: 'current'`
+ keeps the `docs/` folder as the default, served at the root
+ route base path. No change to existing OSS URLs.
+- **Enterprise version at `/enterprise/1.1/toolhive/...`** - the
+ `path: 'enterprise/1.1'` config correctly routes the versioned
+ snapshot under the desired prefix.
+- **Version dropdown** - the `docsVersionDropdown` navbar item
+ renders a dropdown showing "Latest (OSS)" and "Enterprise 1.1".
+ Clicking "Enterprise 1.1" navigates to `/enterprise/1.1/toolhive/`.
+ Clicking "Latest (OSS)" navigates back to `/toolhive/`.
+- **Version badge** - a "Version: Enterprise 1.1" badge appears
+ below the breadcrumb on versioned pages, giving clear context.
+- **No version banner** - `banner: 'none'` suppresses the default
+ "unmaintained version" banner.
+- **Sidebar links** - all sidebar links on versioned pages
+ correctly use `/enterprise/1.1/toolhive/...` paths.
+- **Config ordering constraint** - the `versions` block in the docs
+ plugin config must not reference a version until after
+ `docs:version` has been run and `versions.json` contains it.
+ Docusaurus fails at startup with a validation error otherwise.
+
+## Timeline
+
+### Happy path
+
+```text
+Enterprise v1.1 version cut (happy path)
+─────────────────────────────────────────────────────────
+
+main branch (time flows down)
+│
+│ merge: toolhive v0.21.0 docs PR ◄── latest
+│ merge: toolhive-studio v0.32.1 docs PR ◄── latest
+│ merge: registry-server v1.3.2 docs PR ◄── latest
+│ merge: cloud-ui v0.7.2 docs PR ◄── latest
+│ ← HEAD (matches Enterprise v1.1 manifest)
+│
+│
+│ Enterprise release declared
+│ │
+│ ├─ 1. Verify all pinned docs PRs merged ✓
+│ ├─ 2. Add enterprise overlays to main
+│ ├─ 3. Sanity-check
+│ ├─ 4. npx docusaurus docs:version 1.1
+│ ├─ 5. Update docusaurus.config.ts
+│ ├─ 6. Prune old versions
+│ └─ 7. PR, review, merge, deploy
+```
+
+### Fallback (component rollback)
+
+```text
+Enterprise v1.1 version cut (with rollback)
+─────────────────────────────────────────────────────────
+
+main branch (time flows down)
+│
+│ merge: toolhive v0.21.0 docs PR ◄── pinned
+│ merge: toolhive v0.22.0 docs PR ◄── AHEAD (rollback)
+│ ← HEAD
+│
+│ Enterprise release declared (pins v0.21.0)
+│ │
+│ ├─ 1. Identify base commit (v0.21.0 docs PR merge)
+│ ├─ 2. Create branch: enterprise/v1.1 from base
+│ │ │
+│ │ ├─ 3. Add enterprise overlays
+│ │ ├─ 4. npx docusaurus docs:version 1.1
+│ │ └─ snapshot artifacts ready
+│ │
+│ ├─ 5. Copy snapshot artifacts to main
+│ ├─ 6. Prune old versions
+│ └─ 7. PR, review, merge, deploy
+```
+
+## Automation opportunities
+
+The version cut can be partially or fully automated as part of the
+Enterprise release pipeline in `stacklok-enterprise`:
+
+1. **Trigger:** Enterprise release pipeline reaches the "cut docs"
+ stage. The pipeline provides the component version manifest.
+2. **Verify:** Script checks that all expected component docs PRs
+ (matching the versions in the manifest) have been merged to
+ `main` in the docs-website repo. If any are missing or still
+ open, the automation blocks and alerts.
+3. **Check for drift:** Script compares the pinned versions in the
+ manifest against the latest component docs PRs on `main`. If
+ `main` is ahead of any pin (a component was rolled back), switch
+ to the release branch fallback. Otherwise, proceed with the
+ happy path on `main`.
+4. **Cut:** Script runs `npx docusaurus docs:version ` on
+ `main` (happy path) or the release branch (fallback).
+5. **Config:** Script updates `docusaurus.config.ts` with the new
+ version entry.
+6. **PR to main:** Script opens a PR on docs-website with the
+ snapshot artifacts, config update, and any version pruning.
+7. **Merge and deploy:** Human reviews and merges the PR. Vercel
+ deploys automatically.
+
+Step 2 (verify) is the key gate and depends on being able to
+reliably identify which docs PR corresponds to which component
+version - see open questions below.
+
+## Open questions
+
+- **Component docs PR identification:** How do we reliably match a
+ docs PR to a specific component version? This is critical for
+ the verification gate (and for the fallback, identifying the base
+ commit). Options include PR title conventions, labels, or a
+ manifest file in the PR. A structured label like
+ `component/toolhive/v0.21.0` would make automated lookups
+ straightforward.
+- **Partial updates after cut:** If a critical docs fix lands on
+ `main` after a version is cut, do we re-cut the version or
+ cherry-pick into the `versioned_docs/` directory on `main`? The
+ strategy proposal suggests limiting backports to factual errors
+ and security content.
diff --git a/scripts/generate-crd-pages.mjs b/scripts/generate-crd-pages.mjs
index 0e835043..ff7038cb 100644
--- a/scripts/generate-crd-pages.mjs
+++ b/scripts/generate-crd-pages.mjs
@@ -194,25 +194,29 @@ function kindsByGroup() {
function renderLandingPage() {
const grouped = kindsByGroup();
- const sections = groupOrder.map((group) => {
+ const sections = groupOrder.map((group, index) => {
const label = groupLabels[group];
const cards = grouped[group]
.map(
(item) => ``
)
.join('\n\n');
+ const rationale =
+ index === 0
+ ? `{/* Use relative hrefs so cards resolve to the correct version when this page is snapshotted into a versioned build. */}\n\n`
+ : '';
return `## ${label}
-${cards}
+${rationale}${cards}
`;
});
diff --git a/src/css/custom.css b/src/css/custom.css
index 0ad3f83d..c9c5043a 100644
--- a/src/css/custom.css
+++ b/src/css/custom.css
@@ -178,6 +178,34 @@ thead th {
color: var(--stacklok-white);
}
+/*
+ * Neutralize active-state styling on doc-type navbar items.
+ * Switching to type:'doc' for version-aware links causes Docusaurus
+ * to mark items as "active" whenever any doc page is viewed. Reset
+ * top-level items and dropdown items back to default appearance.
+ */
+.navbar-doc-link.navbar__link--active {
+ color: var(--ifm-navbar-link-color);
+}
+
+.navbar-doc-link.navbar__link--active:hover {
+ color: var(--ifm-navbar-link-hover-color);
+}
+
+.dropdown__link--active {
+ color: inherit !important;
+ background: transparent !important;
+}
+
+.dropdown__link--active:hover {
+ background: var(--ifm-dropdown-hover-background-color) !important;
+}
+
+/* Hide version dropdown on non-docs pages (e.g., blog) */
+html:not(.plugin-docs) .version-dropdown {
+ display: none;
+}
+
/* Navbar logo */
.navbar__logo {
display: flex;
diff --git a/versioned_docs/version-1.0/theme-preview.mdx b/versioned_docs/version-1.0/theme-preview.mdx
new file mode 100644
index 00000000..67de14ff
--- /dev/null
+++ b/versioned_docs/version-1.0/theme-preview.mdx
@@ -0,0 +1,314 @@
+---
+title: Theme preview page
+description: This is a page used to preview a lot of theme elements in one place.
+displayed_sidebar: toolhiveSidebar
+pagination_next: toolhive/guides-cli/install
+pagination_prev: toolhive/index
+---
+
+
+
+
+
+This is a page used to preview a lot of theme elements in one place when working
+on styles.
+
+[Community knowledge base for Docusaurus design tips](https://docusaurus.community/knowledge/design/)
+
+Breadcrumbs above aren't very interesting since this is a top-level page that
+doesn't participate in a sidebar, navigate to a different page to see them.
+
+Another element that can't be easily reproduced here is the DocCardList
+component, but you can see it in action on the
+[ToolHive CLI guides index page](./toolhive/guides-cli/index.mdx).
+
+## Level 2 heading
+
+By default level 2-3 headings generate the TOC on the top right
+([reference](https://docusaurus.io/docs/markdown-features/toc#table-of-contents-heading-level)).
+
+### Level 3 heading
+
+Some text within a section. [Here is a link](/theme-preview.mdx).
+
+And here is some `inline code` to show how it looks, even some
+[`inline code with a link`](/theme-preview.mdx).
+
+#### Level 4 heading
+
+This level won't appear in the TOC by default.
+
+## Code blocks
+
+[Docusaurus reference docs](https://docusaurus.io/docs/markdown-features/code-blocks)
+
+```js title="Some JavaScript with line numbers" showLineNumbers
+console.log('We love marmots.');
+
+function MarmotsAreGreat(agree) {
+ if (agree) {
+ // highlight-next-line
+ return 'I agree, and this line is highlighted!';
+ }
+
+ return 'I am wrong.';
+}
+```
+
+```yaml title="some-yaml.yaml"
+---
+# Sample profile for validating repositories
+version: v1
+type: profile
+name: acme-github-profile
+display_name: Sample Profile
+alert: 'off'
+remediate: 'off'
+repository:
+ - type: allowed_selected_actions
+ def:
+ github_owned_allowed: true
+ verified_allowed: true
+ patterns_allowed: []
+```
+
+```json title="Example JSON with highlighted lines" {2,5-7}
+{
+ "key": "String",
+ "Number": 1,
+ "array": [1, 2, 3],
+ "nested": {
+ "literals": true
+ }
+}
+```
+
+## Admonitions
+
+These are MDX callouts
+([reference](https://docusaurus.io/docs/markdown-features/admonitions)).
+
+To customize the title, use square brackets after the type, e.g.
+`:::tip[My title]`.
+
+To keep Prettier from invalidating the admonition syntax, add empty lines around
+the start and end of the admonition block (see
+[here](https://docusaurus.io/docs/markdown-features/admonitions#usage-with-prettier)).
+
+They can be customized in src/css/custom.css like so
+([reference](https://docusaurus.community/knowledge/design/admonitions/#updating-the-css)):
+
+```css
+/* Customize the "Tip" admonition */
+.alert--success {
+ --ifm-alert-background-color: #59cfa8;
+ --ifm-alert-background-color-highlight: #00bbbe26;
+ --ifm-alert-foreground-color: #002a3e;
+ --ifm-alert-border-color: #002a3e;
+}
+
+/* Use a different border color in dark mode */
+[data-theme='dark'] .alert--success {
+ --ifm-alert-border-color: #008385;
+}
+```
+
+:::note
+
+This is a `note` admonition. Its CSS class is `theme-admonition-note`.
+
+[Here's a link inside the admonition](#tables).
+
+:::
+
+:::tip
+
+This is a `tip` admonition. Its CSS class is `theme-admonition-tip`.
+
+[Here's a link inside the admonition](#tables).
+
+:::
+
+:::info[Hello]
+
+This is an `info` admonition. Its CSS class is `theme-admonition-info` and it
+has a custom title.
+
+[Here's a link inside the admonition](#tables).
+
+:::
+
+:::warning
+
+This is a `warning` admonition. Its CSS class is `theme-admonition-warning`.
+
+[Here's a link inside the admonition](#tables).
+
+:::
+
+:::danger
+
+This is a `danger` admonition. Its CSS class is `theme-admonition-danger`.
+
+[Here's a link inside the admonition](#tables).
+
+:::
+
+:::::info[Parent]
+
+Admonitions can be nested; example here so we can see how the colors look
+together.
+
+::::danger[Child]
+
+Child content
+
+:::tip[Inception]
+
+This is getting silly
+
+:::
+
+::::
+
+:::::
+
+## Tables
+
+A standard Markdown table:
+
+| Column 1 | Column 2 | Column 3 |
+| --------------- | :---------: | ---------------------------- |
+| This | hello | [A link in a table](#tables) |
+| That | hi | `value` |
+| The other thing | how are you | 🙈 |
+| Another row | so you | can see the zebra effect |
+
+(Docusaurus theme enables header row and zebra rows by default)
+
+## Tabs
+
+MDX Tabs component, default theme
+([reference](https://docusaurus.io/docs/markdown-features/tabs))
+
+
+
+ This is an apple 🍎
+
+ ```python title="Code block inside a tab"
+ print('We love marmots.')
+
+ def marmots_are_great(agree):
+ if agree:
+ # highlight-next-line
+ return 'I agree, and this line is highlighted!'
+
+ return 'I am wrong.'
+ ```
+
+
+
+ This is an orange 🍊
+
+
+ This is a banana 🍌
+
+
+
+## Details panel
+
+
+ Click to expand
+
+This is a details panel, which can be used to show additional information
+without cluttering the page.
+
+It can contain any Markdown or MDX content, including `inline code`, code
+blocks, images, even other details panels.
+[Here's a link inside the details panel](#details-panel).
+
+```js title="JavaScript code inside a details panel"
+console.log('This is inside a details panel.');
+```
+
+
+
+## Images
+
+An MDX ThemedImage, which switches based on light/dark theme
+([reference](https://docusaurus.io/docs/markdown-features/assets#themed-images))
+
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+
+
+
+An image using the "screenshot" CSS class to add a border and shadow/glow to
+make them stand out from the background:
+
+
+
+## Diagrams
+
+A mermaid flowchart
+([reference](https://docusaurus.io/docs/markdown-features/diagrams))
+
+```mermaid
+flowchart LR
+ node1["Node"] -- Label --> node2
+ node2["Node"] -- Label --> node3
+ subgraph container["**Subgraph**"]
+ direction LR
+ subgraph container1["Nested subgraph"]
+ node3["Node"]
+ end
+ end
+```
+
+A mermaid sequence diagram
+
+```mermaid
+sequenceDiagram
+ Alice->>+John: Hello John, how are you?
+ note right of John: Note
+ John-->>-Alice: Great!
+ Alice-)John: See you later!
+```
+
+## Other standard elements
+
+Here's some **bold** and _italic_ text.
+
+Unordered list:
+
+- One
+- Two
+- Three
+
+Ordered list:
+
+1. One
+1. Two
+1. Three
+
+Horizontal line:
+
+---
+
+## Pagination
diff --git a/versioned_docs/version-1.0/toolhive/_partials/_auth-troubleshooting.mdx b/versioned_docs/version-1.0/toolhive/_partials/_auth-troubleshooting.mdx
new file mode 100644
index 00000000..fb41b43e
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/_partials/_auth-troubleshooting.mdx
@@ -0,0 +1,42 @@
+
+Authentication issues
+
+If clients can't authenticate:
+
+1. Check that the JWT token is valid and not expired
+2. Verify that the audience and issuer match your configuration
+3. Ensure the JWKS URL is accessible
+4. Check the server logs for specific authentication errors:
+
+ ```bash
+ # View logs
+ thv logs
+
+ # Follow logs in real-time (like tail -f)
+ thv logs --follow
+
+ # View proxy logs instead of container logs
+ thv logs --proxy
+ ```
+
+
+
+
+Authorization issues
+
+If authenticated clients are denied access:
+
+1. Make sure your Cedar policies explicitly permit the specific action
+ (remember, default deny)
+2. Check that the principal, action, and resource match what's in your policies
+ (including case and formatting)
+3. Examine any conditions in your policies to ensure they're satisfied (for
+ example, required JWT claims or tool arguments)
+4. Remember that Cedar uses a default deny policy—if no policy explicitly
+ permits an action, it will be denied
+
+**Troubleshooting tip:** If access is denied, check that your policies
+explicitly permit the action. Cedar uses a default deny model—if no policy
+matches, the request is denied.
+
+
diff --git a/versioned_docs/version-1.0/toolhive/_partials/_basic-cedar-config.mdx b/versioned_docs/version-1.0/toolhive/_partials/_basic-cedar-config.mdx
new file mode 100644
index 00000000..86f6b33b
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/_partials/_basic-cedar-config.mdx
@@ -0,0 +1,35 @@
+Create a JSON or YAML file with Cedar policies. This example demonstrates
+several policy patterns:
+
+- Allow everyone to use the weather tool
+- Restrict the admin_tool to a specific user (alice123)
+- Role-based access: only users with the "premium" role can call any tool
+- Attribute-based: allow the calculator tool only for add/subtract operations
+
+Here's an example in JSON format:
+
+```json
+{
+ "version": "1.0",
+ "type": "cedarv1",
+ "cedar": {
+ "policies": [
+ "permit(principal, action == Action::\"call_tool\", resource == Tool::\"weather\");",
+ "permit(principal == Client::\"alice123\", action == Action::\"call_tool\", resource == Tool::\"admin_tool\");",
+ "permit(principal, action == Action::\"call_tool\", resource) when { principal.claim_roles.contains(\"premium\") };",
+ "permit(principal, action == Action::\"call_tool\", resource == Tool::\"calculator\") when { resource.arg_operation == \"add\" || resource.arg_operation == \"subtract\" };"
+ ],
+ "entities_json": "[]"
+ }
+}
+```
+
+You can also define custom resource attributes in `entities_json` for per-tool
+ownership or sensitivity labels.
+
+:::tip
+
+For more policy examples and advanced usage, see
+[Cedar policies](../concepts/cedar-policies.mdx).
+
+:::
diff --git a/versioned_docs/version-1.0/toolhive/_partials/_client-config-intro.mdx b/versioned_docs/version-1.0/toolhive/_partials/_client-config-intro.mdx
new file mode 100644
index 00000000..0d0eeefb
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/_partials/_client-config-intro.mdx
@@ -0,0 +1,17 @@
+ToolHive automatically configures supported AI clients to work with MCP servers.
+This guide shows you how to set up and manage client configurations.
+
+## Understanding client configuration
+
+Before an AI application can use ToolHive MCP servers, it needs to know where to
+find them. You can configure clients in two ways:
+
+1. **{props.term} a supported client**: {props.term} your client with ToolHive
+ so it automatically manages and updates the client's configuration as you
+ start, stop, and remove MCP servers.
+2. **Manual configuration**: For clients that ToolHive doesn't directly support,
+ manually configure them to connect to ToolHive-managed MCP servers using the
+ SSE or Streamable HTTP protocol.
+
+For a complete list of supported clients and compatibility details, see the
+[Client compatibility reference](../reference/client-compatibility.mdx).
diff --git a/versioned_docs/version-1.0/toolhive/_partials/_oidc-prerequisites.mdx b/versioned_docs/version-1.0/toolhive/_partials/_oidc-prerequisites.mdx
new file mode 100644
index 00000000..99e1cb66
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/_partials/_oidc-prerequisites.mdx
@@ -0,0 +1,22 @@
+Before you begin, make sure you have:
+
+- ToolHive installed and working
+- Basic familiarity with OAuth, OIDC, and JWT concepts
+- An identity provider that supports OpenID Connect (OIDC), such as Google,
+ GitHub, Microsoft Entra ID (Azure AD), Okta, Auth0, or Kubernetes (for service
+ accounts)
+
+From your identity provider, you'll need:
+
+- Client ID
+- Audience value
+- Issuer URL
+- JWKS URL (for key verification)
+
+ToolHive uses OIDC to connect to your existing identity provider, so you can
+authenticate with your own credentials (for example, Google login) or with
+service account tokens (for example, in Kubernetes). ToolHive never sees your
+password, only signed tokens from your identity provider.
+
+For background on authentication, authorization, and Cedar policy examples, see
+[Authentication and authorization framework](../concepts/auth-framework.mdx).
diff --git a/versioned_docs/version-1.0/toolhive/_partials/_remote-mcp-auth-examples.mdx b/versioned_docs/version-1.0/toolhive/_partials/_remote-mcp-auth-examples.mdx
new file mode 100644
index 00000000..af1b84c6
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/_partials/_remote-mcp-auth-examples.mdx
@@ -0,0 +1,82 @@
+#### Remote MCP server with Auto-Discovered authentication
+
+**Auto-Discovered** authentication is the preferred approach for any MCP server
+that supports it, as ToolHive handles all authentication setup automatically
+with minimal configuration required. Notion's remote MCP server is one example
+that supports this feature:
+
+1. Configuration settings:
+ - **Server name**: `notion-remote`
+ - **Server URL**: `https://mcp.notion.com/mcp`
+ - **Transport**: Streamable HTTP
+ - **Authorization method**: Auto-Discovered
+ - **Callback port**: `45673` (or any available port on your system)
+1. When you install the server, ToolHive discovers the OAuth endpoints,
+ registers a new client, and handles the authentication process.
+1. Your browser opens for authentication. After you authorize access, the remote
+ MCP server appears in your server list with a "Running" status.
+
+#### Remote MCP server with OAuth2 authentication
+
+GitHub's remote MCP server requires manual OAuth configuration. You'll need to
+create a GitHub OAuth app and provide the details in ToolHive.
+
+First, create a GitHub OAuth app:
+
+1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
+1. Click **New OAuth App**
+1. Fill in the application details:
+ - **Application name**: Choose a descriptive name (e.g., "ToolHive GitHub
+ MCP")
+ - **Homepage URL**: Your application's homepage or `http://localhost`
+ - **Authorization callback URL**: `http://localhost:45673/callback` (the port
+ number must match the **Callback port** you will enter in ToolHive)
+1. Click **Register application**
+1. Copy the **Client ID** and generate a **Client secret** for use in ToolHive
+
+Configure the remote MCP server in ToolHive:
+
+1. Configuration settings:
+ - **Server name**: `github-remote`
+ - **Server URL**: `https://api.githubcopilot.com/mcp/`
+ - **Transport**: Streamable HTTP
+ - **Authorization method**: OAuth 2.0
+ - **Callback port**: `45673` (or any available port on your system)
+ - **Authorize URL**: `https://github.com/login/oauth/authorize`
+ - **Token URL**: `https://github.com/login/oauth/access_token`
+ - **Client ID**: Your GitHub OAuth app client ID (e.g.,
+ `Og44jirLIaUgSiTDNGA3`)
+ - **Client secret**: Your GitHub OAuth app client secret (optional if PKCE is
+ enabled)
+ - **Scopes**: `repo,user:email` (comma-separated list of required
+ permissions)
+ - **PKCE**: Enable this option for enhanced security without requiring a
+ client secret
+1. When you install the server, ToolHive opens your browser to authenticate with
+ GitHub and authorize the application.
+1. After you authenticate successfully, the remote MCP server appears in your
+ server list with a "Running" status.
+
+#### Remote MCP server with OIDC authentication
+
+GitHub's remote MCP server also supports OIDC authentication, which provides
+additional security features and standardized token handling. Use the same
+GitHub OAuth app from the previous example.
+
+1. Fill in the configuration form:
+ - **Server name**: `github-remote`
+ - **Server URL**: `https://api.githubcopilot.com/mcp/`
+ - **Transport**: Streamable HTTP
+ - **Authorization method**: OIDC
+ - **Callback port**: `45673` (or any available port on your system)
+ - **Issuer URL**: `https://github.com/login/oauth`
+ - **Client ID**: Your GitHub OAuth app client ID (e.g.,
+ `Og44jirLIaUgSiTDNGA3`)
+ - **Client secret**: Your GitHub OAuth app client secret (optional if PKCE is
+ enabled)
+ - **PKCE**: Enable this option for enhanced security without requiring a
+ client secret
+1. When you install the server, ToolHive opens your browser to authenticate with
+ GitHub using OIDC.
+1. After you authenticate successfully, the remote MCP server appears in your
+ server list with a "Running" status.
diff --git a/versioned_docs/version-1.0/toolhive/concepts/auth-framework.mdx b/versioned_docs/version-1.0/toolhive/concepts/auth-framework.mdx
new file mode 100644
index 00000000..cc8286a8
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/concepts/auth-framework.mdx
@@ -0,0 +1,347 @@
+---
+title: Authentication and authorization
+description: Understanding ToolHive's authentication and authorization framework concepts.
+---
+
+This document explains the concepts behind ToolHive's authentication and
+authorization framework, which secures MCP servers by verifying client identity
+and controlling access to resources. You'll learn how these systems work
+together, the reasoning behind their design, and the benefits of this approach.
+
+:::info[Scope of this documentation]
+
+This documentation covers **client-to-MCP-server authentication**—how clients
+authenticate to the MCP server itself. This is about securing access to the MCP
+server's tools and resources.
+
+This is different from **MCP-server-to-backend authentication**, which involves
+how the MCP server authenticates to external services or APIs it calls (for
+example, a GitHub MCP server authenticating to the GitHub API). That topic is
+covered in [Backend authentication](./backend-auth.mdx).
+
+:::
+
+## Understanding authentication vs. authorization
+
+When you secure MCP servers, you need to understand the strong separation
+between two critical security concepts:
+
+- **Authentication (authN):** Verifying the identity of clients connecting to
+ your MCP server ("Who are you?")
+- **Authorization (authZ):** Determining what actions authenticated clients are
+ allowed to perform ("What can you do?")
+
+You should always perform authentication first, using a trusted identity
+provider, and then apply authorization rules to determine what the authenticated
+identity can do. ToolHive helps you follow this best practice by acting as a
+gateway in front of your MCP servers. This approach lets you use proven identity
+systems for authentication, while keeping your authorization policies clear,
+flexible, and auditable. You don't need to add custom authentication or
+authorization logic to every server—ToolHive handles it for you, consistently
+and securely.
+
+## Why ToolHive centralizes authentication
+
+The
+[official MCP specification](https://modelcontextprotocol.io/docs/tutorials/security/authorization)
+recommends OAuth 2.1-based authorization for HTTP transports, where each MCP
+server acts as an OAuth resource server. In practice, this model creates
+significant operational challenges:
+
+- **OAuth client registration burden:** OAuth 2.0 requires pre-registered
+ redirect URIs at each identity provider. Many providers—such as Google,
+ GitHub, and Atlassian—require manual registration of OAuth clients to obtain a
+ client ID and client secret. If each user client (for example, an IDE) were
+ its own OAuth client, the registration burden would be impractical at scale.
+- **No federation with external services:** While token exchange (RFC 8693) and
+ federated identity providers work when the upstream service is in the same
+ trust domain as the MCP server or has an established trust relationship with
+ the identity provider, many MCP servers need to access external services like
+ GitHub, Google, or Atlassian APIs where no federation relationship exists.
+- **Per-server implementation cost:** Each MCP server would need to implement
+ its own token validation and scope management, duplicating security-critical
+ logic across servers.
+
+ToolHive addresses the per-server implementation cost by centralizing
+authentication and authorization in its proxy layer. You configure ToolHive with
+your identity provider and write Cedar policies for fine-grained
+authorization—individual MCP servers don't need to implement token validation or
+scope management.
+
+The [embedded authorization server](#embedded-authorization-server) addresses
+the remaining challenges. It exposes standard OAuth endpoints and handles the
+full OAuth web flow, eliminating the client registration burden through Dynamic
+Client Registration (DCR) and solving the federation gap by obtaining tokens
+directly from external providers like GitHub or Atlassian. ToolHive delegates
+authentication to the upstream provider and issues its own tokens, giving MCP
+clients a spec-compliant OAuth experience while centralizing the complexity of
+token acquisition and management.
+
+## Authentication framework
+
+ToolHive uses OAuth-based authentication with support for both OAuth 2.1 and
+OpenID Connect (OIDC), enabling both JWT tokens and opaque token validation.
+This lets you connect ToolHive to any OAuth 2.1 or OIDC-compliant identity
+provider (IdP), such as Google, GitHub, Microsoft Entra ID (Azure AD), Okta,
+Auth0, or even Kubernetes service accounts. ToolHive never handles your raw
+passwords or credentials; instead, it relies on access tokens issued by your
+trusted provider—either self-contained JWT tokens or opaque tokens validated
+through token introspection.
+
+### Why use OAuth-based authentication?
+
+OAuth-based authentication provides several key advantages for securing MCP
+servers:
+
+- **Standard and interoperable:** You can connect ToolHive to any OAuth 2.1 or
+ OIDC-compliant IdP without custom code, supporting both human users and
+ automated services.
+- **Proven and secure:** Authentication is delegated to battle-tested identity
+ systems, which handle login UI, multi-factor authentication, and password
+ storage.
+- **Decoupled identity management:** You can use your existing SSO/IdP
+ infrastructure, making onboarding and management seamless.
+- **Flexible for users and services:** This authentication framework supports
+ both interactive user login (for example, Google sign-in) and
+ service-to-service authentication (for example, Kubernetes service account
+ tokens).
+
+### Real-world authentication scenarios
+
+Understanding how OAuth-based authentication works in practice helps you design
+better security for your MCP servers:
+
+**User login via Google:** For example, you can run an MCP server that requires
+authentication using your Google credentials. ToolHive delegates login to
+Google, receives an access token (either a JWT or an opaque token), and
+validates it to authenticate you. This means users get a familiar login
+experience while you benefit from Google's security infrastructure.
+
+**Service-to-service auth with Kubernetes:** If you run a microservice in a
+Kubernetes cluster, it can present its service account token (typically an OIDC
+JWT) to ToolHive. ToolHive validates the token using the cluster's OIDC issuer
+and JWKS endpoint, enabling secure, automated authentication for your internal
+services.
+
+### Token-based authentication
+
+ToolHive supports two types of access tokens for authentication:
+
+**JWT tokens (JSON Web Tokens):** Self-contained tokens that include identity
+information within the token itself. JWTs are validated locally using
+cryptographic signatures and consist of three parts:
+
+1. **Header:** Metadata about the token
+2. **Payload:** Claims about the entity (typically you or your service)
+3. **Signature:** Ensures the token hasn't been altered
+
+**Opaque tokens:** Reference tokens that don't contain identity information
+directly. ToolHive validates these tokens by querying the identity provider's
+token introspection endpoint to retrieve the associated claims.
+
+ToolHive automatically detects the token type and uses the appropriate
+validation method—attempting JWT validation first and falling back to token
+introspection if needed.
+
+### Authentication flow
+
+The authentication process follows these steps:
+
+1. **Token acquisition:** You obtain an access token from your identity
+ provider.
+2. **Token presentation:** You include the token in your requests to ToolHive
+ (typically in the Authorization header).
+3. **Token validation:** ToolHive validates the token using either:
+ - **Local validation:** For JWT tokens, verifying the signature, expiration,
+ and claims using the provider's public keys (JWKS)
+ - **Remote validation:** For opaque tokens, querying the provider's token
+ introspection endpoint to verify the token and retrieve claims
+4. **Identity extraction:** ToolHive extracts your identity information from the
+ validated token claims.
+
+```mermaid
+flowchart TD
+ Client -->|Access Token| ToolHive
+ ToolHive -->|Validate Token| Identity_Provider[Identity Provider]
+ ToolHive -->|Evaluate Cedar Policy| Cedar_Authorizer[Cedar Authorizer]
+ Cedar_Authorizer -->|Permit| MCP_Server
+ Cedar_Authorizer -->|Deny| Denied[403 Forbidden]
+```
+
+### Embedded authorization server
+
+In the standard authentication flow described above, clients obtain tokens
+independently from an external identity provider and present them to ToolHive
+for validation. The embedded authorization server provides an alternative model
+where ToolHive itself acts as an OAuth authorization server, retrieving tokens
+from an upstream identity provider on behalf of clients.
+
+:::note
+
+The embedded authorization server is currently available only for Kubernetes
+deployments using the ToolHive Operator.
+
+:::
+
+From the client's perspective, the embedded authorization server provides a
+standard OAuth 2.0 experience:
+
+1. If the client is not yet registered, it registers via Dynamic Client
+ Registration (DCR), receiving a `client_id` and `client_secret`.
+2. The client is directed to the ToolHive authorization endpoint.
+3. ToolHive redirects the client to the upstream identity provider for
+ authentication (for example, signing in with GitHub or Atlassian).
+4. ToolHive exchanges the authorization code for upstream tokens and issues its
+ own JWT to the client, signed with keys you configure.
+5. The client includes this JWT as a `Bearer` token in the `Authorization`
+ header on subsequent requests.
+
+Behind the scenes, ToolHive stores the upstream tokens and uses them to
+authenticate MCP server requests to external APIs. For the complete flow,
+including token storage and forwarding, see
+[Embedded authorization server](./backend-auth.mdx#embedded-authorization-server).
+
+For Kubernetes setup instructions, see
+[Set up embedded authorization server authentication](../guides-k8s/auth-k8s.mdx#set-up-embedded-authorization-server-authentication).
+
+### Identity providers
+
+ToolHive can integrate with any provider that supports OAuth 2.1 or OIDC,
+including:
+
+- Google
+- GitHub
+- Microsoft Entra ID (Azure AD)
+- Okta
+- Auth0
+- Kubernetes (service account tokens)
+
+These same providers work with both external token validation and the embedded
+authorization server. For the embedded authorization server, the upstream
+provider must support the OAuth 2.0 authorization code flow.
+
+### Token validation methods
+
+ToolHive supports multiple token validation methods to work with different
+identity providers:
+
+- **JWT validation:** For providers that issue JWT tokens, ToolHive validates
+ tokens locally using the provider's JWKS endpoint. This verifies the token's
+ signature, expiration, and claims without calling the identity provider for
+ each request.
+- **Token introspection:** For providers that issue opaque tokens, ToolHive
+ validates tokens by querying the provider's introspection endpoint. This
+ supports RFC 7662 (OAuth 2.0 Token Introspection), Google's tokeninfo API, and
+ GitHub's token validation API.
+
+ToolHive automatically detects the token type—it first attempts JWT validation,
+and if that fails, it falls back to token introspection. This means you don't
+need to configure which validation method to use; ToolHive handles it
+automatically based on the token format.
+
+## Authorization framework
+
+After authentication, ToolHive enforces authorization using Amazon's Cedar
+policy language. ToolHive acts as a gateway in front of MCP servers, handling
+all authorization checks before requests reach the server logic. This means MCP
+servers do not need to implement their own OAuth or custom authorization
+logic—ToolHive centralizes and standardizes access control.
+
+### Why Cedar for authorization?
+
+Cedar provides several advantages for MCP server authorization:
+
+- **Expressive and flexible:** Cedar supports both role-based (RBAC) and
+ attribute-based (ABAC) access control patterns, letting you create policies
+ that match your security requirements.
+- **Formally verified:** Cedar's design has been formally verified for safety
+ and security properties, which reduces the risk of policy bugs.
+- **Human-readable:** Cedar policies use clear, declarative syntax that's easy
+ to read, write, and audit.
+- **Policy enforcement point:** ToolHive blocks unauthorized requests before
+ they reach the MCP server, which reduces risk and simplifies server code.
+- **Secure by default:** Authorization is explicit—if a request is not
+ explicitly permitted, it is denied. Deny rules take precedence over permit
+ rules (deny overrides).
+
+### Authorization components
+
+ToolHive's authorization framework consists of:
+
+1. **Cedar authorizer:** Evaluates Cedar policies to determine if a request is
+ authorized
+2. **Authorization middleware:** Extracts information from MCP requests and uses
+ the Cedar Authorizer
+3. **Configuration:** A JSON or YAML file that specifies the Cedar policies and
+ entities
+
+### Authorization flow
+
+When a request arrives at an MCP server with authorization enabled:
+
+1. The authentication middleware authenticates the client and adds token claims
+ to the request context
+2. The authorization middleware extracts information from the request
+ (principal, action, resource, and any arguments)
+3. The Cedar authorizer evaluates policies to determine if the request is
+ authorized
+4. If authorized, the request proceeds; otherwise, a 403 Forbidden response is
+ returned
+
+```mermaid
+flowchart TD
+ Client -->|Access Token| ToolHive
+ ToolHive -->|Validate Token| Auth_Middleware
+ Auth_Middleware -->|Extract Claims| Authz_Middleware
+ Authz_Middleware -->|Evaluate Cedar Policies| Cedar_Authorizer
+ Cedar_Authorizer -->|Permit| MCP_Server
+ Cedar_Authorizer -->|Deny| Denied[403 Forbidden]
+```
+
+## Security and operational benefits
+
+ToolHive's authentication and authorization approach provides several key
+benefits:
+
+- **Separation of concerns:** Authentication and authorization are handled
+ independently, following security best practices.
+- **Integration with existing systems:** Use your existing identity
+ infrastructure (SSO, IdPs, Kubernetes, etc.).
+- **Centralized, flexible policy model:** Define precise, auditable access rules
+ in a single place—no need to modify MCP server code.
+- **Secure by default:** Requests are denied unless explicitly permitted by
+ policy, with deny precedence for maximum safety.
+- **Auditable and versionable:** Policies are clear, declarative, and can be
+ tracked in version control for compliance and review.
+- **Developer and operator friendly:** ToolHive acts as a smart proxy, so you
+ don't need to implement complex OAuth or custom auth logic in every server.
+
+## Client authentication support
+
+While ToolHive provides a robust authentication and authorization framework for
+MCP servers, authentication support varies across the MCP client ecosystem.
+
+### MCP client capabilities
+
+The MCP ecosystem includes numerous clients with varying levels of
+authentication support. Authentication support is not universal. Some clients
+focus primarily on local, unauthenticated MCP servers for development workflows,
+while others provide enterprise-grade authentication for production deployments.
+
+When selecting an MCP client for authenticated workflows, look for clients that
+support the MCP authentication standards, including OAuth 2.1 and
+transport-level authentication mechanisms.
+
+ToolHive's OIDC-based authentication approach aligns with industry standards and
+works with clients that support modern authentication protocols. As the MCP
+ecosystem continues to mature, we expect authentication support to become more
+standardized across clients.
+
+## Related information
+
+- For configuring the embedded authorization server in Kubernetes, see
+ [Embedded authorization server authentication](../guides-k8s/auth-k8s.mdx#set-up-embedded-authorization-server-authentication)
+- For backend authentication concepts, see
+ [Backend authentication](./backend-auth.mdx)
+- For detailed policy writing guidance, see
+ [Cedar policies](./cedar-policies.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/concepts/backend-auth.mdx b/versioned_docs/version-1.0/toolhive/concepts/backend-auth.mdx
new file mode 100644
index 00000000..04efb279
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/concepts/backend-auth.mdx
@@ -0,0 +1,477 @@
+---
+title: Backend authentication
+description:
+ Understanding how MCP servers authenticate to external services using
+ ToolHive's backend authentication patterns, including static credentials,
+ token exchange, and the embedded authorization server.
+---
+
+This document explains how ToolHive helps MCP servers authenticate to
+third-party APIs and backend services exposed through the MCP servers. You'll
+learn about the backend authentication patterns ToolHive supports, why they
+improve security and multi-tenancy, and how they simplify MCP server
+development.
+
+:::info[Scope of this documentation]
+
+This documentation covers **MCP-server-to-backend authentication**—how MCP
+servers authenticate to external services or APIs they call (for example, a
+GitHub MCP server authenticating to the GitHub API).
+
+This is different from **client-to-MCP-server authentication**, which involves
+how clients authenticate to the MCP server itself. That topic is covered in
+[Authentication and authorization](./auth-framework.mdx).
+
+:::
+
+## The challenge of backend authentication
+
+The MCP specification focuses on authorization to the MCP server but doesn't
+specify how an MCP server should authenticate to the services it exposes. This
+is intentionally left to implementers, which makes sense from a specification
+perspective but leaves MCP server developers without clear guidance.
+
+Many MCP servers today either embed static API keys or require custom
+authentication code. This creates several problems:
+
+- **Security risks:** Long-lived credentials stored in configuration files or
+ environment variables can be compromised
+- **Audit challenges:** When multiple users share a service account, you can't
+ trace individual actions
+- **Multi-tenancy complexity:** Supporting multiple tenants with isolated
+ credentials requires significant custom code
+
+ToolHive addresses these challenges with three backend authentication patterns:
+static credentials for services that don't support OAuth, token exchange for
+services in the same or federated trust domain, and the embedded authorization
+server for OAuth-based external APIs where no federation exists.
+
+## How ToolHive handles backend authentication
+
+ToolHive sits between clients and MCP servers, and can acquire backend
+credentials on behalf of the MCP server. Depending on the pattern, it might
+exchange the client's token, run an OAuth flow against an external provider, or
+inject static credentials. In each case, the MCP server receives ready-to-use
+credentials—via an `Authorization: Bearer` header, another header, or
+environment variables, depending on the pattern—without needing to implement
+custom authentication logic or manage secrets directly.
+
+## Backend authentication patterns
+
+ToolHive supports three patterns for backend authentication. Which one you use
+depends on the relationship between your identity provider (IdP) and the backend
+service.
+
+### Static credentials and API keys
+
+When a backend service requires API keys, database passwords, or other static
+credentials, you can configure them directly in ToolHive as environment
+variables, secret files, or injected headers.
+
+This is the simplest pattern, but it provides the least security and
+auditability. Static credentials are long-lived and shared across all users, so
+there is no per-user attribution in audit logs.
+
+When using static credentials, consider integrating with a secret management
+system like [HashiCorp Vault](../integrations/vault.mdx) for secure storage and
+rotation.
+
+### Token exchange
+
+When the backend service trusts the same IdP as your MCP clients—or federation
+is configured between the two IdPs—ToolHive can exchange the client's token for
+one scoped to the backend service using RFC 8693 token exchange. This preserves
+the user's identity across services and provides short-lived, narrowly scoped
+tokens. Because the trust relationship is pre-configured at the IdP, the
+exchange is transparent to the end user—no consent screen required.
+
+#### Same IdP with token exchange
+
+When both the MCP server and the backend service trust the same IdP, and that
+IdP supports [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693) token
+exchange, ToolHive can exchange the internal token for an external one.
+
+```mermaid
+flowchart LR
+ User[User]
+ IDP[Identity Provider]
+ ToolHive[ToolHive Middleware]
+ MCP[MCP Server]
+ Upstream[Upstream Service]
+
+ User -->|login & receive token| IDP
+ User -->|request with token| ToolHive
+ ToolHive -->|subject_token → exchange| IDP
+ IDP -->|external_token issued| ToolHive
+ ToolHive -->|pass external_token| MCP
+ MCP -->|calls with external_token| Upstream
+ Upstream -->|validates token| IDP
+```
+
+**How it works:**
+
+1. The user authenticates to the MCP client and receives an access token from
+ the IdP
+2. ToolHive's token exchange middleware contacts the IdP, presenting the user's
+ access token
+3. The IdP issues a new access token with different audience and scopes
+4. ToolHive passes this access token to the MCP server
+5. The MCP server uses the access token to call the upstream service
+
+#### Federated IdPs with identity mapping
+
+When the backend service trusts a different IdP, but federation is configured
+between the two IdPs, ToolHive can use the federated identity service to issue
+short-lived tokens. Examples include Google's Security Token Service (STS) for
+Google Cloud services and AWS STS for AWS services—both can issue tokens based
+on your corporate identity.
+
+```mermaid
+flowchart LR
+ User[User]
+ IDP1[IdP A - User Login]
+ ToolHive[ToolHive Middleware]
+ IDP2[IdP B - Federated]
+ MCP[MCP Server]
+ Upstream[Upstream Service]
+
+ User -->|login & receive token_A| IDP1
+ User -->|request with token_A| ToolHive
+ ToolHive -->|submit token_A| IDP2
+ IDP2 -->|issue token_B| ToolHive
+ ToolHive -->|pass token_B| MCP
+ MCP -->|call with token_B| Upstream
+ Upstream -->|validate token_B| IDP2
+```
+
+**How it works:**
+
+1. The user authenticates to their MCP client with a corporate IdP and receives
+ token_A
+2. ToolHive submits token_A to the federated identity service
+3. The federated service maps the identity and issues token_B
+4. ToolHive passes token_B to the MCP server
+5. The MCP server uses token_B to call the upstream service
+
+### Embedded authorization server
+
+When the MCP server needs to call an external API where no federation
+relationship exists—such as GitHub, Google Workspace, or Atlassian APIs—the
+embedded authorization server handles the full OAuth web flow against the
+external provider. The proxy redirects the user to authenticate directly with
+the external service, obtains tokens on behalf of the user, and passes the
+upstream token to the MCP server.
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Proxy as ToolHive Proxy
+ participant ExtProvider as External Provider
+
+ User->>Proxy: Connect
+ Proxy-->>User: Redirect to login
+ User->>ExtProvider: Authenticate
+ ExtProvider->>Proxy: Authorization code
+ Proxy->>ExtProvider: Exchange code for token
+ ExtProvider->>Proxy: Upstream tokens
+ Proxy->>User: Issue JWT
+```
+
+On subsequent MCP requests, ToolHive uses the JWT to retrieve the stored
+upstream tokens and forward them to the MCP server. For details on this
+mechanism, see [Token storage and forwarding](#token-storage-and-forwarding).
+
+The embedded authorization server runs in-process within the ToolHive proxy—no
+separate infrastructure is needed. It supports Dynamic Client Registration
+(DCR), so MCP clients can register automatically with ToolHive—no manual client
+configuration in ToolHive is required.
+
+:::note
+
+The embedded authorization server is currently available only for Kubernetes
+deployments using the ToolHive Operator.
+
+:::
+
+#### Key characteristics
+
+- **In-process execution:** The authorization server runs within the ToolHive
+ proxy—no separate infrastructure or sidecar containers needed.
+- **Configurable signing keys:** JWTs are signed with keys you provide,
+ supporting key rotation for zero-downtime updates.
+- **Flexible upstream providers:** Supports both OIDC providers (with automatic
+ endpoint discovery) and OAuth 2.0 providers (with explicit endpoint
+ configuration).
+- **Configurable token lifespans:** Access tokens, refresh tokens, and
+ authorization codes have configurable durations with sensible defaults.
+- **Dynamic Client Registration (DCR):** Supports OAuth 2.0 Dynamic Client
+ Registration (RFC 7591), allowing MCP clients to register automatically with
+ ToolHive's authorization server—no manual client registration in ToolHive is
+ required.
+- **Direct upstream redirect:** The embedded authorization server redirects
+ clients directly to the upstream provider for authentication (for example,
+ GitHub or Atlassian).
+- **Single upstream provider:** Currently supports one upstream identity
+ provider per configuration.
+
+:::info[Chained authentication not yet supported]
+
+The embedded authorization server redirects clients directly to the upstream
+provider. This means the upstream provider must be the service whose API the MCP
+server calls. Chained authentication—where a client authenticates with a
+corporate IdP like Okta, which then federates to an external provider like
+GitHub—is not yet supported. If your deployment requires this pattern, consider
+using [token exchange](#same-idp-with-token-exchange) with a federated identity
+provider instead.
+
+:::
+
+#### Token storage and forwarding
+
+The embedded authorization server stores upstream tokens (access tokens, refresh
+tokens, and ID tokens from external providers) in session storage. When the
+OAuth flow completes, the server generates a unique session ID and stores the
+upstream tokens keyed by this ID. The JWT issued to the client contains a `tsid`
+(Token Session ID) claim that references this session.
+
+When a client makes an MCP request with this JWT:
+
+1. The ToolHive proxy validates the JWT signature and extracts the `tsid` claim
+2. It retrieves the upstream tokens from session storage using the `tsid`
+3. The proxy replaces the `Authorization` header with the upstream access token
+4. The request is forwarded to the MCP server with the external provider's token
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant Proxy as ToolHive Proxy
+ participant Store as Session Storage
+ participant MCP as MCP Server
+ participant API as External API
+
+ Note over Client,Store: Initial OAuth flow
+ Proxy->>Store: Store upstream tokens keyed by session ID
+ Proxy-->>Client: Issue JWT with tsid claim
+
+ Note over Client,API: Subsequent MCP requests
+ Client->>Proxy: MCP request with JWT
+ Proxy->>Proxy: Validate JWT signature
+ Proxy->>Store: Look up upstream token using tsid from JWT
+ Store-->>Proxy: Return upstream access token
+ Proxy->>MCP: Forward request with upstream access token
+ MCP->>API: Call external API
+ API-->>MCP: Response
+ MCP-->>Proxy: Response
+ Proxy-->>Client: Response
+```
+
+This mechanism allows MCP servers to call external APIs with the user's actual
+credentials from the upstream provider, while the client only needs to manage a
+single ToolHive-issued JWT.
+
+#### Automatic token refresh
+
+Upstream access tokens have their own expiration, independent of the ToolHive
+JWT lifespan. When the stored upstream access token has expired, ToolHive
+automatically refreshes it using the stored refresh token before forwarding the
+request — your MCP session continues without re-authentication.
+
+If the refresh token is also expired or has been revoked by the upstream
+provider, ToolHive returns a `401` response, prompting you to re-authenticate
+through the OAuth flow.
+
+:::warning[Session storage limitations]
+
+By default, session storage is in-memory only. Upstream tokens are lost when
+pods restart, requiring users to re-authenticate. For production deployments,
+configure Redis Sentinel as the storage backend for persistent, highly available
+session storage. See
+[Configure session storage](../guides-k8s/auth-k8s.mdx#configure-session-storage)
+for a quick setup, or the full
+[Redis Sentinel session storage](../guides-k8s/redis-session-storage.mdx)
+tutorial for an end-to-end walkthrough.
+
+:::
+
+For the client-facing OAuth flow, see
+[Embedded authorization server](./auth-framework.mdx#embedded-authorization-server).
+For Kubernetes setup instructions, see
+[Set up embedded authorization server authentication](../guides-k8s/auth-k8s.mdx#set-up-embedded-authorization-server-authentication).
+
+## Token exchange in depth
+
+This section provides implementation details for the token exchange patterns
+described above. For setup instructions, see
+[Configure token exchange](../guides-cli/token-exchange.mdx) (CLI) or
+[Configure token exchange in Kubernetes](../guides-k8s/token-exchange-k8s.mdx).
+
+### Same IdP with token exchange
+
+The token exchange flow demonstrates how ToolHive transforms user identity
+tokens into properly scoped service tokens.
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant ToolHive
+ participant IDP
+ participant MCP Server
+ participant Upstream Service
+
+ Client->>ToolHive: Request with user token
+ ToolHive->>IDP: Validate token
+ IDP-->>ToolHive: Token valid
+ ToolHive->>IDP: Exchange token request
+ IDP-->>ToolHive: Service token
+ ToolHive->>MCP Server: Request with service token
+ MCP Server->>Upstream Service: API call
+ Upstream Service-->>MCP Server: Response
+ MCP Server-->>ToolHive: Response
+ ToolHive-->>Client: Response
+```
+
+When a client authenticates to ToolHive, it receives a token scoped for the MCP
+server:
+
+```json
+{
+ "iss": "https://idp.example.com/oauth2/default",
+ "aud": "mcp-server",
+ "scp": ["backend-mcp:tools:call", "backend-mcp:tools:list"],
+ "sub": "user@example.com"
+}
+```
+
+ToolHive's token exchange middleware contacts the IdP and exchanges this token
+for one scoped to the backend service:
+
+```json
+{
+ "iss": "https://idp.example.com/oauth2/default",
+ "aud": "backend-server",
+ "scp": ["backend-api:read"],
+ "sub": "user@example.com"
+}
+```
+
+Notice how the audience (`aud`) and scopes (`scp`) change while preserving the
+user's identity (`sub`). This exchanged token is then injected into the
+`Authorization: Bearer` HTTP header and passed to the MCP server.
+
+### Federated IdPs with identity mapping
+
+When using federated identity providers, ToolHive can map your corporate
+identity to an external service identity. This is particularly useful for
+accessing cloud services like Google Cloud Platform.
+
+The client authenticates with your corporate IdP and receives a token:
+
+```json
+{
+ "iss": "https://idp.example.com/oauth2/default",
+ "aud": "mcp-server",
+ "sub": "user@example.com",
+ "email": "user@example.com",
+ "scp": ["mcp:tools:call", "mcp:tools:list"],
+ "exp": 1729641600,
+ "iat": 1729638000
+}
+```
+
+ToolHive's token exchange middleware calls the external Security Token Service
+(STS) endpoint. For Google Cloud, this looks like:
+
+```http
+POST https://sts.googleapis.com/v1/token
+Content-Type: application/x-www-form-urlencoded
+
+grant_type=urn:ietf:params:oauth:grant-type:token-exchange
+&audience=//iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID
+&scope=https://www.googleapis.com/auth/bigquery
+&requested_token_type=urn:ietf:params:oauth:token-type:access_token
+&subject_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
+&subject_token_type=urn:ietf:params:oauth:token-type:jwt
+```
+
+The federated service returns a token that maps your corporate identity to a
+federated principal:
+
+```json
+{
+ "iss": "https://sts.googleapis.com",
+ "sub": "principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/subject/user@example.com",
+ "aud": "https://bigquery.googleapis.com/",
+ "email": "user@example.com",
+ "scope": "https://www.googleapis.com/auth/bigquery",
+ "exp": 1729641600,
+ "iat": 1729638000
+}
+```
+
+This exchanged token is injected into the `Authorization: Bearer` HTTP header
+and passed to the MCP server. The MCP server uses this token to make upstream
+API calls, with each request attributed to the individual user's federated
+identity rather than a shared service account.
+
+Federation-based token exchange has several important characteristics that
+distinguish it from standard token exchange:
+
+1. **No client authentication required:** The external STS endpoint doesn't
+ require `client_id` or `client_secret`. The OAuth JWT itself serves as proof
+ of identity.
+2. **Identity federation pool as intermediary:** The `audience` parameter points
+ to a federation pool configuration, not directly to the target service.
+3. **Principal mapping:** User attributes (email, sub) from the OAuth token are
+ mapped to federated principals for access control.
+4. **Individual audit trail:** Upstream service audit logs show the individual
+ user identity, not a service account.
+
+## Security and operational benefits
+
+ToolHive's token-based authentication patterns (token exchange and the embedded
+authorization server) provide several key advantages over static credentials:
+
+- **Secure:** MCP servers receive short-lived, properly scoped access tokens
+ instead of embedding long-lived secrets
+- **Auditable:** Each API call is attributed to the individual user identity,
+ making audit trails clear and meaningful
+- **Multi-tenant friendly:** Token scoping naturally supports tenant isolation
+ and separation of duties
+- **Developer friendly:** MCP servers don't need custom authentication
+ logic—they just use the provided token
+- **Least privilege:** Tokens are narrowly scoped to specific audiences and
+ permissions, reducing the blast radius if compromised
+- **Consistent:** The same pattern works across different backend services and
+ identity providers
+
+## Choosing the right backend authentication pattern
+
+| Scenario | Pattern | Why |
+| ------------------------------------------------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------- |
+| Backend only accepts API keys or static credentials | [Static credentials](#static-credentials-and-api-keys) | No OAuth support; configure credentials directly in ToolHive |
+| Backend trusts the same IdP as your clients | [Token exchange (same IdP)](#same-idp-with-token-exchange) | Exchange the client token for a backend-scoped token via RFC 8693 |
+| Backend trusts a federated IdP (for example, Google Cloud) | [Token exchange (federation)](#federated-idps-with-identity-mapping) | Map your corporate identity to the federated service |
+| Backend is an external API with no federation (for example, GitHub) | [Embedded authorization server](#embedded-authorization-server) | Run the full OAuth flow against the external provider |
+
+### Built-in AWS STS support
+
+For AWS services like the
+[AWS MCP Server](https://docs.aws.amazon.com/aws-mcp/), ToolHive has built-in
+support for exchanging OIDC tokens for temporary AWS credentials using
+`AssumeRoleWithWebIdentity`. This handles the STS exchange and SigV4 request
+signing automatically, with claim-based IAM role selection. See the
+[AWS STS integration tutorial](../integrations/aws-sts.mdx) for a step-by-step
+setup guide.
+
+## Related information
+
+- For client authentication concepts, see
+ [Authentication and authorization](./auth-framework.mdx)
+- For the embedded authorization server, see
+ [Embedded authorization server](./auth-framework.mdx#embedded-authorization-server)
+- For configuring the embedded authorization server in Kubernetes, see
+ [Set up embedded authorization server authentication](../guides-k8s/auth-k8s.mdx#set-up-embedded-authorization-server-authentication)
+- For configuring token exchange, see
+ [Configure token exchange](../guides-cli/token-exchange.mdx) (CLI) or
+ [Configure token exchange in Kubernetes](../guides-k8s/token-exchange-k8s.mdx)
+- For policy configuration, see [Cedar policies](./cedar-policies.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/concepts/cedar-policies.mdx b/versioned_docs/version-1.0/toolhive/concepts/cedar-policies.mdx
new file mode 100644
index 00000000..ec598b6e
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/concepts/cedar-policies.mdx
@@ -0,0 +1,408 @@
+---
+title: Cedar policies
+description: Writing and configuring Cedar policies for MCP server authorization.
+---
+
+This document provides detailed guidance on writing and configuring Cedar
+policies for MCP server authorization. You'll learn how to create effective
+policies, configure authorization settings, and troubleshoot common issues.
+
+:::info
+
+For the conceptual overview of authentication and authorization, see
+[Authentication and authorization framework](./auth-framework.mdx).
+
+:::
+
+## Cedar policy language
+
+Cedar policies express authorization rules in a clear, declarative syntax:
+
+```text
+permit|forbid(principal, action, resource) when { conditions };
+```
+
+- `permit` or `forbid`: Whether to allow or deny the operation
+- `principal`: The entity making the request (the client)
+- `action`: The operation being performed
+- `resource`: The object being accessed
+- `conditions`: Optional conditions that must be satisfied
+
+## MCP-specific entities
+
+In the context of MCP servers, Cedar policies use the following entities:
+
+### Principal
+
+The client making the request, identified by the `sub` claim in the access
+token:
+
+- Format: `Client::`
+- Example: `Client::user123`
+
+### Action
+
+The operation being performed on an MCP feature:
+
+- Format: `Action::`
+- Examples:
+ - `Action::"call_tool"`: Call a tool
+ - `Action::"get_prompt"`: Get a prompt
+ - `Action::"read_resource"`: Read a resource
+ - `Action::"list_tools"`: List available tools
+
+### Resource
+
+The object being accessed:
+
+- Format: `::`
+- Examples:
+ - `Tool::"weather"`: The weather tool
+ - `Prompt::"greeting"`: The greeting prompt
+ - `Resource::"data"`: The data resource
+
+## Configuration formats
+
+You can configure Cedar authorization using either JSON or YAML format:
+
+### JSON configuration
+
+```json
+{
+ "version": "1.0",
+ "type": "cedarv1",
+ "cedar": {
+ "policies": [
+ "permit(principal, action == Action::\"call_tool\", resource == Tool::\"weather\");",
+ "permit(principal, action == Action::\"get_prompt\", resource == Prompt::\"greeting\");",
+ "permit(principal, action == Action::\"read_resource\", resource == Resource::\"data\");"
+ ],
+ "entities_json": "[]"
+ }
+}
+```
+
+### YAML configuration
+
+```yaml
+version: '1.0'
+type: cedarv1
+cedar:
+ policies:
+ - 'permit(principal, action == Action::"call_tool", resource ==
+ Tool::"weather");'
+ - 'permit(principal, action == Action::"get_prompt", resource ==
+ Prompt::"greeting");'
+ - 'permit(principal, action == Action::"read_resource", resource ==
+ Resource::"data");'
+ entities_json: '[]'
+```
+
+### Configuration fields
+
+- `version`: The version of the configuration format
+- `type`: The type of authorization configuration (currently only `cedarv1` is
+ supported)
+- `cedar`: The Cedar-specific configuration
+ - `policies`: An array of Cedar policy strings
+ - `entities_json`: A JSON string representing Cedar entities
+
+## Writing effective policies
+
+Understanding how to write Cedar policies is crucial for securing your MCP
+servers effectively. This section provides practical guidance for creating
+policies that match your security requirements.
+
+### Basic policy patterns
+
+Start with simple policies and build complexity as needed:
+
+#### Allow specific tool access
+
+```text
+permit(principal, action == Action::"call_tool", resource == Tool::"weather");
+```
+
+This policy allows any authenticated client to call the weather tool. It's
+useful when you want to provide broad access to specific functionality.
+
+#### Allow specific user access
+
+```text
+permit(principal == Client::"user123", action == Action::"call_tool", resource);
+```
+
+This policy allows a specific user to call any tool. Use this pattern when you
+need to grant broad permissions to trusted users.
+
+### Role-based access control (RBAC)
+
+RBAC policies use roles from JWT claims to determine access:
+
+```text
+permit(principal, action == Action::"call_tool", resource) when {
+ principal.claim_roles.contains("admin")
+};
+```
+
+This policy allows clients with the "admin" role to call any tool. RBAC is
+effective when you have well-defined roles in your organization.
+
+### Attribute-based access control (ABAC)
+
+ABAC policies use multiple attributes to make fine-grained decisions:
+
+```text
+permit(principal, action == Action::"call_tool", resource == Tool::"sensitive_data") when {
+ principal.claim_roles.contains("data_analyst") &&
+ resource.arg_data_level <= principal.claim_clearance_level
+};
+```
+
+This policy allows data analysts to access sensitive data, but only if their
+clearance level is sufficient. ABAC provides the most flexibility for complex
+security requirements.
+
+## Working with JWT claims
+
+JWT claims from your identity provider become available in policies with a
+`claim_` prefix. You can use these claims in two ways:
+
+**On the principal entity:**
+
+```text
+permit(principal, action == Action::"call_tool", resource == Tool::"weather") when {
+ principal.claim_name == "John Doe"
+};
+```
+
+**In the context:**
+
+```text
+permit(principal, action == Action::"call_tool", resource == Tool::"weather") when {
+ context.claim_name == "John Doe"
+};
+```
+
+Both approaches work identically. Choose the one that makes your policies more
+readable.
+
+## Working with tool arguments
+
+Tool arguments become available in policies with an `arg_` prefix. This lets you
+create policies based on the specific parameters of requests:
+
+**On the resource entity:**
+
+```text
+permit(principal, action == Action::"call_tool", resource == Tool::"weather") when {
+ resource.arg_location == "New York" || resource.arg_location == "London"
+};
+```
+
+**In the context:**
+
+```text
+permit(principal, action == Action::"call_tool", resource == Tool::"weather") when {
+ context.arg_location == "New York" || context.arg_location == "London"
+};
+```
+
+This policy allows weather tool calls only for specific locations, demonstrating
+how you can control access based on request parameters.
+
+## List operations and filtering
+
+List operations (`tools/list`, `prompts/list`, `resources/list`) work
+differently from other operations. They're always allowed, but the response is
+automatically filtered based on what the user can actually access:
+
+- `tools/list` shows only tools the user can call (based on `call_tool`
+ policies)
+- `prompts/list` shows only prompts the user can get (based on `get_prompt`
+ policies)
+- `resources/list` shows only resources the user can read (based on
+ `read_resource` policies)
+
+You don't need to write explicit policies for list operations. Instead, focus on
+the underlying access policies, and the lists will be filtered automatically.
+
+For example, if you have this policy:
+
+```text
+permit(principal, action == Action::"call_tool", resource == Tool::"weather");
+```
+
+Then `tools/list` will only show the "weather" tool for that user.
+
+## Policy evaluation and secure defaults
+
+Understanding how Cedar evaluates policies helps you write more effective and
+secure authorization rules.
+
+### Evaluation order
+
+ToolHive's policy evaluation follows a secure-by-default, least-privilege model:
+
+1. **Deny precedence:** If any `forbid` policy matches, the request is denied
+2. **Permit evaluation:** If any `permit` policy matches, the request is
+ authorized
+3. **Default deny:** If no policy matches, the request is denied
+
+This means that `forbid` policies always override `permit` policies, and any
+request not explicitly permitted is denied. This approach minimizes risk and
+ensures that only authorized actions are allowed.
+
+### Designing secure policies
+
+When writing policies, follow these principles:
+
+**Start with least privilege:** Begin by denying everything, then add specific
+permissions as needed. This approach is more secure than starting with broad
+permissions and then trying to restrict them.
+
+**Use explicit deny sparingly:** While `forbid` policies can be useful, they can
+also make your policy set harder to understand. In most cases, the default deny
+behavior is sufficient.
+
+**Test your policies:** Always test policies with real requests to ensure they
+work as expected. Pay special attention to edge cases and error conditions.
+
+## Advanced policy examples
+
+### Combining JWT claims and tool arguments
+
+You can combine JWT claims and tool arguments in your policies to create more
+sophisticated authorization rules:
+
+```text
+permit(principal, action == Action::"call_tool", resource == Tool::"sensitive_data") when {
+ principal.claim_roles.contains("data_analyst") &&
+ resource.arg_data_level <= principal.claim_clearance_level
+};
+```
+
+This policy allows clients with the "data_analyst" role to access the
+sensitive_data tool, but only if their clearance level (from JWT claims) is
+sufficient for the requested data level (from tool arguments).
+
+### Multi-tenant environments
+
+In multi-tenant environments, you can use policies to isolate tenants:
+
+```text
+permit(principal, action, resource) when {
+ principal.claim_tenant_id == resource.tenant_id
+};
+```
+
+This ensures that clients can only access resources belonging to their tenant.
+
+### Data sensitivity levels
+
+For data with different sensitivity levels:
+
+```text
+permit(principal, action == Action::"call_tool", resource == Tool::"data_access") when {
+ principal.claim_clearance_level >= resource.arg_data_sensitivity
+};
+```
+
+This ensures that clients can only access data within their clearance level.
+
+### Geographic restrictions
+
+For geographically restricted resources:
+
+```text
+permit(principal, action == Action::"call_tool", resource == Tool::"geo_restricted") when {
+ principal.claim_location in ["US", "Canada", "Mexico"]
+};
+```
+
+This restricts access based on the client's location.
+
+### Time-based access
+
+For resources that should only be accessible during certain hours:
+
+```text
+permit(principal, action == Action::"call_tool", resource == Tool::"business_hours") when {
+ context.current_hour >= 9 && context.current_hour <= 17
+};
+```
+
+This restricts access to business hours only.
+
+## Entity attributes
+
+Cedar entities can have attributes that can be used in policy conditions. The
+authorization middleware automatically adds JWT claims and tool arguments as
+attributes to the principal entity.
+
+You can also define custom entities with attributes in the `entities_json` field
+of the configuration file:
+
+```json
+{
+ "version": "1.0",
+ "type": "cedarv1",
+ "cedar": {
+ "policies": [
+ "permit(principal, action == Action::\"call_tool\", resource) when { resource.owner == principal.claim_sub };"
+ ],
+ "entities_json": "[
+ {
+ \"uid\": \"Tool::weather\",
+ \"attrs\": {
+ \"owner\": \"user123\"
+ }
+ }
+ ]"
+ }
+}
+```
+
+This configuration defines a custom entity for the weather tool with an `owner`
+attribute set to `user123`. The policy allows clients to call tools only if they
+own them.
+
+## Troubleshooting policies
+
+When policies don't work as expected, follow this systematic approach:
+
+### Request is denied unexpectedly
+
+1. **Check policy syntax:** Ensure your policies are correctly formatted and use
+ valid Cedar syntax.
+2. **Verify entity matching:** Confirm that the principal, action, and resource
+ in your policies match the actual values in the request.
+3. **Test conditions:** Check that any conditions in your policies are satisfied
+ by the request context.
+4. **Remember default deny:** If no policy explicitly permits the request, it
+ will be denied.
+
+### JWT claims are not available
+
+1. **Verify JWT middleware:** Ensure that JWT authentication is configured
+ correctly and running before authorization.
+2. **Check token claims:** Verify that the JWT token contains the expected
+ claims.
+3. **Use correct prefix:** Remember that JWT claims are available with a
+ `claim_` prefix.
+
+### Tool arguments are not available
+
+1. **Check request format:** Ensure that tool arguments are correctly specified
+ in the request.
+2. **Use correct prefix:** Remember that tool arguments are available with an
+ `arg_` prefix.
+3. **Verify argument names:** Confirm that the argument names in your policies
+ match those in the actual requests.
+
+## Related information
+
+- For the conceptual overview, see
+ [Authentication and authorization framework](./auth-framework.mdx)
+- For detailed Cedar policy syntax, see
+ [Cedar documentation](https://docs.cedarpolicy.com/)
diff --git a/versioned_docs/version-1.0/toolhive/concepts/embedded-auth-server.mdx b/versioned_docs/version-1.0/toolhive/concepts/embedded-auth-server.mdx
new file mode 100644
index 00000000..109bb07d
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/concepts/embedded-auth-server.mdx
@@ -0,0 +1,193 @@
+---
+title: Embedded authorization server
+description:
+ How the ToolHive embedded authorization server works, including the OAuth
+ flow, token storage and forwarding, and when to use it.
+---
+
+The embedded authorization server is an OAuth 2.0 authorization server that runs
+in-process within the ToolHive proxy. It solves a specific problem: how to
+authenticate MCP server requests to external APIs—like GitHub, Google Workspace,
+or Atlassian—where no federation relationship exists between your identity
+provider and that external service.
+
+Without the embedded auth server, every MCP client would need to register its
+own OAuth application with each external provider, manage redirect URIs, and
+handle token acquisition separately. The embedded auth server centralizes this:
+it handles the full OAuth web flow against the external provider on behalf of
+clients, stores the resulting tokens, and issues its own JWTs that clients use
+for subsequent requests.
+
+:::note
+
+The embedded authorization server is currently available only for Kubernetes
+deployments using the ToolHive Operator.
+
+:::
+
+## When to use the embedded authorization server
+
+Use the embedded authorization server when your MCP servers call external APIs
+on behalf of individual users and no federation relationship exists between your
+identity provider and those services.
+
+| Scenario | Pattern to use |
+| ------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
+| Backend only accepts API keys or static credentials | [Static credentials](./backend-auth.mdx#static-credentials-and-api-keys) |
+| Backend trusts the same IdP as your clients | [Token exchange (same IdP)](./backend-auth.mdx#same-idp-with-token-exchange) |
+| Backend trusts a federated IdP (for example, Google Cloud, AWS) | [Token exchange (federation)](./backend-auth.mdx#federated-idps-with-identity-mapping) |
+| Backend is an external API with no federation (for example, GitHub) | **Embedded authorization server** (this page) |
+
+## How the OAuth flow works
+
+From the client's perspective, the embedded authorization server provides a
+standard OAuth 2.0 experience:
+
+1. If the client is not yet registered, it registers via Dynamic Client
+ Registration (DCR, RFC 7591), receiving a `client_id` and `client_secret`. No
+ manual client registration in ToolHive is required.
+2. The client is directed to the ToolHive authorization endpoint.
+3. ToolHive redirects the client to the upstream identity provider for
+ authentication (for example, signing in with GitHub or Atlassian).
+4. ToolHive exchanges the authorization code for upstream tokens and issues its
+ own JWT to the client, signed with keys you configure.
+5. The client includes this JWT as a `Bearer` token in the `Authorization`
+ header on subsequent requests.
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Proxy as ToolHive Proxy
+ participant ExtProvider as External Provider
+
+ User->>Proxy: Connect
+ Proxy-->>User: Redirect to login
+ User->>ExtProvider: Authenticate
+ ExtProvider->>Proxy: Authorization code
+ Proxy->>ExtProvider: Exchange code for token
+ ExtProvider->>Proxy: Upstream tokens
+ Proxy->>User: Issue JWT
+```
+
+Behind the scenes, ToolHive stores the upstream tokens in session storage and
+uses them to authenticate MCP server requests to external APIs. The client only
+manages a single ToolHive-issued JWT.
+
+## Token storage and forwarding
+
+When the OAuth flow completes, the embedded auth server generates a unique
+session ID and stores the upstream tokens (access token, refresh token, and ID
+token from the external provider) keyed by this ID in session storage. The JWT
+issued to the client contains a `tsid` (Token Session ID) claim that references
+this session.
+
+When a client makes an MCP request with this JWT:
+
+1. The ToolHive proxy validates the JWT signature and extracts the `tsid` claim.
+2. It retrieves the upstream tokens from session storage using the `tsid`.
+3. The proxy replaces the `Authorization` header with the upstream access token.
+4. The request is forwarded to the MCP server with the external provider's
+ token.
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant Proxy as ToolHive Proxy
+ participant Store as Session Storage
+ participant MCP as MCP Server
+ participant API as External API
+
+ Note over Client,Store: Initial OAuth flow
+ Proxy->>Store: Store upstream tokens keyed by session ID
+ Proxy-->>Client: Issue JWT with tsid claim
+
+ Note over Client,API: Subsequent MCP requests
+ Client->>Proxy: MCP request with JWT
+ Proxy->>Proxy: Validate JWT signature
+ Proxy->>Store: Look up upstream token using tsid from JWT
+ Store-->>Proxy: Return upstream access token
+ Proxy->>MCP: Forward request with upstream access token
+ MCP->>API: Call external API
+ API-->>MCP: Response
+ MCP-->>Proxy: Response
+ Proxy-->>Client: Response
+```
+
+MCP servers receive the upstream access token in the `Authorization: Bearer`
+header—they don't need to implement custom authentication logic or manage
+secrets.
+
+## Automatic token refresh
+
+Upstream access tokens expire independently of the ToolHive JWT lifespan. When
+the stored upstream access token has expired, ToolHive automatically refreshes
+it using the stored refresh token before forwarding the request. Your MCP
+session continues without re-authentication.
+
+If the refresh token is also expired or has been revoked by the upstream
+provider, ToolHive returns a `401` response, prompting re-authentication through
+the OAuth flow.
+
+## Key characteristics
+
+- **In-process execution:** The authorization server runs within the ToolHive
+ proxy—no separate infrastructure or sidecar containers needed.
+- **Dynamic Client Registration (DCR):** Supports OAuth 2.0 DCR (RFC 7591),
+ allowing MCP clients to register automatically. No manual client registration
+ in ToolHive is required.
+- **Direct upstream redirect:** Redirects clients directly to the upstream
+ provider for authentication (for example, GitHub or Atlassian).
+- **Configurable signing keys:** JWTs are signed with keys you provide,
+ supporting key rotation for zero-downtime updates.
+- **Flexible upstream providers:** Supports OIDC providers (with automatic
+ endpoint discovery) and plain OAuth 2.0 providers (with explicit endpoint
+ configuration).
+- **Configurable token lifespans:** Access tokens, refresh tokens, and
+ authorization codes have configurable durations with sensible defaults.
+
+## Session storage
+
+By default, session storage is in-memory. Upstream tokens are lost when pods
+restart, requiring users to re-authenticate.
+
+For production deployments, configure Redis Sentinel as the storage backend for
+persistent, highly available session storage. See
+[Configure session storage](../guides-k8s/auth-k8s.mdx#configure-session-storage)
+for a quick setup, or the full
+[Redis Sentinel session storage](../guides-k8s/redis-session-storage.mdx) guide
+for an end-to-end walkthrough.
+
+## MCPServer vs. VirtualMCPServer
+
+The embedded auth server is available on both `MCPServer` and `VirtualMCPServer`
+resources, with some differences:
+
+| | MCPServer | VirtualMCPServer |
+| ---------------------- | ------------------------------------------- | ------------------------------------------------------------------------------ |
+| Configuration location | Separate `MCPExternalAuthConfig` resource | Inline `authServerConfig` block on the resource |
+| Upstream providers | Single upstream provider | Multiple upstream providers with sequential authorization chaining |
+| Token forwarding | Automatic (single provider, single backend) | Explicit `upstreamInject` or `tokenExchange` config maps providers to backends |
+
+For single-backend deployments on MCPServer, the embedded auth server
+automatically swaps the token for each request. For vMCP with multiple backends,
+you configure which upstream provider's token goes to which backend using
+[upstream token injection](../guides-vmcp/authentication.mdx#upstream-token-injection)
+or
+[token exchange with upstream tokens](../guides-vmcp/authentication.mdx#token-exchange-with-upstream-tokens).
+
+## Next steps
+
+- [Set up embedded authorization server authentication](../guides-k8s/auth-k8s.mdx#set-up-embedded-authorization-server-authentication)
+ — step-by-step setup for MCPServer resources in Kubernetes
+- [vMCP embedded authorization server](../guides-vmcp/authentication.mdx#embedded-authorization-server)
+ — configuring multiple upstream providers on a VirtualMCPServer
+- [Redis Sentinel session storage](../guides-k8s/redis-session-storage.mdx) —
+ production session storage configuration
+
+## Related information
+
+- [Authentication and authorization](./auth-framework.mdx) — client-to-MCP
+ authentication concepts and the overall framework
+- [Backend authentication](./backend-auth.mdx) — all backend authentication
+ patterns, including when to choose the embedded auth server
+- [Cedar policies](./cedar-policies.mdx) — authorization policy configuration
diff --git a/versioned_docs/version-1.0/toolhive/concepts/groups.mdx b/versioned_docs/version-1.0/toolhive/concepts/groups.mdx
new file mode 100644
index 00000000..1d10e784
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/concepts/groups.mdx
@@ -0,0 +1,161 @@
+---
+title: Organizing MCP servers with groups
+sidebar_label: Organizing MCP servers
+description:
+ Understanding when and why to use groups for organizing MCP servers and
+ controlling client access.
+---
+
+As your MCP server usage grows, managing multiple servers becomes increasingly
+complex. Groups provide a way to organize servers logically and control which
+tools are available to different clients.
+
+## The problem groups solve
+
+Without groups, all your MCP servers live in a single pool. This creates
+challenges:
+
+- **Tool overload**: AI clients see every tool from every server, making it
+ harder to select the right one
+- **No separation of concerns**: Development tools mix with production tools
+- **One-size-fits-all access**: Every client gets access to everything, even
+ when they only need a subset
+
+Groups address these issues by letting you organize servers into logical
+collections and control which clients can access each collection.
+
+## When to use groups
+
+Groups are most valuable when you need to:
+
+- **Separate environments**: Keep development, staging, and production servers
+ isolated so you don't accidentally use production tools during development
+- **Organize by project**: Give each team access to the tools they need without
+ cluttering their workspace with irrelevant servers
+- **Customize client access**: Configure different AI clients with different
+ tool sets based on their purpose
+- **Reduce context for AI**: Limit the tools available to an AI client so it can
+ make better decisions about which tool to use
+
+### When groups aren't necessary
+
+Groups add organizational overhead, so they're not always the right choice:
+
+- **Single server setups**: If you're running just one or two MCP servers,
+ groups don't provide much benefit
+- **Universal access needs**: If all clients need access to all servers, a
+ single default group works fine
+- **Simple personal use**: For individual developers with straightforward needs,
+ the default group is often sufficient
+
+## Organizational patterns
+
+Here are common ways teams organize their groups:
+
+### Environment-based groups
+
+Separate servers by deployment environment to prevent mixing development and
+production contexts:
+
+| Group | Purpose |
+| ----------- | ---------------------------------------------- |
+| development | Experimental tools, local databases, test APIs |
+| staging | Pre-production testing with realistic data |
+| production | Live systems with appropriate access controls |
+
+This pattern is useful when:
+
+- You run the same MCP server with different configurations per environment
+- You want to prevent accidental production access during development
+- Different team members need different environment access
+
+This pattern works well when:
+
+- Teams have distinct tool requirements
+- You want to reduce clutter in each team's AI client
+- Different teams work on different parts of your system
+
+### Project-based groups
+
+Create groups for specific projects or products:
+
+| Group | Servers |
+| -------------- | ------------------------------------------- |
+| mobile-app | Firebase, app store tools, mobile analytics |
+| web-platform | CMS tools, CDN management, web analytics |
+| internal-tools | Admin dashboards, reporting tools |
+
+This approach helps when:
+
+- Projects have unique tool requirements
+- You want focused AI assistance for specific work contexts
+- Multiple teams contribute to the same project
+
+## Groups and client access
+
+One of the most powerful aspects of groups is controlling which AI clients can
+access which servers. When you configure a client for a specific group, that
+client only sees servers in that group.
+
+This matters because:
+
+- **Better AI performance**: Fewer tools means the AI can make more confident
+ tool selections
+- **Appropriate access**: Different clients can have different capabilities
+ based on their purpose
+- **Reduced noise**: Developers see only the tools relevant to their current
+ work
+
+For example, you might configure:
+
+- **Visual Studio Code** with access to your development group for day-to-day
+ coding
+- **Claude Desktop** with access to your production group for operational tasks
+- **GitHub Copilot** with access to a restricted group for code review
+
+:::tip
+
+A single client can access multiple groups. This is useful when you need tools
+from several areas, like a developer who works across frontend and backend.
+
+:::
+
+## Default group behavior
+
+Every ToolHive installation has a `default` group that cannot be deleted. This
+group serves as:
+
+- **The starting point**: New servers go here unless you specify otherwise
+- **The fallback**: Servers move here when their group is deleted
+- **The simple path**: If you don't need organization, just use the default
+
+You don't need to create custom groups to use ToolHive effectively. The default
+group works well for simple setups and individual use.
+
+## Designing your group structure
+
+When planning your groups, consider:
+
+1. **Start simple**: Begin with the default group and add custom groups only
+ when you feel the need for organization
+2. **Match your workflow**: Groups should reflect how you actually work, not an
+ idealized structure
+3. **Consider client configuration**: Think about which clients need access to
+ which servers
+4. **Plan for growth**: Choose a pattern that scales as you add more servers
+
+:::info
+
+Each server belongs to exactly one group. If you need the same MCP server in
+multiple groups (for example, a GitHub server in both development and
+production), run separate instances with different names and configurations.
+
+:::
+
+## Next steps
+
+Now that you understand when and why to use groups, you can:
+
+- [Organize servers into groups (CLI)](../guides-cli/group-management.mdx)
+- [Organize servers into groups (UI)](../guides-ui/group-management.mdx)
+- [Configure client access](../guides-cli/client-configuration.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/concepts/mcp-primer.mdx b/versioned_docs/version-1.0/toolhive/concepts/mcp-primer.mdx
new file mode 100644
index 00000000..d3d98330
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/concepts/mcp-primer.mdx
@@ -0,0 +1,72 @@
+---
+title: 'Model Context Protocol (MCP): A friendly primer for builders'
+sidebar_label: MCP primer
+description:
+ A brief introduction to the Model Context Protocol (MCP) and its benefits for
+ developers.
+---
+
+**TL;DR:** MCP offers a pragmatic, language-friendly bridge between
+probabilistic code generators and the real-world systems where your source of
+truth lives. It's young, but it already solves pain points around context size,
+adapter sprawl, and brittle prompts—thanks largely to an open, welcoming
+developer community. If you're building next-gen coding tools, now's the ideal
+moment to give MCP a spin and leave your fingerprints on the spec.
+
+## Why we needed something new
+
+Modern code-generation models work by guessing the next token from probability
+space. By nature they are powerful but probabilistic and work with natural
+language. Context drives everything and they can only work on what they can see.
+Most real-world context developers use lives outside the model: in GitHub repos,
+API docs, RFCs, and issue trackers. Bridging that gap has been messy:
+
+| Traditional approach | Pain point for GenAI tools |
+| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
+| Custom adapters / plugins per data source | Hard to keep in sync; brittle when schemas change |
+| Prompt stuffing (copy-pasting docs into the prompt) | Dilutes effectiveness and reduces response acceptance rates, bloats token budget, hurts latency & cost |
+| REST APIs with rigid schemas | Fine for deterministic code, awkward for probabilistic LLMs that prefer natural language |
+
+MCP tackles these headaches by letting a model **ask external systems for facts
+or files using a concise, natural syntax that itself is easy for generative
+models to emit and parse.**
+
+## What problems does MCP solve?
+
+- **Token-efficient context retrieval** \
+ _One-shot, structured queries_ (e.g.,
+ `mcp://github?repo=owner/project\&path=README.md`) let the model fetch exactly
+ what it needs—no boilerplate, no giant system prompts.
+- **Natural-language-friendly envelope** \
+ The URI-like syntax is short, deterministic, yet readable enough that an LLM
+ can generate it without dedicated training. Embeddings created before MCP work
+ just fine with MCP.
+- **Uniform surface over heterogeneous data** \
+ Git blobs, Swagger files, Confluence pages, or a private vector store all look
+ like "resources" under the same scheme. Tool builders write one resolver and
+ get many back-ends without additional work.
+- **Graceful failure semantics** \
+ Every MCP response carries both _content_ and a lightweight _provenance_
+ object (source, timestamp, hash). Models can decide to retry, ignore, or cite.
+
+## The emergence of open community
+
+A community has sprung up around the MCP protocol incredibly quickly.
+
+The spec is Apache-licensed and refreshingly small, clean, and simple, which
+makes the whole thing pretty easy to grok. SDK's abound and thousands of
+examples exist. The efforts of communities like golang with the go-mcp release
+in April 2025 are moving server development beyond the boundaries of the
+traditional JavaScript and Python ecosystems. The Golang portfolio servers
+inventory is growing incredibly quickly and with it comes a wealth of production
+oriented access to resources.
+
+There's no governing foundation yet, but a lightweight steering group triages
+PRs and publishes version tags.
+
+## Where MCP is headed
+
+Expect iterative, community-driven releases—v1.0 is slated for late 2025 with a
+stable core and optional capability sets (search, write-back, streaming). The
+protocol's youth means rough edges, but that also means **you can still shape
+it**: file issues, prototype adapters, or just lurk and learn.
diff --git a/versioned_docs/version-1.0/toolhive/concepts/observability.mdx b/versioned_docs/version-1.0/toolhive/concepts/observability.mdx
new file mode 100644
index 00000000..f3cb8b26
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/concepts/observability.mdx
@@ -0,0 +1,497 @@
+---
+title: Observability
+description: Understanding ToolHive's observability features for MCP server monitoring
+---
+
+ToolHive provides comprehensive observability for your MCP server interactions
+through built-in OpenTelemetry instrumentation. You get complete visibility into
+how your MCP servers perform, including detailed traces, metrics, and error
+tracking.
+
+## How telemetry works
+
+ToolHive automatically instruments your MCP server interactions without
+requiring changes to your servers. When you enable telemetry, ToolHive captures
+detailed information about every request, tool call, and server interaction.
+
+ToolHive's telemetry captures rich, protocol-aware information because it
+understands MCP operations. You get detailed traces showing tool calls, resource
+access, and prompt operations rather than generic HTTP requests.
+
+## Distributed tracing
+
+Distributed tracing shows you the complete journey of each request through your
+MCP servers. ToolHive creates comprehensive traces that provide end-to-end
+visibility across the proxy-container boundary.
+
+### Trace structure
+
+Here's what a trace looks like when a client calls a tool in the GitHub MCP
+server (some fields omitted for brevity):
+
+```text
+Span: tools/call create_issue (150ms)
+├── service.name: thv-github
+├── service.version: v0.1.9
+├── http.request.method: POST
+├── http.request.body.size: 256
+├── http.response.status_code: 202
+├── http.response.body.size: 1024
+├── url.full: /messages?session_id=b1d22d07-b35f-4260-9c0c-b872f92f64b1
+├── url.path: /messages
+├── url.scheme: https
+├── server.address: localhost:14972
+├── user_agent.original: claude-code/1.0.53
+├── mcp.method.name: tools/call
+├── mcp.server.name: github
+├── mcp.session.id: abc123
+├── rpc.system.name: jsonrpc
+├── jsonrpc.protocol.version: 2.0
+├── jsonrpc.request.id: 5
+├── gen_ai.tool.name: create_issue
+├── gen_ai.operation.name: execute_tool
+├── gen_ai.tool.call.arguments: owner=stacklok, repo=toolhive, pullNumber=1131
+├── network.transport: tcp
+└── network.protocol.name: http
+```
+
+### MCP-specific traces
+
+ToolHive automatically captures traces for all MCP operations, including:
+
+- **Tool calls** (`tools/call`) - When AI assistants use tools
+- **Resource access** (`resources/read`) - When servers read files or data
+- **Prompt operations** (`prompts/get`) - When servers retrieve prompts
+- **Connection events** (`initialize`) - When clients connect to servers
+
+### Trace attributes
+
+Each trace includes detailed context across several layers:
+
+#### Service information
+
+```text
+service.name: thv-github
+service.version: v0.1.9
+host.name: my-machine
+```
+
+#### HTTP layer
+
+```text
+http.request.method: POST
+http.request.body.size: 256
+http.response.status_code: 202
+http.response.body.size: 1024
+url.full: /messages?session_id=b1d22d07-b35f-4260-9c0c-b872f92f64b1
+url.path: /messages
+url.scheme: https
+url.query: session_id=b1d22d07-b35f-4260-9c0c-b872f92f64b1
+server.address: localhost:14972
+user_agent.original: claude-code/1.0.53
+```
+
+#### Network layer
+
+```text
+network.transport: tcp
+network.protocol.name: http
+network.protocol.version: 1.1
+client.address: 127.0.0.1
+client.port: 52431
+```
+
+#### MCP protocol details
+
+Details about the MCP operation being performed (some fields are specific to
+each operation):
+
+```text
+mcp.method.name: tools/call
+mcp.server.name: github
+mcp.session.id: abc123
+mcp.protocol.version: 2025-03-26
+rpc.system.name: jsonrpc
+jsonrpc.protocol.version: 2.0
+jsonrpc.request.id: 123
+```
+
+#### Method-specific attributes
+
+- **`tools/call`** traces include:
+ - `gen_ai.tool.name` - The name of the tool being called
+ - `gen_ai.operation.name` - Set to `execute_tool`
+ - `gen_ai.tool.call.arguments` - Sanitized tool arguments (sensitive values
+ redacted)
+
+- **`resources/read`** traces include:
+ - `mcp.resource.uri` - The URI of the resource being accessed
+
+- **`prompts/get`** traces include:
+ - `gen_ai.prompt.name` - The name of the prompt being retrieved
+
+- **`initialize`** traces include:
+ - `mcp.client.name` - The name of the connecting client (always emitted)
+ - `mcp.protocol.version` - The MCP protocol version negotiated
+
+:::note[Legacy attribute names]
+
+By default, ToolHive emits both the new OpenTelemetry semantic convention
+attribute names shown above and legacy attribute names (e.g., `http.method`,
+`mcp.method`, `mcp.tool.name`) for backward compatibility with existing
+dashboards. You can control this with the `--otel-use-legacy-attributes` flag.
+
+:::
+
+## Metrics collection
+
+ToolHive automatically collects metrics about your MCP server usage and
+performance. These metrics help you understand usage patterns, performance
+characteristics, and identify potential issues.
+
+### Metric labels
+
+All metrics include consistent labels for filtering and aggregation:
+
+- `server` - MCP server name (e.g., `fetch`, `github`)
+- `transport` - Backend transport type (`stdio`, `sse`, or `streamable-http`)
+- `method` - HTTP method (`POST`, `GET`)
+- `mcp_method` - MCP protocol method (e.g., `tools/call`, `resources/read`)
+- `status` - Request outcome (`success` or `error`)
+- `status_code` - HTTP status code (`200`, `400`, `500`)
+- `tool` - Tool name for tool-specific metrics
+
+### Key metrics
+
+Example metrics from the Prometheus `/metrics` endpoint are shown below (some
+fields are omitted for brevity):
+
+#### Request metrics
+
+```promql
+# HELP toolhive_mcp_requests_total Total number of MCP requests
+# TYPE toolhive_mcp_requests_total counter
+toolhive_mcp_requests_total{mcp_method="tools/list",method="POST",server="github",status="success",status_code="202",transport="stdio"} 2
+
+# HELP toolhive_mcp_request_duration_seconds Duration of MCP requests in seconds
+# TYPE toolhive_mcp_request_duration_seconds histogram
+toolhive_mcp_request_duration_seconds_bucket{mcp_method="tools/list",method="POST",server="github",status="success",status_code="202",transport="stdio",le="10000"} 2
+toolhive_mcp_request_duration_seconds_bucket{mcp_method="tools/list",method="POST",server="github",status="success",status_code="202",transport="stdio",le="+Inf"} 2
+toolhive_mcp_request_duration_seconds_sum{mcp_method="tools/list",method="POST",server="github",status="success",status_code="202",transport="stdio"} 0.000219416
+toolhive_mcp_request_duration_seconds_count{mcp_method="tools/list",method="POST",server="github",status="success",status_code="202",transport="stdio"} 2
+```
+
+#### Connection metrics
+
+```promql
+# HELP toolhive_mcp_active_connections Number of active MCP connections
+# TYPE toolhive_mcp_active_connections gauge
+toolhive_mcp_active_connections{connection_type="sse",server="github",transport="stdio"} 3
+```
+
+#### Tool-specific metrics
+
+```promql
+# HELP toolhive_mcp_tool_calls_total Total number of MCP tool calls
+# TYPE toolhive_mcp_tool_calls_total counter
+toolhive_mcp_tool_calls_total{server="github",status="success",tool="get_file_contents"} 15
+toolhive_mcp_tool_calls_total{server="github",status="success",tool="list_pull_requests"} 4
+toolhive_mcp_tool_calls_total{server="github",status="success",tool="search_issues"} 2
+```
+
+### MCP semantic convention metrics
+
+In addition to the ToolHive-prefixed metrics above, ToolHive emits metrics that
+follow the
+[OpenTelemetry MCP semantic conventions](https://github.com/open-telemetry/semantic-conventions):
+
+| Metric | Type | Description |
+| ------------------------------- | --------- | ---------------------------------------- |
+| `mcp.server.operation.duration` | Histogram | Duration of MCP server operations |
+| `mcp.client.operation.duration` | Histogram | Duration of MCP client operations (vMCP) |
+
+These metric names follow the OpenTelemetry MCP semantic conventions in OTLP
+exports and use the same labels as the ToolHive-prefixed metrics. When exposed
+via the Prometheus `/metrics` endpoint, their names are converted to
+Prometheus-safe form by replacing dots (`.`) with underscores (`_`):
+
+- `mcp.server.operation.duration` → `mcp_server_operation_duration`
+- `mcp.client.operation.duration` → `mcp_client_operation_duration`
+
+### vMCP metrics
+
+When using Virtual MCP Server (vMCP), additional metrics are available for
+monitoring backend operations, workflow executions, and optimizer performance.
+For details, see the
+[vMCP telemetry guide](../guides-vmcp/telemetry-and-metrics.mdx).
+
+## Trace context propagation
+
+ToolHive supports two methods of trace context propagation:
+
+- **HTTP headers**: Standard W3C Trace Context (`traceparent` and `tracestate`
+ headers) and W3C Baggage propagation
+- **MCP `_meta` field**: Trace context embedded in MCP request parameters via
+ the `params._meta` field, following the MCP specification
+
+When both are present, the MCP `_meta` trace context takes priority. This
+enables proper trace correlation across MCP server boundaries, even when MCP
+clients inject trace context into the request payload rather than HTTP headers.
+
+## Export options
+
+ToolHive supports multiple export formats to integrate with your existing
+observability infrastructure.
+
+### OTLP export
+
+ToolHive supports OpenTelemetry Protocol (OTLP) export for both traces and
+metrics to any compatible backend, either directly or via a collector
+application.
+
+The [OpenTelemetry ecosystem](https://opentelemetry.io/ecosystem/vendors/)
+includes a wide range of observability backends including open source solutions
+like Jaeger, self-hosted solutions like Splunk, and SaaS solutions like Datadog,
+New Relic, and Honeycomb.
+
+### Prometheus export
+
+ToolHive can expose Prometheus-style metrics at a `/metrics` endpoint, enabling:
+
+- **Direct scraping** by Prometheus servers
+- **Service discovery** in Kubernetes environments
+- **Integration** with existing Prometheus-based monitoring stacks
+
+### Dual export
+
+Both OTLP and Prometheus can be enabled simultaneously, allowing you to:
+
+- Send traces to specialized tracing backends
+- Expose metrics for Prometheus scraping
+- Maintain compatibility with existing monitoring infrastructure
+
+## Data sanitization
+
+ToolHive automatically protects sensitive information in traces:
+
+- **Sensitive arguments**: Tool arguments containing passwords, tokens, or keys
+ are redacted
+- **Sensitive key detection**: Arguments with keys containing patterns like
+ "password", "token", "secret", "key", "auth", or "credential" are redacted
+- **Argument truncation**: Long arguments are truncated to prevent excessive
+ trace size
+
+For example, a tool call with sensitive arguments:
+
+```text
+gen_ai.tool.call.arguments: password=secret123, api_key=abc456, title=Bug report
+```
+
+ToolHive sanitizes this in the trace as:
+
+```text
+gen_ai.tool.call.arguments: password=[REDACTED], api_key=[REDACTED], title=Bug report
+```
+
+## Monitoring examples
+
+These examples show how ToolHive's observability works in practice.
+
+### Tool call monitoring
+
+When a client calls the `create_issue` tool:
+
+**Request**:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": "req_456",
+ "method": "tools/call",
+ "params": {
+ "name": "create_issue",
+ "arguments": {
+ "title": "Bug report",
+ "body": "Found an issue with the API"
+ }
+ }
+}
+```
+
+**Generated trace**:
+
+```text
+Span: tools/call create_issue
+├── mcp.method.name: tools/call
+├── jsonrpc.request.id: req_456
+├── gen_ai.tool.name: create_issue
+├── gen_ai.tool.call.arguments: title=Bug report, body=Found an issue with...
+├── mcp.server.name: github
+├── network.transport: tcp
+├── http.request.method: POST
+├── http.response.status_code: 200
+└── duration: 850ms
+```
+
+**Generated metrics**:
+
+```promql
+toolhive_mcp_requests_total{mcp_method="tools/call",server="github",status="success"} 1
+toolhive_mcp_request_duration_seconds{mcp_method="tools/call",server="github"} 0.85
+toolhive_mcp_tool_calls_total{server="github",tool="create_issue",status="success"} 1
+```
+
+### Error tracking
+
+Failed requests generate error traces and metrics:
+
+**Error trace**:
+
+```text
+Span: tools/call invalid_tool
+├── mcp.method.name: tools/call
+├── gen_ai.tool.name: invalid_tool
+├── http.response.status_code: 400
+├── span.status: ERROR
+├── span.status_message: Tool not found
+└── duration: 12ms
+```
+
+**Error metrics**:
+
+```promql
+toolhive_mcp_requests_total{mcp_method="tools/call",server="github",status="error",status_code="400"} 1
+toolhive_mcp_tool_calls_total{server="github",tool="invalid_tool",status="error"} 1
+```
+
+## Key performance indicators
+
+Monitor these key metrics for optimal MCP server performance:
+
+1. **Request rate**: `rate(toolhive_mcp_requests_total[5m])`
+2. **Error rate**: `rate(toolhive_mcp_requests_total{status="error"}[5m])`
+3. **Response time**:
+ `histogram_quantile(0.95, toolhive_mcp_request_duration_seconds_bucket)`
+4. **Active connections**: `toolhive_mcp_active_connections`
+
+## Setting up dashboards and alerts
+
+This section shows practical examples of integrating ToolHive's observability
+data with common monitoring tools.
+
+### Prometheus integration
+
+Configure Prometheus to scrape ToolHive metrics:
+
+```yaml title="prometheus.yml"
+scrape_configs:
+ - job_name: 'toolhive-mcp-proxy'
+ static_configs:
+ - targets: [
+ 'localhost:43832', # Example MCP server
+ 'localhost:51712' # Example MCP server
+ ]
+ scrape_interval: 15s
+ metrics_path: /metrics
+```
+
+### Grafana dashboard queries
+
+Example queries for monitoring dashboards:
+
+```promql
+# Request rate by server
+sum(rate(toolhive_mcp_requests_total[5m])) by (server)
+
+# Error rate percentage
+sum(rate(toolhive_mcp_requests_total{status="error"}[5m])) by (server) /
+sum(rate(toolhive_mcp_requests_total[5m])) by (server) * 100
+
+# Response time percentiles
+histogram_quantile(0.95, sum(rate(toolhive_mcp_request_duration_seconds_bucket[5m])) by (le, server))
+
+# Tool usage distribution
+sum(rate(toolhive_mcp_tool_calls_total[5m])) by (tool, server)
+
+# Active connections
+toolhive_mcp_active_connections
+```
+
+### Alerting rules
+
+Example Prometheus alerting rules:
+
+```yaml title="prometheus.yml"
+groups:
+ - name: toolhive-mcp-proxy
+ rules:
+ - alert: HighErrorRate
+ expr: rate(toolhive_mcp_requests_total{status="error"}[5m]) > 0.1
+ for: 2m
+ labels:
+ severity: warning
+ annotations:
+ summary: 'High error rate in MCP proxy'
+ description: 'Error rate is {{ $value }} errors per second'
+
+ - alert: HighResponseTime
+ expr:
+ histogram_quantile(0.95, toolhive_mcp_request_duration_seconds_bucket)
+ > 2.0
+ for: 5m
+ labels:
+ severity: warning
+ annotations:
+ summary: 'High response time in MCP proxy'
+ description: '95th percentile response time is {{ $value }}s'
+
+ - alert: ProxyDown
+ expr: up{job="toolhive-mcp-proxy"} == 0
+ for: 1m
+ labels:
+ severity: critical
+ annotations:
+ summary: 'MCP proxy is down'
+ description: 'ToolHive MCP proxy has been down for more than 1 minute'
+```
+
+## Recommendations
+
+### Production deployment
+
+1. Use appropriate sampling rates (1-10% for high-traffic systems)
+2. Configure authentication for OTLP endpoints
+3. Use HTTPS transport in production
+4. Monitor telemetry overhead with metrics
+5. Set up alerting on key performance indicators
+
+### Development and testing
+
+1. Use 100% sampling for complete visibility
+2. Enable local backends (Jaeger, Prometheus)
+3. Test with realistic workloads to validate metrics
+4. Verify trace correlation across service boundaries
+
+### Cost optimization
+
+1. Tune sampling rates based on traffic patterns
+2. Use head-based sampling for consistent trace collection
+3. Monitor backend costs and adjust accordingly
+4. Filter out health check requests if not needed
+
+## Next steps
+
+Now that you understand how ToolHive's observability works, you can:
+
+1. **Choose a monitoring backend** that fits your needs and budget
+2. **Follow the tutorial** to set up a local observability stack with
+ [OpenTelemetry, Jaeger, Prometheus, and Grafana](../integrations/opentelemetry.mdx)
+3. **Enable telemetry** when running your servers:
+ - [using the ToolHive CLI](../guides-cli/telemetry-and-metrics.mdx)
+ - [using the Kubernetes operator](../guides-k8s/telemetry-and-metrics.mdx)
+4. **Set up basic dashboards** to track request rates, error rates, and response
+ times
+5. **Configure alerts** for critical issues
+
+The telemetry system works automatically once enabled, providing immediate
+insights into your MCP server performance and usage patterns.
diff --git a/versioned_docs/version-1.0/toolhive/concepts/registry-criteria.mdx b/versioned_docs/version-1.0/toolhive/concepts/registry-criteria.mdx
new file mode 100644
index 00000000..ca51933f
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/concepts/registry-criteria.mdx
@@ -0,0 +1,166 @@
+---
+title: 'Registry criteria'
+description: Criteria for adding MCP servers to the ToolHive registry
+---
+
+The ToolHive registry is a curated list of MCP servers that meet specific
+criteria. We aim to establish a curated, community-auditable list of
+high-quality MCP servers through clear, observable, and objective criteria.
+
+## Contribute to the registry
+
+If you have an MCP server that you'd like to add to the ToolHive registry, you
+can
+[open an issue](https://github.com/stacklok/toolhive-catalog/issues/new?template=add-an-mcp-server.md)
+or submit a pull request to the
+[toolhive-catalog](https://github.com/stacklok/toolhive-catalog) repository. The
+ToolHive team will review your submission and consider adding it to the
+registry.
+
+Criteria for adding an MCP server to the ToolHive registry are outlined below.
+These criteria ensure that the servers in the registry meet the standards of
+security, quality, and usability that ToolHive aims to uphold.
+
+Registry entries are defined in the
+[toolhive-catalog](https://github.com/stacklok/toolhive-catalog) repository.
+
+## Criteria for MCP servers
+
+### Open source requirements
+
+- Must be fully open source with no exceptions
+- Source code must be publicly accessible
+- Must use an acceptable open source license (see
+ [Acceptable licenses](#acceptable-licenses))
+
+### Security
+
+- Software provenance verification (Sigstore, GitHub Attestations)
+- SLSA compliance level assessment
+- Pinned dependencies and GitHub Actions
+- Published Software Bill of Materials (SBOMs)
+
+### Continuous integration
+
+- Automated dependency updates (Dependabot, Renovate, etc.)
+- Automated security scanning
+- CVE monitoring
+- Code linting and quality checks
+
+### Repository metrics
+
+- Repository stars and forks
+- Commit frequency and recency
+- Contributor activity
+- Issue and pull request statistics
+
+### API compliance
+
+- Full MCP API specification support
+- Implementation of all required endpoints (tools, resources, etc.)
+- Protocol version compatibility
+
+### Tool stability
+
+- Version consistency
+- Breaking change frequency
+- Backward compatibility maintenance
+
+### Code quality
+
+- Presence of automated tests
+- Test coverage percentage
+- Quality CI/CD implementation
+- Code review practices
+
+### Documentation
+
+- Basic project documentation
+- API documentation
+- Deployment and operation guides
+- Regular documentation updates
+
+### Release process
+
+- Established CI-based release process
+- Regular release cadence
+- Semantic versioning compliance
+- Maintained changelog
+
+### Community health
+
+#### Responsiveness
+
+- Active maintainer engagement
+- Regular commit activity
+- Timely issue and pull request responses (issues open 3-4 weeks without
+ response is a red flag)
+- Bug resolution rate
+- User support quality
+
+#### Community strength
+
+- Project backing (individual vs. organizational)
+- Number of active maintainers
+- Contributor diversity
+- Corporate or foundation support
+- Governance model maturity
+
+### Security requirements
+
+#### Authentication and authorization
+
+- Secure authentication mechanisms
+- Proper authorization controls
+- Standard security protocol support (OAuth, TLS)
+
+#### Data protection
+
+- Encryption for data in transit and at rest
+- Proper sensitive information handling
+
+#### Security practices
+
+- Clear incident response channels
+- Security issue reporting mechanisms (email, GHSA, etc.)
+
+## Future considerations
+
+### Automated vs manual checks
+
+- Balance between automated checks (e.g., CI/CD, security scans) and manual
+ reviews (e.g., community health, documentation quality)
+- Automated checks for basic compliance (e.g., license, API support)
+- Manual reviews for nuanced aspects (e.g., community strength, documentation
+ quality)
+
+### Scoring system
+
+- **Required**: Essential attributes (significant penalty if missing)
+- **Expected**: Typical well-executed project attributes (moderate score impact)
+- **Recommended**: Good practice indicators (positive contribution)
+- **Bonus**: Excellence demonstrators (pure positive, no penalty for absence)
+
+### Tiered classifications
+
+- "Verified" vs "Experimental/Community" designations
+- Minimum threshold requirements (stars, maintainers, community indicators)
+- Regular re-evaluation frequency for automated checks
+
+## Acceptable licenses
+
+The following open source licenses are accepted for MCP servers in the ToolHive
+registry:
+
+### Permissive licenses
+
+Licenses such as Apache-2.0, MIT, BSD-2-Clause, and BSD-3-Clause allow maximum
+flexibility for integration, modification, and redistribution with minimal
+restrictions, making MCP servers accessible across all project types and
+commercial applications.
+
+### Excluded licenses
+
+We exclude copyleft and restrictive licenses such as AGPL, GPL2, and GPL3 to
+ensure MCP servers can be freely integrated into various commercial and open
+source projects without legal complications or viral licensing requirements.
diff --git a/versioned_docs/version-1.0/toolhive/concepts/skills.mdx b/versioned_docs/version-1.0/toolhive/concepts/skills.mdx
new file mode 100644
index 00000000..2149c2bd
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/concepts/skills.mdx
@@ -0,0 +1,113 @@
+---
+title: Understanding skills
+description:
+ Learn what skills are in ToolHive, why they exist, and how they relate to MCP
+ servers.
+---
+
+A **skill** is a reusable, versioned bundle of instructions, prompts, and
+configuration that teaches an AI agent how to perform a specific task. If MCP
+servers provide **tools** (the raw capabilities an agent can call), skills
+provide the **knowledge** of when, why, and how to use those tools effectively.
+
+## When you would use skills
+
+Consider these scenarios:
+
+- **You maintain a shared MCP registry** and want teams to publish reusable
+ prompt bundles alongside the MCP servers they connect to, so that other
+ engineers can discover both the tooling and the expertise in one place.
+- **You build internal developer tools** and want to package a "code review"
+ workflow that combines multiple MCP server calls with domain-specific
+ instructions, then distribute it through a central catalog.
+- **You run a platform team** and want to curate a set of approved skills that
+ your organization's AI agents can use, with clear versioning and status
+ tracking.
+
+## How skills relate to MCP servers
+
+MCP servers expose tools; skills consume them. A skill might reference one or
+more tools from one or more MCP servers, wrapping them in a higher-level
+workflow with context-specific instructions.
+
+```mermaid
+flowchart LR
+ Skill["Skill\n(workflow + prompts)"] -->|references| MCP["MCP Server(s)\n(raw tools)"]
+```
+
+Skills are stored in the same Registry server instance as MCP servers, but under
+a separate extensions API path (`/{registryName}/v0.1/x/dev.toolhive/skills`,
+where `{registryName}` is the name of your registry). They are not intermixed
+with MCP server entries.
+
+## Skill structure
+
+Each skill has the following core fields:
+
+| Field | Required | Description |
+| ------------- | -------- | ------------------------------------------------------------------- |
+| `namespace` | Yes | Reverse-DNS identifier (e.g., `io.github.acme`) |
+| `name` | Yes | Skill identifier in kebab-case (e.g., `code-review`) |
+| `description` | Yes | Human-readable summary of what the skill does |
+| `version` | Yes | Semantic version or commit hash (e.g., `1.0.0`) |
+| `status` | No | One of `ACTIVE`, `DEPRECATED`, or `ARCHIVED` (defaults to `ACTIVE`) |
+| `title` | No | Human-friendly display name |
+| `license` | No | SPDX license identifier (e.g., `Apache-2.0`) |
+
+Skills also support optional fields for packages (OCI or Git references), icons,
+repository metadata, allowed tools, compatibility requirements, and arbitrary
+metadata.
+
+### Naming conventions
+
+- **Namespace**: Use reverse-DNS notation. For example, `io.github.your-org` or
+ `com.example.team`. This prevents naming collisions across organizations.
+- **Name**: Use kebab-case identifiers. For example, `code-review`,
+ `deploy-checker`, `security-scan`.
+- **Version**: Use semantic versioning (e.g., `1.0.0`, `2.1.3`) or a commit
+ hash. The registry tracks a "latest" pointer per namespace/name pair.
+
+**Valid examples:**
+
+- `io.github.acme/code-review` version `1.0.0`
+- `com.example.platform/deploy-checker` version `0.3.1`
+
+**Invalid examples:**
+
+- `acme/Code Review` (namespace must be reverse-DNS, name must be kebab-case)
+- Empty namespace or name (both are required)
+
+### Package types
+
+Skills can reference distribution packages in two formats:
+
+- **OCI**: Container registry references with an identifier, digest, and media
+ type
+- **Git**: Repository references with a URL, ref, commit, and optional subfolder
+
+## Versioning
+
+The registry stores multiple versions of each skill and maintains a "latest"
+pointer. When you publish a new version, the registry automatically updates the
+latest pointer if the new version is newer than the current latest. Publishing
+an older version does not change the latest pointer.
+
+You can retrieve a specific version or request `latest` to get the most recent
+one.
+
+## Current status and what's next
+
+The skills API is available as an extension endpoint on the Registry server
+(`/{registryName}/v0.1/x/dev.toolhive/skills`). You can publish, list, search,
+retrieve, and delete skills through this API.
+
+Skill installation via agent clients (such as the ToolHive CLI or IDE
+extensions) is planned for a future release. For now, the registry serves as a
+discovery and distribution layer where you can browse available skills and
+retrieve their package references.
+
+## Next steps
+
+- [Manage skills](../guides-registry/skills.mdx) through the Registry server API
+- [Registry server introduction](../guides-registry/intro.mdx) for an overview
+ of the Registry server
diff --git a/versioned_docs/version-1.0/toolhive/concepts/tool-optimization.mdx b/versioned_docs/version-1.0/toolhive/concepts/tool-optimization.mdx
new file mode 100644
index 00000000..35b5c85b
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/concepts/tool-optimization.mdx
@@ -0,0 +1,260 @@
+---
+title: Optimizing LLM context with tool filtering and overrides
+sidebar_label: Optimizing LLM context
+description:
+ Understanding when and why to use tool filtering and overrides to reduce
+ context pollution and improve AI performance.
+---
+
+When AI assistants interact with MCP servers, they receive information about
+every available tool. While having many tools provides flexibility, it can also
+create problems. This guide explains how tool filtering and overrides help you
+optimize your AI's context window for better performance and more focused
+results.
+
+## The context pollution problem
+
+Modern AI clients work by analyzing all available tools and selecting the most
+appropriate one for each task. This selection process happens in the AI model's
+context, which means every tool's name, description, and schema consumes tokens
+and processing time.
+
+Consider what happens when you connect an AI client to multiple MCP servers:
+
+- A GitHub server might expose 30+ tools for repositories, issues, pull
+ requests, and more
+- A filesystem server adds another 10+ tools for file operations
+- A database server contributes 20+ tools for queries and schema management
+- Additional servers for Slack, Jira, monitoring systems, and other integrations
+
+Before you know it, your AI client is evaluating 100+ tools for every request.
+
+### Why this matters
+
+When your AI receives too many tools, several problems emerge:
+
+**Performance degradation**: More tools mean longer processing time as the AI
+model evaluates each option. Tool selection becomes a bottleneck, especially for
+complex queries.
+
+**Higher costs**: Every tool description consumes tokens. In token-based pricing
+models, exposing unnecessary tools directly increases your costs for every AI
+interaction.
+
+**Reduced accuracy**: When faced with many similar tools, AI models sometimes
+choose incorrectly. A client might use a production database tool when it should
+use a development one, or select a write operation when a read would suffice.
+
+The solution is selective tool exposure - showing your AI only the tools it
+actually needs.
+
+## Tool filtering
+
+Tool filtering restricts which tools from an MCP server are available to
+clients. Think of it as creating a curated subset of functionality for specific
+use cases.
+
+### How filtering works
+
+ToolHive uses an allow-list approach. When you specify a filter, only the tools
+you explicitly list become available. The filtering happens at the HTTP proxy
+level, so:
+
+- The AI only sees allowed tools in its tool list
+- Attempts to call filtered tools result in errors
+- The backend MCP server remains unchanged
+
+An empty filter means all tools are available. Once you add any tool to the
+filter, only listed tools are exposed.
+
+### When to use filtering
+
+Filtering makes sense in several scenarios:
+
+#### Improving AI tool selection
+
+When an MCP server exposes many tools but you only need a subset, filtering
+improves the AI's ability to choose correctly. For example, enable only pull
+request tools from the GitHub server when doing code review, or limit a file
+system server to just `read_file` and `list_directory` for a documentation
+assistant. Removing irrelevant options helps the AI make more confident
+selections.
+
+#### Limiting access to safe operations
+
+An MCP server for database access might include both read and write operations.
+During development or analysis, you might want to expose only read operations
+like `query` and `list_tables`, while filtering out write operations like
+`insert`, `update`, and `delete` that modify data or perform destructive
+operations.
+
+#### Creating role-specific tool sets
+
+Different team members need different capabilities. Junior developers might get
+filtered access to safe operations, while senior developers see the full tool
+set. Security-sensitive tools like deployment commands might be filtered for
+most users but available to DevOps engineers.
+
+#### Compliance and governance
+
+When organizational policies restrict certain operations, you can enforce policy
+by only exposing approved tools, even if the underlying MCP server provides more
+capabilities.
+
+## Tool overrides
+
+Tool overrides let you rename tools and update their descriptions without
+modifying the backend MCP server. This is particularly valuable when tool names
+are unclear or when combining multiple servers.
+
+### How overrides work
+
+Overrides maintain a bidirectional mapping between original and user-facing
+names. When your AI sees the tool list, it receives the overridden names and
+descriptions. When it calls a tool, ToolHive translates the user-facing name
+back to the original name for the backend server.
+
+You can override either the name, the description, or both for each tool.
+
+### When to use overrides
+
+Overrides solve several common problems:
+
+#### Preventing name conflicts
+
+When combining multiple MCP servers through Virtual MCP Server or running
+similar servers for different purposes, naming conflicts are common. Both GitHub
+and Jira might have a `create_issue` tool. Overriding these to
+`github_create_issue` and `jira_create_issue` eliminates ambiguity.
+
+When you run the same MCP server multiple times with different configurations,
+tool names become identical. For example, running the GitHub server twice (once
+for your company's organization and once for open source contributions) requires
+renaming tools to `github_company_create_pr` and `github_oss_create_pr` to make
+the distinction clear.
+
+#### Adding context and clarity
+
+Tool names and descriptions can be improved to provide environment-specific or
+use-case-specific information. Renaming `deploy` to `deploy_to_staging` versus
+`deploy_to_production` makes the destination explicit and reduces mistakes.
+Similarly, you can update descriptions from generic text like "Deploy
+application" to specific guidance like "Deploy to staging environment -
+auto-rollback enabled."
+
+## Combining filters and overrides
+
+Filtering and overrides work together, but understanding their interaction is
+important: **filters apply to user-facing names after overrides**.
+
+This means when you override a tool name, you must use the new name in your
+filter list, not the original name.
+
+### Pattern: Secure subset with clear names
+
+Start by overriding technical names to be more intuitive, then filter to only
+safe operations.
+
+```json
+{
+ "toolsOverride": {
+ "exec_raw_sql": {
+ "name": "run_database_query",
+ "description": "Execute read-only SQL queries against the staging database"
+ },
+ "write_table": {
+ "name": "update_database",
+ "description": "Modify staging database tables (use with caution)"
+ }
+ }
+}
+```
+
+Then filter using the new names:
+
+```bash
+thv run --tools-override overrides.json --tools run_database_query my-db-server
+```
+
+**Why this works**: Clear names guide the AI while filtering enforces safety by
+making destructive operations unavailable.
+
+### Pattern: Environment-specific configurations
+
+Different environments need different tool access. In development, you might
+expose many tools for flexibility. In production, filter to essential tools
+only.
+
+Your development configuration could expose all tools with friendly names
+through overrides. Your production configuration uses the same overrides for
+consistency but adds strict filtering to expose only read and monitoring tools,
+blocking any write or deployment operations.
+
+**Why this works**: Consistent naming across environments with
+environment-specific filtering prevents accidents while maintaining flexibility.
+
+### Pattern: Multi-server aggregation
+
+When using [Virtual MCP Server](vmcp.mdx) to combine multiple MCP servers,
+overrides prevent conflicts and improve clarity:
+
+```json
+{
+ "toolsOverride": {
+ "search": {
+ "name": "github_search",
+ "description": "Search GitHub repositories and code"
+ }
+ }
+}
+```
+
+You can override the `search` tool from different servers to `github_search`,
+`jira_search`, and `confluence_search`. Then filter each server to its relevant
+tools, creating a clean, conflict-free tool set.
+
+**Why this works**: Prefixes eliminate ambiguity about which server a tool
+targets, while filtering prevents context overload from aggregating many
+services.
+
+## Trade-offs to consider
+
+While these optimization features provide significant benefits, they also
+introduce complexity:
+
+**Configuration and maintenance overhead**: Filters and overrides require
+ongoing maintenance. When MCP servers update their tools, you'll need to adjust
+your configurations. When using both features together, remember that filters
+apply to overridden names, not original names.
+
+**Flexibility vs. safety**: Aggressive filtering makes it harder to access tools
+you occasionally need. You may find yourself creating exceptions or
+reconfiguring access. The more you optimize, the less flexible your system
+becomes.
+
+**Discovery and documentation**: When tools are filtered or renamed, team
+members may not realize what capabilities exist. Clear documentation becomes
+essential when your visible tools don't match what the MCP server actually
+provides.
+
+Start simple and add complexity only where it provides clear value.
+
+## Best practices
+
+**Start minimal**: Begin with a focused tool set and expand as you discover
+needs, rather than filtering down from everything.
+
+**Be specific**: When overriding names and descriptions, provide clear,
+context-specific information that helps the AI understand when to use each tool.
+
+## Related information
+
+Now that you understand when and why to use tool filtering and overrides, learn
+how to configure them:
+
+- [Customize tools (CLI)](../guides-cli/run-mcp-servers.mdx)
+- [Customize tools (UI)](../guides-ui/customize-tools.mdx)
+- [Customize tools (Kubernetes)](../guides-k8s/customize-tools.mdx)
+- [MCPToolConfig CRD reference](../reference/crd-spec.md)
+- [Virtual MCP Server tool aggregation](../guides-vmcp/tool-aggregation.mdx)
+- [Optimize tool discovery in vMCP](../guides-vmcp/optimizer.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/concepts/vmcp.mdx b/versioned_docs/version-1.0/toolhive/concepts/vmcp.mdx
new file mode 100644
index 00000000..ff6bff74
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/concepts/vmcp.mdx
@@ -0,0 +1,170 @@
+---
+title: Understanding Virtual MCP Server
+sidebar_label: Virtual MCP Server (vMCP)
+description: Learn what Virtual MCP Server does, why it exists, and when to use it.
+---
+
+This document explains Virtual MCP Server (vMCP), a feature of the ToolHive
+Kubernetes Operator. You'll learn why it exists, when to use it, and how it
+simplifies managing multiple MCP servers while enabling powerful multi-system
+workflows.
+
+## The problem vMCP solves
+
+**Before vMCP**: Engineers manage 10+ separate MCP server connections, each with
+its own authentication, manually coordinate multi-step workflows across systems,
+and repeatedly configure the same parameters.
+
+**With vMCP**: Connect once to a unified endpoint that aggregates all backend
+MCP servers, execute complex multi-system workflows declaratively, and use
+pre-configured tools with sensible defaults.
+
+## Core value propositions
+
+vMCP delivers four key benefits:
+
+1. **Reduce complexity**: Many connections become one, dramatically simplifying
+ configuration
+2. **Speed up workflows**: Parallel execution across systems instead of
+ sequential calls
+3. **Improve security**: Centralized authentication and authorization with a
+ two-boundary model
+4. **Enable reusability**: Define workflows once, use them everywhere
+5. **Optimize tool discovery**: Reduce token usage by replacing all tool
+ definitions with two lightweight search-and-call primitives
+
+## Key capabilities
+
+### Multi-server aggregation
+
+Managing 10-20+ MCP server connections is overwhelming. Each server needs its
+own configuration, authentication, and maintenance. vMCP aggregates all backend
+MCP servers into one endpoint with automatic conflict resolution.
+
+vMCP supports two types of backends:
+
+- **MCPServer**: Container-based MCP servers running in your Kubernetes cluster
+- **MCPRemoteProxy**: Proxies to external remote MCP servers (SaaS platforms,
+ third-party services, or MCP servers hosted outside your cluster)
+
+:::note[MCPRemoteProxy support]
+
+MCPRemoteProxy support in vMCP is currently in development. vMCP can discover
+MCPRemoteProxy backends, but authentication between vMCP and MCPRemoteProxy is
+not yet fully implemented. MCPServer backends work fully with vMCP.
+
+:::
+
+This architecture enables combining internal tools with external SaaS MCP
+endpoints in a single unified interface.
+
+**Example scenario**: An engineering team needs access to 8 backend servers
+(GitHub, Jira, Slack, Confluence, PagerDuty, Datadog, AWS, and internal company
+docs). Some are container-based MCPServer resources running in the cluster,
+while others are remote SaaS MCP endpoints accessed via MCPRemoteProxy. Instead
+of configuring 8 separate connections, they configure one vMCP connection with
+SSO. This significantly reduces configuration complexity and makes onboarding
+new team members much easier.
+
+When multiple backend MCP servers have tools with the same name (for example,
+both GitHub and Jira have `create_issue`), vMCP automatically prefixes them:
+`github_create_issue`, `jira_create_issue`. You can also define custom names for
+clarity.
+
+### Multi-step workflows (composition)
+
+Real-world tasks span multiple systems and require manual orchestration. vMCP
+lets you define declarative workflows with parallel execution, conditionals,
+error handling, and human-in-the-loop approval gates.
+
+**Example scenario**: During an incident investigation, you need logs from your
+logging system, metrics from your monitoring platform, traces from your tracing
+service, and infrastructure status from your cloud provider. Without vMCP, an
+engineer manually runs 4 commands sequentially and aggregates results. With
+vMCP, you fetch all of this in parallel, automatically aggregate it into a
+formatted report, and create a Jira ticket with all the data. This workflow is
+reusable for every incident.
+
+**Example scenario**: For an app deployment, merge the pull request, wait for
+tests, ask a human for approval, deploy only if approved, and notify the team in
+Slack. vMCP handles this entire flow declaratively, with automatic rollback on
+deployment failure.
+
+### Tool customization and overrides
+
+Third-party MCP servers often have generic names, descriptions, and unrestricted
+parameters. vMCP lets you filter, rename, and wrap tools without modifying the
+upstream servers.
+
+**Security policy enforcement**: You can restrict a fetch tool to internal
+domains only (`*.company.com`), validate URLs before calling the backend, and
+provide clear error messages for policy violations.
+
+**Simplified interfaces**: A complex tool like AWS EC2 might have 20+
+parameters, but your frontend team only needs 3. You can create a simplified
+wrapper that uses instance names instead of IDs and pre-fills safe defaults for
+all other parameters.
+
+### Parameter defaults and pre-configuration
+
+Generic servers require repetitive parameter specification. You always use the
+same repo, channel, or database. vMCP lets you create specialized instances with
+pre-configured defaults.
+
+**Example scenario**: Without vMCP, every GitHub query requires specifying
+`repo: stacklok/toolhive`. With vMCP, you pre-configure this default - engineers
+never specify the repo parameter, and they can't accidentally query the wrong
+repository. This eliminates hundreds of repetitive parameter entries per week.
+
+**Example scenario**: Configure staging database as the default (safe for
+development), while production queries require an explicit approval gate.
+Connection details are centralized in vMCP configuration instead of scattered
+across individual tool configurations.
+
+### Authentication boundary separation
+
+Different authentication is needed for clients versus backend MCP servers, and
+managing credentials across multiple systems is complex. vMCP implements a
+two-boundary auth model that separates these concerns.
+
+**How it works**: Clients authenticate to vMCP using OAuth 2.1 authorization as
+defined in the MCP specification. vMCP then handles authentication to each
+backend MCP server independently. Revoking access is simple: disable the user in
+your identity provider and all backend access is revoked instantly.
+
+This approach provides single sign-on for users, centralized access control, and
+a complete audit trail.
+
+## When to use vMCP
+
+### Good fit
+
+- Teams managing 5+ MCP servers (local or remote)
+- Tasks requiring coordination across multiple systems
+- Centralized authentication and authorization requirements
+- Workflows that should be reusable across the team
+- Security policies that need centralized enforcement
+- Aggregating external SaaS MCP servers with internal tools
+
+### Not needed
+
+- Single MCP server usage
+- Simple, one-step operations
+- No orchestration requirements
+
+## Summary
+
+vMCP transforms MCP from individual servers into a unified orchestration
+platform. It aggregates both container-based MCPServer resources and remote
+MCPRemoteProxy backends into a single endpoint. The use cases range from simple
+aggregation to complex workflows with approval gates, making it valuable for
+teams managing multiple MCP servers.
+
+## Related information
+
+- [Deploy vMCP](../guides-vmcp/intro.mdx)
+- [Configure authentication](../guides-vmcp/authentication.mdx)
+- [Tool aggregation and conflict resolution](../guides-vmcp/tool-aggregation.mdx)
+- [Composite tools and workflows](../guides-vmcp/composite-tools.mdx)
+- [Optimize tool discovery](../guides-vmcp/optimizer.mdx)
+- [Proxy remote MCP servers](../guides-k8s/remote-mcp-proxy.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/contributing.mdx b/versioned_docs/version-1.0/toolhive/contributing.mdx
new file mode 100644
index 00000000..1b061c71
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/contributing.mdx
@@ -0,0 +1,194 @@
+---
+title: Contributing to ToolHive
+sidebar_label: Contributing
+description: Learn how to contribute to ToolHive and its ecosystem of projects.
+---
+
+Thank you for your interest in contributing to ToolHive! This page provides an
+overview of the project's architecture and links to resources for contributing
+to each component.
+
+## Project overview
+
+ToolHive is composed of several independently developed components, each with
+its own codebase, contributing guide, and development workflow. The components
+work together to provide a complete platform for running and managing Model
+Context Protocol (MCP) servers.
+
+All ToolHive components are open source and licensed under the Apache 2.0
+license.
+
+## Project components
+
+### ToolHive CLI, API, and Kubernetes Operator
+
+The core ToolHive repository contains the command-line interface, API server,
+and Kubernetes operator. These components share a common codebase and provide
+the runtime environment for deploying and managing MCP servers.
+
+**Repository**: [stacklok/toolhive](https://github.com/stacklok/toolhive)
+
+**Key resources**:
+
+- [Contributing guide](https://github.com/stacklok/toolhive/blob/main/CONTRIBUTING.md)
+- [Developer documentation](https://github.com/stacklok/toolhive/blob/main/docs/README.md)
+- [Kubernetes Operator developer guide](https://github.com/stacklok/toolhive/blob/main/cmd/thv-operator/README.md)
+- [Issue tracker](https://github.com/stacklok/toolhive/issues)
+
+### ToolHive desktop UI
+
+The ToolHive Desktop UI is a cross-platform application that provides a
+graphical interface for discovering, installing, and managing MCP servers. It's
+built with TypeScript and Electron, and runs on macOS, Windows, and Linux.
+
+**Repository**:
+[stacklok/toolhive-studio](https://github.com/stacklok/toolhive-studio)
+
+**Key resources**:
+
+- [Contributing guide](https://github.com/stacklok/toolhive-studio/blob/main/CONTRIBUTING.md)
+- [Developer documentation](https://github.com/stacklok/toolhive-studio/blob/main/docs/README.md)
+- [Issue tracker](https://github.com/stacklok/toolhive-studio/issues)
+
+### ToolHive Cloud UI
+
+The ToolHive Cloud UI is a web application for visualizing MCP servers running
+in user infrastructure with easy URL copying for integration with AI agents.
+Built with Next.js and TypeScript, this is an experimental project that is
+actively being developed and tested.
+
+**Repository**:
+[stacklok/toolhive-cloud-ui](https://github.com/stacklok/toolhive-cloud-ui)
+
+**Key resources**:
+
+- [Contributing guide](https://github.com/stacklok/toolhive-cloud-ui/blob/main/CONTRIBUTING.md)
+- [Issue tracker](https://github.com/stacklok/toolhive-cloud-ui/issues)
+
+### Registry server
+
+The Registry Server is an API server that implements the official MCP Registry
+API. It provides standardized access to MCP servers from multiple backends,
+including file-based and other API-compliant registries.
+
+**Repository**:
+[stacklok/toolhive-registry-server](https://github.com/stacklok/toolhive-registry-server)
+
+**Key resources**:
+
+- [Contributing guide](https://github.com/stacklok/toolhive-registry-server/blob/main/CONTRIBUTING.md)
+- [Issue tracker](https://github.com/stacklok/toolhive-registry-server/issues)
+
+### Built-in registry
+
+The built-in registry contains the curated list of MCP servers available in
+ToolHive. If you want to add a new MCP server to the registry, this is the
+repository to contribute to.
+
+**Repository**:
+[stacklok/toolhive-catalog](https://github.com/stacklok/toolhive-catalog)
+
+**Key resources**:
+
+- [Contributing guide](https://github.com/stacklok/toolhive-catalog/blob/main/CONTRIBUTING.md)
+- [Registry inclusion criteria](./concepts/registry-criteria.mdx)
+- [Submit a new MCP server](https://github.com/stacklok/toolhive-catalog/issues/new?template=add-an-mcp-server.md)
+- [Issue tracker](https://github.com/stacklok/toolhive-catalog/issues)
+
+### Dockyard
+
+Dockyard is a tool that packages MCP servers into containers. It handles the
+containerization process, making it easy to run MCP servers in isolated
+environments.
+
+**Repository**: [stacklok/dockyard](https://github.com/stacklok/dockyard)
+
+**Key resources**:
+
+- [Contributing guide](https://github.com/stacklok/dockyard/blob/main/CONTRIBUTING.md)
+- [Issue tracker](https://github.com/stacklok/dockyard/issues)
+
+### MCP Optimizer
+
+The MCP Optimizer acts as an intelligent intermediary between AI clients and MCP
+servers. It provides tool discovery, unified access to multiple MCP servers
+through a single endpoint, and intelligent routing. The optimizer reduces token
+usage by narrowing down the toolset to only relevant tools for each request.
+
+**Repository**:
+[StacklokLabs/mcp-optimizer](https://github.com/StacklokLabs/mcp-optimizer)
+
+**Key resources**:
+
+- [Contributing guide](https://github.com/StacklokLabs/mcp-optimizer/blob/main/CONTRIBUTING.md)
+- [Usage tutorial](./tutorials/mcp-optimizer.mdx)
+- [Documentation](https://github.com/StacklokLabs/mcp-optimizer/tree/main/docs)
+- [Issue tracker](https://github.com/StacklokLabs/mcp-optimizer/issues)
+
+### Documentation website
+
+The documentation website (this site) is built with Docusaurus and contains
+guides, tutorials, and reference documentation for all ToolHive components.
+
+**Repository**:
+[stacklok/docs-website](https://github.com/stacklok/docs-website)
+
+**Key resources**:
+
+- [Contributing guide](https://github.com/stacklok/docs-website/blob/main/CONTRIBUTING.md)
+- [Writing style guide](https://github.com/stacklok/docs-website/blob/main/STYLE-GUIDE.md)
+- [Issue tracker](https://github.com/stacklok/docs-website/issues)
+
+## Ways to contribute
+
+We welcome contributions of all kinds, and no contribution is too small. Whether
+you're fixing a typo, improving documentation, or adding a major feature, your
+help is appreciated. First-time contributors are especially welcome!
+
+Here are some ways you can contribute to ToolHive:
+
+- **Code**: Fix bugs, add features, or improve performance in any of the project
+ components.
+- **Documentation**: Write guides, improve existing docs, add examples, or fix
+ typos.
+- **Testing**: Test new features, report bugs, or contribute automated tests.
+- **Bug triage**: Help review and reproduce issues reported by others.
+- **Examples and tutorials**: Create sample projects or write tutorials showing
+ how to use ToolHive.
+- **MCP servers**: Submit new MCP servers to the registry or improve existing
+ ones.
+- **Community support**: Help answer questions in GitHub issues or on Discord.
+- **Design and UX**: Contribute to the UI design or suggest improvements to the
+ user experience.
+
+## Getting started
+
+Ready to contribute? Here are some ways to get involved:
+
+1. **Explore the codebase**: Browse the repositories that interest you and read
+ through the contributing guides to understand the development workflow.
+
+2. **Find an issue**: Look for issues labeled `good first issue` or
+ `help wanted` in the issue trackers. These are great starting points for new
+ contributors.
+
+3. **Join the community**: Connect with other contributors in the
+ [Stacklok Discord](https://discord.gg/stacklok) community. The
+ `#toolhive-developers` channel is dedicated to technical discussions.
+
+4. **Report bugs**: If you find a bug, open an issue in the appropriate
+ repository with a clear description and steps to reproduce.
+
+5. **Suggest features**: Have an idea for a new feature? Open an issue to
+ discuss it with the maintainers before starting work.
+
+## Code of conduct
+
+All contributors are expected to follow the
+[Stacklok Code of Conduct](https://github.com/stacklok/toolhive/blob/main/CODE_OF_CONDUCT.md).
+We're committed to providing a welcoming and inclusive environment for everyone.
+
+## Additional resources
+
+- [Frequently asked questions](./faq.mdx)
+- [Stacklok Discord community](https://discord.gg/stacklok)
diff --git a/versioned_docs/version-1.0/toolhive/enterprise.mdx b/versioned_docs/version-1.0/toolhive/enterprise.mdx
new file mode 100644
index 00000000..b949af93
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/enterprise.mdx
@@ -0,0 +1,322 @@
+---
+title: Stacklok Enterprise
+description: Stacklok Enterprise offerings for ToolHive
+---
+
+import HubSpotForm from '@site/src/components/HubSpotForm';
+import Heading from '@theme/Heading';
+import BrandedList from '@site/src/components/BrandedList';
+
+
+ A hardened and production-ready distribution of ToolHive Community
+
+
+Securely scale MCP servers across your enterprise with Stacklok Enterprise's
+signed binaries, hardened images, formal semantic versioning, backported
+security patches, and turnkey identity provider integrations. Kubernetes native
+and LLM agnostic. Self-hosted in your environment, governed by your policies, no
+vendor lock-in.
+
+
+
+---
+
+
+ Running in production at major financial services, technology, and software
+ companies,
+ including Fortune 500 and Global 2000 enterprises
+
+
+---
+
+## When Community isn't enough
+
+Teams typically move to Stacklok Enterprise when they hit one of these walls:
+
+
+
+- Developers are bringing their own MCP servers to work — shadow AI is spreading
+ and there is no central control
+- Your organization has multiple coding assistants and AI agents that need
+ access to business context
+- Your security or compliance team is asking how MCP servers are authenticated,
+ audited, and patched
+- You need SSO and IdP integration (Okta, Entra ID) across your organization
+- You are running MCP in production and need SLA-backed support for incidents
+- You need centralized governance and policy enforcement across multiple teams
+ or business units
+- Your environment requires a semantically versioned, supply-chain-attested
+ distribution rather than continuous rolling release
+
+
+
+Recognizing these challenges in your organization?
+[Schedule a demo](#schedule-a-demo) to see how Stacklok Enterprise addresses
+them.
+
+---
+
+## ToolHive Community vs. Stacklok Enterprise
+
+### Distribution & packaging
+
+| Capability | Community | Enterprise |
+| :-------------------------------------------------------- | :--------: | :----------------------------------------: |
+| ToolHive core platform | ✓ | ✓ |
+| Release model | Continuous | Semantically versioned (MAJOR.MINOR.PATCH) |
+| SigStore Cosign package signing with SBOM | ✓ | ✓ |
+| Patch versions retained for bugfixes and security updates | — | ✓ |
+| Scanning attestations | — | ✓ |
+| SLSA build provenance | — | ✓ |
+
+### Security and supply chain
+
+| Capability | Community | Enterprise |
+| :------------------------------------------------------- | :-------: | :--------: |
+| Basic scanning (Trivy, unit tests, integration tests) | ✓ | ✓ |
+| Static analysis on every release (attested via SigStore) | — | ✓ |
+| Autonomous pen testing on every minor release | — | ✓ |
+| Hardened container base images (Chainguard or equiv.) | — | ✓ |
+| Proactive notification of vulnerabilities | — | ✓ |
+| CVEs addressed within SLO with responsible disclosure | — | ✓ |
+| All Sev 0-3 vulnerabilities backported as patch updates | — | ✓ |
+
+### Auth, identity & governance
+
+| Capability | Community | Enterprise |
+| :---------------------------------------------------- | :-------: | :--------: |
+| Basic authentication | ✓ | ✓ |
+| Policy-as-code engine (CEDAR) | ✓ | ✓ |
+| Audit logging & compliance reporting | ✓ | ✓ |
+| Built-in IdP integration (Okta, Entra ID) | — | ✓ |
+| IdP group → ToolHive role mapping | — | ✓ |
+| Canonical policy packs (read-only, full CRUD, custom) | — | ✓ |
+| Token exchange & credential brokering | — | ✓ |
+
+### Enterprise UI & management
+
+| Capability | Community | Enterprise |
+| :------------------------------------------------- | :-------: | :--------: |
+| ToolHive CLI | ✓ | ✓ |
+| Usage telemetry & analytics (OpenTelemetry) | ✓ | ✓ |
+| Enterprise MCP registry server and catalog | ✓ | ✓ |
+| Enterprise Cloud UI (full CRUD management console) | — | ✓ |
+| Hardened Desktop UI (enterprise lockdown controls) | — | ✓ |
+
+### Versioning, maintenance & support
+
+| Capability | Community | Enterprise |
+| :--------------------------------------------- | :-------: | :--------: |
+| Latest release | ✓ | ✓ |
+| Supported versions: LATEST, LATEST-1, LATEST-2 | — | ✓ |
+| Community support (GitHub) | ✓ | ✓ |
+| Dedicated support with SLA | — | ✓ |
+| Proactive security advisories | — | ✓ |
+| Onboarding & integration assistance | — | ✓ |
+
+### Enterprise Connectors (MCP Servers)
+
+| Attribute | Community | Enterprise |
+| :------------------------------------------ | :---------: | :----------------------------------: |
+| Base image | Open source | Chainguard or equivalent |
+| Signing & attestations | — | SigStore signed with SLSA provenance |
+| Customized tools (tuned to agent workflows) | — | ✓ |
+| Streamable HTTP transport | — | ✓ |
+| SBOM & dependency vetting | — | ✓ |
+| Qualified for target workload | — | ✓ |
+| Maintained on enterprise release cadence | — | ✓ |
+| Backported security patches | — | ✓ |
+
+Seen enough to want a closer look? [Schedule a demo](#schedule-a-demo) to walk
+through the capabilities that matter most to your team.
+
+---
+
+## Product offerings
+
+Stacklok aims to keep pricing and licensing simple. Stacklok Enterprise and its
+Enterprise Connectors are licensed as an annual subscription. Professional
+services are priced based on time and materials.
+
+| SKU | Description | Pricing Model |
+| :------------------------------- | :----------------------------------------------------------------------------------------------------------------------------- | :---------------------------------: |
+| **Stacklok Enterprise Platform** | Enterprise licensed distribution of ToolHive with Cloud UI, Desktop UI, IdP integration, policy engine, and SLA-backed support | Annual subscription |
+| **Enterprise Connectors** | Production-ready connectors, maintained on enterprise release cadence | Annual subscription (per connector) |
+| **Professional Services** | Extended integration, policy configuration, additional IdP onboarding, connector development | Time & materials |
+
+Ready to discuss what the right package looks like for your organization?
+[Schedule a demo](#schedule-a-demo) to talk through your requirements.
+
+---
+
+## Enterprise Platform Components
+
+Stacklok Enterprise Platform secures MCP servers across your organization
+through its registry, runtime, gateway, and portal.
+
+### Registry: No more fighting shadow AI
+
+| The source of truth for approved MCP servers within the enterprise. |
+| :----------------------------------------------------------------------- |
+| Integrate with the official MCP registry |
+| Add custom MCP servers and skills |
+| Group servers based on role or use case |
+| Manage your registry with an API-driven interface |
+| Verify provenance and sign servers with built-in security controls |
+| Preset configurations and permissions for a frictionless user experience |
+
+### Runtime: Kubernetes-native deployment
+
+| Deploy, run, and manage MCP servers in Kubernetes with security guardrails. |
+| :-------------------------------------------------------------------------- |
+| Deploy MCP servers in the cloud via Kubernetes |
+| Run MCP servers locally via Docker or Podman |
+| Proxy remote MCP servers securely for unified management |
+| Kubernetes Operator for fleet and resource management |
+| Leverage OpenTelemetry for centralized monitoring and audit logging |
+
+### Gateway: Single endpoint, full control
+
+| Intelligent MCP gateway for authentication, authorization, and policy enforcement. |
+| :--------------------------------------------------------------------------------------- |
+| Integrate with your IdP for SSO (OIDC/OAuth compatible) |
+| Build composite tools that orchestrate multiple tools in parallel or sequential chains |
+| Customize and filter tools and descriptions |
+| Reduce context bloat and token usage |
+| Connect with local clients like Claude Desktop, Cursor, and Visual Studio Code (VS Code) |
+
+### Portal: Self-service with guardrails
+
+| Custom UI for teams to discover, deploy and manage approved MCP servers. |
+| :----------------------------------------------------------------------- |
+| Cross-platform desktop app and web-based cloud UI |
+| Make it easy for admins to curate MCP servers and tools |
+| Automate server discovery |
+| Install MCP servers with a single click |
+| Compatible with hundreds of AI clients |
+
+Ready to see how the platform works in your environment?
+[Start a proof of concept](#validate-stacklok-enterprise-in-your-environment) to
+take the next step.
+
+---
+
+## Validate Stacklok Enterprise in your environment
+
+Stacklok helps you validate Stacklok Enterprise in your environment at your pace
+with forward-deployed engineering support.
+
+
+
+---
+
+## Frequently asked questions
+
+
+How does Stacklok Enterprise relate to ToolHive Community?
+
+ToolHive Community is an open source distribution optimized for individual
+developers and pre-production use, making it the right tool for evaluating MCP
+and building a proof of concept. Stacklok Enterprise is a separate, hardened
+distribution built for production: semantically versioned, with IdP integration,
+centralized governance, and SLA-backed support. Moving from Community to
+Enterprise is a supported migration where Stacklok provides the enterprise
+binaries and dedicated engineering support to take you from proof of concept to
+production.
+[See the full comparison](#toolhive-community-vs-stacklok-enterprise) or
+[learn about the proof of concept engagement](#validate-stacklok-enterprise-in-your-environment).
+
+
+
+
+What happens to my data if I end my Enterprise contract?
+
+Your data never leaves your environment. Stacklok Enterprise is fully
+self-hosted: you retain complete control over your data and infrastructure,
+regardless of contract status. If you end your subscription, you can downgrade
+to the open-source version at any time. The only things you lose are access to
+Enterprise features, forward-deployed engineers, backported security patches,
+and dedicated support. There is zero vendor lock-in.
+[Learn more about the product offerings](#product-offerings).
+
+
+
+
+How long does a typical deployment take?
+
+Most customers begin to see value in less than 2 weeks of contract signing.
+Stacklok works directly with your platform team, and every Enterprise license
+includes dedicated engineering support throughout the process. You will need an
+existing Kubernetes environment to get started. Timelines are scoped to your
+environment, so if your situation is more complex, Stacklok will work at your
+pace.
+[Learn about the proof of concept engagement](#validate-stacklok-enterprise-in-your-environment).
+
+
+
+
+Why should I use an MCP platform instead of running MCP servers directly?
+
+Running MCP servers directly gives you no isolation, no access controls, and no
+visibility into what those servers are doing. Stacklok Enterprise addresses this
+by running each server in its own container with least-privilege permissions,
+encrypting credentials at rest, and tracing every tool call via OpenTelemetry.
+Stacklok Enterprise adds centralized governance, IdP-backed authentication, and
+audit logging for teams running MCP at scale across their organization.
+[Explore the core concepts](./concepts/) to dig deeper into how ToolHive works.
+
+
+
+
+What AI clients work with Stacklok Enterprise?
+
+Stacklok Enterprise works with any AI coding assistant or agent that supports
+MCP. This includes Claude Code, GitHub Copilot, Cursor, Windsurf, VS Code, Zed,
+Cline, Continue, Roo Code, Goose, LM Studio, OpenAI Codex, and many more. Most
+clients support automatic configuration so developers can connect without manual
+setup.
+[See the full client compatibility reference](./reference/client-compatibility.mdx)
+for the complete list.
+
+
+
+
+Can I run custom MCP servers outside the Stacklok registry?
+
+Yes. Stacklok Enterprise starts with a base registry of vetted, hardened MCP
+servers maintained by Stacklok. From there, you have full control to add your
+own servers from public package managers, Docker images, remote URLs, or build a
+private registry tailored to your organization. You are never limited to
+Stacklok's catalog.
+[See how to run MCP servers in Kubernetes](./guides-k8s/run-mcp-k8s.mdx) for the
+full details.
+
+
+
+---
+
+## Explore ToolHive Community
+
+:::tip[Not ready for Stacklok Enterprise yet?]
+
+ToolHive Community is free, open source, and the best way to evaluate MCP before
+moving to production.
+
+[Get started with ToolHive Community →](./index.mdx)
+
+:::
diff --git a/versioned_docs/version-1.0/toolhive/faq.mdx b/versioned_docs/version-1.0/toolhive/faq.mdx
new file mode 100644
index 00000000..5a4d1fc6
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/faq.mdx
@@ -0,0 +1,237 @@
+---
+title: Frequently asked questions
+sidebar_label: FAQ
+description: Common questions about ToolHive.
+---
+
+## General questions
+
+### What is ToolHive?
+
+ToolHive is an open source platform that simplifies the deployment and
+management of Model Context Protocol (MCP) servers. It runs MCP servers in
+secure, isolated containers and provides tools to manage them easily. You can
+run ToolHive as a graphical desktop app, command-line tool, or Kubernetes
+operator.
+
+### What is the Model Context Protocol (MCP)?
+
+MCP is a protocol that allows AI applications to connect to external data
+sources and tools. It provides a standardized way for AI models to access
+real-world context like APIs, source code repositories, databases, and other
+systems. Think of it as a bridge between AI models and the external systems
+where your data and applications live.
+
+### Do I need to know how to code to use ToolHive?
+
+No. ToolHive includes a graphical interface that doesn't require coding
+knowledge. However, some MCP servers may require configuration or secrets, such
+as API keys, that you'll need to set up. The ToolHive CLI is more technical but
+comes with comprehensive documentation.
+
+### Is ToolHive free to use?
+
+Yes, ToolHive is open source (Apache 2.0 licensed) and free to use. You can find
+the source code on GitHub and use it without any licensing fees.
+
+### Can I use ToolHive UI and the CLI together?
+
+Yes, but ToolHive UI manages the CLI automatically. When you install ToolHive
+UI, it creates a symlink to its bundled CLI and configures your PATH. You can
+use the `thv` command from your terminal—it uses the UI-managed version.
+
+If you have a standalone CLI installed separately (via Homebrew, WinGet, or
+manually), it will conflict with the UI-managed version and show an error.
+Uninstall the standalone version to resolve this.
+
+For more information, see the [CLI access guide](./guides-ui/cli-access.mdx) and
+[CLI conflict resolution](./guides-cli/install.mdx#cli-conflict-resolution).
+
+## Using MCP servers
+
+### How do I find available MCP servers?
+
+ToolHive includes a curated registry of verified MCP servers. You can browse
+them from the **Registry** tab in the UI or by running `thv registry list` in
+the CLI.
+
+### What MCP servers are available?
+
+The registry includes servers for common use cases, such as retrieving content
+from the web using the
+[GoFetch MCP server](https://github.com/StacklokLabs/gofetch), and for popular
+services and platforms like:
+
+- **Atlassian** – Access Jira and Confluence
+- **AWS Documentation** – Query AWS service documentation
+- **GitHub** – Access repositories, issues, and pull requests
+- **Kubernetes** – Interact with Kubernetes clusters via the
+ [MKP MCP server](https://github.com/StacklokLabs/mkp)
+- **MongoDB**, **PostgreSQL**, **Redis** – Connect to databases
+- **Notion** – Connect to Notion workspaces
+- And many more
+
+### Can I run MCP servers that aren't in the registry?
+
+Yes. You can run any MCP server from a container image or source package, even
+if it's not in the registry. Just provide the image name or package details when
+starting the server using the CLI or UI. See the custom MCP server section in
+the [UI guide](./guides-ui/run-mcp-servers.mdx#install-a-custom-mcp-server) and
+[CLI guide](./guides-cli/run-mcp-servers.mdx#run-a-custom-mcp-server) for more
+details.
+
+The Kubernetes operator also supports custom MCP servers that are packaged as
+container images.
+
+:::tip
+
+You can use the ToolHive CLI to run a custom MCP server from a source package
+once, then export the Docker image for import into your container registry or
+Kubernetes cluster to use it with the operator.
+
+:::
+
+## Privacy and data collection
+
+### Does ToolHive collect any data?
+
+ToolHive collects anonymous usage metrics to help improve the product. These
+metrics include only tool call counts and are completely anonymous. No personal
+information, user identifiers, or sensitive data is collected.
+
+The metrics collection:
+
+- Is enabled by default
+- Only tracks the number of tool calls
+- Uses a randomly generated instance ID (not tied to your identity)
+- Is automatically disabled in CI environments
+- Can be easily disabled
+
+### How do I disable usage metrics?
+
+You can opt out of usage metrics collection in two ways:
+
+**Option 1: Persistent configuration (recommended)**
+
+Use the ToolHive CLI to disable metrics permanently:
+
+```bash
+thv config usage-metrics disable
+```
+
+**Option 2: Environment variable**
+
+Set an environment variable to disable metrics for the current session:
+
+```bash
+export TOOLHIVE_USAGE_METRICS_ENABLED=false
+```
+
+Once you opt out, ToolHive stops collecting and sending usage metrics. You need
+to restart any running servers for the change to take effect.
+
+## Security and permissions
+
+### Is it safe to run MCP servers?
+
+ToolHive runs MCP servers in isolated containers with minimal default
+permissions. Each server runs in its own container with restricted access to
+your system and network.
+
+:::tip
+
+For extra security, review the permission profiles and network isolation options
+before running new or untrusted MCP servers.
+
+:::
+
+### How does ToolHive handle secrets like API keys?
+
+ToolHive provides secure secrets management:
+
+- Secrets are encrypted and stored securely on your system
+- They're passed to MCP servers as environment variables
+- Secrets never appear in plaintext in configuration files
+- Integration with 1Password is also supported
+
+### Can I control what an MCP server can access?
+
+Yes. ToolHive uses permission profiles to control:
+
+- **File system access** – Which directories the server can read or write
+- **Network access** – Which hosts and ports the server can connect to
+
+You can use built-in profiles or create custom ones for specific security
+requirements.
+
+### What's network isolation and when should I use it?
+
+Network isolation creates a secure network architecture that filters all
+outbound connections from MCP servers. Use the `--isolate-network` flag when
+running servers that need strict network controls, especially in enterprise
+environments.
+
+## Enterprise and advanced usage
+
+### Can I use ToolHive in my company?
+
+Yes. ToolHive is designed for both individual developers and enterprise teams.
+The Kubernetes Operator provides centralized management, security controls, and
+integration with existing infrastructure for enterprise deployments.
+
+### How do I deploy ToolHive in Kubernetes?
+
+Use the ToolHive Kubernetes Operator to deploy and manage MCP servers as
+Kubernetes resources. See the [Kubernetes guides](./guides-k8s/index.mdx) for
+detailed instructions. The Kubernetes operator is currently experimental.
+
+### Can I run my own custom MCP servers?
+
+Yes. You can run custom MCP servers from Docker images or source packages.
+ToolHive supports:
+
+- Docker images from public or private registries
+- Source packages from package managers like npm, PyPI, or Go modules
+
+### How do I get my MCP server added to the ToolHive registry?
+
+The ToolHive registry has specific
+[inclusion criteria](./concepts/registry-criteria.mdx), such as being open
+source, following good security practices, and maintaining code quality. Review
+the criteria and
+[submit your server for consideration](https://github.com/stacklok/toolhive-catalog/issues/new?template=add-an-mcp-server.md).
+
+### Can I use ToolHive behind a corporate firewall?
+
+Yes. ToolHive supports corporate environments with:
+
+- Custom CA certificate configuration for TLS inspection
+- Network isolation and permission profiles
+- Integration with secret management systems like 1Password
+
+## Getting help
+
+### Where can I get help if I'm stuck?
+
+- **Documentation** – Check the comprehensive guides and reference documentation
+- **GitHub Issues** – Report bugs or request features on the
+ [ToolHive GitHub repository](https://github.com/stacklok/toolhive/issues)
+- **Discord Community** – Join the
+ [Stacklok Discord](https://discord.gg/stacklok) for community support
+- **Troubleshooting sections** – Each guide includes troubleshooting tips for
+ common issues
+
+### How do I report a bug or request a feature?
+
+Open an issue in the appropriate GitHub repository:
+
+- [**ToolHive UI**](https://github.com/stacklok/toolhive-studio/issues) – For
+ issues specific to the graphical desktop app
+- [**ToolHive CLI & Kubernetes**](https://github.com/stacklok/toolhive/issues) –
+ For issues related to the CLI tool or Kubernetes operator
+
+### Is there a community I can join?
+
+Yes! Join the [Stacklok Discord community](https://discord.gg/stacklok) to
+connect with other ToolHive users, ask questions, and share your experiences.
+There's a dedicated `#toolhive-developers` channel for technical discussions.
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/advanced-cicd.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/advanced-cicd.mdx
new file mode 100644
index 00000000..f803324f
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/advanced-cicd.mdx
@@ -0,0 +1,318 @@
+---
+title: Advanced CI/CD patterns
+description:
+ Advanced CI/CD patterns for building and deploying MCP server containers with
+ ToolHive.
+---
+
+This guide covers advanced CI/CD patterns for building MCP server containers
+using ToolHive's [`thv build`](../reference/cli/thv_build.md) command. These
+patterns include multi-architecture builds, supply chain security, and efficient
+change detection.
+
+These patterns are intended for anyone building MCP server containers regularly,
+such as maintainers of MCP servers or organizations deploying custom servers or
+repackaging existing ones.
+
+## Prerequisites
+
+Before implementing these advanced patterns, ensure you have:
+
+- Basic understanding of [`thv build`](./build-containers.mdx) command
+- Experience with CI/CD pipelines (GitHub Actions, GitLab CI, etc.)
+- Container registry access for pushing images
+- Understanding of Docker Buildx for multi-architecture builds
+
+## Multi-architecture MCP server builds
+
+Build MCP server containers for multiple architectures (amd64, arm64) using
+Docker Buildx:
+
+```yaml
+name: Multi-arch Build
+on:
+ push:
+ tags: ['v*']
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Install ToolHive
+ uses: StacklokLabs/toolhive-actions/install@v0
+ with:
+ version: latest
+
+ - name: Generate Dockerfile
+ run: |
+ thv build --dry-run --output Dockerfile uvx://mcp-server-git
+
+ - name: Build multi-arch container
+ run: |
+ docker buildx build \
+ --platform linux/amd64,linux/arm64 \
+ --tag ghcr.io/myorg/mcp-server:${{ github.ref_name }} \
+ --push \
+ .
+```
+
+## Supply chain security
+
+Enhance security with SBOM generation, provenance attestation, and image
+signing:
+
+```yaml
+name: Secure Build
+on:
+ push:
+ tags: ['v*']
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+ id-token: write
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install Cosign
+ uses: sigstore/cosign-installer@v3
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Install ToolHive
+ uses: StacklokLabs/toolhive-actions/install@v0
+ with:
+ version: latest
+
+ - name: Generate Dockerfile
+ run: |
+ thv build --dry-run --output Dockerfile uvx://mcp-server-git
+
+ - name: Build with security features
+ uses: docker/build-push-action@v6
+ id: build
+ with:
+ context: .
+ platforms: linux/amd64,linux/arm64
+ push: true
+ tags: ghcr.io/myorg/mcp-server:${{ github.ref_name }}
+ sbom: true # Generate Software Bill of Materials
+ provenance: true # Generate build provenance
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+
+ - name: Sign container image
+ env:
+ DIGEST: ${{ steps.build.outputs.digest }}
+ run: |
+ cosign sign --yes ghcr.io/myorg/mcp-server@${DIGEST}
+```
+
+## Efficient change detection
+
+Build only when relevant files change to optimize CI/CD performance:
+
+```yaml
+name: Conditional Build
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+jobs:
+ detect-changes:
+ runs-on: ubuntu-latest
+ outputs:
+ build_needed: ${{ steps.changes.outputs.build_needed }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 2
+
+ - name: Detect changes
+ id: changes
+ run: |
+ # Check if MCP server configs or Dockerfiles changed
+ if git diff --name-only HEAD~1..HEAD | grep -E "(mcp-configs/|Dockerfile|\.thv)"; then
+ echo "build_needed=true" >> $GITHUB_OUTPUT
+ else
+ echo "build_needed=false" >> $GITHUB_OUTPUT
+ fi
+
+ build:
+ needs: detect-changes
+ if: needs.detect-changes.outputs.build_needed == 'true'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install ToolHive
+ uses: StacklokLabs/toolhive-actions/install@v0
+ with:
+ version: latest
+
+ - name: Build containers
+ run: |
+ thv build --tag ghcr.io/myorg/mcp-server:latest uvx://mcp-server-git
+```
+
+## Matrix builds for multiple servers
+
+Build multiple MCP servers in parallel using matrix strategies:
+
+```yaml
+name: Matrix Build
+on:
+ push:
+ tags: ['v*']
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ server:
+ - name: git-server
+ scheme: uvx://mcp-server-git
+ - name: filesystem-server
+ scheme: npx://@modelcontextprotocol/server-filesystem
+ - name: custom-server
+ scheme: go://github.com/myorg/custom-mcp-server@latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install ToolHive
+ uses: StacklokLabs/toolhive-actions/install@v0
+ with:
+ version: latest
+
+ - name: Build ${{ matrix.server.name }}
+ run: |
+ thv build --tag ghcr.io/myorg/${{ matrix.server.name }}:${{ github.ref_name }} \
+ ${{ matrix.server.scheme }}
+
+ - name: Push ${{ matrix.server.name }}
+ run: |
+ docker push ghcr.io/myorg/${{ matrix.server.name }}:${{ github.ref_name }}
+```
+
+## Vulnerability scanning
+
+Integrate security scanning into your build pipeline:
+
+```yaml
+name: Secure Build with Scanning
+on:
+ push:
+ tags: ['v*']
+
+jobs:
+ build-and-scan:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install ToolHive
+ uses: StacklokLabs/toolhive-actions/install@v0
+ with:
+ version: latest
+
+ - name: Build container
+ run: |
+ thv build --tag mcp-server:scan uvx://mcp-server-git
+
+ - name: Run Trivy vulnerability scanner
+ uses: aquasecurity/trivy-action@master
+ with:
+ image-ref: 'mcp-server:scan'
+ format: 'sarif'
+ output: 'trivy-results.sarif'
+
+ - name: Upload Trivy scan results
+ uses: github/codeql-action/upload-sarif@v3
+ if: always()
+ with:
+ sarif_file: 'trivy-results.sarif'
+
+ - name: Tag and push if scan passes
+ run: |
+ docker tag mcp-server:scan ghcr.io/myorg/mcp-server:${{ github.ref_name }}
+ docker push ghcr.io/myorg/mcp-server:${{ github.ref_name }}
+```
+
+## GitLab CI example
+
+For GitLab CI users, here's an equivalent pipeline:
+
+```yaml
+# .gitlab-ci.yml
+stages:
+ - build
+ - security
+ - deploy
+
+variables:
+ DOCKER_DRIVER: overlay2
+ DOCKER_TLS_CERTDIR: '/certs'
+
+build:
+ stage: build
+ image: docker:latest
+ services:
+ - docker:dind
+ before_script:
+ # Install ToolHive CLI using curl and jq to get the latest version
+ - |
+ VERSION=$(curl -s https://api.github.com/repos/stacklok/toolhive/releases/latest | jq -r .tag_name)
+ wget "https://github.com/stacklok/toolhive/releases/download/${VERSION}/toolhive_${VERSION#v}_linux_amd64.tar.gz"
+ tar -xzf "toolhive_${VERSION#v}_linux_amd64.tar.gz"
+ install -m 0755 thv /usr/local/bin/
+ - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
+ script:
+ - thv build --tag $CI_REGISTRY_IMAGE/mcp-server:$CI_COMMIT_TAG
+ uvx://mcp-server-git
+ - docker push $CI_REGISTRY_IMAGE/mcp-server:$CI_COMMIT_TAG
+ only:
+ - tags
+
+security_scan:
+ stage: security
+ image: aquasec/trivy:latest
+ script:
+ - trivy image --exit-code 1 --severity HIGH,CRITICAL
+ $CI_REGISTRY_IMAGE/mcp-server:$CI_COMMIT_TAG
+ only:
+ - tags
+```
+
+## Best practices
+
+When implementing advanced CI/CD patterns:
+
+1. **Use specific tags** instead of `latest` for production deployments
+2. **Implement proper caching** to speed up builds
+3. **Scan for vulnerabilities** before pushing to production registries
+4. **Sign images** for supply chain security
+5. **Use matrix builds** for multiple MCP servers
+6. **Implement change detection** to avoid unnecessary builds
+7. **Store sensitive data** in CI/CD secrets, not in code
+
+## Related information
+
+- [Build MCP server containers](./build-containers.mdx)
+- [Run MCP servers in Kubernetes](../guides-k8s/run-mcp-k8s.mdx)
+- [`thv build` command reference](../reference/cli/thv_build.md)
+- [Secrets management](./secrets-management.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/api-server.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/api-server.mdx
new file mode 100644
index 00000000..ca8de827
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/api-server.mdx
@@ -0,0 +1,89 @@
+---
+title: Run the API server
+description: How to run the local ToolHive API server.
+---
+
+ToolHive includes a built-in API server that provides a RESTful interface for
+interacting with MCP servers. The API server is useful for integrating ToolHive
+with other applications or automating tasks.
+
+:::note
+
+The API server isn't intended for production use. It's designed for local
+automation and UI development, and doesn't implement any authentication or
+authorization mechanisms.
+
+For production use cases, consider using the ToolHive Kubernetes operator, which
+provides a more robust and secure way to manage ToolHive instances in a
+multi-user environment.
+
+:::
+
+## Start the API server
+
+To start the API server, use the following command:
+
+```bash
+thv serve
+```
+
+This starts the API server on `localhost` (127.0.0.1) using the default port
+`8080`.
+
+Test the API server using `curl` or a web browser:
+
+```bash
+curl http://localhost:8080/api/v1beta/status
+```
+
+You should see a JSON response with the current ToolHive version.
+
+## Custom networking
+
+By default, the API server listens on `localhost` (127.0.0.1) port `8080`.
+
+You can specify a different port using the `--port` option:
+
+```bash
+thv serve --port
+```
+
+If you're running the API server on a remote host, specify the hostname or IP
+address to bind to using the `--host` option:
+
+```bash
+thv serve --host
+```
+
+## UNIX socket support
+
+The API server can also be exposed via a UNIX socket instead of a TCP port. Use
+the `--socket` option to specify a socket path:
+
+```bash
+thv serve --socket /tmp/toolhive.sock
+```
+
+When using a UNIX socket, the `--socket` argument overrides the host:port
+address configuration.
+
+## API documentation
+
+See the [ToolHive API documentation](../reference/api.mdx) for details on
+available endpoints, request and response formats.
+
+You can also run a local instance of the API documentation using the `--openapi`
+option:
+
+```bash
+thv serve --openapi
+```
+
+Open a browser to `http://localhost:8080/api/doc` to view the API documentation.
+The OpenAPI specification is also available at
+`http://localhost:8080/api/openapi.json`.
+
+## Related information
+
+- [ToolHive API documentation](../reference/api.mdx)
+- [`thv serve` command reference](../reference/cli/thv_serve.md)
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/auth.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/auth.mdx
new file mode 100644
index 00000000..d487daee
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/auth.mdx
@@ -0,0 +1,156 @@
+---
+title: Authentication and authorization
+description:
+ How to set up authentication and authorization for MCP servers using the
+ ToolHive CLI.
+---
+
+import OidcPrerequisites from '../_partials/_oidc-prerequisites.mdx';
+import BasicCedarConfig from '../_partials/_basic-cedar-config.mdx';
+import AuthTroubleshooting from '../_partials/_auth-troubleshooting.mdx';
+
+This guide shows you how to secure your MCP servers using OAuth-based
+authentication and Cedar-based authorization policies with the ToolHive CLI.
+
+:::info
+
+Authentication and authorization are emerging capabilities in the MCP ecosystem.
+The official MCP authorization specification is still evolving, and client
+support for these features is limited. ToolHive is leading the way in
+implementing these capabilities, but you may encounter some limitations with
+certain clients.
+
+:::
+
+## Prerequisites
+
+
+
+## Set up authentication
+
+### Step 1: Gather OIDC configuration
+
+First, collect the necessary information from your identity provider:
+
+- Client ID
+- Audience value
+- Issuer URL
+- JWKS URL (for key verification)
+
+### Step 2: Run an MCP server with authentication
+
+Use the following command to start an MCP server with authentication enabled:
+
+```bash
+thv run \
+ --oidc-audience \
+ --oidc-client-id \
+ --oidc-issuer \
+ --oidc-jwks-url \
+
+```
+
+Replace the placeholders with your actual OIDC configuration.
+
+### Step 3: Test authentication
+
+Once your server is running with authentication enabled, clients must include a
+valid JWT (JSON Web Token) in the `Authorization` header of each HTTP request.
+The token should:
+
+- Be issued by your configured identity provider
+- Include the correct audience claim
+- Not be expired
+- Have a valid signature
+
+:::note[Client support limitations]
+
+Client support for authentication is limited at this time. While some clients
+support HTTP headers with SSE MCP client configurations, you should not pass JWT
+tokens in this way since it requires manual configuration and exposes your token
+in plain text.
+
+ToolHive is working on a solution to securely handle authentication for clients.
+Stay tuned for updates on this feature.
+
+:::
+
+:::note[Obtaining JWT tokens]
+
+How to obtain JWT tokens varies by identity provider and is outside the scope of
+this guide. Consult your identity provider's documentation for specific
+instructions on:
+
+- Interactive user authentication flows (OAuth 2.0 Authorization Code flow)
+- Service-to-service authentication (Client Credentials flow)
+- API token generation and management
+
+For Kubernetes service accounts, tokens are automatically mounted at
+`/var/run/secrets/kubernetes.io/serviceaccount/token` in pods.
+
+:::
+
+To verify that authentication is working, you can use a tool like `curl` to make
+a request to your MCP server:
+
+```bash
+curl -H "Authorization: Bearer " \
+
+```
+
+## Set up authorization
+
+ToolHive uses Amazon's Cedar policy language for fine-grained, secure-by-default
+authorization. Authorization is explicit: if a request is not explicitly
+permitted by a policy, it is denied. Deny rules always take precedence over
+permit rules.
+
+### Step 1: Create an authorization configuration file
+
+
+
+Save this file to a location accessible to ToolHive, such as
+`/path/to/authz-config.json`.
+
+### Step 2: Run an MCP server with authorization
+
+Start your MCP server with the authorization configuration:
+
+```bash
+thv run \
+ --authz-config /path/to/authz-config.json \
+
+```
+
+You can combine this with the authentication parameters from the previous
+section:
+
+```bash
+thv run \
+ --oidc-audience \
+ --oidc-client-id \
+ --oidc-issuer \
+ --oidc-jwks-url \
+ --authz-config /path/to/authz-config.json \
+
+```
+
+### Step 3: Test authorization
+
+Once your server is running with authorization enabled, clients will be subject
+to the Cedar policies defined in your configuration file. When a client attempts
+to perform an action, ToolHive will evaluate the request against the policies.
+If the request is permitted, the action will proceed; otherwise, it will be
+denied with a 403 Forbidden response.
+
+## Related information
+
+- For conceptual understanding, see
+ [Authentication and authorization framework](../concepts/auth-framework.mdx)
+- For detailed Cedar policy syntax, see
+ [Cedar policies](../concepts/cedar-policies.mdx) and the
+ [Cedar documentation](https://docs.cedarpolicy.com/)
+
+## Troubleshooting
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/build-containers.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/build-containers.mdx
new file mode 100644
index 00000000..fae9a1a4
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/build-containers.mdx
@@ -0,0 +1,501 @@
+---
+title: Build MCP containers
+description:
+ How to build MCP server containers without running them using the ToolHive
+ CLI.
+---
+
+This guide explains how to use the [`thv build`](../reference/cli/thv_build.md)
+command to build MCP server containers from protocol schemes without running
+them. This is useful for pre-building containers for Kubernetes deployments,
+CI/CD pipelines, and container registry workflows.
+
+## Overview
+
+The `thv build` command allows you to build containers from protocol schemes
+(`uvx://`, `npx://`, `go://`) without immediately running them. This provides
+several benefits:
+
+- **Pre-build containers** for faster deployment in Kubernetes environments
+- **Separate build and run phases** in CI/CD pipelines
+- **Custom image tagging** for container registry workflows
+- **Dockerfile generation** for inspection and customization
+- **Build validation** before deployment
+
+## Basic usage
+
+To build a container from a protocol scheme:
+
+```bash
+thv build
+```
+
+For example:
+
+```bash
+# Build a Python MCP server using uvx
+thv build uvx://mcp-server-git
+
+# Build a Node.js MCP server using npx with a pinned version
+thv build npx://@upstash/context7-mcp@1.0.26
+
+# Build a Go MCP server
+thv build go://github.com/example/my-mcp-server@latest
+```
+
+:::info[What's happening?]
+
+When you run `thv build`, ToolHive:
+
+1. Detects the protocol scheme and extracts the package reference
+2. Generates a Dockerfile based on the appropriate template
+3. Builds a Docker image with the package installed
+4. Tags the image with an auto-generated name or your custom tag
+5. Displays the built image name for use with other tools
+
+:::
+
+## Custom image tagging
+
+Use the `--tag` (or `-t`) flag to specify a custom name and tag for the built
+image:
+
+```bash
+thv build --tag my-custom-name:latest npx://@modelcontextprotocol/server-filesystem
+```
+
+This is particularly useful for:
+
+- **Container registries**: Tag images for pushing to registries
+- **Kubernetes deployments**: Use predictable image names in manifests
+- **Version management**: Tag images with specific versions
+
+### Tagging examples
+
+
+
+
+Build and tag for pushing to a container registry:
+
+```bash
+# Build and tag for Docker Hub
+thv build --tag myusername/mcp-git-server:v1.0.0 uvx://mcp-server-git
+
+# Build and tag for GitHub Container Registry
+thv build --tag ghcr.io/myorg/mcp-filesystem:latest npx://@modelcontextprotocol/server-filesystem
+
+# Push to registry
+docker push ghcr.io/myorg/mcp-filesystem:latest
+```
+
+
+
+
+Build images with predictable names for Kubernetes manifests:
+
+```bash
+# Build with a consistent tag
+thv build --tag mcp-servers/git-server:stable uvx://mcp-server-git
+```
+
+Use the built image in your Kubernetes manifests:
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: git-server
+ namespace: production
+spec:
+ image: mcp-servers/git-server:stable
+ # ... other spec fields ...
+```
+
+
+
+
+Build multiple versions of the same server:
+
+```bash
+# Build different versions
+thv build --tag mcp-git:v1.0.0 uvx://mcp-server-git@1.0.0
+thv build --tag mcp-git:v1.1.0 uvx://mcp-server-git@1.1.0
+thv build --tag mcp-git:latest uvx://mcp-server-git@latest
+```
+
+
+
+
+## Protocol schemes
+
+The `thv build` command supports the same protocol schemes as
+[`thv run`](./run-mcp-servers.mdx#run-a-server-using-protocol-schemes):
+
+### Python (uvx)
+
+Build Python-based MCP servers using the uv package manager:
+
+```bash
+# Build with auto-generated name
+thv build uvx://mcp-server-git
+
+# Build with custom tag
+thv build --tag my-git-server:latest uvx://mcp-server-git@1.2.0
+```
+
+### Node.js (npx)
+
+Build Node.js-based MCP servers using npm:
+
+```bash
+# Build with auto-generated name
+thv build npx://@modelcontextprotocol/server-filesystem
+
+# Build with custom tag
+thv build --tag filesystem-server:v2.0 npx://@modelcontextprotocol/server-filesystem@2.0.0
+```
+
+### Go
+
+Build Go-based MCP servers:
+
+```bash
+# Build from remote Go module
+thv build --tag grafana-mcp:latest go://github.com/grafana/mcp-grafana/cmd/mcp-grafana@latest
+
+# Build from local Go project
+thv build --tag my-local-server:dev go://./cmd/my-mcp-server
+```
+
+## Build-time arguments
+
+Some MCP servers require specific subcommands or arguments that must always be
+present. You can bake these required arguments directly into the container image
+at build time using the `--` separator at the end of the `thv build` command:
+
+```bash
+thv build --
+```
+
+:::info[Build-time vs runtime arguments]
+
+- **Build-time arguments**: Baked into the container image and always present.
+ These are typically required subcommands or essential configuration flags.
+- **Runtime arguments**: Passed when running the container and appended after
+ build-time arguments. These are typically optional flags or dynamic
+ configuration.
+
+:::
+
+Build-time arguments are embedded in the container's ENTRYPOINT and always
+execute before any runtime arguments. For example, the LaunchDarkly MCP server
+requires a `start` subcommand:
+
+```bash
+# Bake "start" subcommand into container
+thv build --tag launchdarkly-mcp:latest npx://@launchdarkly/mcp-server -- start
+
+# Runtime args still append after baked-in args
+thv run launchdarkly-mcp:latest -- --verbose
+# Executes: npx @launchdarkly/mcp-server start --verbose
+```
+
+You can include multiple build-time arguments as needed:
+
+```bash
+thv build uvx://my-package -- --transport stdio --log-level info
+```
+
+## Dockerfile generation
+
+Use the `--dry-run` flag to generate the Dockerfile without building the image:
+
+```bash
+# Output Dockerfile to stdout
+thv build --dry-run uvx://mcp-server-git
+
+# Save Dockerfile to a file
+thv build --dry-run --output Dockerfile.mcp-git uvx://mcp-server-git
+```
+
+This is useful for:
+
+- **Inspecting the build process** before building
+- **Customizing Dockerfiles** for specific requirements
+- **Understanding dependencies** and build steps
+- **Debugging build issues**
+
+### Example Dockerfile output
+
+```dockerfile
+# Generated by: thv build --dry-run uvx://mcp-server-git
+FROM python:3.12-slim
+
+# Install uv
+RUN pip install uv
+
+# Install the package
+RUN uv tool install mcp-server-git
+
+# Set the entrypoint
+ENTRYPOINT ["uv", "tool", "run", "mcp-server-git"]
+```
+
+## Kubernetes workflows
+
+The `thv build` command is especially useful for Kubernetes deployments where
+you need to pre-build containers before deploying them.
+
+### Pre-build workflow
+
+1. **Build the container** with a specific tag:
+
+ ```bash
+ thv build --tag ghcr.io/myorg/mcp-git:v1.0.0 uvx://mcp-server-git@1.0.0
+ ```
+
+2. **Push to container registry**:
+
+ ```bash
+ docker push ghcr.io/myorg/mcp-git:v1.0.0
+ ```
+
+3. **Deploy to Kubernetes** using the pre-built image:
+
+ ```yaml
+ apiVersion: toolhive.stacklok.dev/v1alpha1
+ kind: MCPServer
+ metadata:
+ name: git-server
+ namespace: production
+ spec:
+ image: ghcr.io/myorg/mcp-git:v1.0.0
+ transport: stdio
+ ```
+
+### CI/CD integration
+
+Integrate `thv build` into your CI/CD pipeline for automated container building:
+
+```yaml
+# Example GitHub Actions workflow
+name: Build and Deploy MCP Server
+on:
+ push:
+ tags: ['v*']
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install ToolHive
+ uses: StacklokLabs/toolhive-actions/install@v0
+ with:
+ version: latest
+
+ - name: Log in to Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Build MCP server
+ run: |
+ thv build --tag ghcr.io/${{ github.repository }}/mcp-server:${{ github.ref_name }} \
+ uvx://mcp-server-git@${{ github.ref_name }}
+
+ - name: Push to registry
+ run: |
+ docker push ghcr.io/${{ github.repository }}/mcp-server:${{ github.ref_name }}
+```
+
+For more advanced CI/CD patterns including multi-architecture builds, supply
+chain security, and change detection, see the
+[Advanced CI/CD patterns](./advanced-cicd.mdx) guide.
+
+## Advanced usage
+
+### Build with custom CA certificates
+
+For corporate environments with custom certificate authorities:
+
+```bash
+# Use global CA certificate configuration
+thv config set-ca-cert /path/to/corporate-ca.crt
+thv build uvx://internal-mcp-server
+
+# Override CA certificate for specific build
+thv build --ca-cert /path/to/special-ca.crt uvx://special-server
+```
+
+### Custom package registries
+
+Enterprise environments often use private package registries or mirrors instead
+of public registries like npm or PyPI. ToolHive supports configuring build
+environment variables that are injected into the Dockerfile during builds,
+allowing you to use custom registries for all protocol scheme builds.
+
+#### Set build environment variables
+
+Use the `thv config set-build-env` command to configure environment variables
+that will be included in all builds:
+
+```bash
+thv config set-build-env
+```
+
+Common environment variables for package registries:
+
+| Package manager | Environment variable | Example value |
+| --------------- | --------------------- | -------------------------------------- |
+| npm | `NPM_CONFIG_REGISTRY` | `https://npm.corp.example.com` |
+| Go | `GOPROXY` | `https://goproxy.corp.example.com` |
+| Go | `GOPRIVATE` | `github.com/mycompany/*` |
+| pip/uv | `PIP_INDEX_URL` | `https://pypi.corp.example.com/simple` |
+| pip/uv | `PIP_TRUSTED_HOST` | `pypi.corp.example.com` |
+
+Example configuration for an enterprise environment:
+
+```bash
+# Configure npm to use a corporate registry
+thv config set-build-env NPM_CONFIG_REGISTRY https://npm.corp.example.com
+
+# Configure Go proxy for private modules
+thv config set-build-env GOPROXY https://goproxy.corp.example.com
+thv config set-build-env GOPRIVATE "github.com/mycompany/*"
+
+# Configure Python/uv to use a corporate PyPI mirror
+thv config set-build-env PIP_INDEX_URL https://pypi.corp.example.com/simple
+```
+
+### Build local Go projects
+
+Build MCP servers from local Go projects:
+
+```bash
+# Build from current directory
+cd my-go-mcp-project
+thv build --tag my-server:dev go://.
+
+# Build from relative path
+thv build --tag my-server:dev go://./cmd/server
+
+# Build from absolute path
+thv build --tag my-server:dev go:///path/to/my-project
+```
+
+## Comparison with thv run
+
+| Feature | `thv build` | `thv run` |
+| ------------------------- | ------------------------ | ------------------------ |
+| **Purpose** | Build containers only | Build and run containers |
+| **Output** | Container image | Running MCP server |
+| **Use case** | Pre-building, CI/CD | Development, testing |
+| **Kubernetes** | Pre-build for deployment | Direct development |
+| **Custom tagging** | ✅ `--tag` flag | ❌ Auto-generated names |
+| **Dockerfile generation** | ✅ `--dry-run` flag | ❌ Not available |
+
+## Next steps
+
+- Use built containers with [`thv run`](./run-mcp-servers.mdx) for local
+ development
+- Deploy pre-built containers to [Kubernetes](../guides-k8s/run-mcp-k8s.mdx)
+- Set up [CI/CD pipelines](#cicd-integration) for automated building
+- Learn about [container registry workflows](#custom-image-tagging)
+
+## Related information
+
+- [`thv build` command reference](../reference/cli/thv_build.md)
+- [Run MCP servers](./run-mcp-servers.mdx)
+- [Run MCP servers in Kubernetes](../guides-k8s/run-mcp-k8s.mdx)
+- [Custom permissions](./custom-permissions.mdx)
+
+## Troubleshooting
+
+
+Build fails with network errors
+
+If builds fail with network connectivity issues:
+
+1. **Check internet connectivity** for downloading packages
+2. **Configure CA certificates** for corporate environments:
+
+ ```bash
+ thv config set-ca-cert /path/to/corporate-ca.crt
+ ```
+
+3. **Use proxy settings** if required by your network
+4. **Verify package names** and versions exist in the respective registries
+
+
+
+
+Invalid image tag format
+
+If you get image tag validation errors:
+
+1. **Use valid Docker image tag format**: `name:tag` or `registry/name:tag`
+2. **Avoid special characters** except hyphens, underscores, and dots
+3. **Use lowercase names** for compatibility
+4. **Check tag length limits** (typically 128 characters)
+
+Example valid tags:
+
+```bash
+thv build --tag my-server:latest uvx://package
+thv build --tag ghcr.io/org/server:v1.0.0 npx://package
+```
+
+
+
+
+Package not found errors
+
+If the build fails because a package cannot be found:
+
+1. **Verify package exists** in the respective registry:
+ - Python: Check [PyPI](https://pypi.org/)
+ - Node.js: Check [npm](https://www.npmjs.com/)
+ - Go: Verify the module path and version
+
+2. **Check version specifiers**:
+
+ ```bash
+ # Correct version formats
+ thv build uvx://package@1.0.0
+ thv build npx://package@latest
+ thv build go://github.com/user/repo@v1.0.0
+ ```
+
+3. **For Go modules**, ensure the path includes the correct import path
+
+
+
+
+Custom registry not being used
+
+If your custom package registry configuration isn't being applied:
+
+1. **Verify your build environment configuration**:
+
+ ```bash
+ thv config get-build-env
+ ```
+
+2. **Check the environment variable names** match what your package manager
+ expects (for example, `NPM_CONFIG_REGISTRY` for npm, `GOPROXY` for Go)
+
+3. **Use `--dry-run` to inspect the generated Dockerfile** and verify your
+ environment variables are being injected:
+
+ ```bash
+ thv build --dry-run uvx://my-package
+ ```
+
+4. **Ensure network connectivity** to your custom registry from inside the
+ container build environment
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/client-configuration.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/client-configuration.mdx
new file mode 100644
index 00000000..0bb06915
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/client-configuration.mdx
@@ -0,0 +1,279 @@
+---
+title: Client configuration
+description: How to configure AI agent clients to work with ToolHive MCP servers.
+---
+
+import ClientIntro from '../_partials/_client-config-intro.mdx';
+
+
+
+## Register clients
+
+The easiest way to register clients is to run the
+[setup command](../reference/cli/thv_client_setup.md):
+
+```bash
+thv client setup
+```
+
+ToolHive discovers supported clients installed on your system and lets you
+select which ones to register. ToolHive detects clients based on the presence of
+the client's configuration file in its default location. See the
+[Client compatibility reference](../reference/client-compatibility.mdx) for
+details on which clients ToolHive supports and how it detects them.
+
+To view the current status of detected and configured clients, run:
+
+```bash
+thv client status
+```
+
+:::note
+
+ToolHive previously included an "auto-discovery" mode. We removed this mode in
+v0.1.0 to simplify client configuration and ensure consistent control and
+behavior. If you previously enabled client auto-discovery, ToolHive will
+explicitly register all detected clients the first time you run v0.1.0 and
+higher.
+
+Going forward, use the `thv client setup` command to manage your client
+configurations.
+
+:::
+
+## Alternative client registration
+
+If you prefer to register clients manually or in an automated script, use the
+`thv client register` command:
+
+```bash
+thv client register
+```
+
+Replace `` with the name of your client. Common client names
+include:
+
+- `claude-code` - Claude Code CLI
+- `cursor` - Cursor IDE
+- `roo-code` - Roo Code extension for Visual Studio Code
+- `cline` - Cline extension for Visual Studio Code
+- `vscode` - Visual Studio Code (GitHub Copilot)
+- `vscode-insider` - VS Code Insiders edition
+
+Example:
+
+```bash
+thv client register vscode
+```
+
+You can register a client with a specific group using the `--group` option:
+
+```bash
+thv client register --group
+```
+
+Run [`thv client register --help`](../reference/cli/thv_client_register.md) for
+the latest list of supported clients.
+
+To list currently registered clients:
+
+```bash
+thv client list-registered
+```
+
+Repeat the registration step for any additional clients you want to configure.
+
+You might need to restart your client application for the configuration to take
+effect.
+
+To remove a client configuration:
+
+```bash
+thv client remove
+```
+
+To remove a client configuration from a specific group:
+
+```bash
+thv client remove --group
+```
+
+## Other clients or tools
+
+If you have other clients or development libraries that ToolHive doesn't
+directly support, you can still configure them to use ToolHive-managed MCP
+servers if they support the SSE or Streamable HTTP protocol. Check your client
+or library documentation for configuration details.
+
+List your running MCP servers to get the URL:
+
+```bash
+thv list
+```
+
+Example output (some fields omitted for brevity):
+
+```text
+NAME PACKAGE STATUS URL PORT
+github ghcr.io/github/github-mcp-server:latest running http://127.0.0.1:55264/mcp 55264
+sqlite ghcr.io/stackloklabs/sqlite-mcp/server:latest running http://127.0.0.1:22089/sse#sqlite 22089
+```
+
+In this example, the `github` server uses the Streamable HTTP transport (URL
+ends in `/mcp`), and the `sqlite` server uses SSE (URL ends in `/sse`).
+
+You can also get the list in JSON format, which works with many clients that use
+the standard configuration format:
+
+```bash
+thv list --format mcpservers
+```
+
+Example output:
+
+```json
+{
+ "mcpServers": {
+ "github": {
+ "url": "http://127.0.0.1:55264/mcp"
+ },
+ "sqlite": {
+ "url": "http://127.0.0.1:22089/sse#sqlite"
+ }
+ }
+}
+```
+
+Configure your client or library to connect to the MCP server using the URL
+ToolHive provides.
+
+## Related information
+
+- [`thv client` command reference](../reference/cli/thv_client.md)
+- [`thv config` command reference](../reference/cli/thv_config.md)
+- [Client compatibility](../reference/client-compatibility.mdx)
+- [Run MCP servers](./run-mcp-servers.mdx)
+
+## Troubleshooting
+
+
+Client is not detected by `thv client setup`
+
+If ToolHive doesn't detect your client:
+
+1. Verify ToolHive supports your client in the
+ [Client compatibility reference](../reference/client-compatibility.mdx).
+
+2. Make sure you installed the client in its default location. ToolHive detects
+ clients based on their configuration files. If the client isn't in its
+ default location, ToolHive can't detect it.
+
+3. Try manually registering the client:
+
+ ```bash
+ thv client register
+ ```
+
+
+
+
+Client can't connect to MCP server
+
+If your client can't connect to the MCP server:
+
+1. Verify the MCP server is running:
+
+ ```bash
+ thv list
+ ```
+
+ See [Test MCP servers](./test-mcp-servers.mdx) for help testing the MCP
+ server's availability.
+
+2. Check if the client is registered:
+
+ ```bash
+ thv client status
+ ```
+
+3. Restart your client application.
+
+
+
+
+Client can connect but tools aren't available
+
+If your client connects to the MCP server but tools aren't available:
+
+1. Make sure the MCP server is running and accessible:
+
+ ```bash
+ thv list
+ ```
+
+ See [Test MCP servers](./test-mcp-servers.mdx) for help testing the MCP
+ server's functionality.
+
+2. Check the MCP server logs:
+
+ ```bash
+ thv logs
+ ```
+
+3. Make sure you properly configured the MCP server in your client.
+4. If the MCP server requires authentication or has authorization policies
+ applied, make sure the client has the necessary permissions to access the
+ tools.
+
+
+
+
+Containerized client can't connect to MCP server
+
+When your MCP client runs in a container (like containerized LibreChat), it
+can't reach the ToolHive proxy on your host machine using `localhost` due to
+container network isolation. The ToolHive proxy runs as an OS process on the
+host and provides access to containerized MCP servers.
+
+Configure your containerized client to use the appropriate host address for your
+platform:
+
+- **Docker Desktop (macOS/Windows)**: `host.docker.internal`
+- **Podman Desktop**: `host.containers.internal`
+- **Docker Engine (Linux)**: `172.17.0.1` (or your custom bridge gateway IP)
+
+For example, change the MCP server URL from `http://localhost:8080/mcp` to
+`http://host.docker.internal:8080/mcp` in your client configuration.
+
+
+
+
+VS Code can't connect to some streamable-http servers
+
+You might encounter errors with Visual Studio Code connecting to some
+Python-based MCP servers using the Streamable HTTP transport protocol:
+
+```text
+[info] Connection state: Error Error sending message to http://localhost:49574/mcp: TypeError: fetch failed
+[error] Server exited before responding to `initialize` request.
+```
+
+This is a known interaction between VS Code and certain versions of the FastMCP
+SDK used by Python-based MCP servers. If you inspect the HTTP connection, you'll
+see a `307 Temporary Redirect` response, which VS Code doesn't handle correctly.
+
+This
+[issue is resolved](https://github.com/modelcontextprotocol/python-sdk/pull/781)
+in the latest versions of the SDK, but if you're using an older version of a
+Python-based MCP server, you might still encounter it.
+
+There are two workarounds:
+
+1. Change the URL in your VS Code settings to add a trailing slash to the MCP
+ server URL. For example, change `http://localhost:49574/mcp` to
+ `http://localhost:49574/mcp/`. You'll need to re-apply this if you stop and
+ restart the MCP server.
+2. If the MCP server supports SSE, switch to using the SSE transport instead of
+ Streamable HTTP.
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/custom-permissions.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/custom-permissions.mdx
new file mode 100644
index 00000000..2fa313fa
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/custom-permissions.mdx
@@ -0,0 +1,237 @@
+---
+title: Custom permissions
+description:
+ How to create and apply file system permissions and network isolation for MCP
+ servers using permission profiles in ToolHive.
+---
+
+ToolHive includes a permission system that lets you control an MCP server's
+access to your host's file system and network resources. This helps you maintain
+security and ensures that MCP servers operate within defined boundaries.
+
+## Understanding permission profiles
+
+Permissions are defined using _permission profiles_—JSON files that specify both
+file system and network access rules for an MCP server. Each MCP server can use
+only one permission profile at a time, so you include all necessary permissions
+in a single profile.
+
+Permission profiles control two types of access:
+
+- **Host file system access** specifies paths on your host that are mounted into
+ the MCP server container — see [file system access](./filesystem-access.mdx)
+ for detailed examples
+- **Network access rules** let you restrict outbound HTTP(S) connectivity from
+ the MCP server — see [network isolation](./network-isolation.mdx) for
+ architecture details and examples
+
+### Profile structure
+
+Profiles are defined in JSON format and can include the following properties:
+
+- `read`: List of paths on your host file system that the MCP server can read
+- `write`: List of file system paths that the MCP server can write to (this also
+ implies read access)
+- `network`: Network access rules for inbound and outbound connections:
+ - `inbound`: Inbound network access rules, which include:
+ - `allow_host`: List of allowed hostnames that can send traffic to the MCP
+ server. If not specified, only the container's own hostname, `localhost`,
+ and `127.0.0.1` are allowed.
+ - `outbound`: Outbound network access rules, which include:
+ - `insecure_allow_all`: If set to `true`, allows unrestricted outbound
+ network access. This isn't recommended for production use.
+ - `allow_host`: List of allowed hostnames or IP addresses for outbound
+ connections. To allow all subdomains of a domain, prefix the domain with a
+ period (e.g., `.github.com` allows any subdomain of `github.com`).
+ Wildcards are not supported.
+ - `allow_port`: List of allowed ports for outbound connections
+
+## Default permissions in the ToolHive registry
+
+ToolHive includes default least-privilege permissions for MCP servers in the
+built-in registry. These defaults balance functionality and security, but you
+should review them to make sure they meet your specific requirements.
+
+View these permissions using the following command:
+
+```bash
+thv registry info
+```
+
+In the output, look for the "Permissions" section:
+
+```test
+Permissions:
+ Network:
+ Allow Host: .google.com
+ Allow Port: 443
+```
+
+This example shows that the MCP server can make outbound network connections to
+`*.google.com` (note the leading `.` which enables subdomain matching) on
+port 443.
+
+:::info
+
+For security reasons, none of the MCP servers in the registry have any default
+file system permissions defined. This means they cannot read or write to any
+paths on your host system unless you explicitly grant access using a custom
+permission profile or use the `--volume` flag when running the server.
+
+:::
+
+Always verify the default permissions and override them with a custom profile if
+needed to meet your security policies.
+
+:::tip
+
+Add `--format json` to the
+[`thv registry info`](../reference/cli/thv_registry_info.md) command to get the
+output in JSON format for easier customization. Use the contents of the
+`permissions` section as a starting point for creating a custom profile.
+
+:::
+
+## Built-in profiles
+
+ToolHive includes two built-in profiles for network access that you can use
+without creating a custom file:
+
+- **`network`**: Permits all outbound network access. This is the default
+ profile applied to MCP servers when you run a custom server without the
+ `--permission-profile` flag.
+
+ :::info[Important]
+
+ This profile is useful for development and testing but isn't recommended for
+ production use since it doesn't restrict network destinations. Create a custom
+ profile that specifies the allowed hosts and ports when possible.
+
+ :::
+
+- **`none`**: Restricts all network access. Use this for MCP servers that don't
+ require external connectivity.
+
+Both built-in profiles provide no file system access by default. To add file
+system permissions, either:
+
+- Use the `--volume` flag to mount specific paths
+- Create a custom profile that includes both network settings and file system
+ permissions
+
+## Create a custom permission profile
+
+You can create a JSON file with your desired permissions. Include file system
+permissions, network permissions, or both in the same profile.
+
+### Example: Combined file system and network permissions
+
+When your MCP server needs both file access and network connectivity, include
+both types of permissions in a single profile:
+
+```json title="~/my-server-profile.json"
+{
+ "read": ["/home/user/documents", "/home/user/config"],
+ "write": ["/home/user/output"],
+ "network": {
+ "outbound": {
+ "insecure_allow_all": false,
+ "allow_host": ["api.github.com", ".googleapis.com"],
+ "allow_port": [443]
+ }
+ }
+}
+```
+
+This profile:
+
+- Mounts `/home/user/documents` and `/home/user/config` as read-only paths
+- Mounts `/home/user/output` with read and write access (note that `write` also
+ implies read access)
+- Allows outbound HTTPS connections to `api.github.com` and any subdomain of
+ `googleapis.com`
+
+### Example: Network-only permissions
+
+Use this approach when your MCP server needs to make API calls but doesn't
+require file system access:
+
+```json title="~/network-only-profile.json"
+{
+ "network": {
+ "outbound": {
+ "allow_host": ["host.docker.internal", ".intranet.example.com"],
+ "allow_port": [8080, 3000]
+ }
+ }
+}
+```
+
+This profile allows the server to connect to local development servers and
+internal company resources without granting any file system access.
+
+See [network isolation](./network-isolation.mdx) for more details about network
+permissions and how isolation works.
+
+### Example: File system-only permissions
+
+Use this when your MCP server works with local files but doesn't need network
+access:
+
+```json title="~/filesystem-only-profile.json"
+{
+ "read": ["/var/log"],
+ "write": ["/tmp/mcp-output"]
+}
+```
+
+This profile grants file system access without defining any network permissions.
+(Note, to actually block network access, use the `--isolate-network` flag when
+running the server.)
+
+See [file system access](./filesystem-access.mdx) for more details and specific
+examples.
+
+## Apply a permission profile
+
+### Using a built-in profile
+
+To run an MCP server with unrestricted network access (the default):
+
+```bash
+thv run --permission-profile network
+```
+
+To run an MCP server with no network access:
+
+```bash
+thv run --isolate-network --permission-profile none
+```
+
+### Using a custom profile file
+
+To run an MCP server with your custom profile:
+
+```bash
+thv run --isolate-network --permission-profile /path/to/custom-profile.json
+```
+
+## Security best practices
+
+When creating and using permission profiles:
+
+- Use the `none` profile when possible for MCP servers that don't require
+ network or file access
+- Only grant necessary permissions
+- Avoid enabling `network.outbound.insecure_allow_all`, as this allows
+ unrestricted outbound network access
+- Review and test custom profiles thoroughly
+- Keep permission profiles in version control to track changes and share them
+ with your team
+
+## Related information
+
+- [`thv run` command reference](../reference/cli/thv_run.md)
+- [Run MCP servers](./run-mcp-servers.mdx)
+- [File system access](./filesystem-access.mdx)
+- [Network isolation](./network-isolation.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/filesystem-access.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/filesystem-access.mdx
new file mode 100644
index 00000000..7e6af4d9
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/filesystem-access.mdx
@@ -0,0 +1,248 @@
+---
+title: File system access
+description: How to enable MCP server access to your host's file system in ToolHive.
+---
+
+Since ToolHive runs MCP servers in isolated containers, they don't have access
+to your host's file system by default. However, some servers need to read or
+write files on the host system to function properly or persist data.
+
+There are two ways to enable file system access for MCP servers in ToolHive:
+
+1. **The `--volume` flag**: Mount files using Docker volume syntax.
+
+2. **Permission profiles**: Create a custom permission profile that defines
+ which host paths the MCP server can read or write.
+
+## Choose a method
+
+When deciding how to enable file system access for an MCP server, consider the
+differences between permission profiles and the `volume` flag:
+
+- If you use a permission profile, ToolHive mounts the paths you specify in the
+ `read` and `write` properties to the same location inside the container. The
+ MCP server will see files at the same path as on your host. If your server
+ expects files in a different location, it might not work properly.
+
+- With the `--volume` flag, you use Docker's syntax to set both the source path
+ on your host and the destination path inside the container. This gives you
+ more control over where files appear in the MCP server's environment. Use this
+ when your server needs files in a specific location.
+
+Choose the method that matches your server's requirements. Use permission
+profiles for simple, direct mappings. Use the `--volume` flag when you need to
+customize the destination path inside the container.
+
+## The `--volume` flag
+
+To enable file system access using the `--volume` flag, you specify the host
+path and the container path in the `thv run` command. The syntax is the same as
+Docker's volume syntax for bind mounts:
+
+```bash
+thv run --volume :[:ro]
+```
+
+You can add the `--volume` flag multiple times to mount multiple paths.
+
+The optional `:ro` suffix makes the volume read-only in the container. This is
+useful for sharing files without letting the MCP server modify them. For
+example, to mount a host directory `/home/user/data` as read-only in the
+container at `/data`, run:
+
+```bash
+thv run --volume /home/user/data:/data:ro
+```
+
+To mount a host file `/home/user/config.json` as read-write in the container at
+`/app/config.json`, run:
+
+```bash
+thv run --volume /home/user/config.json:/app/config.json
+```
+
+### Use with built-in network profiles
+
+You can also use the `--volume` flag with built-in network profiles for a
+flexible approach that doesn't require creating a custom profile file.
+
+For example, the AWS Diagram MCP server doesn't need any network connectivity,
+but generates diagrams locally. You can use the built-in `none` profile to block
+all network access and mount a directory for the generated diagrams:
+
+```bash
+thv run --isolate-network --permission-profile none --volume /home/user/aws-diagrams:/tmp/generated-diagrams aws-diagram
+```
+
+This approach is useful when you need simple file system access alongside
+standard network restrictions.
+
+## Permission profiles
+
+To enable file system access using a permission profile, create a custom profile
+that specifies which host paths the MCP server can read or write. You can
+include file system permissions alone or pair them with network permissions in
+the same profile.
+
+:::note
+
+Paths in permission profiles must be absolute paths on your host system.
+Relative paths and expanded paths (like `~/`) are not supported. Always use full
+paths starting from the root directory (e.g., `/home/user/documents`).
+
+:::
+
+### File system-only profile
+
+Create a JSON file with your desired file system permissions:
+
+```json title="file-permissions.json"
+{
+ "read": ["/home/user/documents", "/var/log/app"],
+ "write": ["/home/user/output"]
+}
+```
+
+This profile:
+
+- Mounts `/home/user/documents` and `/var/log/app` as read-only paths
+- Mounts `/home/user/output` as a read-write path (note that `write` also
+ implies read access)
+
+### Paired with network permissions
+
+You can also pair file system permissions with network access in the same
+profile:
+
+```json title="combined-permissions.json"
+{
+ "read": ["/home/user/config"],
+ "write": ["/home/user/data"],
+ "network": {
+ "outbound": {
+ "allow_host": ["api.example.com"],
+ "allow_port": [443]
+ }
+ }
+}
+```
+
+### Apply the profile
+
+To run an MCP server with your profile:
+
+```bash
+thv run --permission-profile ./file-permissions.json
+```
+
+For more details on permission profiles and network permissions, see
+[custom permissions](./custom-permissions.mdx).
+
+## Examples
+
+Below are some examples of how to use file system access with some common MCP
+servers in the ToolHive registry.
+
+:::tip
+
+The example MCP servers below don't require network access, and they have
+restricted permission profiles in the ToolHive registry. To further secure these
+examples, add the `--isolate-network` flag to block network access entirely.
+This ensures the MCP server can only access the file system as specified in your
+profile or volume mounts.
+
+:::
+
+### Filesystem MCP server
+
+The
+[Filesystem MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem)
+lets you read and write files on your host system. By default, it expects paths
+to be mounted under `/projects` inside the container.
+
+Here's how to mount two directories using the `--volume` flag, with one as
+read-only:
+
+```bash
+thv run --volume ~/documents:/projects/docs:ro --volume ~/code:/projects/code filesystem
+```
+
+You can achieve the same result using a permission profile. Create a file called
+`filesystem-profile.json`:
+
+```json title="filesystem-profile.json"
+{
+ "read": ["/home/user/documents"],
+ "write": ["/home/user/code"]
+}
+```
+
+Since permission profiles mount paths at the same location inside the container
+as they exist on your host, you need to tell the filesystem server which base
+directory to allow. Pass `/home/user` as an argument:
+
+```bash
+thv run --permission-profile ./filesystem-profile.json filesystem -- /home/user
+```
+
+### Memory MCP server
+
+The
+[Memory MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/memory)
+uses a local knowledge graph to persist information across chats.
+
+To preserve data between runs, you need to mount a file. First, create an empty
+JSON file, then mount it to the expected location inside the container:
+
+```bash
+touch ./memory.json
+thv run --volume ./memory.json:/app/dist/memory.json memory
+```
+
+### SQLite MCP server
+
+The [SQLite MCP server](https://github.com/StacklokLabs/sqlite-mcp) works with
+database files on your host system. By default, it expects to find a database
+file at `/database.db` inside the container:
+
+```bash
+thv run --volume ~/my-database.db:/database.db sqlite
+```
+
+If you want to place the database file in a different location inside the
+container, use the server's `--db` flag to specify the new path:
+
+```bash
+thv run --volume ~/my-database.db:/data/my-database.db sqlite -- --db /data/my-database.db
+```
+
+## Related information
+
+- [`thv run` command reference](../reference/cli/thv_run.md)
+- [Run MCP servers](./run-mcp-servers.mdx)
+- [Custom permissions](./custom-permissions.mdx)
+- [Network isolation](./network-isolation.mdx)
+
+## Troubleshooting
+
+
+File system access issues
+
+If your MCP server can't access the file system as expected:
+
+1. Verify that the paths in your profile or volume flag are correct
+2. Ensure the host paths exist and have the correct permissions
+ - The MCP server runs as a specific user inside the container, so the host
+ paths must be accessible to that user
+3. Check that the permissions are set correctly (read/write)
+4. Inspect the container's mounted paths to ensure they match your expectations:
+
+ ```bash
+ docker inspect
+ ```
+
+ Look for the `Mounts` section to see how paths are mapped.
+
+5. Restart the server with the updated profile or corrected volume mount
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/group-management.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/group-management.mdx
new file mode 100644
index 00000000..4370aae9
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/group-management.mdx
@@ -0,0 +1,127 @@
+---
+title: Organize servers into groups
+description: How to organize MCP servers into logical groups and configure client access.
+---
+
+This guide explains how to organize your MCP servers into logical groups and
+configure which groups your MCP clients can access.
+
+:::tip[New to groups?]
+
+If you're not sure whether groups are right for your use case, see
+[Organizing MCP servers with groups](../concepts/groups.mdx) for an overview of
+when and why to use them.
+
+:::
+
+:::info[What's the default behavior?]
+
+All MCP servers are automatically assigned to the `default` group unless you
+specify otherwise. MCP clients configured without a specific group can access
+all servers in the `default` group.
+
+:::
+
+## Create and organize groups
+
+### Create a group
+
+```bash
+thv group create
+```
+
+For example, to create separate groups for different environments:
+
+```bash
+thv group create development
+thv group create production
+```
+
+### Run servers in a group
+
+When running an MCP server, specify the group using the `--group` flag:
+
+```bash
+thv run --group development fetch
+thv run --group production filesystem --volume /prod/repo:/projects:ro
+```
+
+:::info[What's happening?]
+
+When you specify a group:
+
+1. The server is assigned to that group and labeled accordingly
+2. The server can only be accessed by clients configured for that group
+
+:::
+
+A single workload can only belong to one group at a time. To run multiple
+instances of the same MCP server in different groups, use a unique name for each
+instance:
+
+```bash
+thv run --group development --name fetch-dev fetch
+thv run --group production --name fetch-prod fetch
+```
+
+## Configure client access to groups
+
+You can configure MCP clients to access specific groups, giving you control over
+which tools are available in different contexts.
+
+### Configure a client for a specific group
+
+When registering a client, you can specify which group it should access:
+
+```bash
+thv client register --group development
+```
+
+This configures the client to only access servers in the `development` group.
+
+## Example workflows
+
+### Project-based organization
+
+```bash
+# Create groups for different projects
+thv group create webapp-frontend
+thv group create webapp-backend
+
+# Run project-specific servers
+thv run --group webapp-frontend mcp-react-tools
+thv run --group webapp-backend mcp-database-tools
+
+# Configure clients with appropriate access
+thv client register --client vscode --group webapp-frontend
+thv client register --client cursor --group webapp-backend
+```
+
+## Manage groups
+
+### Remove a group
+
+Remove a group and move its servers to the default group:
+
+```bash
+thv group rm development
+```
+
+Remove a group and delete all its servers:
+
+```bash
+thv group rm development --with-workloads
+```
+
+:::warning
+
+Using `--with-workloads` permanently deletes all servers in the group.
+
+:::
+
+## Related information
+
+- [Client configuration](client-configuration.mdx)
+- [Run MCP servers](run-mcp-servers.mdx)
+- [`thv group` command reference](../reference/cli/thv_group.md)
+- [`thv client` command reference](../reference/cli/thv_client.md)
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/index.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/index.mdx
new file mode 100644
index 00000000..cd1057cd
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/index.mdx
@@ -0,0 +1,31 @@
+---
+title: Using the ToolHive CLI
+description:
+ How-to guides for using the ToolHive command-line interface to run and manage
+ MCP servers.
+---
+
+import DocCardList from '@theme/DocCardList';
+
+## Introduction
+
+The ToolHive CLI (`thv`) is a command-line tool that allows you to deploy and
+manage MCP servers on your local machine or in development environments. It
+provides quick deployment of MCP servers and supports advanced features like
+custom permissions, network access filtering, and telemetry.
+
+It's designed for developers who prefer working in a terminal or need to
+integrate MCP server management into scripts or automation workflows.
+
+:::info[Using ToolHive UI?]
+
+ToolHive UI includes and manages the CLI automatically. You don't need to
+install the CLI separately. See
+[CLI conflict resolution](./install.mdx#cli-conflict-resolution) if you have
+both installed.
+
+:::
+
+## Contents
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/install.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/install.mdx
new file mode 100644
index 00000000..95d0ac22
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/install.mdx
@@ -0,0 +1,473 @@
+---
+title: Install ToolHive
+description: How to install, upgrade, and manage the ToolHive CLI.
+---
+
+This guide walks you through installing, upgrading, and managing the ToolHive
+CLI ([`thv`](../reference/cli/thv.md)).
+
+
+
+
+## Prerequisites
+
+Before installing ToolHive, make sure your system meets these requirements:
+
+- **Operating systems**: macOS, Linux, or Windows
+- **Container runtime**:
+ - Docker / Docker Desktop
+ - Podman / Podman Desktop
+ - Colima with Docker runtime
+ - Rancher Desktop with the dockerd/moby runtime (experimental)
+
+ToolHive requires minimal CPU, memory, and disk space. The exact requirements
+depend on how many MCP servers you run and the resources they use.
+
+## Install ToolHive
+
+You can install ToolHive using several methods:
+
+
+
+
+Homebrew is the easiest installation method on macOS or Linux:
+
+```bash
+brew tap stacklok/tap
+brew install thv
+```
+
+
+
+
+WinGet is available by default on Windows, making this the easiest installation
+method:
+
+```bash
+winget install stacklok.thv
+```
+
+Open a new terminal window after installation to ensure the `thv` command is
+available.
+
+
+
+
+1. Visit the
+ [ToolHive releases page](https://github.com/stacklok/toolhive/releases/latest)
+
+2. Download the appropriate binary for your platform:
+ - `toolhive__darwin_amd64.tar.gz` for macOS (Intel)
+ - `toolhive__darwin_arm64.tar.gz` for macOS (Apple Silicon)
+ - `toolhive__linux_amd64.tar.gz` for Linux (x86_64)
+ - `toolhive__linux_arm64.tar.gz` for Linux (ARM64)
+ - `toolhive__windows_amd64.zip` for Windows (x86_64)
+ - `toolhive__windows_arm64.zip` for Linux (ARM64)
+
+3. Extract the archive and copy the binary to a directory in your PATH:
+
+ macOS/Linux:
+
+ ```bash
+ tar -xzf toolhive__.tar.gz
+ sudo install -m 0755 thv /usr/local/bin/
+ ```
+
+ On Windows, extract the ZIP file to a folder and add that folder to your PATH
+ environment variable.
+
+
+
+
+#### Prerequisites for building from source
+
+- Go 1.24 or newer
+- Git
+- Your `$GOPATH/bin` directory should be in your PATH
+
+#### Using Task (recommended)
+
+:::note
+
+The Task scripts currently only support macOS and Linux. Windows users should
+use the pre-compiled binaries or build from source using Go tools.
+
+:::
+
+If you have [Task](https://taskfile.dev/installation/) installed:
+
+1. Clone the repository:
+
+ ```bash
+ git clone https://github.com/stacklok/toolhive.git
+ cd toolhive
+ ```
+
+2. Build and install the binary in your `$GOPATH/bin`:
+
+ ```bash
+ task install
+ ```
+
+#### Using Go tools
+
+1. Clone the repository:
+
+ ```bash
+ git clone https://github.com/stacklok/toolhive.git
+ cd toolhive
+ ```
+
+2. Build and install the binary in your `$GOPATH/bin`:
+
+ ```bash
+ go install ./cmd/thv
+ ```
+
+
+
+
+## Verify your installation
+
+To verify that ToolHive is installed correctly:
+
+```bash
+thv version
+```
+
+You should see output showing the version number, build date, and Git commit:
+
+```text title="ToolHive version output"
+ToolHive v0.1.1
+Commit: 18956ca1710e11c9952d13a8dde039d5d1d147d6
+Built: 2025-06-30 13:59:34 UTC
+Go version: go1.24.1
+Platform: darwin/arm64
+```
+
+:::info[Privacy: Anonymous usage metrics]
+
+ToolHive collects anonymous usage metrics (tool call counts only) to help
+improve the product. No personal information or sensitive data is collected. You
+can disable this at any time using `thv config usage-metrics disable`. For more
+information, see the [FAQ](../faq.mdx#does-toolhive-collect-any-data).
+
+:::
+
+## CLI conflict resolution
+
+If you have the ToolHive UI installed, it automatically manages the CLI for
+version compatibility. ToolHive UI creates a symlink to its bundled CLI and
+configures your shell's PATH, so you don't need to install the CLI separately.
+
+Running a standalone CLI binary (installed via Homebrew, WinGet, or manually)
+while ToolHive UI is installed shows a conflict error:
+
+```text title="CLI conflict error"
+Error: CLI conflict detected
+
+The ToolHive Desktop application manages a CLI installation at:
+ /Applications/ToolHive Studio.app/Contents/Resources/bin/darwin-arm64/thv
+
+You are running a different CLI binary at:
+ /usr/local/bin/thv
+
+To avoid conflicts, please use the desktop-managed CLI or uninstall
+the ToolHive Desktop application.
+
+To use the desktop-managed CLI, ensure your PATH includes:
+ ~/.toolhive/bin
+
+Or run the desktop CLI directly:
+ ~/.toolhive/bin/thv [command]
+
+Desktop version: 0.8.3
+```
+
+### Resolving the conflict
+
+If you see this error, you have two options:
+
+1. **Use the UI-managed CLI (recommended)**: Open a new terminal window to pick
+ up the PATH changes. The `thv` command should now use the UI-managed CLI.
+
+2. **Uninstall the standalone CLI**: If you want to use only the UI-managed CLI,
+ uninstall the standalone version:
+ - Homebrew: `brew uninstall thv`
+ - WinGet: `winget uninstall stacklok.thv`
+ - Manual: Remove the binary from your PATH
+
+:::note[Debugging override]
+
+For debugging purposes, you can bypass the conflict check by setting
+`TOOLHIVE_SKIP_DESKTOP_CHECK=1`. This is not recommended for normal use as it
+may cause version compatibility issues.
+
+:::
+
+## Upgrade ToolHive
+
+ToolHive automatically checks for updates and notifies you when a new version is
+available. When you run a ToolHive command, it displays a message if an update
+exists.
+
+To upgrade ToolHive:
+
+
+
+
+If you installed ToolHive via Homebrew, upgrade it with:
+
+```bash
+brew upgrade thv
+```
+
+
+
+
+:::note
+
+On Windows, you must stop all running MCP servers before upgrading ToolHive,
+otherwise the upgrade will fail because Windows locks the executable while it
+runs.
+
+Run `thv stop --all` to stop all running servers. After you complete the
+upgrade, restart them using `thv restart `.
+
+:::
+
+If you installed ToolHive via WinGet, upgrade it with:
+
+```bash
+winget upgrade stacklok.thv
+```
+
+
+
+
+Follow the same steps as the [initial installation](#install-toolhive),
+downloading the latest release and overwriting the previous binary.
+
+
+
+
+If you built ToolHive from source, upgrade it by pulling the latest changes and
+rebuilding:
+
+#### Using Task:
+
+```bash
+git pull
+
+task install
+```
+
+#### Using Go tools
+
+```bash
+git pull
+
+go install ./cmd/thv
+```
+
+
+
+
+## Get help with ToolHive commands
+
+ToolHive has built-in help for all commands:
+
+```bash
+# General help
+thv --help
+
+# Help for a specific command
+thv --help
+```
+
+For detailed documentation on each command, see the
+[CLI reference documentation](../reference/cli/thv.md).
+
+## Enable shell completion
+
+ToolHive can generate auto-completion scripts for your shell to make it easier
+to use. The `thv completion` command generates scripts for bash, zsh, fish, and
+PowerShell.
+
+Each shell has different requirements for where to store completion scripts and
+how to enable them. The help output for each shell provides specific
+instructions:
+
+```bash
+# Get help on completion options
+thv completion --help
+
+# Get specific instructions for your shell
+thv completion bash --help
+thv completion zsh --help
+thv completion fish --help
+thv completion powershell --help
+```
+
+## Uninstall ToolHive
+
+To uninstall ToolHive:
+
+1. First, remove any MCP servers managed by ToolHive:
+
+ ```bash
+ # List running servers
+ thv list
+ # Stop and remove each server
+ thv stop --all
+ thv rm
+ ```
+
+2. Remove all ToolHive configuration and log files:
+
+ ```bash
+ # Remove the secrets encryption password entry from your OS keyring
+ thv secret reset-keyring
+
+ # Delete the ToolHive configuration and log files
+ # macOS:
+ rm -rf ~/Library/Application\ Support/toolhive/
+
+ # Linux:
+ rm -rf ~/.config/toolhive/
+ rm -rf ~/.local/share/toolhive/
+ rm -rf ~/.local/state/toolhive/
+
+ # Windows:
+ Remove-Item "$env:LOCALAPPDATA\toolhive" -Recurse -Force
+ ```
+
+3. Remove the ToolHive CLI:
+
+{/* prettier-ignore */}
+
+
+
+ If you installed ToolHive via Homebrew, uninstall it with:
+
+ ```bash
+ brew uninstall thv
+ ```
+
+
+
+
+ If you installed ToolHive via WinGet, uninstall it with:
+
+ ```bash
+ winget uninstall stacklok.thv
+ ```
+
+
+
+
+ Delete the binary from your PATH:
+
+ ```bash
+ sudo rm /usr/local/bin/thv
+ ```
+
+
+
+
+ Remove the binary from your `$GOPATH`:
+
+ ```bash
+ rm $(go env GOPATH)/bin/thv
+ ```
+
+
+
+
+
+## Next steps
+
+Now that you have ToolHive installed, you can start using it to run and manage
+MCP servers. See [Explore the registry](./registry.mdx) and
+[Run MCP servers](./run-mcp-servers.mdx) to get started.
+
+## Related information
+
+- Quickstart: [Getting started with the ToolHive CLI](./quickstart.mdx)
+- [`thv` CLI reference](../reference/cli/thv.md)
+- [Client configuration](./client-configuration.mdx)
+- [Secrets management](./secrets-management.mdx)
+
+## Troubleshooting
+
+
+Permission denied errors
+
+If you see "permission denied" errors when running ToolHive:
+
+1. Make sure the binary is executable:
+
+ ```bash
+ chmod +x /path/to/thv
+ ```
+
+2. If using Docker on Linux, make sure your user has permission to access the
+ Docker socket:
+
+ ```bash
+ sudo usermod -aG docker $USER
+ ```
+
+ (Log out and back in or run `newgrp docker` for this to take effect)
+
+ See
+ [Docker documentation](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user)
+ for more details.
+
+
+
+
+Upgrade error on Windows
+
+If you encounter an error when upgrading ToolHive on Windows, it may be due to
+the ToolHive executable being locked by a running MCP server proxy.
+
+```text title="Error example"
+An unexpected error occurred while executing the command:
+remove: Access is denied.: "C:\Users\USERNAME\AppData\Local\Microsoft\WinGet\Packages\stacklok.thv_Microsoft.Winget.Source_8wekyb3d8bbwe\thv.exe"
+Uninstall failed with exit code: 0x8a150003 : Executing command failed
+```
+
+To resolve this:
+
+1. Stop all running MCP servers:
+
+ ```powershell
+ thv stop --all
+ ```
+
+2. After stopping all servers, run the upgrade command again:
+
+ ```powershell
+ winget upgrade stacklok.thv
+ ```
+
+3. If you still encounter issues, check if any ToolHive processes are still
+ running in the background. You can use Task Manager to end any lingering
+ `thv.exe` processes.
+
+4. Restart your MCP servers:
+
+ ```powershell
+ thv list --all
+ thv restart
+ # repeat for each server
+ ```
+
+
+
+### Other issues
+
+For other installation issues, check the
+[GitHub issues page](https://github.com/stacklok/toolhive/issues) or join the
+[Discord community](https://discord.gg/stacklok).
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/manage-mcp-servers.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/manage-mcp-servers.mdx
new file mode 100644
index 00000000..6b98a568
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/manage-mcp-servers.mdx
@@ -0,0 +1,160 @@
+---
+title: Manage servers
+description: How to monitor and manage the lifecycle of MCP servers using ToolHive.
+---
+
+## Monitoring
+
+ToolHive provides visibility into the status of your MCP servers. You can check
+the status of running servers and view their logs using the ToolHive CLI.
+
+### List running servers
+
+To see all currently running MCP servers:
+
+```bash
+thv list
+```
+
+This shows details about each running server, including its name, package
+(container image), status, URL for connecting clients, group, and when it was
+created.
+
+To include stopped servers in the list:
+
+```bash
+thv list --all
+```
+
+### View server logs
+
+To view logs for an MCP server, use the
+[`thv logs`](../reference/cli/thv_logs.md) command. You can optionally follow
+the logs with the `--follow` option, which shows the most recent log entries and
+updates in real time.
+
+#### Container logs
+
+For local servers, view the container logs:
+
+```bash
+thv logs [--follow]
+```
+
+#### Proxy logs
+
+All servers (both local and remote) run through a proxy process managed by
+ToolHive. To view the proxy logs instead of the container logs, use the
+`--proxy` flag:
+
+```bash
+thv logs --proxy [--follow]
+```
+
+For remote servers, the `--proxy` flag is required since there is no container
+to read logs from.
+
+Alternatively, you can check the log files directly in the ToolHive application
+directory. The path depends on your platform:
+
+- **macOS**: `~/Library/Application Support/toolhive/logs/.log`
+- **Linux**: `~/.local/share/toolhive/logs/.log`
+- **Windows**: `%LOCALAPPDATA%\toolhive\logs\.log`
+
+The specific log file path is displayed when you start a server with
+[`thv run`](../reference/cli/thv_run.md).
+
+## Lifecycle management
+
+MCP servers can be started, stopped, restarted, and removed using the ToolHive
+CLI. The commands are similar to Docker commands, but they're designed to work
+with the ToolHive CLI and MCP servers.
+
+### Stop a server
+
+To stop a running MCP server:
+
+```bash
+thv stop
+```
+
+This stops the server and the associated proxy process, removes the MCP server's
+entry from your configured clients, but keeps the container for future use. For
+remote servers, this terminates the proxy process but preserves the
+configuration.
+
+Add the `--group` flag to stop all servers in a specific group:
+
+```bash
+thv stop --group
+```
+
+Add the `--all` flag to stop all running servers.
+
+### Restart a server
+
+To start a stopped MCP server and add it back to your configured clients:
+
+```bash
+thv start
+```
+
+For remote servers, restarting will:
+
+1. Recreate the proxy process
+2. Re-establish connection to the remote server
+3. Re-authenticate with the remote server (triggers new OAuth flow)
+
+Add the `--group` flag to start all servers in a specific group:
+
+```bash
+thv start --group
+```
+
+### Remove a server
+
+To remove an MCP server:
+
+```bash
+thv rm
+```
+
+This removes the container and cleans up the MCP server's entry in your
+configured clients. If the server is still running, it will be stopped as part
+of the removal. For remote servers, this removes the proxy process,
+configuration, and stored authentication tokens.
+
+Add the `--group` flag to remove all servers in a specific group:
+
+```bash
+thv rm --group
+```
+
+:::note
+
+If you use `docker rm` to remove an MCP container that ToolHive created, it
+won't clean up the MCP server's entry in your configured clients. Use
+[`thv rm`](../reference/cli/thv_rm.md) to make sure the entry is removed.
+
+:::
+
+### Remote server authentication
+
+Remote servers with OAuth authentication will automatically refresh tokens when
+they expire during normal operation. However, starting a remote server always
+triggers a new OAuth authentication flow:
+
+```bash
+thv start
+```
+
+This will always prompt for re-authentication, even if valid tokens exist.
+
+## Related information
+
+- [`thv list` command reference](../reference/cli/thv_list.md)
+- [`thv logs` command reference](../reference/cli/thv_logs.md)
+- [`thv stop` command reference](../reference/cli/thv_stop.md)
+- [`thv start` command reference](../reference/cli/thv_start.md)
+- [`thv rm` command reference](../reference/cli/thv_rm.md)
+- [Monitor with OpenTelemetry](../guides-cli/telemetry-and-metrics.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/network-isolation.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/network-isolation.mdx
new file mode 100644
index 00000000..2375ba07
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/network-isolation.mdx
@@ -0,0 +1,355 @@
+---
+title: Network isolation
+description: How to configure network isolation for MCP servers in ToolHive
+---
+
+Most MCP servers require network access to function properly—for example, to
+access APIs, download data, or communicate with other services. However,
+malicious or misconfigured servers can also exfiltrate sensitive data or
+download unwanted content.
+
+When you run an MCP server in ToolHive, you can optionally enable _network
+isolation_. This feature restricts the MCP server's network access to only the
+resources you specify.
+
+## Enable network isolation
+
+To enforce network access rules, use the `--isolate-network` flag when running
+the MCP server. Network rules can come from:
+
+- The MCP server's default registry permissions
+- A custom permission profile you create
+
+```bash
+thv run --isolate-network [--permission-profile ]
+```
+
+:::tip
+
+You can combine file system and network permissions in the same permission
+profile. For details on creating profiles that include both types of
+permissions, see [custom permissions](./custom-permissions.mdx).
+
+:::
+
+When you enable network isolation, ToolHive creates a secure network
+architecture around your MCP server that includes several components working
+together to control network access.
+
+### Network architecture components
+
+Along with the main MCP server container, ToolHive launches additional
+containers to manage network traffic:
+
+- An egress proxy container that filters outgoing network traffic
+- A DNS container that provides controlled domain name resolution
+- An ingress proxy container that handles incoming requests (only for MCP
+ servers using SSE or Streamable HTTP transport; stdio MCP servers don't need
+ this since they don't expose ports)
+
+### Network topology
+
+ToolHive creates two separate networks in the container runtime:
+
+- A shared external network (`toolhive-external`) that connects to your host's
+ network
+- An internal network (`toolhive--internal`) for each MCP server
+ that isolates it from external access
+
+The MCP server container connects only to the internal network, while the proxy
+and DNS containers connect to both networks. This design ensures that all
+network traffic flows through controlled points, allowing ToolHive to enforce
+the access rules you specify in your permission profile.
+
+The following diagrams show how network traffic flows through the isolation
+architecture for different transport types:
+
+
+
+
+For MCP servers using stdio transport, the ToolHive proxy process communicates
+directly with the MCP server container through standard input and output. All
+outbound network requests from the MCP server flow through the egress proxy and
+DNS containers:
+
+```mermaid
+architecture-beta
+ service mcp_client(server)[MCP client]
+ service toolhive_proxy(server)[ToolHive HTTP proxy process]
+
+ group container_runtime[Container runtime]
+ group thv_internal[Isolated network] in container_runtime
+ service egress(server)[Egress proxy container] in container_runtime
+ service mcp_server(server)[MCP server container] in thv_internal
+ service dns_container(server)[DNS container] in container_runtime
+
+ group external[External Resources]
+ service external_service(internet)[External service] in external
+ service external_dns(internet)[External DNS] in external
+
+ junction outbound in container_runtime
+
+ mcp_client:R --> L:toolhive_proxy
+ toolhive_proxy:R --> L:mcp_server
+ mcp_server:R -- L:outbound
+ egress:B <-- T:outbound
+ dns_container:T <-- B:outbound
+ egress:R --> L:external_service
+ dns_container:R --> L:external_dns
+```
+
+
+
+
+For MCP servers using SSE or Streamable HTTP transport, ToolHive includes an
+additional ingress proxy container. This proxy handles incoming HTTP requests
+and ensures the MCP server remains isolated from direct external access:
+
+```mermaid
+architecture-beta
+ service mcp_client(server)[MCP client]
+ service toolhive_proxy(server)[ToolHive HTTP proxy process]
+
+ group container_runtime[Container runtime]
+ group thv_internal[Isolated network] in container_runtime
+ service ingress(server)[Ingress proxy container] in container_runtime
+ service egress(server)[Egress proxy container] in container_runtime
+ service mcp_server(server)[MCP server container] in thv_internal
+ service dns_container(server)[DNS container] in container_runtime
+
+ group external[External resources]
+ service external_service(internet)[External service] in external
+ service external_dns(internet)[External DNS] in external
+
+ junction outbound in container_runtime
+
+ mcp_client:R --> L:toolhive_proxy
+ toolhive_proxy:R --> L:ingress
+ ingress:R --> L:mcp_server
+ mcp_server:R -- L:outbound
+ egress:B <-- T:outbound
+ dns_container:T <-- B:outbound
+ egress:R --> L:external_service
+ dns_container:R --> L:external_dns
+```
+
+
+
+
+:::info[Important]
+
+Network isolation supports HTTP and HTTPS protocols. If your MCP server needs to
+use other protocols (like direct TCP connections for database access), run it
+without the `--isolate-network` flag and rely on the container's built-in
+isolation instead.
+
+:::
+
+## Example: Enable network isolation using registry defaults
+
+Many MCP servers in the ToolHive registry have default permission profiles that
+allow access to the specific resources they need. For example, the `atlassian`
+MCP server has a default profile that allows access to Atlassian services.
+
+First, check the registry to see the default permissions for the `atlassian` MCP
+server:
+
+```bash
+thv registry info atlassian
+```
+
+Look for the `Permissions` section in the output:
+
+```text
+Permissions:
+ Network:
+ Allow Host: .atlassian.net, .atlassian.com
+ Allow Port: 443
+```
+
+To run the `atlassian` MCP server with these default permissions and enable
+network isolation, use the following command:
+
+```bash
+thv run --isolate-network atlassian
+```
+
+## Example: Customize network access
+
+The GitHub MCP server in the registry has a default profile that allows access
+to `github.com`, but you might need to customize it for a self-hosted GitHub
+Enterprise instance:
+
+```json title="github-enterprise-profile.json"
+{
+ "network": {
+ "outbound": {
+ "insecure_allow_all": false,
+ "allow_host": ["github.example.com"],
+ "allow_port": [443]
+ }
+ }
+}
+```
+
+Run the GitHub MCP server with this profile:
+
+```bash
+thv run --isolate-network --permission-profile ./github-enterprise-profile.json --secret github,target=GITHUB_PERSONAL_ACCESS_TOKEN github
+```
+
+## Example: Combined network and file system permissions
+
+Some MCP servers need both network restrictions and file system access. For
+example, the `aws-diagram` MCP server generates diagrams locally but doesn't
+need network access:
+
+```json title="aws-diagram-profile.json"
+{
+ "write": ["/tmp/generated-diagrams"],
+ "network": {
+ "outbound": {
+ "insecure_allow_all": false,
+ "allow_host": [],
+ "allow_port": []
+ }
+ }
+}
+```
+
+This profile:
+
+- Blocks all network access (equivalent to the `none` built-in profile)
+- Allows the server to write generated diagrams to `/tmp/generated-diagrams`
+
+Run the server with this combined profile:
+
+```bash
+thv run --isolate-network --permission-profile ./aws-diagram-profile.json aws-diagram
+```
+
+### Alternative: Using built-in profiles with volume mounts
+
+You can achieve the same result without creating a custom profile file by using
+the built-in `none` profile and the `--volume` flag:
+
+```bash
+thv run --isolate-network --permission-profile none --volume /home/user/aws-diagrams:/tmp/generated-diagrams aws-diagram
+```
+
+This approach is more flexible since you can easily change the host directory
+without editing a profile file.
+
+## Accessing other workloads on the same container network
+
+ToolHive allows you to configure both outbound and inbound network access for
+MCP servers. This is commonly needed when your MCP server needs to communicate
+with databases, APIs, or other services that are running on your local host
+during development, or when other containers need to communicate with your MCP
+server.
+
+### Outbound access: MCP server to other workloads
+
+To allow an MCP server to access other workloads on the same network, you need
+to configure outbound network isolation to include the appropriate hostnames and
+ports.
+
+For example, in Docker environments, you can add `host.docker.internal` to
+access services on the host. `host.docker.internal` is a special hostname
+provided by Docker that resolves to the host machine's IP address from within
+containers.
+
+Create a permission profile that allows this hostname and the required port:
+
+```json title="internal-access-profile.json"
+{
+ "network": {
+ "outbound": {
+ "insecure_allow_all": false,
+ "allow_host": ["host.docker.internal"],
+ "allow_port": [3000]
+ }
+ }
+}
+```
+
+Run the MCP server with this profile:
+
+```bash
+thv run --isolate-network --permission-profile ./internal-access-profile.json
+```
+
+### Inbound access: Other containers to MCP server
+
+By default, the ingress proxy only allows traffic from the container's own
+hostname, `localhost`, and `127.0.0.1`. If you need to allow other containers or
+workloads to communicate with your MCP server, configure the
+`network.inbound.allow_host` setting in your permission profile.
+
+This is useful when:
+
+- Other containers need to call your MCP server's API
+- You're running multiple services that need to communicate with each other
+- You need to allow traffic from specific internal hostnames or domains
+
+Create a permission profile that allows specific inbound hostnames:
+
+```json title="inbound-access-profile.json"
+{
+ "network": {
+ "inbound": {
+ "allow_host": ["host.docker.internal", "localhost"]
+ }
+ }
+}
+```
+
+Run the MCP server with this profile:
+
+```bash
+thv run --isolate-network --permission-profile ./inbound-access-profile.json
+```
+
+:::info
+
+If no `network.inbound` configuration is specified, the ingress proxy uses the
+default behavior of allowing traffic only from the container's own hostname,
+`localhost`, and `127.0.0.1`.
+
+:::
+
+## Related information
+
+- [`thv run` command reference](../reference/cli/thv_run.md)
+- [Run MCP servers](./run-mcp-servers.mdx)
+- [Custom permissions](./custom-permissions.mdx)
+- [File system access](./filesystem-access.mdx)
+
+## Troubleshooting
+
+
+Network connectivity issues
+
+If your MCP server can't connect to external services:
+
+1. Verify that your profile allows the necessary hosts and ports
+2. Check that the transport protocol (TCP/UDP) is allowed
+3. Check the logs of the egress proxy container for blocked requests:
+
+ ```bash
+ docker logs -egress
+ ```
+
+ Look for messages indicating denied connections.
+
+4. Try temporarily using the default `network` profile to confirm it's a
+ permissions issue:
+
+ ```bash
+ thv run --isolate-network --permission-profile network
+ ```
+
+5. If the default profile works, review the egress proxy logs to identify which
+ specific permissions are missing in your custom profile.
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/quickstart.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/quickstart.mdx
new file mode 100644
index 00000000..35725d5e
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/quickstart.mdx
@@ -0,0 +1,335 @@
+---
+title: 'Quickstart: ToolHive CLI'
+sidebar_label: Quickstart
+description:
+ A step-by-step guide to installing the ToolHive CLI and running your first MCP
+ server.
+---
+
+In this tutorial, you'll install the ToolHive command-line application and run
+your first MCP server. By the end, you'll have a working MCP server that can
+fetch content from websites and be used by AI applications like GitHub Copilot
+or Cursor.
+
+## What you'll learn
+
+- How to install ToolHive on your system
+- How to find available MCP servers
+- How to run an MCP server
+- How to verify the server is working
+- How to use the server with an AI client application
+
+## Prerequisites
+
+Before starting this tutorial, make sure you have:
+
+- [Docker](https://docs.docker.com/get-docker/) or
+ [Podman](https://podman-desktop.io/downloads) or
+ [Colima](https://github.com/abiosoft/colima) installed and running
+- A [supported MCP client](../reference/client-compatibility.mdx) like GitHub
+ Copilot in VS Code, Cursor, Claude Code, and more
+
+## Step 1: Install ToolHive
+
+First, install ToolHive on your system. ToolHive provides the `thv` command-line
+tool that manages MCP servers in secure containers.
+
+For detailed installation instructions, see the
+[installing ToolHive](../guides-cli/install.mdx) guide. Here's a quick summary:
+
+
+
+
+```bash
+brew tap stacklok/tap
+brew install thv
+```
+
+
+
+
+```bash
+winget install stacklok.thv
+```
+
+Open a new terminal window after installation to ensure the `thv` command is
+available.
+
+
+
+
+Download the appropriate binary for your platform from the
+[ToolHive releases page](https://github.com/stacklok/toolhive/releases/latest).
+
+```bash
+# Extract the archive
+tar -xzf toolhive__.tar.gz
+# Move the binary to a directory in your PATH
+sudo mv thv /usr/local/bin/
+# Make it executable (if needed)
+sudo chmod +x /usr/local/bin/thv
+```
+
+
+
+
+### Verify your installation
+
+After installing, verify that ToolHive is working correctly:
+
+```bash
+thv version
+```
+
+You should see output similar to this:
+
+```text
+ToolHive v0.1.1
+Commit: 18956ca1710e11c9952d13a8dde039d5d1d147d6
+Built: 2025-06-30 13:59:34 UTC
+Go version: go1.24.1
+Platform: darwin/arm64
+```
+
+This confirms ToolHive is installed and ready to use.
+
+## Step 2: Register your client
+
+Next, run the ToolHive client setup command to register your MCP client:
+
+```bash
+thv client setup
+```
+
+Select one or more clients from the list using the spacebar to toggle selection.
+Press Enter to confirm your selection.
+
+:::info[What's happening?]
+
+When you run the setup command, ToolHive automatically finds
+[supported clients](../reference/client-compatibility.mdx) on your system. When
+you register a client, ToolHive automatically configures it to use MCP servers
+that you run. This means you don't have to manually configure the client to
+connect to the MCP server.
+
+:::
+
+Confirm that your client is registered successfully:
+
+```bash
+thv client status
+```
+
+You should see output similar to this:
+
+```text
+┌────────────────┬───────────┬────────────┐
+│ CLIENT TYPE │ INSTALLED │ REGISTERED │
+├────────────────┼───────────┼────────────┤
+│ vscode │ ✅ Yes │ ❌ No │
+└────────────────┴───────────┴────────────┘
+```
+
+## Step 3: Find an MCP server to run
+
+See what MCP servers are available in the registry:
+
+```bash
+thv registry list
+```
+
+You'll see output similar to this:
+
+```text
+NAME DESCRIPTION TIER STARS PULLS
+atlassian Model Context Protocol (MCP) server for Atlassian product... Community 2194 7789
+fetch A Model Context Protocol server that provides web content... Community 56714 9078
+github The GitHub MCP Server provides seamless integration with ... Official 16578 5000
+notion Official Notion MCP server. Official 2358 1109
+...
+```
+
+This shows all the MCP servers available in the ToolHive registry.
+
+:::info[What's happening?]
+
+ToolHive maintains a curated registry of MCP servers that have been verified to
+work correctly. The registry includes information about what each server does
+and how to use it.
+
+:::
+
+For this tutorial, you'll use the "fetch" server, which is a simple MCP server
+that lets AI agents get the contents of a website. To learn more about the
+server before running it, run:
+
+```bash
+thv registry info fetch
+```
+
+This shows you detailed information about the server, including what tools it
+provides and any configuration options.
+
+## Step 4: Run the Fetch MCP server
+
+Now, run the Fetch server:
+
+```bash
+thv run fetch
+```
+
+ToolHive will pull the container image and start the server. You'll see output
+similar to this:
+
+```text
+8:41AM INF MCP server ghcr.io/stackloklabs/gofetch/server:latest is verified successfully
+8:41AM INF Image ghcr.io/stackloklabs/gofetch/server:latest has 'latest' tag, pulling to ensure we have the most recent version...
+8:41AM INF Pulling image: ghcr.io/stackloklabs/gofetch/server:latest
+Pulling from stackloklabs/gofetch/server: latest
+Digest: sha256:b9cbe3a8367f39e584d3fdd96d9c5046643c5f4798c3372b0c9049ece202cdef
+Status: Image is up to date for ghcr.io/stackloklabs/gofetch/server:latest
+8:41AM INF Successfully pulled image: ghcr.io/stackloklabs/gofetch/server:latest
+8:41AM INF Using target port: 15266
+8:41AM INF Logging to: ~/Library/Application Support/toolhive/logs/fetch.log
+8:41AM INF MCP server is running in the background (PID: 16834)
+8:41AM INF Use 'thv stop fetch' to stop the server
+```
+
+:::info[What's happening?]
+
+When you run an MCP server, ToolHive:
+
+- Verifies the MCP server image provenance (if attestation information is
+ available in the registry)
+- Downloads the container image
+- Sets up the container with the necessary security settings and starts it in
+ the background
+- Sets up a reverse proxy that lets your AI client applications communicate with
+ the server
+
+:::
+
+## Step 5: Verify the server is running
+
+Check that the server is running:
+
+```bash
+thv list
+```
+
+You should see output similar to this:
+
+```text
+NAME PACKAGE STATUS URL PORT TOOL TYPE CREATED AT
+fetch ghcr.io/stackloklabs/gofetch/server:latest running http://127.0.0.1:15266/mcp 15266 mcp 2025-07-10 08:41:56 -0400 EDT
+```
+
+This confirms that the fetch server is running and available on port 15266.
+
+:::info[What's happening?]
+
+ToolHive keeps track of all the MCP servers it's managing. The
+[`list`](../reference/cli/thv_list.md) command shows you which servers are
+running and how they're configured.
+
+:::
+
+## Step 6: Use the MCP server with your AI client
+
+Now that your MCP server is running, you can use it with your AI client
+application. Open your supported client (VS Code, Cursor, etc.) and ask the AI
+to fetch content from a website. Note that you might need to restart your client
+for the changes to take effect.
+
+For example, try asking: "Can you fetch the content from https://toolhive.dev
+and summarize it for me?"
+
+The AI should be able to use the Fetch MCP server to retrieve the content and
+provide a summary.
+
+:::info[What's happening?]
+
+When you ask the AI to fetch content, it detects that it needs external data. It
+discovers the fetch tool provided by your MCP server, calls the tool with the
+URL you specified, receives the webpage content, and then processes that content
+to create a summary.
+
+:::
+
+## Step 7: Stop the server when you're done
+
+When you're finished using the server, you can stop it:
+
+```bash
+thv stop fetch
+```
+
+If you want to remove it completely:
+
+```bash
+thv rm fetch
+```
+
+:::info[What's happening?]
+
+Stopping a server pauses it and terminates the associated proxy process but
+keeps the container around so you can start it quickly later using
+[`thv start`](../reference/cli/thv_start.md) or
+[`thv run`](../reference/cli/thv_run.md). Removing a server completely deletes
+the container, freeing up resources.
+
+:::
+
+## What's next?
+
+Congratulations! You've successfully installed ToolHive and run your first MCP
+server. Here are some next steps to explore:
+
+- Try running other MCP servers from the registry with
+ [`thv registry list`](../reference/cli/thv_registry_list.md) and
+ [`thv run`](../reference/cli/thv_run.md)
+- Learn about [secrets management](../guides-cli/secrets-management.mdx) for MCP
+ servers that require authentication
+- Explore [custom permissions](../guides-cli/custom-permissions.mdx) for MCP
+ servers
+- Learn how to
+ [share and reuse server configurations](../guides-cli/run-mcp-servers.mdx#share-and-reuse-server-configurations)
+ using the export and import functionality
+- Set up [shell completion](../guides-cli/install.mdx#enable-shell-completion)
+ to make ToolHive commands easier to use
+- Learn how to [upgrade ToolHive](../guides-cli/install.mdx#upgrade-toolhive)
+ when new versions are available
+
+## Troubleshooting
+
+
+Server fails to start
+
+If the server fails to start, check:
+
+- Is Docker, Podman, or Colima running?
+- Do you have internet access to pull the container image?
+- Is the port already in use by another application?
+
+Try running with a specific port:
+
+```bash
+thv run --proxy-port 8081 fetch
+```
+
+
+
+
+Client can't use the server
+
+If your AI client application can't use the server:
+
+- Make sure your client is registered with ToolHive (see Step 2)
+- Check that your client is supported
+- Restart your client to pick up the new configuration
+- Verify the server is running with [`thv list`](../reference/cli/thv_list.md)
+
+
+
+If you encounter any problems, please join our
+[Discord community](https://discord.gg/stacklok) for help.
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/registry.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/registry.mdx
new file mode 100644
index 00000000..84a76aad
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/registry.mdx
@@ -0,0 +1,397 @@
+---
+title: Explore the registry
+description: How to use the built-in registry to find MCP servers.
+---
+
+ToolHive includes a built-in registry of MCP servers with verified
+configurations that meet a
+[minimum quality standard](../concepts/registry-criteria.mdx), allowing you to
+discover and deploy high-quality tools effortlessly. Simply select one from the
+list and run it securely with a single command.
+
+## Find MCP servers
+
+To list all MCP servers in the ToolHive registry, run:
+
+```bash
+thv registry list
+```
+
+This command displays a list of servers with their name, description, tier
+(official or community), and the number of stars and downloads to help you
+identify the most popular and useful servers.
+
+Example output:
+
+```text
+NAME DESCRIPTION TIER STARS PULLS
+atlassian Model Context Protocol (MCP) server for Atlassian product... Community 2194 7789
+elasticsearch Connect to your Elasticsearch data directly from any MCP ... Official 305 5429
+everything This MCP server attempts to exercise all the features of ... Community 56714 10441
+fetch A Model Context Protocol server that provides web content... Community 56714 9078
+filesystem Node.js server implementing Model Context Protocol (MCP) ... Community 56714 14041
+firecrawl A powerful web scraping and content extraction MCP server... Official 3605 7630
+git A Model Context Protocol server for Git repository intera... Community 56714 7000
+github The GitHub MCP Server provides seamless integration with ... Official 16578 5000
+grafana A Model Context Protocol (MCP) server for Grafana that pr... Official 1014 4900
+k8s MKP is a Model Context Protocol (MCP) server for Kubernet... Community 32 8064
+
+<... trimmed for brevity ...>
+```
+
+You can also search by keyword to find servers related to a specific topic or
+capability:
+
+```bash
+thv search
+```
+
+For example, to locate servers related to GitHub:
+
+```bash
+thv search github
+```
+
+## View server details
+
+To view detailed information about a specific MCP server, run:
+
+```bash
+thv registry info
+```
+
+For example:
+
+```bash
+thv registry info github
+```
+
+ToolHive provides the server's description, available tools, configuration
+options, and other metadata.
+
+By default, ToolHive displays the server's configuration in a human-readable
+format. To view the configuration in JSON format, use the `--format` option:
+
+```bash
+thv registry info --format json
+```
+
+### Example output
+
+```yaml {1,11,19,24} showLineNumbers
+Name: github
+Image: ghcr.io/github/github-mcp-server:latest
+Description: The GitHub MCP Server provides seamless integration with GitHub APIs, enabling advanced automation and interaction capabilities for developers and tools
+Tier: Official
+Status: Active
+Transport: stdio
+Repository URL: https://github.com/github/github-mcp-server
+Has Provenance: Yes
+Popularity: 13894 stars, 5000 pulls
+Last Updated: 2025-05-20T00:21:46Z
+Tools:
+ - get_me
+ - get_issue
+ - create_issue
+ - add_issue_comment
+ - list_issues
+<... trimmed for brevity ...>
+
+Environment Variables:
+ - GITHUB_PERSONAL_ACCESS_TOKEN (required): GitHub personal access token with appropriate permissions
+ - GH_HOST: GitHub Enterprise Server hostname (optional)
+Tags:
+ api, create, fork, github, list, pull-request, push, repository, search, update, issues
+Permissions:
+ Network:
+ Allow Transport: tcp
+ Allow Host: docs.github.com, github.com
+ Allow Port: 443
+Example Command:
+ thv run github
+```
+
+This information helps you understand the server's capabilities, requirements,
+and security profile before running it.
+
+- **Server name** (line 1): The server name to use with the
+ [`thv run`](../reference/cli/thv_run.md) command
+- **Metadata** (lines 2-10): Details about the server, including the image name,
+ description, status, transport method, repository URL, whether the server has
+ SLSA provenance available for verification, and popularity
+- **Tools list** (line 11): The list of tools this MCP server provides
+- **Configuration** (line 19): Required and optional environment variables
+ needed to run the server
+- **Permissions** (line 24): The permission profile applied to the server,
+ including file system and network access (see
+ [Custom permissions](./custom-permissions.mdx))
+
+## Use a custom registry
+
+By default, ToolHive uses a built-in registry of verified MCP servers. You can
+configure ToolHive to use a custom registry instead. This is useful for
+organizations that want to maintain their own private registry of MCP servers.
+
+ToolHive supports two types of custom registries:
+
+- **File-based registries**: JSON files that follow either the
+ [ToolHive registry schema](../reference/registry-schema-toolhive.mdx) or the
+ [upstream registry schema](../reference/registry-schema-upstream.mdx)
+- **API-based registries**: REST API endpoints that implement the
+ [MCP Registry API specification](../guides-registry/index.mdx), which use the
+ [upstream registry schema](../reference/registry-schema-upstream.mdx)
+
+Once you configure a custom registry, ToolHive uses it for all commands that
+interact with the registry, such as `thv registry list`, `thv registry info`,
+and `thv run`.
+
+### Set a remote registry
+
+To configure ToolHive to use a remote registry, set the registry URL or API
+endpoint:
+
+```bash
+thv config set-registry
+```
+
+The CLI automatically detects the registry type:
+
+- URLs ending with `.json` are treated as static registry files
+- Other URLs are probed to detect MCP Registry API endpoints, falling back to
+ static files if the probe fails
+- Local paths are treated as local registry files
+
+Examples:
+
+```bash
+# Static registry file
+thv config set-registry https://example.com/registry.json
+
+# API endpoint
+thv config set-registry https://registry.example.com
+```
+
+### Set a local registry file
+
+To configure ToolHive to use a local registry file, set the file path:
+
+```bash
+thv config set-registry
+```
+
+For example:
+
+```bash
+thv config set-registry /path/to/local-registry.json
+```
+
+### Check the current registry
+
+To see which registry is currently configured:
+
+```bash
+thv config get-registry
+```
+
+This displays the configured registry URL, API endpoint, or file path. If no
+custom registry is configured, this command indicates that the built-in registry
+is being used.
+
+### Revert to the built-in registry
+
+To remove the custom registry configuration and revert to using the built-in
+registry:
+
+```bash
+thv config unset-registry
+```
+
+This restores the default behavior of using ToolHive's built-in registry.
+
+## Organize servers with registry groups
+
+Registry groups allow you to organize related MCP servers and run them together
+as a cohesive unit. This is particularly useful for creating team-specific
+toolkits, workflow-based server collections, or environment-specific
+configurations.
+
+:::note
+
+Registry groups are different from [runtime groups](./group-management.mdx).
+Registry groups organize server definitions within registry files, while runtime
+groups organize running server instances for access control.
+
+:::
+
+### Group structure
+
+Groups are defined as a top-level array in your custom registry:
+
+```json
+{
+ "servers": {
+ // Individual servers
+ },
+ "groups": [
+ {
+ "name": "group-name",
+ "description": "Description of what this group provides",
+ "servers": {
+ "server-name": {
+ // Complete server definition
+ }
+ },
+ "remote_servers": {
+ "remote-server-name": {
+ // Complete remote server definition
+ }
+ }
+ }
+ ]
+}
+```
+
+### Key characteristics
+
+- **Optional**: Groups are entirely optional. Omit the `groups` section if you
+ only need individual servers
+- **Independent server definitions**: Each group contains complete server
+ configurations, not references to top-level servers
+- **Self-contained**: Groups can define servers with the same names as top-level
+ servers but with different configurations
+- **Flexible membership**: The same server can appear in multiple groups with
+ different configurations
+
+### Example: Multi-environment groups
+
+Here's an example showing how groups can organize servers for different
+purposes:
+
+```json title='registry-with-groups.json'
+{
+ "$schema": "https://raw.githubusercontent.com/stacklok/toolhive-core/main/registry/types/data/toolhive-legacy-registry.schema.json",
+ "version": "1.0.0",
+ "last_updated": "2025-08-15T10:00:00Z",
+ "servers": {
+ "production-fetch": {
+ "description": "Production web content fetching server",
+ "image": "ghcr.io/stackloklabs/gofetch/server:latest",
+ "status": "Active",
+ "tier": "Community",
+ "transport": "streamable-http",
+ "permissions": {
+ "network": {
+ "outbound": {
+ "allow_host": [".company.com"],
+ "allow_port": [443]
+ }
+ }
+ }
+ }
+ },
+ "groups": [
+ {
+ "name": "devops-toolkit",
+ "description": "Complete DevOps toolkit for development teams",
+ "servers": {
+ "github": {
+ "description": "GitHub integration for repository and issue management",
+ "image": "ghcr.io/github/github-mcp-server:v0.15.0",
+ "status": "Active",
+ "tier": "Official",
+ "transport": "stdio",
+ "env_vars": [
+ {
+ "name": "GITHUB_PERSONAL_ACCESS_TOKEN",
+ "description": "GitHub personal access token",
+ "required": true,
+ "secret": true
+ }
+ ]
+ },
+ "heroku": {
+ "description": "Heroku platform deployment and management",
+ "image": "ghcr.io/stacklok/dockyard/npx/heroku-mcp-server:1.0.7",
+ "status": "Active",
+ "tier": "Community",
+ "transport": "stdio",
+ "env_vars": [
+ {
+ "name": "HEROKU_API_KEY",
+ "description": "Heroku API key",
+ "required": true,
+ "secret": true
+ }
+ ]
+ }
+ }
+ },
+ {
+ "name": "testing-suite",
+ "description": "Servers needed for automated testing workflows",
+ "servers": {
+ "test-fetch": {
+ "description": "Restricted fetch server for testing",
+ "image": "ghcr.io/stackloklabs/gofetch/server:latest",
+ "status": "Active",
+ "tier": "Community",
+ "transport": "streamable-http",
+ "args": ["--timeout", "5s"],
+ "permissions": {
+ "network": {
+ "outbound": {
+ "allow_host": ["test.example.com"],
+ "allow_port": [443]
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
+}
+```
+
+This registry provides:
+
+- A production-ready `production-fetch` server at the top level
+- A `devops-toolkit` group with GitHub and Heroku servers for complete DevOps
+ workflows
+- A `testing-suite` group with a restricted `test-fetch` server
+
+Notice how the `devops-toolkit` group contains multiple servers that work
+together—GitHub for repository management and Heroku for
+deployment—demonstrating how groups can bundle related functionality.
+
+### Run registry groups
+
+You can run entire groups using the group command:
+
+```bash
+# Run all servers in the devops-toolkit group (GitHub + Heroku)
+thv group run devops-toolkit
+
+# Run all servers in the testing-suite group
+thv group run testing-suite
+
+# You can also pass environment variables and secrets to specific servers in the group
+thv group run devops-toolkit --secret github-token,target=github.GITHUB_PERSONAL_ACCESS_TOKEN --secret heroku-key,target=heroku.HEROKU_API_KEY
+```
+
+Groups provide a convenient way to start multiple related servers with a single
+command.
+
+## Next steps
+
+See [Run MCP servers](./run-mcp-servers.mdx) to run an MCP server from the
+registry.
+
+Learn how to [create a custom MCP registry](../tutorials/custom-registry.mdx).
+
+## Related information
+
+- [`thv registry` command reference](../reference/cli/thv_registry.md)
+- [`thv search` command reference](../reference/cli/thv_search.md)
+- [`thv config set-registry` command reference](../reference/cli/thv_config_set-registry.md)
+- [`thv config get-registry` command reference](../reference/cli/thv_config_get-registry.md)
+- [`thv config unset-registry` command reference](../reference/cli/thv_config_unset-registry.md)
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/run-mcp-servers.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/run-mcp-servers.mdx
new file mode 100644
index 00000000..f7a1ada3
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/run-mcp-servers.mdx
@@ -0,0 +1,1029 @@
+---
+title: Run MCP servers
+description: How to run MCP servers with the ToolHive CLI.
+---
+
+This guide explains how to run Model Context Protocol (MCP) servers using
+ToolHive. It covers how to run servers from the ToolHive registry, customize
+server settings, and run custom servers using Docker images or protocol schemes.
+
+## Run a server from the registry
+
+To run an MCP server from the [ToolHive registry](./registry.mdx), use the
+[`thv run`](../reference/cli/thv_run.md) command with the name of the server you
+want to run. The server name is the same as its name in the registry.
+
+```bash
+thv run
+```
+
+The ToolHive registry contains both local containerized MCP servers and remote
+MCP servers. ToolHive automatically handles the appropriate setup based on the
+server type.
+
+### Local containerized servers
+
+For example, to run the `fetch` server, which is a local containerized MCP
+server that fetches website contents:
+
+```bash
+thv run fetch
+```
+
+### Remote MCP servers
+
+Remote MCP servers in the registry don't run as local containers but instead use
+ToolHive's transparent HTTP proxy to forward requests to remote servers. For
+example:
+
+```bash
+thv run neon
+thv run stripe
+```
+
+When you run a remote server from the registry, ToolHive uses the pre-configured
+remote URL and authentication settings.
+
+:::note[Naming convention]
+
+Remote MCP servers use the `-remote` suffix **when they have a local
+containerized counterpart** to distinguish between the two versions. For
+example:
+
+- `notion-remote` indicates this is the remote version of a server that also has
+ a local `notion` version
+- `neon` and `stripe` don't have local counterparts, so they don't use the
+ `-remote` suffix
+
+To run a remote notion mcp server, you should use the `notion-remote` name.
+
+```bash
+thv run notion-remote
+```
+
+Use `thv search ` or `thv registry list` to discover available servers.
+
+:::
+
+### Run registry groups
+
+If you use a [custom registry](./registry.mdx#use-a-custom-registry) that
+includes groups, you can run multiple related servers together as a unit. This
+is useful when you need several servers for a specific workflow or project:
+
+```bash
+thv group run
+```
+
+For example, to run all servers in a group called `dev-toolkit`:
+
+```bash
+thv group run dev-toolkit
+```
+
+Running a group starts all servers defined within that group simultaneously,
+saving you from running each server individually. See
+[Registry groups](./registry.mdx#organize-servers-with-registry-groups) for more
+information about organizing servers into groups in your custom registry.
+
+:::info[What's happening?]
+
+When you run an MCP server from the registry, ToolHive handles different server
+types automatically:
+
+**For local containerized servers:**
+
+1. Pulls the image and launches a container using the configuration from the
+ registry.
+2. Starts an HTTP proxy process on a random port to forward client requests to
+ the container.
+3. Labels the container so it can be tracked by ToolHive:
+ ```yaml
+ toolhive: true
+ toolhive-name:
+ ```
+
+**For remote MCP servers:**
+
+1. Uses the pre-configured remote URL from the registry.
+2. Automatically detects if the remote server requires authentication.
+3. Handles OAuth/OIDC authentication flows if needed.
+4. Starts an HTTP proxy process on a random port to forward client requests to
+ the remote server.
+5. Manages the server like any other ToolHive workload. No container is created
+ for remote MCP servers.
+
+:::
+
+See [Run a custom MCP server](#run-a-custom-mcp-server) to run a server that is
+not in the registry, or [Run a remote MCP server](#run-a-remote-mcp-server) for
+more details about remote server configuration.
+
+## Customize server settings
+
+You might need to customize the behavior of an MCP server, such as changing the
+port, mounting a local directory, or passing secrets. ToolHive provides several
+options to customize the server's configuration when you run it.
+
+For a complete list of options, run
+[`thv run --help`](../reference/cli/thv_run.md) or see the
+[`thv run` command reference](../reference/cli/thv_run.md).
+
+### Run a server with a custom name
+
+By default, the container name matches the MCP server's name in the registry or
+is automatically generated from the image name when you run a custom server. To
+give your server instance a custom name, use the `--name` option:
+
+```bash
+thv run --name
+```
+
+For example:
+
+```bash
+thv run --name my-fetch fetch
+```
+
+### Run a server with secrets
+
+Many MCP servers require secrets or other configuration variables to function
+correctly. ToolHive lets you pass these secrets as environment variables when
+starting the server.
+
+To pass a secret to an MCP server, use the `--secret` option:
+
+```bash
+thv run --secret ,target=
+```
+
+The `target` parameter specifies the name of the environment variable in the MCP
+server's container. This is useful for passing secrets like API tokens or other
+sensitive information.
+
+For example:
+
+```bash
+thv run --secret github,target=GITHUB_PERSONAL_ACCESS_TOKEN github
+```
+
+See [Secrets management](./secrets-management.mdx) to learn how to manage
+secrets in ToolHive.
+
+### Run a server within a group
+
+To run an MCP server within a specific group, use the `--group` option. This
+allows you to organize your servers and manage them collectively.
+
+```bash
+thv run --group
+```
+
+:::note
+
+The group must exist before you can run a server in it.
+
+:::
+
+See [Organize servers into groups](./group-management.mdx) to learn more about
+organizing servers and configuring client access.
+
+### Mount a local file or directory
+
+To enable file system access for an MCP server, you can either use the
+`--volume` flag to mount specific paths or create a custom permission profile
+that defines read and write permissions.
+
+See [File system access](./filesystem-access.mdx) for detailed examples. To
+prevent sensitive files from being exposed when mounting a project, use
+[.thvignore](./thvignore.mdx).
+
+### Restrict network access
+
+To restrict an MCP server's network access, use the `--isolate-network` flag.
+This enforces network access rules from either the server's default registry
+permissions or a custom permission profile you create.
+
+See [Network isolation](./network-isolation.mdx) for network architecture
+details and examples.
+
+### Add command-line arguments
+
+Some MCP servers require additional arguments to run correctly. You can pass
+these arguments after the server name in the
+[`thv run`](../reference/cli/thv_run.md) command:
+
+```bash
+thv run --
+```
+
+For example:
+
+```bash
+thv run my-mcp-server:latest -- --arg1 value1 --arg2 value2
+```
+
+Check the MCP server's documentation for the required arguments.
+
+:::warning
+
+Some MCP servers in the ToolHive registry include default arguments that are
+essential for proper operation. When you provide custom arguments using
+`-- `, these replace the registry defaults entirely rather than adding to
+them.
+
+Before adding custom arguments, check the server's registry entry:
+
+```bash
+thv registry info --format json | jq '.args'
+```
+
+If default arguments are listed, include them along with your custom arguments
+to ensure the server functions correctly.
+
+:::
+
+### Run a server on a specific port
+
+ToolHive creates a reverse proxy on a random port that forwards requests to the
+container. This is the port that client applications connect to. To set a
+specific proxy port instead, use the `--proxy-port` flag:
+
+```bash
+thv run --proxy-port
+```
+
+### Run a server exposing only selected tools
+
+ToolHive can filter the tools returned to the client as result of a `tools/list`
+command as well as block calls to tools that you don't want to expose.
+
+This can help reduce the amount of tools sent to the LLM while still using the
+same MCP server, but it is not meant as a security feature.
+
+To filter the list of tools, use the `--tools` flag either once
+
+```bash
+thv run --tools
+```
+
+Or multiple times
+
+```bash
+thv run --tools --tools
+```
+
+For example:
+
+```bash
+thv run --tools list_issues --tools get_issue github
+```
+
+If the server comes from the registry, ToolHive can validate the tool names
+against the list advertised in the image reference. An error is returned in case
+ToolHive cannot find one of the specified tools.
+
+### Override tool names and descriptions
+
+With ToolHive you can modify how tools exposed by an MCP server are exposed to
+clients. In particular, tool names and descriptions can be changed.
+
+This is useful when you want to guide an agent toward calling a specific tool
+for particular questions.
+
+One common use case is running multiple copies of the same MCP server with
+different [network access levels](./network-isolation.mdx)—one for internal
+resources and another for public internet access. By overriding the tool names
+and descriptions, you can help your agent choose the right server for each task.
+
+For example, the `fetch` MCP server exposes a single `fetch` tool with a
+description like:
+
+```
+"Fetches a URL from the internet and optionally extracts its contents as markdown."
+```
+
+To override this, create a configuration file with one entry under
+`toolsOverride` for each tool you want to modify:
+
+```json
+{
+ "toolsOverride": {
+ "fetch": {
+ "name": "toolhive-docs-fetch",
+ "description": "Fetches a URL from https://docs.stacklok.com/toolhive website."
+ }
+ }
+}
+```
+
+Then pass this file to [`thv run`](../reference/cli/thv_run.md):
+
+```sh
+thv run --tools-override override.json fetch
+```
+
+The key in the override object is the _original tool name_, while the `name`
+field contains the _new name_ that clients will see.
+
+:::info
+
+Take care when using `--tools` and `--tools-override` together in the same
+command.
+
+Tool filtering and tool overrides work independently: filtering limits access to
+a subset of tools, while overrides change how those tools appear to clients.
+
+When using both options, `--tools` must reference the _overridden names_ (the
+new names you define) since those are what clients will see.
+
+:::
+
+## Run a custom MCP server
+
+To run an MCP server that isn't in the registry, you can use a
+[Docker image](#run-a-server-from-a-docker-image) or a
+[protocol scheme](#run-a-server-using-protocol-schemes) to dynamically build the
+server.
+
+ToolHive supports the following transport methods:
+
+- **Standard I/O** (`stdio`), default:\
+ ToolHive redirects SSE or Streamable HTTP traffic from the client to the
+ container's standard input and output. This acts as a secure proxy, ensuring
+ that the container doesn't have direct access to the network or the host
+ machine.
+
+- **HTTP with SSE (server-sent events)** (`sse`):\
+ ToolHive creates a reverse proxy that forwards requests to the container using
+ the HTTP/SSE protocol.
+
+- **Streamable HTTP** (`streamable-http`):\
+ ToolHive creates a reverse proxy that forwards requests to the container using
+ the Streamable HTTP protocol, which replaced SSE in the MCP specification as
+ of the `2025-03-26` revision.
+
+:::info
+
+As of ToolHive CLI version 0.6.0, the default proxy mode for `stdio` MCP servers
+is `streamable-http`.
+
+For backward compatibility with the deprecated SSE transport, you can explicitly
+set the transport to `sse` using the `--proxy-mode sse` flag when running the
+server.
+
+:::
+
+### Run a server from a Docker image
+
+To run an MCP server from a Docker image, specify the image name and tag in the
+[`thv run`](../reference/cli/thv_run.md) command. You can also specify a custom
+name for the server instance, the transport method, and any additional arguments
+required by the MCP server.
+
+```bash
+thv run [--name ] [--transport ] --
+```
+
+For example, to run an MCP server from a Docker image named
+`my-mcp-server-image` that uses the Streamable HTTP transport method and takes
+additional arguments:
+
+```bash
+thv run --name my-mcp-server --transport streamable-http my-mcp-server-image:latest -- --arg1 value1 --arg2 value2
+```
+
+Check your MCP server's documentation for the required arguments.
+
+:::info[What's happening?]
+
+When you run an MCP server from a Docker image, ToolHive:
+
+1. Pulls the image (`my-mcp-server-image:latest`) and launches a container with
+ the options and arguments you specified.
+2. Launches an HTTP proxy on a random port (optionally, add
+ `--proxy-port ` to specify the port).
+3. Labels the container so it can be tracked by ToolHive:
+ ```yaml
+ toolhive: true
+ toolhive-name: my-mcp-server
+ ```
+4. Sets up the specified `--transport` method (`stdio`, `sse`, or
+ `streamable-http`).
+
+:::
+
+See [`thv run --help`](../reference/cli/thv_run.md) for more options.
+
+### Run a server using protocol schemes
+
+ToolHive also supports running MCP servers directly from package managers. This
+means you can launch MCP servers without building or publishing a Docker image,
+and without installing language-specific build tools on your machine.
+
+Currently, three protocol schemes are supported:
+
+- `uvx://`: For Python-based MCP servers using the uv package manager
+- `npx://`: For Node.js-based MCP servers using npm
+- `go://`: For Go-based MCP servers
+
+```bash
+thv run ://@
+```
+
+You'll likely need to specify additional arguments like the transport method,
+volumes, and environment variables. Check your MCP server's documentation and
+see [`thv run --help`](../reference/cli/thv_run.md) for more options.
+
+:::info[What's happening?]
+
+When you use a protocol scheme, ToolHive:
+
+1. Detects the protocol scheme and extracts the package reference
+2. Generates a Dockerfile based on the appropriate template
+3. Builds a Docker image with the package installed
+4. Runs the MCP server using the new image (see
+ [Run a server from a Docker image](#run-a-server-from-a-docker-image) for
+ details)
+
+:::
+
+To build the image without running it, see
+[Build MCP containers](./build-containers.mdx).
+
+#### Examples
+
+
+
+
+The `uvx://` protocol is used for Python-based MCP servers. The package name
+must be a valid package in the [PyPI registry](https://pypi.org/). The
+`@` suffix is _optional_ and defaults to the latest version if omitted.
+
+```bash
+thv run --name aws-docs uvx://awslabs.aws-documentation-mcp-server@latest
+```
+
+
+
+
+The `npx://` protocol is used for Node.js-based MCP servers. The package name
+must be a valid package in the [npm registry](https://www.npmjs.com/). The
+`@` suffix is _optional_ and defaults to the latest version if omitted.
+
+```bash
+thv run --name pulumi npx://@pulumi/mcp-server@latest
+```
+
+
+
+
+The `go://` protocol is used for Go-based MCP servers. The package name must be
+a valid Go module repo URI referencing the `main` package. The `@`
+suffix is **required**.
+
+```bash
+thv run --name grafana go://github.com/grafana/mcp-grafana/cmd/mcp-grafana@latest
+```
+
+You can also run a local Go module by specifying the path to the module:
+
+```bash
+# Run from a relative path
+thv run go://./cmd/my-mcp-server
+
+# Run from the current directory
+cd my-go-mcp-project
+thv run go://.
+
+# Run from an absolute path
+thv run go:///path/to/my-go-project
+```
+
+
+
+
+### Configure network transport
+
+When you run custom MCP servers using the SSE (`--transport sse`) or Streamable
+HTTP (`--transport streamable-http`) transport method, ToolHive automatically
+selects a random port to expose from the container to the host and sets the
+`MCP_PORT` and `FASTMCP_PORT` environment variables in the container.
+
+```mermaid
+graph LR
+ A[MCP client] -->|HTTP request to proxy port: 5432| B[ToolHive proxy process Port: 5432]
+ B -->|Forwards to host port: 9876| C[MCP container Host: 9876 → Container: 7654 ENV: MCP_PORT=7654]
+ C -->|Response| B
+ B -->|Response| A
+```
+
+This is equivalent to running a Docker container with
+`docker run -p : ...`
+
+For MCP servers that use a specific port or don't recognize those environment
+variables, specify the container port for ToolHive to expose using the
+`--target-port` flag:
+
+```bash
+thv run --transport streamable-http --target-port
+```
+
+ToolHive still maps the container port to a random port on the host to avoid
+conflicts with commonly used ports. This is equivalent to running a Docker
+container with `docker run -p : ...`
+
+```mermaid
+graph LR
+ A[MCP client] -->|HTTP request to proxy port: 5432| B[ToolHive proxy process Port: 5432]
+ B -->|Forwards to host port: 9876| C["MCP container (--target-port 3000) Host: 9876 → Container: 3000 ENV: MCP_PORT=3000"]
+ C -->|Response| B
+ B -->|Response| A
+```
+
+Some MCP servers use command-line arguments to specify their transport and port.
+For example, if your server expects the transport type as a positional argument
+and requires the `--port` flag, you can pass it like this:
+
+```bash
+thv run --transport streamable-http --target-port -- http --port
+```
+
+Check your MCP server's documentation for the required transport and port
+configuration.
+
+### Add a custom CA certificate
+
+In corporate environments with TLS inspection or custom certificate authorities,
+you may need to configure a CA certificate for ToolHive to use when building
+containers from protocol schemes like `uvx://`, `npx://`, and `go://`.
+
+ToolHive provides both global configuration and per-command options for CA
+certificates.
+
+#### Configure a global CA certificate
+
+To set a CA certificate that ToolHive will use for all container builds:
+
+```bash
+thv config set-ca-cert /path/to/corporate-ca.crt
+```
+
+To view the currently configured CA certificate:
+
+```bash
+thv config get-ca-cert
+```
+
+To remove the CA certificate configuration:
+
+```bash
+thv config unset-ca-cert
+```
+
+#### Override CA certificate per command
+
+You can override the global CA certificate configuration for a specific run
+using the `--ca-cert` flag:
+
+```bash
+thv run --ca-cert /path/to/other-ca.crt uvx://some-package
+```
+
+This is useful when you need to use different CA certificates for different
+servers or when testing with a specific certificate.
+
+#### Priority order
+
+ToolHive uses the following priority order for CA certificates:
+
+1. Command-line flag (`--ca-cert`)
+2. Global configuration (`thv config set-ca-cert`)
+3. No custom CA certificate (default behavior)
+
+For example:
+
+```bash
+# Set a global CA certificate
+thv config set-ca-cert /path/to/corporate-ca.crt
+
+# This uses the configured CA certificate
+thv run uvx://some-package
+
+# This overrides the configured CA certificate
+thv run --ca-cert /path/to/special-ca.crt uvx://other-package
+```
+
+## Run a remote MCP server
+
+You can run remote MCP servers directly by providing their URL. This allows you
+to connect to MCP servers hosted elsewhere without needing to manage containers
+locally. ToolHive creates a transparent proxy that handles authentication and
+forwards requests to the remote server.
+
+### Basic remote server setup
+
+To run a remote MCP server, simply provide its URL:
+
+```bash
+thv run [--name ]
+```
+
+For example:
+
+```bash
+thv run https://api.example.com/mcp
+```
+
+If you don't specify a name with `--name`, ToolHive will automatically derive a
+name from the URL by extracting the main domain name (e.g., `notion` from
+`https://api.notion.com/mcp`).
+
+By default, remote servers use the `streamable-http` transport. If the server
+uses Server-Sent Events (SSE), specify the transport flag:
+
+```bash
+thv run https://api.example.com/sse --transport sse
+```
+
+:::info[What's happening?]
+
+When you run a remote MCP server, ToolHive:
+
+1. Automatically detects if the remote server requires authentication.
+2. Handles OAuth/OIDC authentication flows if needed.
+3. Starts an HTTP proxy process on a random port to forward client requests to
+ the remote server.
+4. Manages the server like any other ToolHive workload. No container is created
+ for remote MCP servers.
+
+:::
+
+### Authentication setup
+
+Many remote MCP servers require authentication. ToolHive supports automatic
+authentication detection and OAuth/OIDC flows.
+
+#### Auto-detect authentication
+
+ToolHive can automatically detect if a remote server requires authentication by
+examining the server's response headers and status codes:
+
+```bash
+thv run https://protected-api.com/mcp --name my-server
+```
+
+If authentication is required, ToolHive will prompt you to complete the OAuth
+flow. When no client credentials are provided, ToolHive automatically registers
+an OAuth client with the authorization server using RFC 7591 dynamic client
+registration, eliminating the need to pre-configure client ID and secret.
+
+#### OIDC authentication
+
+For servers using OpenID Connect (OIDC), provide the issuer URL, client ID, and
+client secret obtained from the application provider:
+
+```bash
+thv run https://api.example.com/mcp \
+ --name my-server \
+ --remote-auth-issuer https://auth.example.com \
+ --remote-auth-client-id my-client-id \
+ --remote-auth-client-secret my-client-secret
+```
+
+#### OAuth2 authentication
+
+For servers using OAuth2, specify the authorization and token URLs along with
+the client ID and secret obtained from the application provider:
+
+```bash
+thv run https://api.example.com/mcp \
+ --name my-server \
+ --remote-auth-authorize-url https://auth.example.com/oauth/authorize \
+ --remote-auth-token-url https://auth.example.com/oauth/token \
+ --remote-auth-client-id my-client-id \
+ --remote-auth-client-secret my-client-secret
+```
+
+#### Automatic token refresh
+
+ToolHive automatically handles OAuth token refresh for remote MCP servers. When
+you authenticate with a remote server, ToolHive stores both the access token and
+refresh token securely. The refresh token is used to automatically obtain new
+access tokens when they expire, ensuring uninterrupted service without requiring
+manual re-authentication.
+
+### Advanced remote server configuration
+
+#### OAuth scopes and parameters
+
+Specify custom OAuth scopes for the authentication flow:
+
+```bash
+thv run https://api.example.com/mcp \
+ ... \
+ --remote-auth-scopes read,write,admin
+```
+
+#### Resource indicator (RFC 8707)
+
+When authenticating to remote MCP servers, you can specify a resource indicator
+as defined by [RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707). This
+allows the authorization server to return an access token with a scoped
+audience, which will then be passed to and validated by the remote MCP server.
+
+By default, ToolHive automatically uses the remote server URL as the resource
+indicator when authenticating. The URL is validated, normalized (lowercase
+scheme and host, fragments stripped), and included in the OAuth token request.
+
+To explicitly set a different resource indicator, use the
+`--remote-auth-resource` flag:
+
+```bash
+thv run https://api.example.com/mcp \
+ ... \
+ --remote-auth-resource https://api.example.com
+```
+
+The resource parameter must include a scheme and host, and cannot contain
+fragments. If you provide an invalid resource parameter, ToolHive will return an
+error.
+
+#### Custom authentication timeout
+
+Adjust the authentication timeout for slow networks:
+
+```bash
+thv run https://api.example.com/mcp \
+ ... \
+ --remote-auth-timeout 2m
+```
+
+#### Skip browser authentication
+
+For headless environments, skip the browser-based OAuth flow:
+
+```bash
+thv run https://api.example.com/mcp \
+ ... \
+ --remote-auth-skip-browser
+```
+
+#### Inject custom headers
+
+Some remote MCP servers require custom headers for tenant identification, API
+keys, or other purposes. ToolHive can inject headers into every request
+forwarded to the remote server.
+
+To add plaintext headers, use the `--remote-forward-headers` flag:
+
+```bash
+thv run https://api.example.com/mcp \
+ --name my-server \
+ --remote-forward-headers "X-Tenant-ID=tenant123" \
+ --remote-forward-headers "X-Custom-Header=value"
+```
+
+For sensitive values like API keys, use the `--remote-forward-headers-secret`
+flag to reference values stored in ToolHive's secrets manager:
+
+```bash
+# First, store the secret (enter the value when prompted)
+thv secret set my-api-key
+
+# Then reference it by name
+thv run https://api.example.com/mcp \
+ --name my-server \
+ --remote-forward-headers-secret "X-Api-Key=my-api-key"
+```
+
+You can combine plaintext and secret-backed headers in a single command:
+
+```bash
+thv run https://api.example.com/mcp \
+ --name my-server \
+ --remote-forward-headers "X-Tenant-ID=tenant123" \
+ --remote-forward-headers-secret "X-Api-Key=my-api-key"
+```
+
+:::warning[Security considerations]
+
+- Plaintext header values are stored in the server's configuration file. For
+ sensitive values (API keys, tokens), always use
+ `--remote-forward-headers-secret`.
+- Secret-backed header values are resolved at runtime and never stored in
+ configuration files.
+- Certain headers cannot be configured for security reasons, including `Host`,
+ `Connection`, `Transfer-Encoding`, and proxy-related headers like
+ `X-Forwarded-For`.
+
+:::
+
+### Remote server management
+
+Remote MCP servers are managed like any other ToolHive workload:
+
+- **List servers**: `thv list` shows remote servers with their target URLs
+- **View logs**: `thv logs ` shows proxy and authentication logs
+- **Stop/restart**: `thv stop ` and `thv restart `
+- **Remove**: `thv rm ` removes the proxy and configuration
+
+## Share and reuse server configurations
+
+ToolHive allows you to export a server's configuration and run servers using
+previously exported configurations. This is useful for:
+
+- Sharing server setups with team members
+- Creating backups of complex configurations
+- Running identical server instances across different environments
+
+### Export a server configuration
+
+To export a running server's configuration to a file, use the
+[`thv export`](../reference/cli/thv_export.md) command:
+
+```bash
+thv export
+```
+
+For example, to export the configuration of a running server named "fetch":
+
+```bash
+thv export fetch ./fetch-config.json
+```
+
+This creates a JSON file containing all the server's configuration, including:
+
+- Container image and version
+- Environment variables and secrets references
+- Volume mounts and permissions
+- Network settings
+- Transport configuration
+
+### Run a server from an exported configuration
+
+To run a server using a previously exported configuration, use the
+[`thv run`](../reference/cli/thv_run.md) command with the `--from-config` flag:
+
+```bash
+thv run --from-config
+```
+
+For example, to run a server using the exported configuration:
+
+```bash
+thv run --from-config ./fetch-config.json
+```
+
+This creates a new server instance with identical settings to the original. If
+the original server used secrets, you must have the same secrets available in
+your ToolHive secrets store.
+
+:::note
+
+When you use `--from-config`, you cannot specify any other command-line flags.
+The configuration file must contain all server settings.
+
+:::
+
+## Next steps
+
+See [Monitor and manage MCP servers](./manage-mcp-servers.mdx) to monitor and
+control your servers.
+
+[Test your MCP server](./test-mcp-servers.mdx) using the MCP Inspector or
+`thv mcp` commands.
+
+## Related information
+
+- [`thv run` command reference](../reference/cli/thv_run.md)
+- [Client configuration](./client-configuration.mdx)
+- [Secrets management](./secrets-management.mdx)
+- [Custom permissions](./custom-permissions.mdx)
+ - [File system access](./filesystem-access.mdx)
+ - [Network isolation](./network-isolation.mdx)
+
+## Troubleshooting
+
+
+Server fails to start
+
+If a server fails to start:
+
+1. Check if Docker, Podman, or Colima is running
+2. Verify you have internet access to pull images
+3. Check if the port is already in use
+4. Look at the error message for specific issues
+
+
+
+
+Server starts but isn't accessible
+
+If a server starts but isn't accessible:
+
+1. Check the server logs:
+
+ ```bash
+ thv logs
+ ```
+
+2. Verify the port isn't blocked by a firewall
+
+3. Make sure clients are properly configured (see
+ [Client configuration](./client-configuration.mdx))
+
+
+
+
+Server crashes or exits unexpectedly
+
+If a server crashes or exits unexpectedly:
+
+1. List all MCP servers including stopped ones:
+
+ ```bash
+ thv list --all
+ ```
+
+2. Check the logs for error messages:
+
+ ```bash
+ thv logs
+ ```
+
+3. Check if the server requires any secrets or environment variables
+
+4. Verify the server's configuration and arguments
+
+
+
+
+Remote server authentication fails
+
+If a remote MCP server authentication fails:
+
+1. Check the server logs for authentication errors (see
+ [View server logs](./manage-mcp-servers.mdx#view-server-logs) for the correct
+ log file path on your platform)
+
+2. Verify the issuer URL is correct and accessible
+
+3. Check if the OAuth client ID and secret are valid
+
+4. Ensure the remote server supports the OAuth flow you're using
+
+5. Try re-authenticating by restarting the server:
+
+ ```bash
+ thv restart
+ ```
+
+
+
+
+Remote server connection issues
+
+If you can't connect to a remote MCP server:
+
+1. Verify the remote server URL is correct and accessible
+
+2. Check your network connectivity:
+
+ ```bash
+ curl -I https://api.example.com/mcp
+ ```
+
+3. Check if the remote server requires specific headers or authentication
+
+4. Review the server logs for connection errors:
+
+ ```bash
+ thv logs
+ ```
+
+5. Try running with debug mode for more detailed logs:
+
+ ```bash
+ thv run --debug https://api.example.com/mcp --name my-server
+ ```
+
+
+
+
+MCP server can't connect to services on the same host
+
+When ToolHive runs MCP servers in containers, they can't reach services on your
+host machine using `localhost` due to container network isolation.
+
+Replace `localhost` in your MCP server configuration with the appropriate host
+address for your platform:
+
+- **Docker Desktop (macOS/Windows)**: `host.docker.internal`
+- **Podman Desktop**: `host.containers.internal`
+- **Docker Engine (Linux)**: `172.17.0.1` (or your custom bridge gateway IP)
+
+For example, change `http://localhost:3000` to
+`http://host.docker.internal:3000`.
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/secrets-management.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/secrets-management.mdx
new file mode 100644
index 00000000..3bbbed6c
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/secrets-management.mdx
@@ -0,0 +1,362 @@
+---
+title: Secrets management
+description: How to securely manage API tokens and other sensitive data in ToolHive.
+---
+
+MCP servers often need secrets like API tokens, connection strings, and other
+sensitive parameters. ToolHive provides built-in secrets management features,
+letting you manage these values securely without exposing them in plaintext
+configuration files.
+
+## Secrets providers
+
+ToolHive supports multiple secrets providers to fit different security and
+workflow requirements:
+
+- `encrypted` - ToolHive encrypts secrets using a password stored in your
+ operating system's keyring
+- `1password` - ToolHive retrieves secrets from a 1Password vault
+
+You can use only one provider at a time. To select your preferred provider, run:
+
+```bash
+thv secret setup
+```
+
+If you plan to use 1Password, first set up a 1Password service account and
+obtain an API token. See the 1Password tab below for details.
+
+
+
+
+When you select the `encrypted` provider, ToolHive prompts you to create an
+encryption password that protects your secrets.
+
+ToolHive stores this encryption password in your operating system's keyring
+(Keychain Access on macOS, Credential Manager on Windows, and dbus/Gnome Keyring
+on Linux). This means you don't need to enter the password every time you use a
+[`thv secret`](../reference/cli/thv_secret.md) command.
+
+
+
+
+:::note
+
+The 1Password provider is read-only. You can list and view secrets, but you
+can't create or delete them through ToolHive. Secrets must already exist in your
+1Password vault.
+
+If you'd like to see write operations added, please
+[open an issue](https://github.com/stacklok/toolhive/issues) or join the
+`#toolhive-developers` channel in [Discord](https://discord.gg/stacklok).
+Contributions are welcome!
+
+:::
+
+To use 1Password as your secrets provider, set up a 1Password service account.
+For detailed instructions, see the
+[1Password documentation](https://developer.1password.com/docs/service-accounts/get-started#create-a-service-account).
+
+Next, set the `OP_SERVICE_ACCOUNT_TOKEN` environment variable to your service
+account's API token (displayed during the service account creation process).
+This token is required for all [`thv secret`](../reference/cli/thv_secret.md)
+commands:
+
+```bash
+export OP_SERVICE_ACCOUNT_TOKEN=
+```
+
+Then, run `thv secret setup` and select `1password` when prompted.
+
+To reference a secret from 1Password, use the
+[1Password secret reference](https://developer.1password.com/docs/cli/secret-reference-syntax)
+URI format:
+
+```text
+op:////[section-name/]
+```
+
+For example, to retrieve the `password` field from the `github` item in the
+`MCPVault` vault:
+
+```bash
+thv secret get op://MCPVault/github/password
+```
+
+Run [`thv secret list`](../reference/cli/thv_secret_list.md) to see all secrets
+accessible to your service account, along with their URIs.
+
+
+
+
+## Manage secrets
+
+### Create or update a secret
+
+The [`thv secret set`](../reference/cli/thv_secret_set.md) command lets you
+create or update a secret in your secret store. You can set a secret
+interactively by running:
+
+```bash
+thv secret set
+```
+
+ToolHive prompts you to enter the secret value, and the input remains hidden for
+security.
+
+Example:
+
+```bash
+thv secret set github
+# Enter your GitHub personal access token when prompted
+```
+
+Alternatively, you can set a secret using standard input:
+
+```bash
+echo "MY_SECRET_VALUE" | thv secret set
+```
+
+:::tip[Example]
+
+Create a secret named `github` and set its value to your GitHub authentication
+token using the GitHub CLI:
+
+```bash
+gh auth token | thv secret set github
+```
+
+:::
+
+### List and view secrets
+
+To list the names of all secrets in your secret store without revealing their
+values:
+
+```bash
+thv secret list
+```
+
+To decrypt and view a secret's value:
+
+```bash
+thv secret get
+```
+
+### Remove a secret
+
+To delete a secret when it's no longer needed:
+
+```bash
+thv secret delete
+```
+
+### Reset your secret store
+
+ToolHive doesn't currently support changing the encryption password. If you need
+to reset your secret store, delete the encrypted secrets file and recreate your
+secrets.
+
+First, remove the encryption password from the keyring:
+
+```bash
+thv secret reset-keyring
+```
+
+Then, delete the encrypted secrets file:
+
+
+
+
+ ```bash
+ rm ~/Library/Application\ Support/toolhive/secrets_encrypted
+ ```
+
+
+
+
+ ```bash
+ rm ~/.config/toolhive/secrets_encrypted
+ ```
+
+
+
+
+ ```powershell
+ Remove-Item "$env:LOCALAPPDATA\toolhive\secrets_encrypted"
+ ```
+
+
+
+
+The next time you run a `thv secret` command, ToolHive prompts you to create a
+new encryption password and starts with a fresh secret store.
+
+## Use secrets with MCP servers
+
+ToolHive can securely pass secrets to an MCP server when you run it. This lets
+the server access sensitive information without exposing it in plaintext.
+
+To do this, use the `--secret` flag with the
+[`thv run`](../reference/cli/thv_run.md) command. The secret value is injected
+into the container as an environment variable.
+
+```bash
+thv run --secret ,target=
+```
+
+Check the MCP server's documentation to find the expected environment variable
+names. For example, the GitHub MCP server expects the GitHub token to be passed
+as `GITHUB_PERSONAL_ACCESS_TOKEN`.
+
+For MCP servers in the ToolHive registry, you can find the expected environment
+variable names in the server's registry entry:
+
+```bash
+thv registry info
+```
+
+### Example: GitHub API token
+
+This example shows how to set up a GitHub authentication token and use it with
+the GitHub MCP server:
+
+1. Set the secret:
+
+ ```bash
+ thv secret set github
+ # Enter your GitHub personal access token when prompted
+ ```
+
+2. Run the GitHub MCP server with the token:
+
+ ```bash
+ thv run --secret github,target=GITHUB_PERSONAL_ACCESS_TOKEN github
+ ```
+
+The GitHub MCP server now has access to your GitHub token and can make
+authenticated API requests.
+
+### Example: Multiple secrets
+
+You can provide multiple secrets to a server by using the `--secret` flag
+multiple times:
+
+```bash
+thv run \
+ --secret github,target=GITHUB_TOKEN \
+ --secret openai,target=OPENAI_API_KEY \
+ multi-api-server
+```
+
+### Example: 1Password secret
+
+To use a secret from 1Password with an MCP server, set the
+`OP_SERVICE_ACCOUNT_TOKEN` environment variable with your 1Password service
+account API token and reference the secret using the `op://` URI format.
+
+```bash
+OP_SERVICE_ACCOUNT_TOKEN= thv run \
+ --secret op://MCPVault/slackbot/token,target=SLACK_BOT_TOKEN \
+ --secret op://MCPVault/slackbot/team_id,target=SLACK_TEAM_ID \
+ slack
+```
+
+This command retrieves the `token` and `team_id` fields from the `slackbot` item
+in the `MCPVault` vault and passes them to the `slack` MCP server as the
+`SLACK_BOT_TOKEN` and `SLACK_TEAM_ID` environment variables.
+
+## Related information
+
+- [`thv secret` command reference](../reference/cli/thv_secret.md)
+- [Run MCP servers](../guides-cli/run-mcp-servers.mdx)
+
+## Troubleshooting
+
+
+Keyring access issues
+
+If you run into errors related to the system keyring:
+
+1. Make sure your system's keyring service is running
+2. Check that you have the necessary permissions
+3. On some Linux systems, you might need to install additional packages:
+
+ ```bash
+ # For Debian/Ubuntu
+ sudo apt-get install gnome-keyring
+
+ # For Fedora/RHEL
+ sudo dnf install gnome-keyring
+ ```
+
+
+
+
+Secret not available to MCP server
+
+If your MCP server can't access a secret:
+
+1. Verify the secret exists:
+
+ ```bash
+ thv secret list
+ ```
+
+2. Verify the secret value:
+
+ ```bash
+ thv secret get
+ ```
+
+3. Check that you're using the correct secret name and target environment
+ variable. Inspect the MCP server's expected environment variables in the
+ registry:
+
+ ```bash
+ thv registry info
+ ```
+
+4. Inspect the server logs for any errors:
+
+ ```bash
+ thv logs
+ ```
+
+
+
+
+Forgot encryption password
+
+If the keyring entry is lost or corrupted and you forget your encryption
+password, you won't be able to access your secrets. In this case, delete the
+[encrypted secrets file](#reset-your-secret-store) and recreate your secrets.
+
+
+
+
+Issues accessing 1Password secrets
+
+If you can't access 1Password secrets:
+
+1. Verify the `OP_SERVICE_ACCOUNT_TOKEN` environment variable is set:
+
+ ```bash
+ echo $OP_SERVICE_ACCOUNT_TOKEN
+ ```
+
+2. Check that the token is valid and has the necessary permissions to access the
+ vault and item:
+
+ ```bash
+ thv secret list
+ ```
+
+3. Make sure the secret reference URI is correct and matches the vault, item,
+ and field names in 1Password:
+
+ ```bash
+ thv secret get op:////[section-name/]
+ ```
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/skills-management.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/skills-management.mdx
new file mode 100644
index 00000000..1f107ac9
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/skills-management.mdx
@@ -0,0 +1,434 @@
+---
+title: Manage agent skills
+sidebar_label: Agent skills
+description: How to install, distribute, and manage agent skills with the ToolHive CLI.
+---
+
+[Agent skills](https://agentskills.io/) are reusable bundles of instructions,
+scripts, and resources that teach AI agents how to perform specific tasks. While
+MCP servers provide **tools** (the raw capabilities an agent can call), skills
+provide the **knowledge** of when, why, and how to use those tools effectively.
+
+ToolHive lets you install skills from a
+[ToolHive Registry Server](../guides-registry/skills.mdx) (by name), OCI
+registries (by reference), or Git repositories (by URL), and manages their
+lifecycle across multiple AI clients.
+
+:::tip[New to skills?]
+
+If you're not sure what skills are or how they relate to MCP servers, see
+[Understanding skills](../concepts/skills.mdx) for a conceptual overview.
+
+:::
+
+## Prerequisites
+
+- The [ToolHive API server](./api-server.mdx) must be running. Start it in a
+ separate terminal window (the command blocks while running):
+
+ ```bash
+ thv serve
+ ```
+
+ The server must remain running while you use `thv skill` commands.
+
+ :::tip[Using the ToolHive desktop app?]
+
+ If the ToolHive desktop app is already running, the API server is available
+ automatically. You can skip the `thv serve` step and use `thv skill` commands
+ directly.
+
+ :::
+
+- A supported AI client installed. See the
+ [client compatibility reference](../reference/client-compatibility.mdx) for
+ which clients support skills.
+
+## Install a skill
+
+You can install skills from a ToolHive Registry Server (by name), an OCI
+registry (by reference), or a Git repository (by URL).
+
+### Install from the registry
+
+The simplest way to install a skill is by name. ToolHive looks up the skill in
+the configured Registry Server and installs it automatically:
+
+```bash
+thv skill install
+```
+
+For example:
+
+```bash
+thv skill install toolhive-cli-user
+```
+
+### Install from an OCI registry
+
+To install a specific version of a skill from an OCI registry, provide the full
+OCI reference:
+
+```bash
+thv skill install ghcr.io//skills/:
+```
+
+### Install from a Git repository
+
+To install a skill directly from a Git repository:
+
+```bash
+thv skill install git://github.com/anthropics/skills@main#skills/webapp-testing
+```
+
+The URL format is `git://host/owner/repo[@ref][#path/to/skill]`, where `@ref` is
+a branch, tag, or commit hash, and `#path` points to the skill subdirectory.
+
+:::info[What's happening?]
+
+When you install a skill, ToolHive:
+
+1. Resolves the skill source (registry, OCI, or Git)
+2. Downloads and extracts the skill files
+3. Writes the `SKILL.md` and supporting files to your AI client's skills
+ directory
+4. Records the installation in its database
+
+Your AI client discovers the skill automatically by reading from its skills
+directory.
+
+:::
+
+### Overwrite an existing skill
+
+If a skill is already installed and you want to replace it, use the `--force`
+flag:
+
+```bash
+thv skill install my-skill --force
+```
+
+### Target a specific client
+
+If you have multiple supported clients, ToolHive installs the skill for the
+first one it detects. To control which client receives the skill, use the
+`--client` flag:
+
+```bash
+thv skill install my-skill --client claude-code
+```
+
+See the [client compatibility reference](../reference/client-compatibility.mdx)
+for the full list of clients that support skills.
+
+### Add a skill to a group
+
+You can organize skills into groups, just like MCP servers. Skills in a group
+are automatically installed to clients registered with that group:
+
+```bash
+thv skill install my-skill --group development
+```
+
+## Choose a scope
+
+Skills support two scopes that control where the skill files are installed:
+
+- **User scope** (default): Installs the skill globally for your user account.
+ The skill is available across all projects.
+- **Project scope**: Installs the skill in a specific project directory. The
+ skill is only available when working in that project.
+
+### Install a user-scoped skill
+
+```bash
+thv skill install my-skill
+```
+
+This installs the skill to your home directory (for example,
+`~/.claude/skills/my-skill/` for Claude Code).
+
+### Install a project-scoped skill
+
+```bash
+thv skill install my-skill --scope project --project-root /path/to/project
+```
+
+This installs the skill to the project directory (for example,
+`/path/to/project/.claude/skills/my-skill/` for Claude Code).
+
+:::note
+
+The project root must be a Git repository. ToolHive validates this to prevent
+installing skills in arbitrary directories.
+
+:::
+
+## List installed skills
+
+To see all installed skills:
+
+```bash
+thv skill list
+```
+
+The output shows the name, version, scope, status, clients, and source reference
+for each skill.
+
+### Filter the list
+
+You can filter skills by scope, client, or group:
+
+```bash
+thv skill list --scope user
+thv skill list --client claude-code
+thv skill list --group development
+```
+
+### Get JSON output
+
+```bash
+thv skill list --format json
+```
+
+## View skill details
+
+To see detailed information about a specific skill:
+
+```bash
+thv skill info
+```
+
+This shows the skill's name, version, description, scope, status, source
+reference, installation date, and associated clients.
+
+## Uninstall a skill
+
+To remove an installed skill:
+
+```bash
+thv skill uninstall
+```
+
+For project-scoped skills, specify the scope and project root:
+
+```bash
+thv skill uninstall my-skill --scope project --project-root /path/to/project
+```
+
+This removes the skill files from all associated client directories and deletes
+the database record.
+
+## Create a skill
+
+Every skill requires a `SKILL.md` file at the root of its directory. At a
+minimum, the file needs `name` and `description` fields in YAML frontmatter,
+followed by Markdown instructions for the agent:
+
+```yaml title="my-skill/SKILL.md"
+---
+name: my-skill
+description: >-
+ What this skill does and when to use it. Include keywords that help agents
+ identify relevant tasks.
+---
+# Instructions
+
+Step-by-step instructions for the agent...
+```
+
+The `name` must be lowercase alphanumeric with hyphens, must match the directory
+name, and must not start or end with a hyphen.
+
+You can also add optional files in `scripts/`, `references/`, and `assets/`
+subdirectories for executable code, documentation, and static resources.
+
+For the full list of frontmatter fields (version, license, allowed-tools, and
+more) and directory structure details, see
+[Understanding skills](../concepts/skills.mdx#skill-structure).
+
+## Build and publish skills
+
+Once you've created a skill, you can package it as an OCI artifact and publish
+it to a registry for others to install.
+
+### Validate a skill
+
+Before building, validate your skill directory to check for errors:
+
+```bash
+thv skill validate ./my-skill
+```
+
+This verifies that:
+
+- A `SKILL.md` file exists with valid frontmatter
+- The `name` and `description` fields are present
+- The `name` matches the directory name
+- No symlinks or path traversal issues exist
+
+### Build an OCI artifact
+
+Package your skill into an OCI artifact:
+
+```bash
+thv skill build ./my-skill
+```
+
+To specify a custom tag:
+
+```bash
+thv skill build ./my-skill --tag ghcr.io/my-org/skills/my-skill:v1.0.0
+```
+
+The build command stores the artifact in your local OCI store and prints the
+reference.
+
+### Push to a registry
+
+After building, push the artifact to a remote OCI registry:
+
+```bash
+thv skill push ghcr.io/my-org/skills/my-skill:v1.0.0
+```
+
+:::note
+
+Pushing to a remote registry uses your existing container registry credentials
+(for example, from `docker login` or `podman login`). Make sure you're
+authenticated before pushing.
+
+:::
+
+## List and remove locally-built skill artifacts
+
+After building skills locally, you can view and manage the artifacts stored in
+your local OCI store.
+
+### List locally-built artifacts
+
+```bash
+thv skill builds
+```
+
+This lists all OCI skill artifacts built locally with `thv skill build`. The
+output shows the tag, digest, name, and version of each artifact:
+
+```text
+TAG DIGEST NAME VERSION
+ghcr.io/my-org/skills/my-skill:v1.0.0 sha256:a1b2c3d4... my-skill 1.0.0
+my-skill:latest sha256:e5f6a7b8... my-skill
+```
+
+For JSON output:
+
+```bash
+thv skill builds --format json
+```
+
+### Remove a locally-built artifact
+
+To remove an artifact from the local OCI store:
+
+```bash
+thv skill builds remove
+```
+
+For example:
+
+```bash
+thv skill builds remove my-skill:latest
+```
+
+This removes the artifact and cleans up its blobs from the local store. If
+multiple tags share the same digest, the blobs are retained until all tags
+pointing to that digest are removed.
+
+## Next steps
+
+- [Configure your AI client](./client-configuration.mdx) to register clients
+ with ToolHive for automatic MCP server and skill configuration
+- [Manage skills in the registry](../guides-registry/skills.mdx) to publish
+ skills for your team
+
+## Related information
+
+- [Understanding skills](../concepts/skills.mdx) for a conceptual overview
+- [`thv skill` command reference](../reference/cli/thv_skill.md)
+- [`thv serve` command reference](../reference/cli/thv_serve.md)
+- [Agent Skills specification](https://agentskills.io/specification)
+
+## Troubleshooting
+
+
+Skill command fails with a connection error
+
+All skill commands require the ToolHive API server to be running. Start it with:
+
+```bash
+thv serve
+```
+
+Then retry your skill command.
+
+
+
+
+Skill not discovered by your AI client
+
+If your AI client doesn't see an installed skill:
+
+1. Verify the skill is installed:
+
+ ```bash
+ thv skill list
+ ```
+
+2. Check that the skill was installed for the correct client:
+
+ ```bash
+ thv skill info
+ ```
+
+3. Verify the skill files exist in the expected directory. For Claude Code,
+ user-scoped skills are at `~/.claude/skills//` and project-scoped
+ skills are at `/.claude/skills//`.
+
+4. Restart your AI client to trigger skill discovery.
+
+
+
+
+Skill validation fails
+
+Run `thv skill validate` to see specific errors:
+
+```bash
+thv skill validate ./my-skill
+```
+
+Common issues include:
+
+- Missing `SKILL.md` file in the directory
+- Missing `name` or `description` in the frontmatter
+- Skill `name` doesn't match the directory name
+- Symlinks present in the skill directory (not allowed for security)
+
+
+
+
+Push to registry fails with authentication error
+
+Make sure you're authenticated with your container registry:
+
+```bash
+# For GitHub Container Registry
+echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin
+
+# For Docker Hub
+docker login
+```
+
+Then retry the push command.
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/telemetry-and-metrics.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/telemetry-and-metrics.mdx
new file mode 100644
index 00000000..16e6335a
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/telemetry-and-metrics.mdx
@@ -0,0 +1,457 @@
+---
+title: Telemetry and metrics
+description:
+ How to enable OpenTelemetry and Prometheus instrumentation for ToolHive MCP
+ servers.
+---
+
+ToolHive includes built-in instrumentation using OpenTelemetry, which gives you
+comprehensive observability for your MCP server interactions. You can export
+traces and metrics to popular observability backends like Jaeger, Honeycomb,
+Datadog, and Grafana Cloud, or expose Prometheus metrics locally.
+
+## What you can monitor
+
+ToolHive's telemetry captures detailed information about MCP interactions
+including traces, metrics, and performance data. For a comprehensive overview of
+the telemetry architecture, metrics collection, and monitoring capabilities, see
+the [observability overview](../concepts/observability.mdx).
+
+## Enable telemetry
+
+You can enable telemetry when running an MCP server by using the OpenTelemetry
+flags with the [`thv run`](../reference/cli/thv_run.md) command or configure
+defaults for all MCP servers using the
+[`thv config otel`](../reference/cli/thv_config_otel.md) commands.
+
+### Export to an OTLP endpoint
+
+You can export traces and metrics to an OpenTelemetry Protocol (OTLP) compatible
+backend using the `--otel-endpoint` flag. You can also specify headers for
+authentication and configure sampling rates.
+
+This example runs the Fetch MCP server and exports traces to a local instance of
+the [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/):
+
+```bash
+thv run \
+ --otel-endpoint localhost:4318 \
+ --otel-service-name fetch-mcp \
+ --otel-insecure \
+ fetch
+```
+
+:::note
+
+The `--otel-endpoint` is provided as a hostname and optional port, without a
+scheme or path (e.g., use `api.honeycomb.io` or `api.honeycomb.io:443`, not
+`https://api.honeycomb.io`). ToolHive automatically uses HTTPS unless
+`--otel-insecure` is specified.
+
+:::
+
+By default, the service name is set to `thv-` (e.g., `thv-fetch`),
+and the sampling rate is `0.1` (10%). You can customize these settings with
+additional [configuration options](#configuration-options).
+
+:::tip[Recommendation]
+
+Set the `--otel-service-name` flag to a meaningful name for each MCP server.
+This helps you identify the server in your observability backend.
+
+:::
+
+### Include environment variables
+
+You can include specific environment variables from your host system in
+telemetry spans using the `--otel-env-vars` flag. This is useful for adding
+context like deployment environment or service version to your traces.
+
+```bash
+thv run \
+ --otel-endpoint api.honeycomb.io \
+ --otel-headers "x-honeycomb-team=" \
+ --otel-env-vars "NODE_ENV,DEPLOYMENT_ENV,SERVICE_VERSION" \
+ fetch
+```
+
+Only the environment variables you specify will be included in spans, and
+they'll appear as attributes with the `environment.` prefix (e.g.,
+`environment.NODE_ENV`).
+
+### Add custom resource attributes
+
+You can add custom metadata to all telemetry signals (traces and metrics) using
+custom resource attributes. These attributes are useful for adding context like
+team ownership, deployment region, server type, or any other metadata that helps
+you filter and query your telemetry data in observability platforms.
+
+Custom attributes are attached globally to all telemetry signals from the MCP
+server, making them available for filtering, grouping, and searching in your
+observability backend.
+
+#### Using the CLI flag
+
+Use the `--otel-custom-attributes` flag to specify custom attributes as
+comma-separated key=value pairs:
+
+```bash
+thv run \
+ --otel-endpoint api.honeycomb.io \
+ --otel-headers "x-honeycomb-team=" \
+ --otel-custom-attributes "server_type=production,region=us-east-1,team=platform" \
+ fetch
+```
+
+Attribute keys may contain letters, numbers, dots (`.`), underscores (`_`), and
+hyphens (`-`). Spaces and most other punctuation are not allowed in keys.
+Attribute values can contain any UTF-8 string, including URLs. For more details,
+see the
+[OpenTelemetry Resource Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/resource/#attributes).
+
+#### Using environment variables
+
+You can also set custom attributes using the standard OpenTelemetry environment
+variable `OTEL_RESOURCE_ATTRIBUTES`:
+
+```bash
+export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=staging,service.namespace=backend"
+thv run --otel-endpoint api.honeycomb.io fetch
+```
+
+#### Combining both methods
+
+When using both the CLI flag and environment variable, attributes from both
+sources are combined:
+
+```bash
+export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production"
+thv run \
+ --otel-endpoint api.honeycomb.io \
+ --otel-custom-attributes "region=us-west-2,team=infrastructure" \
+ fetch
+```
+
+This will add all three attributes to your telemetry data:
+
+- `deployment.environment=production` (from environment variable)
+- `region=us-west-2` (from CLI flag)
+- `team=infrastructure` (from CLI flag)
+
+#### Common use cases
+
+Custom attributes are particularly useful for:
+
+- **Critical context**: Add critical context to telemetry (e.g., service, host,
+ or environment) so data is meaningful and traceable.
+- **Fast filtering and troubleshooting**: Enable fast filtering, grouping, and
+ correlation across distributed components during troubleshooting.
+- **Consistent metadata:** Support standardized metadata via OpenTelemetry
+ semantic conventions for reliable observability across systems.
+- **Root-cause analysis:** Pinpoint which resource is responsible for
+ performance issues.
+- **Telemetry enrichment:** Enable automatic or manual enrichment using resource
+ detectors and the OpenTelemetry Collector.
+
+:::tip
+
+Choose attribute names that follow OpenTelemetry semantic conventions when
+possible. For example, use `service.version` instead of `app_version` for better
+compatibility with observability tools.
+
+:::
+
+### Enable Prometheus metrics
+
+You can expose Prometheus-style metrics at `/metrics` on the main transport port
+for local scraping using the `--otel-enable-prometheus-metrics-path` flag.
+
+This example runs the Fetch MCP server and enables the Prometheus metrics
+endpoint:
+
+```bash
+thv run --otel-enable-prometheus-metrics-path fetch
+```
+
+To access the metrics, you can use `curl` or any Prometheus-compatible scraper.
+The metrics are available at `http://127.0.0.1:/metrics`, where ``
+is the port assigned to the MCP server.
+
+```bash
+# Get the port number assigned to the MCP server
+thv list
+
+# Replace with the actual port number from the output of `thv list`
+curl http://127.0.0.1:/metrics
+```
+
+### Dual export
+
+You can export to both an OTLP endpoint and expose Prometheus metrics
+simultaneously.
+
+This example exports to Honeycomb and enables the Prometheus metrics endpoint:
+
+```bash
+thv run \
+ --otel-endpoint api.honeycomb.io \
+ --otel-headers "x-honeycomb-team=" \
+ --otel-enable-prometheus-metrics-path \
+ fetch
+```
+
+## Configuration options
+
+You can configure telemetry settings globally or per MCP server. The global
+configuration is stored in the ToolHive configuration file, while per-server
+configuration can be specified using command-line flags when running an MCP
+server.
+
+### Per-server configuration
+
+The table below lists the available configuration options for enabling telemetry
+when running an MCP server with the `thv run` command:
+
+```bash
+thv run [--otel-endpoint ] [--otel-service-name ] \
+ [--otel-metrics-enabled=] [--otel-tracing-enabled=] \
+ [--otel-sampling-rate ] [--otel-headers ] \
+ [--otel-custom-attributes ] [--otel-env-vars ] \
+ [--otel-insecure] [--otel-enable-prometheus-metrics-path] \
+ [--otel-use-legacy-attributes=] \
+
+```
+
+| Flag | Description | Default |
+| --------------------------------------- | ------------------------------------------------------------------- | -------------- |
+| `--otel-endpoint` | OTLP endpoint (e.g., `api.honeycomb.io`) | None |
+| `--otel-metrics-enabled` | Enable OTLP metrics export (when OTLP endpoint is configured) | `true` |
+| `--otel-tracing-enabled` | Enable distributed tracing (when OTLP endpoint is configured) | `true` |
+| `--otel-service-name` | Service name for telemetry | `thv-` |
+| `--otel-sampling-rate` | Trace sampling rate (0.0-1.0) | `0.1` (10%) |
+| `--otel-headers` | Authentication headers in `key=value` format | None |
+| `--otel-custom-attributes` | Custom resource attributes in `key=value` format | None |
+| `--otel-env-vars` | List of environment variables to include in telemetry spans | None |
+| `--otel-insecure` | Connect using HTTP instead of HTTPS | `false` |
+| `--otel-enable-prometheus-metrics-path` | Enable `/metrics` endpoint | `false` |
+| `--otel-use-legacy-attributes` | Emit legacy attribute names alongside new OTel semantic conventions | `true` |
+
+### Global configuration
+
+You can set default telemetry options for all MCP servers using the
+[`thv config otel`](../reference/cli/thv_config_otel.md) command. This way you
+don't have to set the same flags every time you run an MCP server.
+
+These defaults are applied to all MCP servers unless overridden by command-line
+flags when you run a specific server.
+
+Currently the OpenTelemetry endpoint, sampling rate, and environment variables
+can be set globally. For example, to set the default OTLP endpoint and sampling
+rate:
+
+```bash
+thv config otel set-endpoint api.honeycomb.io
+thv config otel set-metrics-enabled true
+thv config otel set-tracing-enabled true
+thv config otel set-sampling-rate 0.25
+thv config otel set-enable-prometheus-metrics-path true
+thv config otel set-insecure true
+```
+
+Each command has a corresponding `get` and `unset` command to retrieve or remove
+the configuration. For example, to check the current OTLP endpoint:
+
+```bash
+thv config otel get-endpoint
+```
+
+## Observability backends
+
+ToolHive can export telemetry data to many different observability backends. It
+supports exporting traces and metrics to any backend that implements the OTLP
+protocol. Some common examples are listed below, but specific configurations
+will vary based on your environment and requirements.
+
+### OpenTelemetry Collector (recommended)
+
+The OpenTelemetry Collector is a vendor-agnostic way to receive, process and
+export telemetry data. It supports many backend services, scalable deployment
+options, and advanced processing capabilities.
+
+```mermaid
+graph LR
+ A[ToolHive] -->|traces & metrics| B[OpenTelemetry Collector]
+ B --> C[AWS CloudWatch]
+ B --> D[Splunk]
+ B --> E[New Relic]
+ B --> F[Other OTLP backends]
+```
+
+You can run the OpenTelemetry Collector locally or use a managed service. For
+local deployment, follow the
+[OpenTelemetry Collector documentation](https://opentelemetry.io/docs/collector/).
+
+To export data to a local OpenTelemetry Collector, set your OTLP endpoint to the
+OTLP http receiver port (default is `4318`):
+
+```bash
+thv run \
+ --otel-endpoint localhost:4318 \
+ --otel-insecure \
+ fetch
+```
+
+### Prometheus
+
+To collect metrics using Prometheus, run your MCP server with the
+`--otel-enable-prometheus-metrics-path` flag and add the following to your
+Prometheus configuration:
+
+```yaml title="prometheus.yml"
+scrape_configs:
+ - job_name: 'toolhive-mcp-proxy'
+ static_configs:
+ - targets: ['localhost:']
+ scrape_interval: 15s
+ metrics_path: /metrics
+```
+
+You can add multiple MCP servers to the `targets` list. Replace
+`` with the port number assigned to each MCP server.
+
+### Jaeger
+
+Jaeger is a popular open-source distributed tracing system. You can run it
+locally or use a managed service within your enterprise or from a third-party
+provider.
+
+Follow the
+[Jaeger getting started guide](https://www.jaegertracing.io/docs/latest/getting-started/#all-in-one)
+to set up a local Jaeger instance. Once running, you can export traces to Jaeger
+by setting the OTLP endpoint to Jaeger's collector:
+
+```bash
+thv run \
+ --otel-endpoint localhost:4318 \
+ --otel-metrics-enabled=false \
+ --otel-insecure \
+
+```
+
+Access the Jaeger UI at `http://localhost:16686` to view traces.
+
+### Honeycomb
+
+You can send OpenTelemetry data directly to
+[Honeycomb's OTLP endpoint](https://docs.honeycomb.io/send-data/opentelemetry/#using-the-honeycomb-opentelemetry-endpoint),
+or use the [OpenTelemetry Collector](#opentelemetry-collector-recommended) to
+forward data to Honeycomb. This example sends data directly to Honeycomb:
+
+```bash
+thv run \
+ --otel-endpoint api.honeycomb.io \
+ --otel-headers "x-honeycomb-team=" \
+ --otel-service-name production-mcp-proxy \
+
+```
+
+You'll need your Honeycomb API key, which you can find in your
+[Honeycomb account settings](https://ui.honeycomb.io/account).
+
+### Datadog
+
+Datadog has [multiple options](https://docs.datadoghq.com/opentelemetry/) for
+collecting OpenTelemetry data:
+
+- The
+ [**OpenTelemetry Collector**](https://docs.datadoghq.com/opentelemetry/setup/collector_exporter/)
+ is recommended for existing OpenTelemetry users or users wanting a
+ vendor-neutral solution.
+
+- The [**Datadog Agent**](https://docs.datadoghq.com/opentelemetry/setup/agent)
+ is recommended for existing Datadog users.
+
+### Grafana Cloud
+
+You can send OpenTelemetry data to Grafana Cloud using
+[Grafana Alloy](https://grafana.com/docs/opentelemetry/collector/grafana-alloy/),
+Grafana Labs' supported distribution of the OpenTelemetry Collector. This is the
+recommended method for production deployments.
+
+You can also send data directly to
+[Grafana Cloud's OTLP endpoint](https://grafana.com/docs/grafana-cloud/send-data/otlp/send-data-otlp/#manual-opentelemetry-setup-for-advanced-users):
+
+```bash
+thv run \
+ --otel-endpoint otlp-gateway-prod-us-central-0.grafana.net \
+ --otel-headers "Authorization=Basic $(echo -n 'user:password' | base64)" \
+
+```
+
+## Performance considerations
+
+### Sampling rates
+
+Adjust sampling rates based on your environment:
+
+- **Development**: `--otel-sampling-rate 1.0` (100% sampling)
+- **Production**: `--otel-sampling-rate 0.01` (1% sampling for high-traffic
+ systems)
+- **Default**: `--otel-sampling-rate 0.1` (10% sampling)
+
+### Network overhead
+
+Telemetry adds minimal overhead when properly configured:
+
+- Use appropriate sampling rates for your traffic volume
+- Monitor your observability backend costs and adjust sampling accordingly
+
+## Related information
+
+- Tutorial:
+ [Collect telemetry for MCP workloads](../integrations/opentelemetry.mdx)
+- [Telemetry and monitoring concepts](../concepts/observability.mdx)
+- [`thv run` command reference](../reference/cli/thv_run.md)
+- [Run MCP servers](run-mcp-servers.mdx)
+
+## Troubleshooting
+
+
+Traces not received by collector
+
+If traces aren't showing up in your backend:
+
+1. Verify the endpoint URL and authentication headers
+2. Check network connectivity to the OTLP endpoint
+3. Ensure sampling rate isn't too low (set to `1.0` temporarily for testing)
+4. Check the ToolHive log file for errors related to telemetry (the log file
+ path is displayed when you start a server with `thv run`)
+5. If your endpoint uses a self-signed certificate or a certificate from a
+ custom CA, use the `--thv-ca-bundle` flag to add your CA or self-signed
+ certificate.
+
+
+
+
+Prometheus metrics not available
+
+If the `/metrics` endpoint isn't accessible:
+
+1. Confirm `--otel-enable-prometheus-metrics-path` is set
+2. Check that you're accessing the correct port
+3. Verify no firewall is blocking the port
+
+
+
+
+High CPU or memory usage
+
+If telemetry is consuming too many resources on your system:
+
+1. Reduce the sampling rate with `--otel-sampling-rate` for specific servers or
+ globally with `thv config otel set-sampling-rate`
+2. Only enable telemetry for servers you need to monitor
+3. Monitor the resource usage of the OpenTelemetry Collector or other backend
+ services
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/test-mcp-servers.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/test-mcp-servers.mdx
new file mode 100644
index 00000000..37b70a69
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/test-mcp-servers.mdx
@@ -0,0 +1,95 @@
+---
+title: Test MCP servers
+description: Learn how to test and validate MCP servers using the ToolHive CLI.
+---
+
+ToolHive includes several commands to help you test and validate MCP servers.
+These commands ensure that your servers are functioning correctly. This is
+useful for:
+
+- Validating new MCP server installations
+- Troubleshooting existing MCP servers
+- Testing custom MCP servers during development
+
+## MCP Inspector
+
+The [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) is an
+interactive tool for testing and debugging MCP servers.
+
+You can quickly launch the MCP Inspector and connect it to an MCP server running
+with ToolHive using the `thv inspector` command:
+
+```bash
+thv inspector
+```
+
+ToolHive downloads and runs the official Inspector container image and outputs
+the URL to open it in your web browser with the target MCP server's details
+pre-populated.
+
+Example:
+
+```bash
+thv run fetch
+thv inspector fetch
+```
+
+The output will look like this:
+
+```text
+2:52PM INFO Pulling image: ghcr.io/modelcontextprotocol/inspector:0.17.0
+2:52PM INFO Pull complete for image: ghcr.io/modelcontextprotocol/inspector:0.17.0
+2:52PM INFO Waiting for MCP Inspector to be ready...
+2:52PM INFO Connected to MCP server: fetch
+2:52PM INFO Inspector UI is now available at http://localhost:6274?transport=streamable-http&serverUrl=http://host.docker.internal:21309/mcp&MCP_PROXY_AUTH_TOKEN=
+```
+
+Open the provided URL in your web browser to access the MCP Inspector UI, where
+you can interactively test the MCP server's tools, prompts, and resources.
+
+## `thv mcp` commands
+
+ToolHive also includes several
+[`thv mcp` commands](../reference/cli/thv_mcp_list.md) to test and validate MCP
+servers. You can list the MCP server's tools, prompts, and resources:
+
+```bash
+thv mcp list tools --server
+thv mcp list prompts --server
+thv mcp list resources --server
+```
+
+Replace `` with either the MCP server name or its ToolHive
+proxy URL. You can find both values in the output of `thv list`.
+
+Add the `--format json` flag to get the output in JSON format.
+
+:::note
+
+Currently, the `thv mcp` commands only support using server names for local MCP
+servers. For remote servers, you must use the full URL instead of the server
+name. You can track
+[this issue on GitHub](https://github.com/stacklok/toolhive/issues/2035).
+
+To use URLs instead of names, get the server URL from `thv list` and use it
+directly, like `thv mcp list tools --server http://localhost:12345/mcp`.
+
+:::
+
+## ToolHive playground
+
+While the MCP Inspector and `thv mcp` commands are great for basic functional
+testing and validating spec compliance, you may want to experiment with MCP
+servers in a more interactive way. It's also important to see how MCP servers
+perform in real-world scenarios with actual AI models.
+
+The _playground_ in the ToolHive UI lets you test different tools interactively
+and see how your MCP server responds to various prompts.
+
+See [Test MCP servers in the ToolHive UI](../guides-ui/playground.mdx) to learn
+about the playground's features and how to get started.
+
+## Related information
+
+- [`thv inspector` command reference](../reference/cli/thv_inspector.md)
+- [`thv mcp list` command reference](../reference/cli/thv_mcp_list.md)
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/thvignore.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/thvignore.mdx
new file mode 100644
index 00000000..90f50ba3
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/thvignore.mdx
@@ -0,0 +1,136 @@
+---
+title: Hide sensitive files
+description:
+ Use .thvignore to prevent secrets from leaking into MCP containers while
+ keeping fast bind mounts for development.
+---
+
+Some MCP servers need access to your project files, but you don't want to expose
+secrets like `.env`, SSH keys, or cloud credentials. ToolHive supports a
+`.thvignore` mechanism that masks selected paths from the container while
+keeping all other files available through a live bind mount for a smooth
+developer experience.
+
+## How it works
+
+When you mount a directory and a `.thvignore` file is present at the mount
+source, ToolHive resolves the ignore patterns and overlays those paths inside
+the container:
+
+- Directories (for example, `.ssh/`, `node_modules/`): overlaid using a tmpfs
+ mount at the container path
+- Files (for example, `.env`, `secrets.json`): overlaid using a bind mount of a
+ shared, empty host file at the container path
+
+The rest of the files remain bind-mounted from your host, so edits are visible
+in the container immediately.
+
+## Create an ignore file
+
+Create a file named `.thvignore` at the root of the directory you intend to
+mount. Use simple, gitignore-like patterns:
+
+```text
+# secrets
+.env
+.env.*
+*.key
+*.pem
+
+# cloud credentials
+.aws/
+.gcp/
+
+# SSH keys
+.ssh/
+
+# OS junk
+.DS_Store
+```
+
+Guidelines:
+
+- `dir/` matches a directory directly under the mount source
+- `file.ext` matches a file directly under the mount source
+- `*.ext` matches any file with that extension directly under the mount source
+- Lines starting with `#` are comments; blank lines are ignored
+
+:::info[Pattern matching]
+
+ToolHive uses simple gitignore-like patterns. Advanced gitignore glob syntax
+like `**/*.env` (to match files in any subdirectory) is not currently supported.
+Patterns only match files and directories directly under the mount source.
+
+:::
+
+## Run a server with .thvignore
+
+Mount your project directory as usual. ToolHive automatically reads `.thvignore`
+if present:
+
+```bash
+thv run --volume ./my-project:/projects filesystem
+```
+
+To print resolved overlay targets for debugging:
+
+```bash
+thv run --volume ./my-project:/projects \
+ --print-resolved-overlays \
+ filesystem
+```
+
+The resolved overlays are logged to the workload's log file. For a complete list
+of options, see the [`thv run` command reference](../reference/cli/thv_run.md).
+
+## Global ignore patterns
+
+You can define global ignore patterns in a platform-specific location:
+
+- **Linux**: `~/.config/toolhive/thvignore`
+- **macOS**: `~/Library/Application Support/toolhive/thvignore`
+- **Windows**: `%LOCALAPPDATA%\toolhive\thvignore`
+
+Patterns contained in the global configuration are loaded in addition to a local
+`.thvignore` file. To disable global patterns for a specific workload, use the
+`--ignore-globally=false` option:
+
+```bash
+thv run --ignore-globally=false --volume ./my-project:/projects filesystem
+```
+
+:::tip[Recommendation]
+
+Set machine-wide patterns (for example, `.aws/`, `.gcp/`, `.ssh/`, `*.pem`,
+`.docker/config.json`) in the global file, and keep app-specific patterns (for
+example, `.env*`, build artifacts) in your project's local `.thvignore`.
+
+:::
+
+## Troubleshooting
+
+
+Overlays didn't apply
+
+- Ensure `.thvignore` exists in the mount source directory (not elsewhere)
+- Confirm patterns match actual names relative to the mount source
+- Run with `--print-resolved-overlays` and check the workload's log file path
+ displayed by `thv run`
+
+
+
+
+Can't list a parent directory
+
+- On SELinux systems, listing a parent directory may fail even though specific
+ files are accessible. Probe individual paths instead (for example, `stat` or
+ `cat`).
+
+
+
+## Related information
+
+- [File system access](./filesystem-access.mdx)
+- [Run MCP servers](./run-mcp-servers.mdx)
+- [Network isolation](./network-isolation.mdx)
+- [`thv run` command reference](../reference/cli/thv_run.md)
diff --git a/versioned_docs/version-1.0/toolhive/guides-cli/token-exchange.mdx b/versioned_docs/version-1.0/toolhive/guides-cli/token-exchange.mdx
new file mode 100644
index 00000000..72a86409
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-cli/token-exchange.mdx
@@ -0,0 +1,210 @@
+---
+title: Configure token exchange for backend authentication
+description:
+ How to set up token exchange so MCP servers can authenticate to backend
+ services using ToolHive.
+---
+
+This guide shows you how to configure token exchange, which allows MCP servers
+to authenticate to backend APIs using short-lived, properly scoped tokens
+instead of embedded secrets.
+
+For conceptual background on how token exchange works and its security benefits,
+see [Backend authentication](../concepts/backend-auth.mdx), which includes a
+[sequence diagram](../concepts/backend-auth.mdx#same-idp-with-token-exchange)
+illustrating the complete flow.
+
+## Prerequisites
+
+Before you begin, make sure you have:
+
+- ToolHive installed and working
+- Basic familiarity with OAuth, OIDC, and JWT concepts
+- An identity provider that supports
+ [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693) token exchange (such
+ as Okta, Auth0, or Keycloak)
+- A backend service configured to accept tokens from your identity provider
+- Familiarity with [Authentication and authorization](./auth.mdx) setup in
+ ToolHive
+
+From your identity provider, you'll need:
+
+- Audience value for the MCP server
+- Issuer URL
+- JWKS URL (for key verification)
+- Token exchange endpoint URL
+- Client credentials for the token exchange client
+
+## Configure your identity provider
+
+Token exchange requires your identity provider to issue tokens for the backend
+service when presented with a valid MCP server token. The exact configuration
+steps vary by provider, but generally include:
+
+### Register a token exchange client
+
+Create an OAuth application in your identity provider for ToolHive to use when
+performing token exchange:
+
+- Note the client ID and client secret
+- Grant the application permission to use the
+ `urn:ietf:params:oauth:grant-type:token-exchange` grant type
+
+Token exchange is an authenticated flow—ToolHive uses these credentials to prove
+its identity when requesting exchanged tokens from the identity provider.
+
+:::tip[Okta]
+
+Create an API Services application for ToolHive and enable the token exchange
+grant type in the application settings.
+
+:::
+
+### Define audience and scopes for the backend service
+
+Configure your identity provider to recognize the backend service:
+
+- Define the audience value that identifies your backend service (for example,
+ `backend-api`)
+- Specify the scopes the backend service accepts (for example, `api:read`,
+ `api:write`)
+
+:::tip[Okta]
+
+Create a custom authorization server for the backend service and define the
+scopes under **Security > API > Authorization Servers**.
+
+:::
+
+### Create an access policy
+
+Set up a policy that permits token exchange and controls what scopes are
+included in exchanged tokens:
+
+- Enable the token exchange grant type for the ToolHive client
+- Define which users or groups can obtain tokens for the backend service
+- Specify the scopes included in exchanged tokens
+
+:::tip[Okta]
+
+Add a trust relationship from the MCP authorization server to the backend
+authorization server, then create access policies on the backend server to
+permit token exchange.
+
+Consult the
+[Okta token exchange documentation](https://developer.okta.com/docs/guides/set-up-token-exchange/main/)
+for detailed steps.
+
+:::
+
+## MCP server requirements
+
+The MCP server that ToolHive fronts must accept a per-request authentication
+token. Specifically, the server should:
+
+- Read the access token from the `Authorization: Bearer` header (or a custom
+ header if you configure `--token-exchange-header-name`)
+- Use this token to authenticate to the backend service
+- Not rely on hardcoded secrets or environment variables for backend
+ authentication
+
+ToolHive injects the exchanged token into each request, so the MCP server
+receives a fresh, properly scoped token for every call.
+
+:::tip[Example]
+
+The [Apollo MCP server](https://github.com/apollographql/apollo-mcp-server)
+supports token passthrough via its config
+(`disable_auth_token_passthrough: false`).
+
+:::
+
+## Run an MCP server with token exchange
+
+Once your identity provider is configured, start your MCP server with token
+exchange enabled:
+
+```bash
+thv run \
+ --oidc-audience \
+ --oidc-issuer \
+ --oidc-jwks-url \
+ --token-exchange-url \
+ --token-exchange-client-id \
+ --token-exchange-client-secret \
+ --token-exchange-audience \
+ --token-exchange-scopes \
+
+```
+
+### Parameter reference
+
+| Parameter | Description |
+| -------------------------------- | ------------------------------------------------------------------------- |
+| `--oidc-*` | Standard OIDC parameters for validating incoming client tokens |
+| `--token-exchange-url` | Your identity provider's token exchange endpoint |
+| `--token-exchange-client-id` | Client ID for ToolHive to authenticate to the IdP |
+| `--token-exchange-client-secret` | Client secret for ToolHive (or use `--token-exchange-client-secret-file`) |
+| `--token-exchange-audience` | Target audience for the exchanged token (your backend service) |
+| `--token-exchange-scopes` | Scopes to request for the backend service |
+
+Optional parameters:
+
+- `--token-exchange-header-name`: Custom header name for injecting the exchanged
+ token (defaults to replacing the `Authorization` header)
+- `--token-exchange-subject-token-type`: Type of subject token to exchange; use
+ `id_token` for Google STS (defaults to `access_token`)
+
+## Verify the configuration
+
+To confirm token exchange is working:
+
+1. Connect to the MCP server with a client that supports authentication
+2. Make a tool call that requires backend access
+3. Check that the request succeeds and the backend receives a properly scoped
+ token
+
+You can also verify by examining your identity provider's logs for successful
+token exchange requests, or by checking audit logs on your backend service to
+confirm requests arrive with the correct user identity and scopes.
+
+## Example: Okta configuration
+
+This example shows a complete configuration for an MCP server that connects to a
+backend API, using Okta for token exchange:
+
+```bash
+thv run \
+ --oidc-audience mcp-server \
+ --oidc-issuer https://dev-123456.okta.com/oauth2/aus1234567890 \
+ --oidc-jwks-url https://dev-123456.okta.com/oauth2/aus1234567890/v1/keys \
+ --token-exchange-url https://dev-123456.okta.com/oauth2/aus9876543210/v1/token \
+ --token-exchange-client-id 0oa0987654321fedcba \
+ --token-exchange-client-secret-file /path/to/client-secret \
+ --token-exchange-audience backend-api \
+ --token-exchange-scopes "api:read,api:write" \
+
+```
+
+Key points in this example:
+
+- **Two authorization servers**: The `--oidc-issuer` URL (`aus1234567890`) is
+ the MCP authorization server that validates incoming client tokens. The
+ `--token-exchange-url` uses a different authorization server (`aus9876543210`)
+ that issues tokens for the backend API.
+- **Audience transformation**: Client tokens arrive with audience `mcp-server`.
+ ToolHive exchanges them for tokens with audience `backend-api`, which the
+ backend service expects.
+- **Scope transformation**: The original token has MCP-specific scopes (like
+ `mcp:tools:call`), while the exchanged token has backend-specific scopes
+ (`api:read`, `api:write`). The user's identity is preserved, but the
+ permissions are transformed for the target service.
+
+## Related information
+
+- [Backend authentication](../concepts/backend-auth.mdx) - conceptual overview
+ of token exchange and federation
+- [Authentication and authorization](./auth.mdx) - basic auth setup for MCP
+ servers
+- [CLI reference for `thv run`](../reference/cli/thv_run.md) - complete list of
+ flags
diff --git a/versioned_docs/version-1.0/toolhive/guides-k8s/auth-k8s.mdx b/versioned_docs/version-1.0/toolhive/guides-k8s/auth-k8s.mdx
new file mode 100644
index 00000000..3195b8fa
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-k8s/auth-k8s.mdx
@@ -0,0 +1,869 @@
+---
+title: Authentication and authorization
+description:
+ How to set up authentication and authorization for MCP servers in Kubernetes
+ using the ToolHive Operator.
+---
+
+import OidcPrerequisites from '../_partials/_oidc-prerequisites.mdx';
+import BasicCedarConfig from '../_partials/_basic-cedar-config.mdx';
+import AuthTroubleshooting from '../_partials/_auth-troubleshooting.mdx';
+
+This guide shows you how to secure your MCP servers in Kubernetes using
+authentication and authorization with the ToolHive Operator.
+
+:::info
+
+Authentication and authorization are emerging capabilities in the MCP ecosystem.
+The official MCP authorization specification is still evolving, and client
+support for these features is limited. ToolHive is leading the way in
+implementing these capabilities, but you may encounter some limitations with
+certain clients.
+
+:::
+
+## Prerequisites
+
+You'll need:
+
+- Kubernetes cluster with RBAC enabled
+- ToolHive Operator installed (see
+ [Deploy the ToolHive Operator](./deploy-operator.mdx))
+- `kubectl` access to your cluster
+
+## Choose your authentication approach
+
+There are four main ways to authenticate with MCP servers running in Kubernetes:
+
+### Approach 1: External identity provider authentication
+
+Use this when you want to authenticate users or external services using
+providers like Google, GitHub, Microsoft Entra ID, Okta, or Auth0.
+
+**Prerequisites for external IdP:**
+
+
+
+### Approach 2: Shared OIDC configuration with ConfigMap
+
+Use this when you want to share the same OIDC configuration across multiple
+MCPServers. This is ideal for managing multiple servers with the same external
+identity provider.
+
+**Prerequisites for shared OIDC:**
+
+- External identity provider configured (same as Approach 1)
+- Understanding of Kubernetes ConfigMaps
+
+### Approach 3: Kubernetes service-to-service authentication
+
+Use this when you have client applications running in the same Kubernetes
+cluster that need to call MCP servers. This approach uses Kubernetes service
+account tokens for authentication.
+
+**Prerequisites for service-to-service:**
+
+- Client applications running in Kubernetes pods
+- Understanding of Kubernetes service accounts and RBAC
+
+### Approach 4: Embedded authorization server authentication
+
+Use this when you want ToolHive to handle the full OAuth flow, including
+redirecting users to an upstream identity provider for authentication. This
+approach is ideal for MCP servers that accept `Authorization: Bearer` tokens.
+
+For conceptual background, see
+[Embedded authorization server](../concepts/auth-framework.mdx#embedded-authorization-server).
+
+**Prerequisites for embedded authorization server:**
+
+- An upstream identity provider that supports the OAuth 2.0 authorization code
+ flow (such as Okta, Microsoft Entra ID, Auth0, or any OIDC-compliant provider)
+- A registered OAuth application/client with your upstream provider
+- Client ID and client secret from your upstream provider
+
+## Set up external identity provider authentication
+
+**Step 1: Create an MCPServer with external OIDC**
+
+Create an `MCPServer` resource configured to accept tokens from your external
+identity provider. The ToolHive proxy will handle authentication before
+forwarding requests to the MCP server.
+
+```yaml title="mcp-server-external-auth.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: weather-server-external
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stackloklabs/weather-mcp/server
+ transport: sse
+ port: 8080
+ permissionProfile:
+ type: builtin
+ name: network
+ # Authentication configuration for external IdP
+ oidcConfig:
+ type: inline
+ inline:
+ issuer: 'https://your-oidc-issuer.com'
+ audience: 'your-audience'
+ clientId: 'your-client-id'
+ jwksUrl: 'https://your-oidc-issuer.com/path/to/jwks'
+ resources:
+ limits:
+ cpu: '100m'
+ memory: '128Mi'
+ requests:
+ cpu: '50m'
+ memory: '64Mi'
+```
+
+Replace the OIDC placeholders with your actual identity provider configuration.
+
+**Step 2: Apply the MCPServer resource**
+
+```bash
+kubectl apply -f mcp-server-external-auth.yaml
+```
+
+**Step 3: Test external authentication**
+
+Clients connecting to this MCP server must include a valid JWT token from your
+configured identity provider in their requests. The ToolHive proxy will validate
+the token before allowing access to the MCP server.
+
+:::note[Obtaining JWT tokens]
+
+How to obtain JWT tokens varies by identity provider and is outside the scope of
+this guide. Consult your identity provider's documentation for specific
+instructions on:
+
+- Interactive user authentication flows (OAuth 2.0 Authorization Code flow)
+- Service-to-service authentication (Client Credentials flow)
+- API token generation and management
+
+For Kubernetes service accounts, tokens are automatically mounted at
+`/var/run/secrets/kubernetes.io/serviceaccount/token` in pods.
+
+:::
+
+## Set up shared OIDC configuration with ConfigMap
+
+**Step 1: Create OIDC ConfigMap**
+
+Create a ConfigMap containing the OIDC configuration:
+
+```yaml title="shared-oidc-config.yaml"
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: shared-oidc-config
+ namespace: toolhive-system
+data:
+ issuer: 'https://auth.example.com'
+ audience: 'https://mcp.example.com'
+ clientId: 'shared-client-id'
+ jwksUrl: 'https://auth.example.com/.well-known/jwks.json'
+```
+
+```bash
+kubectl apply -f shared-oidc-config.yaml
+```
+
+**Step 2: Reference ConfigMap in MCPServer**
+
+Create MCPServer resources that reference the shared configuration:
+
+```yaml title="mcp-server-with-configmap-oidc.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: weather-server-shared-oidc
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stackloklabs/weather-mcp/server
+ transport: sse
+ port: 8080
+ permissionProfile:
+ type: builtin
+ name: network
+ # Reference shared OIDC configuration
+ oidcConfig:
+ type: configMap
+ configMap:
+ name: shared-oidc-config
+ resources:
+ limits:
+ cpu: '100m'
+ memory: '128Mi'
+ requests:
+ cpu: '50m'
+ memory: '64Mi'
+```
+
+```bash
+kubectl apply -f mcp-server-with-configmap-oidc.yaml
+```
+
+### Benefits of ConfigMap approach
+
+- **Centralized management**: Update OIDC settings in one place
+- **Consistency**: Ensure all MCPServers use identical authentication config
+- **GitOps friendly**: Manage configuration separately from MCPServer resources
+- **Multi-server deployments**: Deploy multiple servers with same auth easily
+
+## Set up Kubernetes service-to-service authentication
+
+This approach is ideal when you have client applications running in the same
+Kubernetes cluster that need to call MCP servers.
+
+**Step 1: Create service account for client application**
+
+Create a service account that your client application will use:
+
+```yaml title="client-service-account.yaml"
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: mcp-client
+ namespace: client-apps
+```
+
+```bash
+kubectl apply -f client-service-account.yaml
+```
+
+**Step 2: Create MCPServer for service-to-service auth**
+
+Create an `MCPServer` resource configured to accept Kubernetes service account
+tokens:
+
+```yaml title="mcp-server-k8s-auth.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: weather-server-k8s
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stackloklabs/weather-mcp/server
+ transport: sse
+ port: 8080
+ permissionProfile:
+ type: builtin
+ name: network
+ # Authentication configuration for Kubernetes service accounts
+ oidcConfig:
+ type: kubernetes
+ kubernetes:
+ serviceAccount: 'mcp-client'
+ namespace: 'client-apps'
+ audience: 'toolhive'
+ issuer: 'https://kubernetes.default.svc'
+ jwksUrl: 'https://kubernetes.default.svc/openid/v1/jwks'
+ resources:
+ limits:
+ cpu: '100m'
+ memory: '128Mi'
+ requests:
+ cpu: '50m'
+ memory: '64Mi'
+```
+
+This configuration only allows requests from pods using the `mcp-client` service
+account in the `client-apps` namespace.
+
+```bash
+kubectl apply -f mcp-server-k8s-auth.yaml
+```
+
+**Step 3: Deploy client application with service account**
+
+Deploy your client application using the service account:
+
+```yaml title="client-app.yaml"
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: mcp-client-app
+ namespace: client-apps
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: mcp-client-app
+ template:
+ metadata:
+ labels:
+ app: mcp-client-app
+ spec:
+ serviceAccountName: mcp-client
+ containers:
+ - name: client
+ image: your-client-app:latest
+ env:
+ - name: MCP_SERVER_URL
+ value: 'http://weather-server-k8s.toolhive-system.svc.cluster.local:8080'
+```
+
+```bash
+kubectl apply -f client-app.yaml
+```
+
+Your client application can now authenticate to the MCP server using its
+Kubernetes service account token, which is automatically mounted at
+`/var/run/secrets/kubernetes.io/serviceaccount/token`.
+
+## Set up embedded authorization server authentication
+
+The embedded authorization server runs an OAuth authorization server within the
+ToolHive proxy. It handles the full OAuth flow by redirecting users to your
+upstream identity provider for authentication, then issuing JWTs that the proxy
+validates on subsequent requests. This provides MCP servers with
+`Authorization: Bearer` tokens without requiring separate authorization server
+infrastructure.
+
+This setup uses the `MCPExternalAuthConfig` custom resource, following the same
+pattern as [token exchange configuration](./token-exchange-k8s.mdx).
+
+**Step 1: Create a Secret for the upstream provider client credentials**
+
+Store the OAuth client secret for your upstream identity provider:
+
+```yaml title="upstream-idp-secret.yaml"
+apiVersion: v1
+kind: Secret
+metadata:
+ name: upstream-idp-secret
+ namespace: toolhive-system
+type: Opaque
+stringData:
+ client-secret: ''
+```
+
+```bash
+kubectl apply -f upstream-idp-secret.yaml
+```
+
+**Step 2: Create a Secret for JWT signing keys**
+
+The embedded authorization server signs JWTs with a private key you provide.
+Generate a PEM-encoded private key (RSA or EC), for example:
+
+```bash
+openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out signing-key.pem
+```
+
+Then create a Secret containing the key:
+
+```yaml title="auth-server-signing-key.yaml"
+apiVersion: v1
+kind: Secret
+metadata:
+ name: auth-server-signing-key
+ namespace: toolhive-system
+type: Opaque
+stringData:
+ signing-key: |
+ -----BEGIN PRIVATE KEY-----
+
+ -----END PRIVATE KEY-----
+```
+
+```bash
+kubectl apply -f auth-server-signing-key.yaml
+```
+
+:::tip[Key rotation]
+
+For key rotation, you can reference multiple signing key Secrets in the
+`signingKeySecretRefs` list. The first key is used for signing new tokens.
+Additional keys are used for verification only, so tokens signed before rotation
+remain valid.
+
+:::
+
+**Step 3: Create a Secret for HMAC keys**
+
+The embedded authorization server uses a symmetric HMAC key to sign
+authorization codes and refresh tokens. The key must be at least 32 bytes and
+cryptographically random, for example:
+
+```bash
+openssl rand -base64 32
+```
+
+```yaml title="auth-server-hmac-secret.yaml"
+apiVersion: v1
+kind: Secret
+metadata:
+ name: auth-server-hmac-secret
+ namespace: toolhive-system
+type: Opaque
+stringData:
+ hmac-key: ''
+```
+
+```bash
+kubectl apply -f auth-server-hmac-secret.yaml
+```
+
+:::warning[Ephemeral keys for development only]
+
+If you omit the `signingKeySecretRefs` and `hmacSecretRefs` fields, ToolHive
+generates ephemeral keys that are lost on pod restart. All previously issued
+tokens become invalid after a restart. Only omit these Secrets for development
+and testing.
+
+:::
+
+**Step 4: Create the MCPExternalAuthConfig resource**
+
+Create an `MCPExternalAuthConfig` resource with the `embeddedAuthServer` type.
+This example configures an OIDC upstream provider (the most common case):
+
+```yaml title="embedded-auth-config.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPExternalAuthConfig
+metadata:
+ name: embedded-auth-server
+ namespace: toolhive-system
+spec:
+ type: embeddedAuthServer
+ embeddedAuthServer:
+ issuer: 'https://mcp.example.com'
+ signingKeySecretRefs:
+ - name: auth-server-signing-key
+ key: signing-key
+ hmacSecretRefs:
+ - name: auth-server-hmac-secret
+ key: hmac-key
+ tokenLifespans:
+ accessTokenLifespan: '1h'
+ refreshTokenLifespan: '168h'
+ authCodeLifespan: '10m'
+ upstreamProviders:
+ - name: google
+ type: oidc
+ oidcConfig:
+ issuerUrl: 'https://accounts.google.com'
+ clientId: ''
+ clientSecretRef:
+ name: upstream-idp-secret
+ key: client-secret
+ scopes:
+ - openid
+ - profile
+ - email
+```
+
+```bash
+kubectl apply -f embedded-auth-config.yaml
+```
+
+**Configuration reference:**
+
+| Field | Description |
+| ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
+| `issuer` | HTTPS URL identifying this authorization server. Appears in the `iss` claim of issued JWTs. |
+| `signingKeySecretRefs` | References to Secrets containing JWT signing keys. First key is active; additional keys support rotation. |
+| `hmacSecretRefs` | References to Secrets with symmetric keys for signing authorization codes and refresh tokens. |
+| `tokenLifespans` | Configurable durations for access tokens (default: 1h), refresh tokens (default: 168h), and auth codes (default: 10m). |
+| `upstreamProviders` | Configuration for the upstream identity provider. Currently supports one provider. |
+
+**Step 5: Create the MCPServer resource**
+
+The MCPServer needs two configuration references: `externalAuthConfigRef`
+enables the embedded authorization server, and `oidcConfig` validates the JWTs
+that the embedded authorization server issues. Unlike approaches 1–3 where
+`oidcConfig` points to an external identity provider, here it points to the
+embedded authorization server itself—the `oidcConfig` issuer must match the
+`issuer` in your `MCPExternalAuthConfig`.
+
+```yaml title="mcp-server-embedded-auth.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: weather-server-embedded
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stackloklabs/weather-mcp/server
+ transport: streamable-http
+ proxyPort: 8080
+ permissionProfile:
+ type: builtin
+ name: network
+ # Reference the embedded authorization server configuration
+ externalAuthConfigRef:
+ name: embedded-auth-server
+ # Validate JWTs issued by the embedded authorization server
+ oidcConfig:
+ type: inline
+ resourceUrl: 'https://mcp.example.com/mcp'
+ inline:
+ # This must match the embedded authorization server issuer url
+ issuer: 'https://mcp.example.com'
+ resources:
+ limits:
+ cpu: '100m'
+ memory: '128Mi'
+ requests:
+ cpu: '50m'
+ memory: '64Mi'
+```
+
+```bash
+kubectl apply -f mcp-server-embedded-auth.yaml
+```
+
+:::note
+
+The `oidcConfig` issuer must match the `issuer` in your `MCPExternalAuthConfig`.
+The embedded authorization server exposes a JWKS endpoint that the proxy uses to
+validate the JWTs it issues. The proxy also exposes OAuth discovery endpoints
+(`/.well-known/oauth-authorization-server`) so MCP clients can discover the
+authorization endpoints automatically.
+
+:::
+
+### Configure session storage
+
+By default, the embedded authorization server stores sessions in memory.
+Upstream tokens are lost when pods restart, requiring users to re-authenticate.
+For production deployments, configure Redis Sentinel as the storage backend by
+adding a `storage` block to your `MCPExternalAuthConfig`:
+
+```yaml title="storage block for MCPExternalAuthConfig"
+storage:
+ type: redis
+ redis:
+ sentinelConfig:
+ masterName: mymaster
+ sentinelService:
+ name: redis-sentinel
+ namespace: redis
+ aclUserConfig:
+ usernameSecretRef:
+ name: redis-acl-secret
+ key: username
+ passwordSecretRef:
+ name: redis-acl-secret
+ key: password
+```
+
+Create the Secret containing your Redis ACL credentials:
+
+```bash
+kubectl create secret generic redis-acl-secret \
+ --namespace toolhive-system \
+ --from-literal=username=toolhive-auth \
+ --from-literal=password="YOUR_REDIS_ACL_PASSWORD"
+```
+
+For a complete walkthrough including deploying Redis Sentinel from scratch, see
+[Redis Sentinel session storage](./redis-session-storage.mdx).
+
+### Using an OAuth 2.0 upstream provider
+
+If your upstream identity provider does not support OIDC discovery, you can
+configure it as an OAuth 2.0 provider with explicit endpoints. This is useful
+for providers like GitHub that use OAuth 2.0 but don't implement the full OIDC
+specification.
+
+```yaml title="embedded-auth-oauth2-config.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPExternalAuthConfig
+metadata:
+ name: embedded-auth-oauth2
+ namespace: toolhive-system
+spec:
+ type: embeddedAuthServer
+ embeddedAuthServer:
+ issuer: 'https://mcp.example.com'
+ signingKeySecretRefs:
+ - name: auth-server-signing-key
+ key: signing-key
+ hmacSecretRefs:
+ - name: auth-server-hmac-secret
+ key: hmac-key
+ upstreamProviders:
+ - name: github
+ type: oauth2
+ oauth2Config:
+ authorizationEndpoint: 'https://github.com/login/oauth/authorize'
+ tokenEndpoint: 'https://github.com/login/oauth/access_token'
+ userInfo:
+ endpointUrl: 'https://api.github.com/user'
+ httpMethod: GET
+ additionalHeaders:
+ Accept: 'application/vnd.github+json'
+ fieldMapping:
+ subjectFields:
+ - id
+ - login
+ nameFields:
+ - name
+ - login
+ emailFields:
+ - email
+ clientId: ''
+ clientSecretRef:
+ name: upstream-idp-secret
+ key: client-secret
+ scopes:
+ - user:email
+ - read:user
+```
+
+:::note
+
+OAuth 2.0 providers require explicit endpoint configuration and a `userInfo`
+section, unlike OIDC providers which auto-discover these from the issuer URL.
+The `fieldMapping` section maps provider-specific response fields to standard
+user identity fields. For example, GitHub returns `login` instead of the
+standard `name` field.
+
+:::
+
+## Set up authorization
+
+All authentication approaches can use the same authorization configuration using
+Cedar policies.
+
+**Step 1: Create authorization configuration**
+
+
+
+**Step 2: Create a ConfigMap with policies**
+
+Store your authorization configuration in a ConfigMap:
+
+```yaml title="authz-configmap.yaml"
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: authz-config
+ namespace: toolhive-system
+data:
+ authz-config.json: |
+ {
+ "version": "1.0",
+ "type": "cedarv1",
+ "cedar": {
+ "policies": [
+ "permit(principal, action == Action::\"call_tool\", resource == Tool::\"weather\");",
+ "permit(principal == Client::\"alice123\", action == Action::\"call_tool\", resource == Tool::\"admin_tool\");",
+ "permit(principal, action == Action::\"call_tool\", resource) when { principal.claim_roles.contains(\"premium\") };"
+ ],
+ "entities_json": "[]"
+ }
+ }
+```
+
+```bash
+kubectl apply -f authz-configmap.yaml
+```
+
+**Step 3: Update MCPServer to use authorization**
+
+Add the authorization configuration to your `MCPServer` resources:
+
+```yaml title="mcp-server-with-authz.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: weather-server-with-authz
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stackloklabs/weather-mcp/server
+ transport: sse
+ port: 8080
+ permissionProfile:
+ type: builtin
+ name: network
+ # Authentication configuration
+ oidcConfig:
+ type: kubernetes
+ kubernetes:
+ serviceAccount: 'mcp-client'
+ namespace: 'client-apps'
+ audience: 'toolhive'
+ issuer: 'https://kubernetes.default.svc'
+ jwksUrl: 'https://kubernetes.default.svc/openid/v1/jwks'
+ # Authorization configuration
+ authzConfig:
+ type: configMap
+ configMap:
+ name: authz-config
+ key: authz-config.json
+ resources:
+ limits:
+ cpu: '100m'
+ memory: '128Mi'
+ requests:
+ cpu: '50m'
+ memory: '64Mi'
+```
+
+```bash
+kubectl apply -f mcp-server-with-authz.yaml
+```
+
+## Test your setup
+
+### Test external IdP authentication
+
+1. Deploy the external IdP configuration
+2. Obtain a valid JWT token from your identity provider
+3. Make a request to the MCP server including the token
+
+### Test service-to-service authentication
+
+1. Deploy both the MCP server and client application
+2. Check that the client can successfully call the MCP server
+3. Verify authentication in the ToolHive proxy logs:
+
+```bash
+kubectl logs -n toolhive-system -l app.kubernetes.io/name=weather-server-k8s
+```
+
+### Test embedded authorization server authentication
+
+1. Deploy the `MCPExternalAuthConfig` and `MCPServer` resources
+2. Check that the MCPServer is running:
+
+ ```bash
+ kubectl get mcpserver -n toolhive-system weather-server-embedded
+ ```
+
+3. If the server is exposed outside the cluster, verify the OAuth discovery
+ endpoint is available:
+
+ ```bash
+ curl https:///.well-known/oauth-authorization-server
+ ```
+
+4. Connect with an MCP client that supports the MCP OAuth specification. The
+ client should be redirected to your upstream identity provider for
+ authentication.
+5. Check the proxy logs for successful authentication:
+
+ ```bash
+ kubectl logs -n toolhive-system \
+ -l app.kubernetes.io/name=weather-server-embedded
+ ```
+
+### Test authorization
+
+1. Make requests that should be permitted by your policies
+2. Make requests that should be denied
+3. Check the proxy logs to see authorization decisions
+
+## Related information
+
+- For conceptual understanding, see
+ [Authentication and authorization framework](../concepts/auth-framework.mdx)
+- For conceptual background on the embedded authorization server, see
+ [Embedded authorization server](../concepts/auth-framework.mdx#embedded-authorization-server)
+- For a similar configuration pattern using token exchange, see
+ [Configure token exchange](./token-exchange-k8s.mdx)
+- For detailed Cedar policy syntax, see
+ [Cedar policies](../concepts/cedar-policies.mdx) and the
+ [Cedar documentation](https://docs.cedarpolicy.com/)
+- For running MCP servers without authentication, see
+ [Run MCP servers in Kubernetes](./run-mcp-k8s.mdx)
+- For ToolHive Operator installation, see
+ [Deploy the ToolHive Operator](./deploy-operator.mdx)
+
+## Troubleshooting
+
+
+
+
+Kubernetes-specific issues
+
+**MCPServer resource issues:**
+
+- Check the MCPServer status: `kubectl get mcpserver -n toolhive-system`
+- Describe the resource for details:
+ `kubectl describe mcpserver weather-server-k8s -n toolhive-system`
+
+**Service account issues:**
+
+- Verify the service account exists: `kubectl get sa -n client-apps mcp-client`
+- Check RBAC permissions if needed
+
+**ConfigMap mounting issues:**
+
+- Verify the ConfigMap exists:
+ `kubectl get configmap -n toolhive-system authz-config`
+- Check the ConfigMap content:
+ `kubectl get configmap authz-config -n toolhive-system -o yaml`
+
+**OIDC configuration issues:**
+
+- For external IdP: Ensure the issuer URL is accessible from within the cluster
+- For Kubernetes auth: Ensure the Kubernetes API server has OIDC enabled
+- Check that the JWKS URL returns valid keys
+
+**Network connectivity:**
+
+- Verify pods can reach the Kubernetes API server
+- Check cluster DNS resolution
+- Test service-to-service connectivity:
+ `kubectl exec -n client-apps deployment/mcp-client-app -- curl http://weather-server-k8s.toolhive-system.svc.cluster.local:8080`
+
+**ToolHive Operator issues:**
+
+- Check operator logs:
+ `kubectl logs -n toolhive-system -l app.kubernetes.io/name=toolhive-operator`
+- Verify the operator is running: `kubectl get pods -n toolhive-system`
+
+
+
+
+Embedded authorization server issues
+
+**OAuth flow not initiating:**
+
+- Verify the `MCPExternalAuthConfig` resource exists in the same namespace:
+ `kubectl get mcpexternalauthconfig -n toolhive-system`
+- Check that the `externalAuthConfigRef.name` in your `MCPServer` matches the
+ `MCPExternalAuthConfig` resource name
+- Verify the upstream provider's client ID and redirect URI are correctly
+ configured in the `MCPExternalAuthConfig`
+
+**Token validation failures after restart:**
+
+- Ensure you have configured `signingKeySecretRefs` and `hmacSecretRefs` with
+ persistent keys
+- Without these, ephemeral keys are generated on startup, invalidating all
+ previously issued tokens
+
+**Upstream IdP redirect errors:**
+
+- Verify the redirect URI configured in your upstream provider matches the
+ ToolHive proxy's callback URL (typically
+ `https:///oauth/callback`)
+- Check that the upstream provider's issuer URL is accessible from within the
+ cluster
+- For OIDC providers, ensure the `/.well-known/openid-configuration` endpoint is
+ reachable from the proxy pod
+
+**JWT signing key issues:**
+
+- Verify signing key Secrets exist:
+ `kubectl get secret -n toolhive-system auth-server-signing-key`
+- Ensure the key format is correct (PEM-encoded RSA or EC private key)
+- Check proxy logs for key loading errors:
+ `kubectl logs -n toolhive-system -l app.kubernetes.io/name=weather-server-embedded`
+
+**OIDC configuration mismatch:**
+
+- Ensure the `oidcConfig.inline.issuer` on your `MCPServer` matches the `issuer`
+ in your `MCPExternalAuthConfig`
+- Verify the `resourceUrl` in `oidcConfig` matches the external URL of the MCP
+ server
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-k8s/connect-clients.mdx b/versioned_docs/version-1.0/toolhive/guides-k8s/connect-clients.mdx
new file mode 100644
index 00000000..9ba6ed51
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-k8s/connect-clients.mdx
@@ -0,0 +1,1102 @@
+---
+title: Connect clients to MCP servers
+description:
+ Learn how to connect clients to your Kubernetes-hosted MCP servers in
+ ToolHive.
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+## Overview
+
+After deploying MCP servers in your Kubernetes cluster, you need to connect
+clients to use them. This guide covers two main connection scenarios:
+
+1. **External clients** - Connecting from outside the cluster using Ingress or
+ Gateway API to expose MCP servers
+2. **Internal clients** - Connecting from applications running within the same
+ Kubernetes cluster
+
+```mermaid
+flowchart TB
+ subgraph External["External clients"]
+ UI["ToolHive UI"]
+ CLI["ToolHive CLI"]
+ Other["Other MCP clients"]
+ end
+
+ subgraph K8s["Kubernetes cluster"]
+ Ingress["Ingress/Gateway"]
+
+ subgraph NS["MCP Namespace"]
+ ProxyService["Service: MCP proxy"]
+ ProxyPod["Pod: ToolHive proxy"]
+ MCPPod["Pod: MCP server"]
+ end
+
+ subgraph AppNS["App namespace"]
+ App["Your application"]
+ end
+ end
+
+ UI -->|HTTPS| Ingress
+ CLI -->|HTTPS| Ingress
+ Other -->|HTTPS| Ingress
+ Ingress --> ProxyService
+ ProxyService --> ProxyPod
+ ProxyPod --> MCPPod
+
+ App -->|HTTP| ProxyService
+```
+
+## Prerequisites
+
+- A Kubernetes cluster with MCP servers deployed (see
+ [Run MCP servers in Kubernetes](./run-mcp-k8s.mdx))
+- An Ingress controller or Gateway API implementation installed in your cluster
+ (for external access)
+- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) configured to communicate
+ with your cluster
+
+## Connect from outside the cluster
+
+To make your MCP servers accessible to external clients like the ToolHive UI,
+ToolHive CLI, or other MCP clients, you need to expose the proxy service using
+an Ingress resource or Gateway API.
+
+:::info[Service naming convention]
+
+The ToolHive operator automatically creates a Kubernetes Service for each
+MCPServer, MCPRemoteProxy, and VirtualMCPServer resource using the following
+naming patterns using the resource name:
+
+- MCPServer: `mcp--proxy`
+- MCPRemoteProxy: `mcp--remote-proxy`
+- VirtualMCPServer: `vmcp-`
+
+For example, an MCPServer named `fetch` gets a Service named `mcp-fetch-proxy`.
+
+:::
+
+:::warning[Security requirements]
+
+When exposing MCP servers externally, you should:
+
+- Always use HTTPS with valid TLS certificates
+- Configure authentication to control access (see
+ [Authentication and authorization](./auth-k8s.mdx))
+- Consider network policies to restrict traffic
+
+Running MCP servers without authentication on public networks is a security
+risk.
+
+:::
+
+### Option 1: Using Ingress
+
+Ingress provides a stable API for exposing HTTP/HTTPS services. This example
+shows a generic Ingress configuration that works with popular Ingress
+controllers like Traefik, Contour, and HAProxy, as well as cloud provider
+implementations like AWS Load Balancer Controller, Google Cloud Load Balancer,
+and Azure Application Gateway.
+
+First, ensure you have an MCP server deployed. This example uses the `fetch`
+server:
+
+```yaml title="fetch-server.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: fetch
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stackloklabs/gofetch/server:latest
+ transport: streamable-http
+ mcpPort: 8080
+ proxyPort: 8080
+```
+
+Create an Ingress resource to expose the MCP server proxy. You can use either
+host-based routing (separate subdomain per server) or path-based routing (single
+domain with paths):
+
+
+
+
+Each MCP server gets its own subdomain:
+
+```yaml title="fetch-ingress.yaml"
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: fetch-mcp-ingress
+ namespace: toolhive-system
+ annotations:
+ cert-manager.io/cluster-issuer: 'letsencrypt-prod'
+spec:
+ ingressClassName: traefik
+ tls:
+ - hosts:
+ - fetch-mcp.example.com
+ secretName: fetch-mcp-tls
+ rules:
+ - host: fetch-mcp.example.com
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: mcp-fetch-proxy
+ port:
+ number: 8080
+```
+
+The MCP server is accessible at `https://fetch-mcp.example.com/mcp`.
+
+
+
+
+Multiple MCP servers share a single domain using path prefixes. This approach
+requires URL rewriting to strip the path prefix before forwarding to the backend
+service.
+
+:::note
+
+Path rewriting support and syntax varies by Ingress controller. Check your
+controller's documentation for the correct annotations or resources.
+
+:::
+
+```yaml title="mcp-ingress.yaml"
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: mcp-servers-ingress
+ namespace: toolhive-system
+ annotations:
+ cert-manager.io/cluster-issuer: 'letsencrypt-prod'
+ # Traefik example: strip path prefix
+ traefik.ingress.kubernetes.io/router.middlewares: toolhive-system-strip-mcp-prefix@kubernetescrd
+spec:
+ ingressClassName: traefik
+ tls:
+ - hosts:
+ - mcp.example.com
+ secretName: mcp-tls
+ rules:
+ - host: mcp.example.com
+ http:
+ paths:
+ # Fetch MCP server
+ - path: /fetch
+ pathType: Prefix
+ backend:
+ service:
+ name: mcp-fetch-proxy
+ port:
+ number: 8080
+ # Another MCP server
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: mcp--proxy
+ port:
+ number: 8080
+---
+# Traefik Middleware to strip path prefixes
+apiVersion: traefik.io/v1alpha1
+kind: Middleware
+metadata:
+ name: strip-mcp-prefix
+ namespace: toolhive-system
+spec:
+ stripPrefix:
+ prefixes:
+ - /fetch
+ - /
+```
+
+The MCP servers are accessible at `https://mcp.example.com/fetch/mcp` and
+`https://mcp.example.com//mcp`.
+
+
+
+
+This example is the same as the previous path-based routing example but includes
+additional rules in the Ingress to direct the `.well-known` path for each MCP
+server to the corresponding backend. This is necessary when using OAuth
+authentication since the OAuth flow requires access to the `.well-known`
+endpoint for discovery.
+
+First, in the MCPServer spec for each server, ensure the `resourceUrl` property
+is set to the full client-facing URL:
+
+```yaml title="fetch-server-oauth.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+# ...
+spec:
+ oidcConfig:
+ type: inline
+ resourceUrl: https://mcp.example.com//mcp
+ inline:
+ # ... other OIDC config ...
+```
+
+The `inline.audience` value should match the audience expected by your identity
+provider, and is likely the same for all servers using the same authorization
+server. See [Authentication and authorization](./auth-k8s.mdx) for full OIDC
+setup instructions.
+
+Configure the Ingress with additional rules for the `.well-known` paths of each
+MCP server that has OAuth enabled:
+
+:::note
+
+Path rewriting support and syntax varies by Ingress controller. Check your
+controller's documentation for the correct annotations or resources.
+
+:::
+
+```yaml {28-34,43-49} title="mcp-ingress.yaml"
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: mcp-servers-ingress
+ namespace: toolhive-system
+ annotations:
+ cert-manager.io/cluster-issuer: 'letsencrypt-prod'
+ # Traefik example: strip path prefix
+ traefik.ingress.kubernetes.io/router.middlewares: toolhive-system-strip-mcp-prefix@kubernetescrd
+spec:
+ ingressClassName: traefik
+ tls:
+ - hosts:
+ - mcp.example.com
+ secretName: mcp-tls
+ rules:
+ - host: mcp.example.com
+ http:
+ paths:
+ # Fetch MCP server
+ - path: /fetch
+ pathType: Prefix
+ backend:
+ service:
+ name: mcp-fetch-proxy
+ port:
+ number: 8080
+ - path: /.well-known/oauth-protected-resource/fetch/mcp
+ pathType: Exact
+ backend:
+ service:
+ name: mcp-fetch-proxy
+ port:
+ number: 8080
+ # Another MCP server
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: mcp--proxy
+ port:
+ number: 8080
+ - path: /.well-known/oauth-protected-resource//mcp
+ pathType: Exact
+ backend:
+ service:
+ name: mcp--proxy
+ port:
+ number: 8080
+---
+# Traefik Middleware to strip path prefixes
+apiVersion: traefik.io/v1alpha1
+kind: Middleware
+metadata:
+ name: strip-mcp-prefix
+ namespace: toolhive-system
+spec:
+ stripPrefix:
+ prefixes:
+ - /fetch
+ - /
+```
+
+The MCP servers are accessible at `https://mcp.example.com/fetch/mcp` and
+`https://mcp.example.com//mcp`.
+
+
+
+
+Apply the resources:
+
+```bash
+kubectl apply -f fetch-server.yaml
+kubectl apply -f fetch-ingress.yaml # or mcp-ingress.yaml for path-based
+```
+
+Verify the Ingress is configured:
+
+```bash
+kubectl get ingress -n toolhive-system
+```
+
+### Option 2: Using Gateway API
+
+The [Gateway API](https://gateway-api.sigs.k8s.io/) is a more expressive way to
+expose services and is the successor to Ingress. This example works with Gateway
+API implementations like Cilium, Istio, Envoy Gateway, and Traefik, as well as
+cloud provider implementations like AWS Gateway API Controller, Google
+Kubernetes Engine (GKE) Gateway controller, and Azure Application Gateway for
+Containers. See the
+[full list of implementations](https://gateway-api.sigs.k8s.io/implementations/).
+
+:::tip
+
+For a complete working example using the ngrok Gateway API implementation, see
+the
+[Configure secure ingress for MCP servers on Kubernetes](../integrations/ingress-ngrok.mdx)
+tutorial.
+
+:::
+
+Many Gateway API implementations create a Gateway resource automatically during
+installation. For example, Traefik's Helm chart creates a `traefik-gateway` in
+the default namespace when enabled. Check if a Gateway already exists:
+
+```bash
+kubectl get gateway --all-namespaces
+```
+
+If a Gateway exists, note its name and namespace to use in your HTTPRoute. If
+you need to create a new Gateway, use this example:
+
+```yaml title="mcp-gateway.yaml"
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+ name: mcp-gateway
+ namespace: toolhive-system
+spec:
+ gatewayClassName: traefik # Change to match your Gateway implementation
+ listeners:
+ - name: https
+ protocol: HTTPS
+ port: 443
+ tls:
+ mode: Terminate
+ certificateRefs:
+ - name: mcp-gateway-cert
+ allowedRoutes:
+ namespaces:
+ from: Same
+```
+
+Create an HTTPRoute to expose your MCP server. You can use either host-based
+routing (separate subdomain per server) or path-based routing (single domain
+with paths):
+
+
+
+
+Each MCP server gets its own subdomain:
+
+```yaml title="fetch-route.yaml"
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ name: fetch-mcp-route
+ namespace: toolhive-system
+spec:
+ parentRefs:
+ - name: mcp-gateway # Reference your Gateway name (e.g., traefik-gateway)
+ # namespace: default # Uncomment if Gateway is in a different namespace
+ hostnames:
+ - fetch-mcp.example.com # Change to your domain
+ rules:
+ - backendRefs:
+ - name: mcp-fetch-proxy # Format: mcp--proxy
+ port: 8080 # This matches the proxyPort
+```
+
+The MCP server is accessible at `https://fetch-mcp.example.com/mcp`.
+
+
+
+
+Multiple MCP servers share a single domain using path prefixes. This approach
+uses URL rewriting to strip the path prefix before forwarding to the backend
+service.
+
+```yaml title="mcp-routes.yaml"
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ name: mcp-servers-route
+ namespace: toolhive-system
+spec:
+ parentRefs:
+ - name: mcp-gateway # Reference your Gateway name (e.g., traefik-gateway)
+ # namespace: default # Uncomment if Gateway is in a different namespace
+ hostnames:
+ - mcp.example.com # Change to your domain
+ rules:
+ # Fetch MCP server
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /fetch
+ filters:
+ - type: URLRewrite
+ urlRewrite:
+ path:
+ type: ReplacePrefixMatch
+ replacePrefixMatch: /
+ backendRefs:
+ - name: mcp-fetch-proxy # Format: mcp--proxy
+ port: 8080 # This matches the proxyPort
+ # Another MCP server
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /
+ filters:
+ - type: URLRewrite
+ urlRewrite:
+ path:
+ type: ReplacePrefixMatch
+ replacePrefixMatch: /
+ backendRefs:
+ - name: mcp--proxy
+ port: 8080
+```
+
+The MCP servers are accessible at `https://mcp.example.com/fetch/mcp` and
+`https://mcp.example.com//mcp`.
+
+The `URLRewrite` filter removes the path prefix (e.g., `/fetch`) before
+forwarding requests to the backend service, so the MCP server receives requests
+at `/mcp` as expected.
+
+
+
+
+This example is the same as the previous path-based routing example but includes
+additional rules in the HTTPRoute to direct the `.well-known` path for each MCP
+server to the corresponding backend. This is necessary when using OAuth
+authentication since the OAuth flow requires access to the `.well-known`
+endpoint for discovery.
+
+First, in the MCPServer spec for each server, ensure the `resourceUrl` property
+is set to the full client-facing URL:
+
+```yaml title="fetch-server-oauth.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+# ...
+spec:
+ oidcConfig:
+ type: inline
+ resourceUrl: https://mcp.example.com//mcp
+ inline:
+ # ... other OIDC config ...
+```
+
+The `inline.audience` value should match the audience expected by your identity
+provider, and is likely the same for all servers using the same authorization
+server. See [Authentication and authorization](./auth-k8s.mdx) for full OIDC
+setup instructions.
+
+Configure the HTTPRoute with additional rules for the `.well-known` paths of
+each MCP server that has OAuth enabled:
+
+```yaml {27-33,48-54} title="mcp-routes.yaml"
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ name: mcp-servers-route
+ namespace: toolhive-system
+spec:
+ parentRefs:
+ - name: mcp-gateway # Reference your Gateway name (e.g., traefik-gateway)
+ # namespace: default # Uncomment if Gateway is in a different namespace
+ hostnames:
+ - mcp.example.com # Change to your domain
+ rules:
+ # Fetch MCP server
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /fetch
+ filters:
+ - type: URLRewrite
+ urlRewrite:
+ path:
+ type: ReplacePrefixMatch
+ replacePrefixMatch: /
+ backendRefs:
+ - name: mcp-fetch-proxy # Format: mcp--proxy
+ port: 8080 # This matches the proxyPort
+ - matches:
+ - path:
+ type: Exact
+ value: /.well-known/oauth-protected-resource/fetch/mcp
+ backendRefs:
+ - name: mcp-fetch-proxy
+ port: 8080
+ # Another MCP server
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /
+ filters:
+ - type: URLRewrite
+ urlRewrite:
+ path:
+ type: ReplacePrefixMatch
+ replacePrefixMatch: /
+ backendRefs:
+ - name: mcp--proxy
+ port: 8080
+ - matches:
+ - path:
+ type: Exact
+ value: /.well-known/oauth-protected-resource//mcp
+ backendRefs:
+ - name: mcp--proxy
+ port: 8080
+```
+
+The MCP servers are accessible at `https://mcp.example.com/fetch/mcp` and
+`https://mcp.example.com//mcp`.
+
+The `URLRewrite` filter removes the path prefix (e.g., `/fetch`) before
+forwarding requests to the backend service, so the MCP server receives requests
+at `/mcp` as expected.
+
+
+
+
+Apply the resources:
+
+```bash
+kubectl apply -f mcp-gateway.yaml # If creating a new Gateway
+kubectl apply -f fetch-route.yaml # or mcp-routes.yaml for path-based
+```
+
+Verify the route is configured:
+
+```bash
+kubectl get httproute -n toolhive-system
+```
+
+### TLS certificates
+
+For production deployments, use valid TLS certificates from a trusted
+certificate authority. The most common approaches are:
+
+
+
+
+[cert-manager](https://cert-manager.io/) automates certificate management in
+Kubernetes. Install cert-manager and create a ClusterIssuer:
+
+```yaml title="letsencrypt-issuer.yaml"
+apiVersion: cert-manager.io/v1
+kind: ClusterIssuer
+metadata:
+ name: letsencrypt-prod
+spec:
+ acme:
+ server: https://acme-v02.api.letsencrypt.org/directory
+ email: your-email@example.com # Change to your email
+ privateKeySecretRef:
+ name: letsencrypt-prod
+ solvers:
+ - http01:
+ ingress:
+ class: traefik # Change to match your Ingress controller
+```
+
+Apply it:
+
+```bash
+kubectl apply -f letsencrypt-issuer.yaml
+```
+
+The Ingress example above already includes the cert-manager annotation. Once
+cert-manager is installed, it will automatically provision and renew
+certificates.
+
+
+
+
+If you have existing certificates, create a Kubernetes Secret:
+
+```bash
+kubectl create secret tls fetch-mcp-tls \
+ --cert=path/to/tls.crt \
+ --key=path/to/tls.key \
+ -n toolhive-system
+```
+
+Reference this secret in your Ingress or Gateway configuration as shown in the
+examples above.
+
+
+
+
+### Connect with ToolHive UI or CLI
+
+Once your MCP server is exposed with HTTPS, you can connect to it as a remote
+MCP server from the ToolHive UI or CLI.
+
+
+
+
+In the ToolHive UI:
+
+1. Click **Add an MCP server** on the **MCP Servers** page
+2. Select **Add a remote MCP server**
+3. Enter the connection details:
+ - **Name**: A friendly name for the server
+ - **Server URL**: Use the appropriate URL based on your routing approach:
+ - Host-based: `https://fetch-mcp.example.com/mcp`
+ - Path-based: `https://mcp.example.com/fetch/mcp`
+ - **Transport**: Streamable HTTP (or SSE if your server uses SSE)
+4. If authentication is configured, select the method and enter the required
+ OAuth or OIDC details
+5. Click **Install server**
+
+The MCP server appears in your server list and you can use it with any connected
+MCP client.
+
+
+
+
+Use the `thv run` command to connect:
+
+```bash
+# Host-based routing: separate subdomain per server
+thv run --name fetch-k8s https://fetch-mcp.example.com/mcp
+
+# Path-based routing: single domain with paths
+thv run --name fetch-k8s https://mcp.example.com/fetch/mcp
+```
+
+If authentication is configured, add the appropriate flags. See the
+[ToolHive CLI guide](../guides-cli/run-mcp-servers.mdx#authentication-setup) for
+details.
+
+The MCP server is now available to your configured MCP clients.
+
+
+
+
+For more details on using remote MCP servers, see:
+
+- [Run remote MCP servers (UI)](../guides-ui/run-mcp-servers.mdx?custom-type=custom_remote#install-a-custom-mcp-server)
+- [Run remote MCP servers (CLI)](../guides-cli/run-mcp-servers.mdx#run-a-remote-mcp-server)
+
+## Connect from within the cluster
+
+Applications running inside your Kubernetes cluster can connect directly to MCP
+server proxy services using Kubernetes service discovery. This is more efficient
+and secure than routing through an external Ingress.
+
+### Service DNS names
+
+Each MCPServer, MCPRemoteProxy, or VirtualMCPServer automatically gets a
+Kubernetes Service that other pods can use to connect using the following naming
+patterns:
+
+- MCPServer: `mcp--proxy`
+- MCPRemoteProxy: `mcp--remote-proxy`
+- VirtualMCPServer: `vmcp-`
+
+The full DNS name follows the standard Kubernetes pattern:
+
+```text
+..svc.cluster.local:
+```
+
+For example, if you have an MCPServer named `fetch` in the `toolhive-system`
+namespace with `proxyPort: 8080`, the full URL is:
+
+```text
+http://mcp-fetch-proxy.toolhive-system.svc.cluster.local:8080
+```
+
+Within the same namespace, you can use the short form:
+
+```text
+http://mcp-fetch-proxy:8080
+```
+
+### Example: Configuring applications
+
+When deploying applications like AI agents in the same cluster, configure them
+to use the service DNS name. This example shows how to pass the connection URL
+as an environment variable:
+
+```yaml title="agent-app.yaml"
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: my-agent-app
+ namespace: my-app
+spec:
+ # ... other deployment configuration ...
+ template:
+ spec:
+ containers:
+ - name: app
+ image: my-agent-app:latest
+ env:
+ - name: MCP_SERVER_URL
+ # Different namespace: use full DNS name
+ value: 'http://mcp-fetch-proxy.toolhive-system.svc.cluster.local:8080/mcp'
+ # Same namespace: use short name
+ # value: 'http://mcp-fetch-proxy:8080/mcp'
+```
+
+### Network policies for cross-namespace access
+
+If your cluster uses network policies, you may need to create a policy to allow
+traffic between namespaces:
+
+```yaml title="allow-cross-namespace.yaml"
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: allow-app-to-mcp
+ namespace: toolhive-system
+spec:
+ podSelector:
+ matchLabels:
+ app.kubernetes.io/name: toolhive-proxy
+ policyTypes:
+ - Ingress
+ ingress:
+ - from:
+ - namespaceSelector:
+ matchLabels:
+ name: my-app # Your app's namespace must have this label
+```
+
+## Authentication for external clients
+
+When exposing MCP servers externally, configure authentication to control
+access. ToolHive supports multiple authentication methods:
+
+- **OIDC authentication** - Use external identity providers like Google, GitHub,
+ Okta, or Microsoft Entra ID
+- **Kubernetes service accounts** - For service-to-service authentication within
+ the cluster
+
+See the [Authentication and authorization](./auth-k8s.mdx) guide for detailed
+setup instructions.
+
+## Check connection status
+
+### Test external connectivity
+
+If you have the ToolHive CLI installed, you can test connectivity to your MCP
+server:
+
+```bash
+thv mcp list tools --server https://fetch-mcp.example.com/mcp
+```
+
+Or use `curl` to send a JSON-RPC request:
+
+```bash
+# Host-based routing
+curl -X POST https://fetch-mcp.example.com/mcp \
+ -H "Content-Type: application/json" \
+ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
+
+# Path-based routing
+curl -X POST https://mcp.example.com/fetch/mcp \
+ -H "Content-Type: application/json" \
+ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
+```
+
+You should receive a JSON response with a list of available tools.
+
+:::tip
+
+If you've configured authentication on your MCP server, see the
+[Authentication and authorization](./auth-k8s.mdx) guide for how to test
+authenticated connections.
+
+:::
+
+### Test internal connectivity
+
+Test connectivity from within the cluster:
+
+```bash
+# Port-forward to test locally
+kubectl port-forward -n toolhive-system service/mcp-fetch-proxy 8080:8080
+
+# In another terminal, test the connection
+curl -X POST http://localhost:8080/mcp \
+ -H "Content-Type: application/json" \
+ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
+```
+
+### Verify the connection path
+
+Check that the Ingress or Gateway is properly configured and the Service has
+running pods:
+
+```bash
+# For Ingress: verify it exists and has an address
+kubectl get ingress -n toolhive-system fetch-mcp-ingress
+
+# For Gateway API: check HTTPRoute status (look for "Accepted: True" in conditions)
+kubectl describe httproute -n toolhive-system fetch-mcp-route
+
+# Verify the Service exists
+kubectl get service -n toolhive-system mcp-fetch-proxy
+
+# Check that the proxy pod is running
+kubectl get pods -n toolhive-system -l app.kubernetes.io/instance=fetch
+```
+
+If the Ingress shows an address or the HTTPRoute status shows "Accepted: True",
+and the pod is running, the connection path is properly configured.
+
+## Next steps
+
+Learn how to secure your MCP servers with
+[Authentication and authorization](./auth-k8s.mdx).
+
+Configure [Telemetry and metrics](./telemetry-and-metrics.mdx) to monitor your
+MCP server usage and performance.
+
+[Set up logging](./logging.mdx) to track requests and audit MCP server activity.
+
+## Related information
+
+- [Run MCP servers in Kubernetes](./run-mcp-k8s.mdx) - Deploy MCP servers in
+ your cluster
+- [Proxy remote MCP servers](./remote-mcp-proxy.mdx) - Create proxies for
+ external MCP servers
+- [Client compatibility](../reference/client-compatibility.mdx) - Supported MCP
+ clients and configuration
+- [Kubernetes CRD reference](../reference/crd-spec.md#apiv1alpha1mcpserver) -
+ Full MCPServer specification
+- [Configure secure ingress tutorial](../integrations/ingress-ngrok.mdx) -
+ Complete tutorial using ngrok and Gateway API
+
+## Troubleshooting
+
+
+Ingress returns 503 Service Unavailable
+
+If your Ingress returns a 503 error:
+
+```bash
+# Check if the service exists
+kubectl get service -n toolhive-system mcp-fetch-proxy
+
+# Check the proxy pod is running
+kubectl get pods -n toolhive-system -l app.kubernetes.io/instance=fetch
+
+# Check pod logs for errors
+kubectl logs -n toolhive-system -l app.kubernetes.io/instance=fetch
+```
+
+Common causes:
+
+- **Proxy pod not running**: Ensure the MCPServer resource was created
+ successfully
+- **Wrong service name**: The Ingress backend service name must follow the
+ naming conventions defined above
+- **Wrong port**: The Ingress backend port must match the `proxyPort` in the
+ MCPServer spec
+- **Pod health check failing**: Check the proxy pod logs for errors
+
+
+
+
+TLS certificate issues
+
+If you see certificate errors when connecting:
+
+```bash
+# Check the certificate secret exists
+kubectl get secret -n toolhive-system fetch-mcp-tls
+
+# Describe the secret to verify it contains tls.crt and tls.key
+kubectl describe secret -n toolhive-system fetch-mcp-tls
+
+# If using cert-manager, check certificate status
+kubectl get certificate -n toolhive-system
+
+# Check cert-manager logs
+kubectl logs -n cert-manager -l app=cert-manager
+```
+
+Common causes:
+
+- **Certificate not ready**: Wait for cert-manager to provision the certificate
+ (can take a few minutes)
+- **DNS not configured**: Ensure your domain points to the Ingress load balancer
+- **Challenge validation failing**: Check cert-manager logs for ACME challenge
+ errors
+- **Wrong ClusterIssuer**: Verify the cert-manager annotation references an
+ existing ClusterIssuer
+
+
+
+
+Authentication failures
+
+If OAuth authentication is failing when connecting to your MCP server:
+
+```bash
+# Test if the OAuth metadata endpoint is accessible
+# For host-based routing
+curl https://fetch-mcp.example.com/.well-known/oauth-protected-resource
+
+# For path-based routing
+curl https://mcp.example.com/.well-known/oauth-protected-resource/fetch/mcp
+
+# Check proxy logs for authentication errors
+kubectl logs -n toolhive-system -l app.kubernetes.io/instance=fetch
+
+# Verify the MCPServer OIDC configuration
+kubectl get mcpserver -n toolhive-system fetch -o yaml | grep -A15 oidcConfig
+```
+
+Common causes:
+
+- **`.well-known` endpoint not accessible**: When using path-based routing with
+ OAuth, you must configure your Ingress or HTTPRoute to forward the
+ `.well-known` path to the backend. See the "Path-based routing with OAuth" tab
+ in the Ingress or Gateway API sections above for configuration examples.
+- **Missing or incorrect `resourceUrl`**: Ensure the `resourceUrl` in your
+ MCPServer's `oidcConfig` matches the client-facing URL (e.g.,
+ `https://mcp.example.com/fetch/mcp` for path-based routing).
+- **Token validation failure**: The token's `aud` claim must match the
+ configured audience in your MCPServer's OIDC configuration.
+- **Issuer mismatch**: The token's `iss` claim must match the configured issuer.
+- **JWKS endpoint unreachable**: Check that the proxy can reach your identity
+ provider's JWKS endpoint to validate tokens.
+
+For detailed OAuth setup and troubleshooting, see the
+[Authentication and authorization](./auth-k8s.mdx) guide.
+
+
+
+
+Cannot connect from within cluster
+
+If pods cannot connect to the MCP server service:
+
+```bash
+# Verify service exists
+kubectl get service -n toolhive-system mcp-fetch-proxy
+
+# Check that pods are running
+kubectl get pods -n toolhive-system -l app.kubernetes.io/instance=fetch
+
+# Test DNS resolution from a pod
+kubectl run test-dns -n toolhive-system --image=busybox --restart=Never -- \
+ nslookup mcp-fetch-proxy.toolhive-system.svc.cluster.local
+
+# Check the DNS test results
+kubectl logs -n toolhive-system test-dns
+
+# Clean up the test pod
+kubectl delete pod -n toolhive-system test-dns
+
+# Test connectivity from a pod
+kubectl run test-curl -n toolhive-system --image=curlimages/curl --restart=Never -- \
+ curl -X POST http://mcp-fetch-proxy:8080/mcp \
+ -H "Content-Type: application/json" \
+ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
+
+# Check the connectivity test results
+kubectl logs -n toolhive-system test-curl
+
+# Clean up the test pod
+kubectl delete pod -n toolhive-system test-curl
+```
+
+Common causes:
+
+- **Network policies blocking traffic**: Check for network policies that might
+ prevent pod-to-pod communication
+- **Wrong namespace**: Ensure you're using the correct service DNS name for
+ cross-namespace access
+- **Service not created**: The operator automatically creates services, but
+ verify it exists
+- **Wrong port**: Ensure you're using the `proxyPort` value from the MCPServer
+ spec
+- **Wrong service name**: Remember the service name follows the naming
+ conventions defined above
+
+
+
+
+Gateway API not working
+
+If using Gateway API and connections fail:
+
+```bash
+# Check Gateway status
+kubectl get gateway -n toolhive-system mcp-gateway
+
+# Check HTTPRoute status
+kubectl get httproute -n toolhive-system fetch-mcp-route
+
+# Describe the HTTPRoute for detailed status
+kubectl describe httproute -n toolhive-system fetch-mcp-route
+
+# Check Gateway implementation logs (example for Istio)
+kubectl logs -n istio-system -l app=istio-ingressgateway
+```
+
+Common causes:
+
+- **Gateway not ready**: Wait for the Gateway to be accepted and programmed by
+ the controller
+- **Wrong gateway class**: Ensure `gatewayClassName` matches your installed
+ Gateway API implementation
+- **Listener configuration issues**: Verify the Gateway listener configuration
+ matches the HTTPRoute requirements
+- **Certificate issues**: For HTTPS, ensure the certificate reference exists and
+ is valid
+
+
+
+
+Cross-namespace access denied
+
+If cross-namespace connections fail:
+
+```bash
+# Check network policies in the MCP server namespace
+kubectl get networkpolicy -n toolhive-system
+
+# Describe network policies to see rules
+kubectl describe networkpolicy -n toolhive-system
+
+# Check if the app namespace has the required labels
+kubectl get namespace my-app --show-labels
+```
+
+Solutions:
+
+- Create or update network policies to allow traffic from your app namespace
+- Add required labels to your application namespace
+- Test connectivity using a debug pod in the app namespace
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-k8s/customize-tools.mdx b/versioned_docs/version-1.0/toolhive/guides-k8s/customize-tools.mdx
new file mode 100644
index 00000000..3d2453a4
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-k8s/customize-tools.mdx
@@ -0,0 +1,241 @@
+---
+title: Customize tools
+description: Filter and rename MCP server tools using the MCPToolConfig CRD and
+ toolConfigRef.
+---
+
+## Overview
+
+Use the MCPToolConfig Custom Resource Definition (CRD) to centrally manage which
+tools an MCP server exposes, and optionally rename tools or override their
+descriptions. You reference the configuration from an MCPServer using the
+`toolConfigRef` field.
+
+- toolsFilter: allow-list the tools to expose.
+- toolsOverride: rename tools and/or change their descriptions.
+- Same-namespace only: an MCPServer can reference only MCPToolConfig objects in
+ the same namespace.
+- Precedence: toolConfigRef takes precedence over the deprecated spec.tools
+ field on MCPServer.
+
+## Define a basic tool filter
+
+This example exposes only three tools on a server:
+
+```yaml title="toolconfig-basic.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPToolConfig
+metadata:
+ name: basic-tool-filter
+ namespace: toolhive-system
+spec:
+ toolsFilter:
+ - read_file
+ - write_file
+ - list_directory
+```
+
+:::note[Empty filter]
+
+If `toolsFilter` is omitted or empty, all tools are allowed.
+
+:::
+
+## Rename tools and override descriptions
+
+You can rename tools to clearly distinguish different deployments or scopes, and
+refine their descriptions to add usage guidance.
+
+A common pattern is running the same MCP server multiple times for different
+scopes (for example, separate GitHub orgs, repos, or environments). Renaming
+tools makes intent obvious and helps prevent mistakes.
+
+```yaml title="toolconfig-with-overrides.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPToolConfig
+metadata:
+ name: github-tools-config
+ namespace: toolhive-system
+spec:
+ # Only expose GitHub PR-related tools
+ toolsFilter:
+ - create_pull_request
+ - get_pull_request
+ - list_pull_requests
+ - merge_pull_request
+
+ # You can override name, description, or both (they are independent)
+ toolsOverride:
+ # Override only the name
+ create_pull_request:
+ name: github_create_pr
+
+ # Override only the description (keep the original name)
+ get_pull_request:
+ description: Retrieve details of a specific GitHub pull request
+
+ # Override both name and description
+ list_pull_requests:
+ name: github_list_prs
+ description: List pull requests in a repository
+
+ merge_pull_request:
+ name: github_merge_pr
+ description: Merge a GitHub pull request
+```
+
+The key in the `toolsOverride` map is the original tool name; the `name` field
+is the user-visible (overridden) name.
+
+:::info[Override fields]
+
+You can override name or description independently. Leave one unset to keep the
+original value from the MCP server. Both fields cannot be empty at the same
+time.
+
+:::
+
+:::tip[When to rename?]
+
+If you run the same server for different scopes (for example, prod vs. sandbox),
+use distinct tool names like `github_prod_create_pr` and
+`github_sandbox_create_pr` to make intent clear to clients.
+
+:::
+
+## Reference the configuration from an MCP server
+
+Add `toolConfigRef` to the `spec` section of your MCPServer or MCPRemoteProxy
+resource.
+
+
+
+
+```yaml {10-11} title="mcpserver-with-toolconfig.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: github
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/github/github-mcp-server
+ transport: stdio
+ proxyPort: 8080
+ toolConfigRef:
+ name: github-tools-config
+ # ... other spec fields ...
+```
+
+
+
+
+```yaml {10-11} title="mcpremoteproxy-with-toolconfig.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRemoteProxy
+metadata:
+ name: github
+ namespace: toolhive-system
+spec:
+ remoteUrl: https://github.com/github/github-mcp-server
+ transport: streamable-http
+ proxyPort: 8080
+ toolConfigRef:
+ name: github-tools-config
+ # ... auth config ...
+```
+
+
+
+
+:::note[Filtering and overrides together]
+
+When you use `toolsFilter` and `toolsOverride` together, filter by the
+user-visible (overridden) names. Tool calls using overridden names are forwarded
+to the actual tool.
+
+:::
+
+## Example: org-scoped tool names
+
+Run the GitHub MCP twice, once per organization, and rename tools so intent is
+clear to clients.
+
+```yaml title="github-org-scoped-tools.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPToolConfig
+metadata:
+ name: github-acme-tools
+ namespace: toolhive-system
+spec:
+ toolsFilter:
+ - github_acme_create_pr
+ - github_acme_get_pr
+ toolsOverride:
+ create_pull_request:
+ name: github_acme_create_pr
+ get_pull_request:
+ name: github_acme_get_pr
+---
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPToolConfig
+metadata:
+ name: github-foocorp-tools
+ namespace: toolhive-system
+spec:
+ toolsFilter:
+ - github_foocorp_create_pr
+ - github_foocorp_get_pr
+ toolsOverride:
+ create_pull_request:
+ name: github_foocorp_create_pr
+ get_pull_request:
+ name: github_foocorp_get_pr
+---
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: github-acme
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/github/github-mcp-server
+ transport: stdio
+ proxyPort: 8080
+ # (Use credentials that scope access to the acme-org here)
+ toolConfigRef:
+ name: github-acme-tools
+---
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: github-foocorp
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/github/github-mcp-server
+ transport: stdio
+ proxyPort: 8080
+ # (Use credentials that scope access to the foo-corp org here)
+ toolConfigRef:
+ name: github-foocorp-tools
+```
+
+## Inspect status and troubleshoot
+
+Use kubectl to inspect status and track change propagation:
+
+```bash
+kubectl -n toolhive-system get mcptoolconfigs
+kubectl -n toolhive-system get mcptoolconfig github-tools-config -o yaml
+kubectl -n toolhive-system get mcpserver github -o yaml
+```
+
+- If an MCPToolConfig is still referenced, deletion is blocked by a finalizer.
+ Remove all toolConfigRef references first.
+- If an MCPServer references a missing MCPToolConfig, the server enters Failed
+ and the controller logs include the missing name and namespace.
+
+## Related information
+
+- See the
+ [Kubernetes CRD reference](../reference/crd-spec.md#apiv1alpha1mcptoolconfig)
+ for the full MCPToolConfig and MCPServerSpec schemas.
+- Learn how to [run the MKP server in Kubernetes](../guides-mcp/k8s.mdx).
diff --git a/versioned_docs/version-1.0/toolhive/guides-k8s/deploy-operator.mdx b/versioned_docs/version-1.0/toolhive/guides-k8s/deploy-operator.mdx
new file mode 100644
index 00000000..7f586d00
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-k8s/deploy-operator.mdx
@@ -0,0 +1,596 @@
+---
+title: Deploy the operator
+description:
+ How to deploy the ToolHive operator in a Kubernetes cluster using Helm or
+ kubectl
+---
+
+## Prerequisites
+
+- A Kubernetes cluster (current and two previous minor versions are supported)
+- Permissions to create resources in the cluster
+- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) configured to communicate
+ with your cluster
+- [Helm](https://helm.sh/docs/intro/install/) (v3.10 minimum, v3.14+
+ recommended)
+
+## Install the CRDs
+
+
+
+The ToolHive operator requires Custom Resource Definitions (CRDs) to manage
+MCPServer resources. The CRDs define the structure and behavior of MCPServers in
+your cluster.
+
+Choose an installation method based on your needs:
+
+- **Helm** (recommended): Provides customization options and manages the full
+ lifecycle of the operator. CRDs are installed and upgraded automatically as
+ part of the Helm chart.
+- **kubectl**: Uses static manifests for a simple installation. Useful for
+ environments where Helm isn't available or for GitOps workflows.
+
+
+
+
+This command installs the latest version of the ToolHive operator CRDs Helm
+chart:
+
+```bash
+helm upgrade --install toolhive-operator-crds oci://ghcr.io/stacklok/toolhive/toolhive-operator-crds \
+ -n toolhive-system --create-namespace
+```
+
+:::warning[Namespace consistency]
+
+When you install this chart, Helm stamps all CRDs with a
+`meta.helm.sh/release-namespace` annotation set to the namespace used at install
+time and is fixed for that release. You must continue to use the same namespace
+on all future `helm upgrade` commands for the CRDs. If you decide to specify a
+different namespace, an error will occur due to ownership issues.
+
+If you need to migrate to a different namespace, see the
+[CRD namespace mismatch troubleshooting](#crd-upgrade-fails-with-namespace-mismatch)
+section.
+
+:::
+
+To install a specific version, append `--version ` to the command, for
+example:
+
+```bash
+helm upgrade --install toolhive-operator-crds oci://ghcr.io/stacklok/toolhive/toolhive-operator-crds \
+ -n toolhive-system --version 0.12.1
+```
+
+#### CRD configuration options
+
+The Helm chart provides fine-grained control over which CRDs are installed. By
+default, all CRDs are installed. You can selectively enable or disable CRD
+groups using these values:
+
+| Value | Description | Default |
+| ------------------------- | ----------------------------------------------------- | ------- |
+| `crds.install.server` | Install server CRDs (MCPServer, MCPRemoteProxy, etc.) | `true` |
+| `crds.install.registry` | Install registry CRDs (MCPRegistry) | `true` |
+| `crds.install.virtualMcp` | Install vMCP CRDs (VirtualMCPServer, etc.) | `true` |
+| `crds.keep` | Preserve CRDs when uninstalling the chart | `true` |
+
+For example, to install only server-related CRDs without vMCP support:
+
+```bash
+helm upgrade --install toolhive-operator-crds oci://ghcr.io/stacklok/toolhive/toolhive-operator-crds \
+ -n toolhive-system --set crds.install.virtualMcp=false
+```
+
+:::note
+
+The `crds.keep` option adds the `helm.sh/resource-policy: keep` annotation to
+CRDs, which prevents Helm from deleting them during `helm uninstall`. This
+protects your custom resources from accidental deletion. If you want to remove
+CRDs during uninstall, set `crds.keep=false`.
+
+:::
+
+
+
+
+To install the CRDs using `kubectl`, run the following, ensuring you only apply
+the CRDs you need for the features you plan to use:
+
+```bash
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_embeddingservers.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpexternalauthconfigs.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcptoolconfigs.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpremoteproxies.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpservers.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpgroups.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpregistries.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_virtualmcpcompositetooldefinitions.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_virtualmcpservers.yaml
+```
+
+Replace `v0.12.1` in the commands above with your target CRD version.
+
+
+
+
+## Install the operator
+
+
+
+To install the ToolHive operator using default settings, run the following
+command:
+
+```bash
+helm upgrade --install toolhive-operator oci://ghcr.io/stacklok/toolhive/toolhive-operator -n toolhive-system --create-namespace
+```
+
+This command installs the latest version of the ToolHive operator CRDs Helm
+chart. To install a specific version, append `--version ` to the
+command, for example:
+
+```bash
+helm upgrade --install toolhive-operator oci://ghcr.io/stacklok/toolhive/toolhive-operator -n toolhive-system --create-namespace --version 0.12.1
+```
+
+Verify the installation:
+
+```bash
+kubectl get pods -n toolhive-system
+```
+
+After about 30 seconds, you should see the `toolhive-operator` pod running.
+
+Check the logs of the operator pod:
+
+```bash
+kubectl logs -f -n toolhive-system
+```
+
+This shows you the logs of the operator pod, which can help you debug any
+issues. For comprehensive logging and audit capabilities, see the
+[Logging infrastructure](./logging.mdx) guide.
+
+## Customize the operator
+
+You can customize the operator installation by providing a `values.yaml` file
+with your configuration settings. For example, to change the number of replicas
+and set a specific ToolHive version, create a `values.yaml` file:
+
+```yaml title="values.yaml"
+operator:
+ replicaCount: 2
+ toolhiveRunnerImage: ghcr.io/stacklok/toolhive:v0.2.17 # or `latest`
+```
+
+Install the operator with your custom values:
+
+```bash {3}
+helm upgrade --install toolhive-operator oci://ghcr.io/stacklok/toolhive/toolhive-operator\
+ -n toolhive-system --create-namespace\
+ -f values.yaml
+```
+
+To see all available configuration options, run:
+
+```bash
+helm show values oci://ghcr.io/stacklok/toolhive/toolhive-operator
+```
+
+## Operator deployment modes
+
+The ToolHive operator supports two distinct deployment modes to accommodate
+different security requirements and organizational structures.
+
+### Cluster mode (default)
+
+Cluster mode provides the operator with cluster-wide access to manage MCPServer
+resources in any namespace. This is the default mode and is suitable for
+platform teams managing MCPServers across the entire cluster.
+
+**Characteristics:**
+
+- Full cluster-wide access to manage MCPServers in any namespace
+- Uses `ClusterRole` and `ClusterRoleBinding` for broad permissions
+- Simplest configuration and management
+- Best for single-tenant clusters or trusted environments
+
+To explicitly configure cluster mode, include the following property in your
+Helm `values.yaml` file:
+
+```yaml title="values.yaml"
+operator:
+ rbac:
+ scope: 'cluster'
+```
+
+Reference the `values.yaml` file when you install the operator using Helm:
+
+```bash {3}
+helm upgrade --install toolhive-operator oci://ghcr.io/stacklok/toolhive/toolhive-operator \
+ -n toolhive-system --create-namespace
+ -f values.yaml
+```
+
+This is the default configuration used in the standard installation commands.
+
+### Namespace mode
+
+Namespace mode restricts the operator's access to only specified namespaces.
+This mode is perfect for multi-tenant environments and organizations following
+the principle of least privilege.
+
+**Characteristics:**
+
+- Restricted access to only specified namespaces
+- Uses `ClusterRole` with namespace-specific `RoleBindings` for precise access
+ control
+- Enhanced security through reduced blast radius
+- Ideal for multi-tenant environments and compliance requirements
+
+To configure namespace mode, include the following in your Helm `values.yaml`:
+
+```yaml title="values.yaml"
+operator:
+ rbac:
+ scope: 'namespace'
+ allowedNamespaces:
+ - 'team-frontend'
+ - 'team-backend'
+ - 'staging'
+ - 'production'
+```
+
+This example lets the operator manage MCPServer resources in the four namespaces
+listed in the `allowedNamespaces` property. Adjust the list to match your
+environment.
+
+Reference the `values.yaml` file when you install the operator using Helm:
+
+```bash {3}
+helm upgrade --install toolhive-operator oci://ghcr.io/stacklok/toolhive/toolhive-operator \
+ -n toolhive-system --create-namespace
+ -f values.yaml
+```
+
+Verify the RoleBindings are created:
+
+```bash
+kubectl get rolebinding --all-namespaces | grep toolhive
+```
+
+You should see RoleBindings in the specified namespaces, granting the operator
+access to manage MCPServers. Example output:
+
+```text
+NAMESPACE NAME ROLE
+team-frontend toolhive-operator-manager-rolebinding ClusterRole/toolhive-operator-manager-role
+team-backend toolhive-operator-manager-rolebinding ClusterRole/toolhive-operator-manager-role
+staging toolhive-operator-manager-rolebinding ClusterRole/toolhive-operator-manager-role
+production toolhive-operator-manager-rolebinding ClusterRole/toolhive-operator-manager-role
+toolhive-system toolhive-operator-leader-election-rolebinding Role/toolhive-operator-leader-election-role
+```
+
+### Migrate between modes
+
+You can switch between cluster mode and namespace mode by updating the
+`values.yaml` file and reapplying the Helm chart as shown above. Migration in
+both directions is supported.
+
+## Check operator status
+
+To verify the operator is working correctly:
+
+```bash
+# Verify CRDs are installed
+kubectl get crd | grep toolhive
+
+# Check operator deployment status
+kubectl get deployment -n toolhive-system toolhive-operator
+
+# Check operator service account and RBAC
+kubectl get serviceaccount -n toolhive-system
+kubectl get clusterrole | grep toolhive
+kubectl get clusterrolebinding | grep toolhive
+
+# Check operator pod status
+kubectl get pods -n toolhive-system
+# Check operator pod logs
+kubectl logs -n toolhive-system
+```
+
+## Upgrade the operator
+
+To upgrade the ToolHive operator to a new version, you need to upgrade both the
+CRDs and the operator installation.
+
+### Upgrade the CRDs
+
+Choose an upgrade method based on your needs:
+
+- **Helm** (recommended): Provides customization options and manages the full
+ lifecycle of the operator. CRDs are installed and upgraded automatically as
+ part of the Helm chart.
+- **kubectl**: Uses static manifests for a simple installation. Useful for
+ environments where Helm isn't available or for GitOps workflows.
+
+
+
+
+
+
+To upgrade the ToolHive operator to a new version, upgrade the CRDs first by
+upgrading with the desired CRDs chart:
+
+```bash
+helm upgrade -i toolhive-operator-crds oci://ghcr.io/stacklok/toolhive/toolhive-operator-crds --version 0.12.1
+```
+
+
+
+
+To upgrade the CRDs using `kubectl`, run the following, ensuring you only apply
+the CRDs you need for the features you want:
+
+```bash
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_embeddingservers.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpexternalauthconfigs.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcptoolconfigs.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpremoteproxies.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpservers.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpgroups.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpregistries.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_virtualmcpcompositetooldefinitions.yaml
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/tags/v0.12.1/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_virtualmcpservers.yaml
+```
+
+
+
+
+Replace `v0.12.1` in the commands above with your target CRD version.
+
+### Upgrade the operator Helm release
+
+Then, upgrade the operator installation using Helm.
+
+
+
+```bash
+helm upgrade -i toolhive-operator oci://ghcr.io/stacklok/toolhive/toolhive-operator -n toolhive-system
+```
+
+This upgrades the operator to the latest version available in the OCI registry.
+To upgrade to a specific version, add the `--version` flag:
+
+```bash
+helm upgrade -i toolhive-operator oci://ghcr.io/stacklok/toolhive/toolhive-operator -n toolhive-system --version 0.12.1
+```
+
+If you have a custom `values.yaml` file, include it with the `-f` flag:
+
+```bash
+helm upgrade -i toolhive-operator oci://ghcr.io/stacklok/toolhive/toolhive-operator -n toolhive-system -f values.yaml
+```
+
+## Uninstall the operator
+
+To uninstall the operator and CRDs:
+
+First, uninstall the operator:
+
+```bash
+helm uninstall toolhive-operator -n toolhive-system
+```
+
+Then, if you want to completely remove ToolHive including all CRDs and related
+resources, delete the CRDs.
+
+:::warning
+
+This will delete all MCPServer and related resources in your cluster!
+
+:::
+
+
+
+
+```bash
+helm uninstall toolhive-operator-crds
+```
+
+:::note
+
+If you installed the CRDs with Helm and have `crds.keep` still set to `true`,
+first upgrade the chart with `--set crds.keep=false` so that when you uninstall
+the CRDs chart, it completely removes all CRDs too:
+
+```bash
+helm upgrade toolhive-operator-crds oci://ghcr.io/stacklok/toolhive/toolhive-operator-crds --set crds.keep=false
+```
+
+:::
+
+
+
+
+To remove the CRDs using `kubectl`, run the following:
+
+```bash
+kubectl delete crd embeddingservers.toolhive.stacklok.dev
+kubectl delete crd mcpexternalauthconfigs.toolhive.stacklok.dev
+kubectl delete crd mcptoolconfigs.toolhive.stacklok.dev
+kubectl delete crd mcpremoteproxies.toolhive.stacklok.dev
+kubectl delete crd mcpservers.toolhive.stacklok.dev
+kubectl delete crd mcpgroups.toolhive.stacklok.dev
+kubectl delete crd mcpregistries.toolhive.stacklok.dev
+kubectl delete crd virtualmcpcompositetooldefinitions.toolhive.stacklok.dev
+kubectl delete crd virtualmcpservers.toolhive.stacklok.dev
+```
+
+
+
+
+If you created the `toolhive-system` namespace with Helm's `--create-namespace`
+flag, delete it manually:
+
+```bash
+kubectl delete namespace toolhive-system
+```
+
+## Next steps
+
+See [Run MCP servers in Kubernetes](./run-mcp-k8s.mdx) to learn how to create
+and manage MCP servers using the ToolHive operator in your Kubernetes cluster.
+The operator supports deploying MCPServer resources based on the deployment mode
+configured during installation.
+
+## Related information
+
+- [Kubernetes introduction](./intro.mdx) - Overview of ToolHive's Kubernetes
+ integration
+- [ToolHive operator tutorial](./quickstart.mdx) - Step-by-step tutorial for
+ getting started using a local kind cluster
+
+## Troubleshooting
+
+
+Authentication error with ghcr.io
+
+If you encounter an authentication error when pulling the Helm chart, it might
+indicate a problem with your access to the GitHub Container Registry
+(`ghcr.io`).
+
+ToolHive's charts and images are public, but if you've previously logged into
+`ghcr.io` using a personal access token, you might need to re-authenticate if
+your token has expired or been revoked.
+
+See the GitHub documentation to
+[re-authenticate to the registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-with-a-personal-access-token-classic).
+
+
+
+
+Operator pod fails to start
+
+If the operator pod is not starting or is in a `CrashLoopBackOff` state, check
+the pod logs for error messages:
+
+```bash
+kubectl get pods -n toolhive-system
+# Note the name of the toolhive-operator pod
+
+kubectl describe pod -n toolhive-system
+kubectl logs -n toolhive-system
+```
+
+Common causes include:
+
+- **Missing CRDs**: Ensure the CRDs were installed successfully before
+ installing the operator. The operator requires the CRDs to function properly.
+- **Configuration errors**: Check your `values.yaml` file for any
+ misconfigurations
+- **Insufficient permissions**: Ensure your cluster has the necessary RBAC
+ permissions for the operator to function
+- **Resource constraints**: Check if the cluster has sufficient CPU and memory
+ resources available
+- **Image pull issues**: Verify that the cluster can pull images from `ghcr.io`
+
+
+
+
+CRD upgrade fails with namespace mismatch
+
+If you see an error like the following when upgrading the CRD chart:
+
+```text
+Error: invalid ownership metadata; annotation validation error:
+key "meta.helm.sh/release-namespace" must equal "toolhive-system":
+current value is "default"
+```
+
+This means the CRD chart was originally installed in a different namespace than
+the one you're now targeting. To fix this, patch the
+`meta.helm.sh/release-namespace` annotation on all CRDs to match your desired
+namespace:
+
+```bash
+for crd in $(kubectl get crd -o name | grep toolhive.stacklok.dev); do
+ kubectl annotate "$crd" \
+ meta.helm.sh/release-namespace= --overwrite
+done
+```
+
+Replace `` with the namespace you want to use going forward
+(for example, `toolhive-system`). This is a one-time operation. After patching,
+future upgrades work as long as you use the same namespace consistently.
+
+
+
+
+CRDs installation fails
+
+If the CRDs installation fails, you might see errors about existing resources or
+permission issues:
+
+```bash
+# Check if CRDs already exist
+kubectl get crd | grep toolhive
+
+# Remove existing CRDs if needed (this will delete all related resources)
+kubectl delete crd
+```
+
+To reinstall the CRDs:
+
+```bash
+helm uninstall toolhive-operator-crds
+helm upgrade -i toolhive-operator-crds oci://ghcr.io/stacklok/toolhive/toolhive-operator-crds
+```
+
+
+
+
+Namespace creation issues
+
+If you encounter permission errors when creating the `toolhive-system`
+namespace, create it manually first:
+
+```bash
+kubectl create namespace toolhive-system
+```
+
+Then install the operator without the `--create-namespace` flag:
+
+```bash
+helm upgrade -i toolhive-operator oci://ghcr.io/stacklok/toolhive/toolhive-operator -n toolhive-system
+```
+
+
+
+
+Helm chart not found
+
+If Helm cannot find the chart, ensure you're using the correct OCI registry URL
+and that your Helm version supports OCI registries (v3.8.0+):
+
+```bash
+# Check Helm version
+helm version
+
+# Try pulling the chart explicitly
+helm pull oci://ghcr.io/stacklok/toolhive/toolhive-operator
+```
+
+
+
+
+Network connectivity issues
+
+If you're experiencing network timeouts or connection issues:
+
+- Verify your cluster has internet access to reach `ghcr.io`
+- Check if your organization uses a proxy or firewall that might block access
+- Consider using a private registry mirror if direct access is restricted
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-k8s/index.mdx b/versioned_docs/version-1.0/toolhive/guides-k8s/index.mdx
new file mode 100644
index 00000000..1e16a229
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-k8s/index.mdx
@@ -0,0 +1,20 @@
+---
+title: Using the ToolHive Kubernetes Operator
+description:
+ How-to guides for using the ToolHive Kubernetes operator to run and manage MCP
+ servers.
+---
+
+import DocCardList from '@theme/DocCardList';
+
+## Introduction
+
+The ToolHive Kubernetes operator manages MCP servers in Kubernetes clusters. It
+provides a way to deploy, manage, and scale MCP servers in multi-user
+environments. By defining MCP servers as Kubernetes resources, the operator
+automates their deployment and management, making it easier to run MCP servers
+in multi-user environments.
+
+## Contents
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-k8s/intro.mdx b/versioned_docs/version-1.0/toolhive/guides-k8s/intro.mdx
new file mode 100644
index 00000000..bdfa8624
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-k8s/intro.mdx
@@ -0,0 +1,69 @@
+---
+title: Overview
+description: How to manage MCP servers in Kubernetes with the ToolHive operator
+---
+
+The ToolHive Kubernetes operator manages MCP servers in Kubernetes clusters. It
+lets you define MCP servers as Kubernetes resources and automates their
+deployment and management.
+
+:::info
+
+See the [ToolHive Operator quickstart tutorial](./quickstart.mdx) to get started
+quickly using a local kind cluster. We'd love for you to try it out and send
+feedback!
+
+:::
+
+## Overview
+
+The operator introduces new Custom Resource Definitions (CRDs) into your
+Kubernetes cluster. The primary CRDs for MCP server workloads are `MCPServer`,
+which represents a single MCP server running in Kubernetes, `MCPRemoteProxy`,
+which represents an MCP server running outside the cluster that is proxied by
+ToolHive, and `VirtualMCPServer`, which represents a virtual MCP server gateway
+that aggregates multiple backend MCP servers.
+
+When you create an `MCPServer` resource, the operator automatically:
+
+1. Creates a Deployment to run the MCP server
+2. Sets up a Service to expose the MCP server
+3. Configures the appropriate permissions and settings
+4. Manages the lifecycle of the MCP server
+
+```mermaid
+flowchart TB
+ subgraph K8s["
Kubernetes cluster
"]
+ subgraph K8s1["**Deployment**"]
+ Svc1["HTTP Proxy Service"] -- http --> Proxy1["HTTP Proxy Pod"] -- stdio or http --> MCP1["MCP Server Pod"]
+ Proxy1 -.->|creates| MCP1
+ end
+ subgraph K8s2["**Deployment**"]
+ Svc2["HTTP Proxy Service"] -- http --> Proxy2["HTTP Proxy Pod"] -- stdio or http --> MCP2["MCP Server Pod"]
+ Proxy2 -.->|creates| MCP2
+ end
+ Ingress["Ingress"] -- http --> Svc1 & Svc2
+ Operator["ToolHive Operator"] -.->|creates| K8s1 & K8s2
+ end
+
+ Client["MCP Client [ex: Copilot]"] -- http --> Ingress
+```
+
+`MCPRemoteProxy` and `VirtualMCPServer` resources work similarly, with the
+operator managing a proxy pod that connects to remote MCP servers or aggregates
+multiple backends, respectively.
+
+The diagram shows how clients connect to MCP servers through a standard Ingress
+or Gateway. To learn how to expose your MCP servers and connect clients, see
+[Connect clients to MCP servers](./connect-clients.mdx).
+
+## Installation
+
+[Deploy the ToolHive operator](./deploy-operator.mdx) in your Kubernetes
+cluster.
+
+Once the operator is installed, you can create and manage MCP servers:
+
+- [Run MCP servers in Kubernetes](./run-mcp-k8s.mdx)
+- [Proxy remote MCP servers](./remote-mcp-proxy.mdx)
+- [Virtual MCP Server (vMCP)](../guides-vmcp/index.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-k8s/logging.mdx b/versioned_docs/version-1.0/toolhive/guides-k8s/logging.mdx
new file mode 100644
index 00000000..7a7bbb07
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-k8s/logging.mdx
@@ -0,0 +1,411 @@
+---
+title: Audit logging
+description: Configure and manage logging for ToolHive in Kubernetes environments
+---
+
+ToolHive provides structured JSON logging for MCP servers in Kubernetes, giving
+you detailed operational insights and compliance audit trails. You can configure
+log levels, enable audit logging for tracking MCP operations, and integrate with
+common log collection systems like Fluentd, Filebeat, and Splunk.
+
+## Overview
+
+The ToolHive operator provides two types of logs:
+
+1. **Standard application logs** - Structured operational logs from the ToolHive
+ operator and proxy components
+2. **Audit logs** - Security and compliance logs tracking all MCP operations
+
+```mermaid
+flowchart TB
+ subgraph Sources["Log sources"]
+ Op["ToolHive operator"]
+ Proxy["HTTP proxy pods"]
+ MCP["MCP server pods"]
+ end
+
+ subgraph Processing["Log processing"]
+ Stdout["Standard output (structured JSON)"]
+ Audit["Audit logger (structured JSON)"]
+ end
+
+ subgraph Destinations["Log destinations"]
+ K8s["Kubernetes logs"]
+ ELK["ELK stack"]
+ Splunk["Splunk"]
+ Datadog["Datadog"]
+ Etc["...other collectors"]
+ end
+
+ Op --> Stdout
+ Proxy --> Stdout & Audit
+ MCP --> Stdout
+
+ Stdout --> K8s
+ Audit --> K8s
+
+ K8s --> ELK & Splunk & Datadog & Etc
+```
+
+## Structured application logs
+
+ToolHive automatically outputs structured JSON logs to the standard output
+(stdout) of the operator and HTTP proxy (`proxyrunner`) pods.
+
+All logs use a consistent format for easy parsing by log collectors:
+
+```json
+{
+ "level": "info",
+ "ts": 1761934317.963125,
+ "caller": "logger/logger.go:39",
+ "msg": "MCP server github started successfully"
+}
+```
+
+### Key fields in application logs
+
+| Field | Type | Description |
+| -------- | ------ | ------------------------------------------------ |
+| `level` | string | Log level: `debug`, `info`, `warn`, `error` |
+| `ts` | float | Unix timestamp with microseconds |
+| `caller` | string | Source file and line number of the log statement |
+| `msg` | string | Log message (exact content varies by event) |
+
+## Enable audit logging
+
+Audit logs provide detailed records of all MCP operations for security and
+compliance. To enable audit logging, set the `audit.enabled` field to `true` in
+your MCP server manifest:
+
+
+
+
+```yaml {11-12}
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name:
+ namespace: toolhive-system
+spec:
+ image:
+ # ... other spec fields ...
+
+ # Enable audit logging
+ audit:
+ enabled: true
+```
+
+
+
+
+```yaml {11-12}
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRemoteProxy
+metadata:
+ name:
+ namespace: toolhive-system
+spec:
+ remoteUrl:
+ # ... other spec fields ...
+
+ # Enable audit logging
+ audit:
+ enabled: true
+```
+
+
+
+
+```yaml {11-14}
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name:
+ namespace: toolhive-system
+spec:
+ config:
+ groupRef:
+
+ # Enable audit logging
+ audit:
+ enabled: true
+ includeRequestData: true
+ includeResponseData: true
+
+ # ... other configs ...
+```
+
+
+
+
+ToolHive writes audit logs to stdout alongside standard application logs. Your
+log collector can differentiate them using the `audit_id` field or by filtering
+for `"msg": "audit_event"`.
+
+### Audit log format
+
+When audit logging is enabled, each MCP operation generates a structured audit
+event. For example, here is a sample audit log entry for a tool execution
+request from an MCPServer resource:
+
+```json
+{
+ "time": "2024-01-01T12:00:00.123456789Z",
+ "level": "INFO+2",
+ "msg": "audit_event",
+ "audit_id": "550e8400-e29b-41d4-a716-446655440000",
+ "type": "mcp_tool_call",
+ "logged_at": "2024-01-01T12:00:00.123456Z",
+ "outcome": "success",
+ "component": "github-server",
+ "source": {
+ "type": "network",
+ "value": "10.0.1.5",
+ "extra": {
+ "user_agent": "node"
+ }
+ },
+ "subjects": {
+ "user": "john.doe@example.com",
+ "user_id": "user-123"
+ },
+ "target": {
+ "endpoint": "/messages",
+ "method": "tools/call",
+ "name": "search_issues",
+ "type": "tool"
+ },
+ "metadata": {
+ "extra": {
+ "duration_ms": 245,
+ "transport": "http"
+ }
+ }
+}
+```
+
+:::info[User information in audit logs]
+
+User information in the `subjects` field comes from JWT claims when OIDC
+authentication is configured. The system uses the `name`, `preferred_username`,
+or `email` claim (in that order) for the display name. If authentication is not
+configured, the `user_id` field is set to `local`.
+
+:::
+
+#### Key fields in audit logs
+
+| Field | Description |
+| --------------- | --------------------------------------------- |
+| `audit_id` | Unique identifier for the audit event |
+| `type` | Type of MCP operation (see event types below) |
+| `outcome` | Result: `success` or `failure` |
+| `component` | Name of the MCP server |
+| `subjects.user` | User display name (from JWT claims) |
+| `target.method` | MCP method called |
+| `target.name` | Tool/resource name |
+
+#### Common audit event types
+
+| Event Type | Description |
+| -------------------- | ------------------------- |
+| `mcp_initialize` | MCP server initialization |
+| `mcp_tool_call` | Tool execution request |
+| `mcp_tools_list` | List available tools |
+| `mcp_resource_read` | Resource access |
+| `mcp_resources_list` | List available resources |
+
+
+Complete audit field reference
+
+#### Audit log fields
+
+| Field | Type | Description |
+| ---------------------------- | ------ | ----------------------------------------------------------------------- |
+| `time` | string | Timestamp when the log was generated |
+| `level` | string | Log level (INFO+2 for audit events) |
+| `msg` | string | Always "audit_event" for audit logs |
+| `audit_id` | string | Unique identifier for the audit event |
+| `type` | string | Type of MCP operation (see event types below) |
+| `logged_at` | string | UTC timestamp of the event |
+| `outcome` | string | Result of the operation: `success` or `failure` |
+| `component` | string | Name of the MCP server |
+| `source` | object | Request source information |
+| `source.type` | string | Source type (e.g., "network") |
+| `source.value` | string | Source identifier (e.g., IP address) |
+| `source.extra` | object | Additional source metadata |
+| `subjects` | object | User and identity information |
+| `subjects.user` | string | User display name (from JWT claims: name, preferred_username, or email) |
+| `subjects.user_id` | string | User identifier (from JWT sub claim) |
+| `subjects.client_name` | string | Client application name (optional, from JWT claims) |
+| `subjects.client_version` | string | Client version (optional, from JWT claims) |
+| `target` | object | Target resource information |
+| `target.endpoint` | string | API endpoint path |
+| `target.method` | string | MCP method called |
+| `target.name` | string | Tool or resource name |
+| `target.type` | string | Target type (e.g., "tool") |
+| `metadata` | object | Additional metadata |
+| `metadata.extra.duration_ms` | number | Operation duration in milliseconds |
+| `metadata.extra.transport` | string | Transport protocol used |
+
+#### Audit event types
+
+| Event Type | Description |
+| -------------------- | ------------------------- |
+| `mcp_initialize` | MCP server initialization |
+| `mcp_tool_call` | Tool execution request |
+| `mcp_tools_list` | List available tools |
+| `mcp_resource_read` | Resource access |
+| `mcp_resources_list` | List available resources |
+| `mcp_prompt_get` | Prompt retrieval |
+| `mcp_prompts_list` | List available prompts |
+| `mcp_notification` | MCP notifications |
+| `mcp_ping` | Health check pings |
+| `mcp_completion` | Request completion |
+
+
+
+## Set up log collection
+
+ToolHive outputs structured JSON logs that work with your existing log
+collection infrastructure. The examples below show basic host-based
+configurations for common log collectors. Adapt these patterns to match your
+organization's logging setup.
+
+:::note[Prerequisites]
+
+These examples assume:
+
+- Container logs are available at `/var/log/pods/`
+- You have a standard Kubernetes logging setup
+- Deployment manifests are handled separately
+- You're using host-based log collection
+
+:::
+
+:::tip[Use your existing collection methods]
+
+If your organization uses sidecar-based or operator-based log collection (such
+as Fluent Bit sidecars or the Fluentd Operator), adapt these configuration
+patterns to work with your existing infrastructure.
+
+:::
+
+### Configure Fluentd
+
+```text
+# fluentd.conf
+
+ @type tail
+ path /var/log/pods/*toolhive*.log
+ tag toolhive
+ read_from_head true
+
+ @type json
+ time_key time
+ time_format %Y-%m-%dT%H:%M:%S.%NZ
+
+
+
+# Route standard logs
+
+ @type elasticsearch
+ host elasticsearch.logging.svc.cluster.local
+ port 9200
+ index_name toolhive
+
+
+# Route audit logs (entries that contain audit_id) to a separate index
+
+ @type grep
+
+ key audit_id
+ pattern .+
+
+ @label @AUDIT
+
+
+
+```
+
+### Configure Filebeat
+
+```yaml
+filebeat.inputs:
+ - type: container
+ paths:
+ - /var/log/pods/*toolhive*.log
+ json.keys_under_root: true
+ json.add_error_key: true
+
+output.elasticsearch:
+ hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
+ indices:
+ - index: 'toolhive-audit-%{+yyyy.MM.dd}'
+ when.has_fields: ['audit_id']
+ - index: 'toolhive-%{+yyyy.MM.dd}'
+```
+
+### Configure Splunk
+
+```ini
+# inputs.conf
+[monitor:///var/log/pods/*toolhive*]
+sourcetype = _json
+index = toolhive
+
+# props.conf
+[_json]
+KV_MODE = json
+SHOULD_LINEMERGE = false
+TRANSFORMS-route_audit = route_audit
+
+# transforms.conf
+[route_audit]
+REGEX = "audit_id":\s*".+"
+DEST_KEY = _MetaData:Index
+FORMAT = toolhive_audit
+```
+
+## Security considerations
+
+Protect your log data by implementing appropriate access controls and
+encryption:
+
+### Encrypt logs
+
+- Encrypt audit logs at rest and in transit
+- Use TLS for log shipping to external systems
+
+### Restrict log access
+
+Implement RBAC to control who can access pod logs:
+
+```yaml
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: log-reader
+ namespace: toolhive-system
+rules:
+ - apiGroups: ['']
+ resources: ['pods/log']
+ verbs: ['get', 'list']
+```
+
+## Next steps
+
+- Learn about [telemetry and metrics](./telemetry-and-metrics.mdx) to complement
+ your logging setup
+- See the [observability overview](../concepts/observability.mdx) for the
+ complete monitoring picture
+- Check the [Kubernetes CRD reference](../reference/crd-spec.md) for complete
+ configuration options
diff --git a/versioned_docs/version-1.0/toolhive/guides-k8s/mcp-server-entry.mdx b/versioned_docs/version-1.0/toolhive/guides-k8s/mcp-server-entry.mdx
new file mode 100644
index 00000000..2be428c1
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-k8s/mcp-server-entry.mdx
@@ -0,0 +1,461 @@
+---
+title: Declare remote MCP server entries
+description:
+ Register remote MCP servers as lightweight catalog entries for vMCP discovery
+ without deploying proxy infrastructure.
+---
+
+## Overview
+
+MCPServerEntry is a zero-infrastructure catalog entry that declares a remote MCP
+server endpoint for [Virtual MCP Server (vMCP)](../guides-vmcp/index.mdx)
+discovery and routing. Unlike [MCPRemoteProxy](./remote-mcp-proxy.mdx), it
+creates no pods, services, or deployments. Use MCPServerEntry when you want to
+include a remote server in vMCP routing without the overhead of running a proxy.
+
+```mermaid
+flowchart LR
+ Client["Client"] -->|HTTP| vMCP["vMCP Gateway"]
+ vMCP -->|"direct (no proxy)"| RemoteMCP["Remote MCP Server"]
+
+ subgraph K8s["Kubernetes Cluster"]
+ subgraph NS["toolhive-system"]
+ Operator["ToolHive Operator"]
+ vMCP
+ Entry["MCPServerEntry (catalog entry only)"]
+ end
+ end
+
+ Operator -.->|validates| Entry
+ vMCP -.->|discovers| Entry
+```
+
+MCPServerEntry is part of an
+[MCPGroup](../reference/crd-spec.md#apiv1alpha1mcpgroup), which groups related
+backend MCP servers together for vMCP discovery. When vMCP starts in
+[discovered mode](../guides-vmcp/backend-discovery.mdx), it queries all
+MCPServer, MCPRemoteProxy, and MCPServerEntry resources in the referenced group
+and connects to them directly.
+
+## Prerequisites
+
+- A Kubernetes cluster (current and two previous minor versions are supported)
+- Permissions to create resources in the cluster
+- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) configured to communicate
+ with your cluster
+- The ToolHive operator installed in your cluster (see
+ [Deploy the operator](./deploy-operator.mdx))
+- A remote MCP server that supports HTTP transport (SSE or Streamable HTTP)
+
+## When to use MCPServerEntry vs. MCPRemoteProxy
+
+| | MCPServerEntry | MCPRemoteProxy |
+| ------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
+| **Infrastructure** | No pods, services, or deployments | Creates a proxy pod and service |
+| **Use case** | Lightweight catalog entries for well-known remote servers | Proxied connections requiring request transformation, caching, or the full middleware chain |
+| **Discovery** | Discovered by VirtualMCPServer through MCPGroup membership | Discovered by VirtualMCPServer through MCPGroup membership |
+| **Authentication** | Token exchange via `externalAuthConfigRef` | Full OIDC validation of incoming client requests |
+| **Authorization** | Not applicable (no proxy layer) | Cedar policy enforcement on every request |
+| **Audit logging** | Not applicable (no proxy layer) | Structured audit logs with user identity |
+| **Telemetry** | Not applicable (no proxy layer) | OpenTelemetry tracing and Prometheus metrics |
+| **SSRF protection** | Built-in URL validation blocks internal and metadata endpoints | N/A (proxy runs inside the cluster) |
+
+Choose MCPServerEntry when:
+
+- You trust the remote server and don't need per-request policy enforcement
+- You want the simplest possible configuration with no workload resources (pods,
+ services, deployments)
+- The remote server handles its own authentication
+
+Choose MCPRemoteProxy when:
+
+- You need to validate incoming client tokens with OIDC
+- You need Cedar authorization policies on tool calls
+- You need audit logging with user identity
+- You need tool filtering or renaming at the proxy layer
+
+## Create an MCPServerEntry
+
+MCPServerEntry resources must be part of an MCPGroup. Create the group first if
+it doesn't exist:
+
+```yaml title="my-group.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPGroup
+metadata:
+ name: my-group
+ namespace: toolhive-system
+spec:
+ description: Group of backend MCP servers for vMCP aggregation
+```
+
+Then create a basic MCPServerEntry:
+
+```yaml title="my-entry.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServerEntry
+metadata:
+ name: my-remote-tool
+ namespace: toolhive-system
+spec:
+ groupRef: my-group
+ remoteURL: https://mcp.example.com/mcp
+ transport: streamable-http
+```
+
+Apply both resources:
+
+```bash
+kubectl apply -f my-group.yaml -f my-entry.yaml
+```
+
+:::info[What's happening?]
+
+When you apply an MCPServerEntry resource:
+
+1. The ToolHive operator detects the new resource
+2. The operator validates the spec: checks that the referenced MCPGroup exists,
+ validates the remote URL against SSRF patterns, and verifies any referenced
+ auth or TLS resources
+3. The operator sets the entry's phase to `Valid` if all checks pass, or
+ `Failed` with a descriptive condition if something is wrong
+4. When a VirtualMCPServer in
+ [discovered mode](../guides-vmcp/backend-discovery.mdx) starts, it discovers
+ the entry through its MCPGroup membership and connects directly to the remote
+ URL
+
+:::
+
+### Required fields
+
+| Field | Description | Validation |
+| ----------- | ------------------------------------------ | -------------------------- |
+| `remoteURL` | URL of the remote MCP server | Must match `^https?://` |
+| `transport` | Transport protocol for the remote server | `sse` or `streamable-http` |
+| `groupRef` | Name of the MCPGroup this entry belongs to | Required, minimum length 1 |
+
+## Configure authentication
+
+When the remote MCP server requires authentication, reference an
+[MCPExternalAuthConfig](./token-exchange-k8s.mdx) resource to configure token
+exchange. The MCPExternalAuthConfig must exist in the same namespace as the
+MCPServerEntry.
+
+```yaml title="auth-entry.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPExternalAuthConfig
+metadata:
+ name: my-auth-config
+ namespace: toolhive-system
+spec:
+ type: tokenExchange
+ tokenExchange:
+ tokenUrl: https://auth.company.com/protocol/openid-connect/token
+ clientId: remote-mcp-client
+ clientSecretRef:
+ name: remote-mcp-secret
+ key: client-secret
+ audience: https://mcp.example.com
+---
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServerEntry
+metadata:
+ name: internal-tool
+ namespace: toolhive-system
+spec:
+ groupRef: my-group
+ remoteURL: https://internal-mcp.corp.example.com/mcp
+ transport: streamable-http
+ # highlight-next-line
+ externalAuthConfigRef:
+ name: my-auth-config
+```
+
+When vMCP discovers this entry, it uses the referenced MCPExternalAuthConfig to
+perform token exchange before forwarding requests to the remote server.
+
+## Configure custom TLS certificates
+
+If the remote server uses a certificate signed by an internal CA, provide a
+custom CA bundle so that vMCP can verify the TLS connection.
+
+First, create a ConfigMap containing the CA certificate:
+
+```bash
+kubectl create configmap internal-ca-bundle \
+ --from-file=ca.crt=/path/to/ca-certificate.pem \
+ -n toolhive-system
+```
+
+Then reference it in the MCPServerEntry:
+
+```yaml title="tls-entry.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServerEntry
+metadata:
+ name: internal-tool
+ namespace: toolhive-system
+spec:
+ groupRef: my-group
+ remoteURL: https://internal-mcp.corp.example.com/mcp
+ transport: streamable-http
+ # highlight-start
+ caBundleRef:
+ configMapRef:
+ name: internal-ca-bundle
+ key: ca.crt
+ # highlight-end
+```
+
+## Inject custom headers
+
+Some remote MCP servers require custom headers for tenant identification, API
+keys, or other purposes. Use the `headerForward` field to inject headers into
+requests forwarded to the remote server.
+
+```yaml title="header-entry.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServerEntry
+metadata:
+ name: my-remote-tool
+ namespace: toolhive-system
+spec:
+ groupRef: my-group
+ remoteURL: https://mcp.example.com/mcp
+ transport: streamable-http
+ # highlight-start
+ headerForward:
+ addPlaintextHeaders:
+ X-Custom-Header: my-value
+ # highlight-end
+```
+
+For sensitive values like API keys, use `addHeadersFromSecret` instead. See the
+[Inject custom headers](./remote-mcp-proxy.mdx#inject-custom-headers) section of
+the MCPRemoteProxy guide for the full syntax, which MCPServerEntry shares.
+
+## Complete example
+
+This example creates the MCPServerEntry-related resources for authentication and
+custom TLS. If you reference a CA bundle ConfigMap such as `partner-ca-bundle`,
+it must already exist or be created separately:
+
+```yaml title="complete-entry.yaml"
+---
+# 1. Create the MCPGroup
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPGroup
+metadata:
+ name: engineering-tools
+ namespace: toolhive-system
+spec:
+ description: Engineering team MCP servers
+
+---
+# 2. Create authentication config for token exchange
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPExternalAuthConfig
+metadata:
+ name: remote-auth
+ namespace: toolhive-system
+spec:
+ type: tokenExchange
+ tokenExchange:
+ tokenUrl: https://auth.company.com/protocol/openid-connect/token
+ clientId: remote-mcp-client
+ clientSecretRef:
+ name: remote-mcp-secret
+ key: client-secret
+ audience: https://mcp.partner.example.com
+
+---
+# 3. Create the MCPServerEntry
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServerEntry
+metadata:
+ name: partner-tools
+ namespace: toolhive-system
+spec:
+ groupRef: engineering-tools
+ remoteURL: https://mcp.partner.example.com/mcp
+ transport: streamable-http
+ externalAuthConfigRef:
+ name: remote-auth
+ caBundleRef:
+ configMapRef:
+ name: partner-ca-bundle
+ key: ca.crt
+ headerForward:
+ addPlaintextHeaders:
+ X-Tenant-ID: engineering
+```
+
+Apply all resources:
+
+```bash
+kubectl apply -f complete-entry.yaml
+```
+
+## Check MCPServerEntry status
+
+To check the status of your entries:
+
+```bash
+kubectl get mcpserverentries -n toolhive-system
+```
+
+The status shows the current phase of each entry:
+
+| Phase | Description |
+| --------- | ------------------------------------------------------------------ |
+| `Valid` | All validations passed and the entry is usable |
+| `Pending` | Initial state before the first reconciliation |
+| `Failed` | One or more referenced resources are missing or the URL is invalid |
+
+For more details about a specific entry:
+
+```bash
+kubectl describe mcpserverentry partner-tools -n toolhive-system
+```
+
+Check the `Conditions` section for specific validation results:
+
+```bash
+kubectl get mcpserverentry partner-tools -n toolhive-system -o yaml
+```
+
+## SSRF protection
+
+MCPServerEntry URLs are validated against Server-Side Request Forgery (SSRF)
+patterns. The operator rejects URLs that target:
+
+- **Loopback addresses**: `127.0.0.0/8`, `::1`
+- **Link-local addresses**: `169.254.0.0/16`, `fe80::/10`
+- **Cloud metadata endpoints**: `169.254.169.254` (AWS, GCP, Azure)
+- **Private network ranges**: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`
+
+If a URL fails SSRF validation, the entry's phase is set to `Failed` with a
+condition describing the rejection reason.
+
+## Next steps
+
+- [Configure a VirtualMCPServer](../guides-vmcp/configuration.mdx) to aggregate
+ MCPServerEntry backends with other MCP servers
+- [Set up backend discovery](../guides-vmcp/backend-discovery.mdx) to control
+ how vMCP finds and connects to backends
+- [Configure authentication](../guides-vmcp/authentication.mdx) for
+ client-to-vMCP and vMCP-to-backend security
+
+## Related information
+
+- [MCPServerEntry CRD specification](../reference/crd-spec.md#apiv1alpha1mcpserverentry) -
+ Full MCPServerEntry field reference
+- [Introduction to the Kubernetes Operator](./intro.mdx) - Overview of all
+ operator resource types
+- [Proxy remote MCP servers](./remote-mcp-proxy.mdx) - Full-featured proxy for
+ remote MCP servers
+- [Run MCP servers in Kubernetes](./run-mcp-k8s.mdx) - Deploy container-based
+ MCP servers
+
+## Troubleshooting
+
+
+MCPServerEntry stuck in Pending phase
+
+If an MCPServerEntry remains in `Pending` phase after creation:
+
+```bash
+# Check the entry status
+kubectl describe mcpserverentry -n toolhive-system
+
+# Check operator logs
+kubectl logs -n toolhive-system -l app.kubernetes.io/name=toolhive-operator
+```
+
+Common causes:
+
+- **Operator not running**: Verify the ToolHive operator pod is healthy
+- **RBAC issues**: The operator may not have permission to reconcile
+ MCPServerEntry resources
+
+
+
+
+MCPServerEntry in Failed phase
+
+If the entry's phase is `Failed`, check the conditions for the specific reason:
+
+```bash
+kubectl get mcpserverentry -n toolhive-system \
+ -o jsonpath='{.status.conditions}' | jq
+```
+
+Common causes:
+
+- **SSRF validation failure**: The `remoteURL` targets a blocked address range
+ (loopback, link-local, private network, or cloud metadata). Use an externally
+ routable URL
+- **Missing MCPGroup**: The group referenced in `groupRef` doesn't exist. Create
+ the MCPGroup first
+- **Missing MCPExternalAuthConfig**: The auth config referenced in
+ `externalAuthConfigRef` doesn't exist in the same namespace
+- **Missing CA ConfigMap**: The ConfigMap referenced in `caBundleRef` doesn't
+ exist or the specified key is missing
+
+
+
+
+MCPServerEntry not appearing in vMCP backends
+
+If a `Valid` MCPServerEntry doesn't appear in the VirtualMCPServer's discovered
+backends:
+
+```bash
+# Verify the entry is Valid
+kubectl get mcpserverentry -n toolhive-system
+
+# Check the VirtualMCPServer status
+kubectl get virtualmcpserver -n toolhive-system \
+ -o jsonpath='{.status.discoveredBackends}' | jq
+
+# Check vMCP pod logs
+kubectl logs -n toolhive-system deployment/vmcp-
+```
+
+Common causes:
+
+- **Group mismatch**: The entry's `groupRef` doesn't match the
+ VirtualMCPServer's `config.groupRef`
+- **vMCP not restarted**: Backend changes require a pod restart to be
+ discovered. Restart the vMCP deployment:
+ ```bash
+ kubectl rollout restart deployment vmcp- -n toolhive-system
+ ```
+- **Inline mode**: The VirtualMCPServer uses `outgoingAuth.source: inline`,
+ which doesn't discover backends at runtime. Switch to `discovered` mode or add
+ the backend explicitly to `config.backends`
+
+
+
+
+Remote server connection failures
+
+If vMCP discovers the entry but can't connect to the remote server:
+
+```bash
+# Check vMCP logs for connection errors
+kubectl logs -n toolhive-system deployment/vmcp- | grep -i error
+```
+
+Common causes:
+
+- **TLS certificate errors**: If the remote server uses an internal CA, add a
+ `caBundleRef` pointing to the CA certificate
+- **Authentication failures**: Verify the MCPExternalAuthConfig references valid
+ credentials and the token exchange endpoint is reachable
+- **Network policies**: Ensure egress from the vMCP pod to the remote server is
+ allowed
+- **Transport mismatch**: Verify the `transport` field matches the remote
+ server's actual transport protocol
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-k8s/quickstart.mdx b/versioned_docs/version-1.0/toolhive/guides-k8s/quickstart.mdx
new file mode 100644
index 00000000..6a4f5e33
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-k8s/quickstart.mdx
@@ -0,0 +1,485 @@
+---
+title: 'Quickstart: ToolHive Kubernetes Operator'
+sidebar_label: Quickstart
+description:
+ Learn how to deploy the ToolHive Kubernetes operator and use it to manage MCP
+ servers in a Kubernetes cluster.
+---
+
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+
+In this tutorial, you'll learn how to deploy the ToolHive Kubernetes operator
+and use it to manage MCP servers in a Kubernetes cluster. By the end, you'll
+have a working operator deployment that automatically manages MCP servers using
+Kubernetes resources.
+
+## What you'll learn
+
+- How to set up a local Kubernetes cluster with kind
+- How to deploy the ToolHive operator to your cluster
+- How to create your first MCP server using Kubernetes resources
+- How to verify that your MCP server is running correctly
+- How to connect an AI agent (like GitHub Copilot) to your MCP server
+
+## Prerequisites
+
+Before starting this tutorial, make sure you have:
+
+- [Helm](https://helm.sh/docs/intro/install/) (v3.10 minimum, v3.14+
+ recommended) installed
+- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) installed
+- [Docker](https://docs.docker.com/get-docker/) or
+ [Podman](https://podman-desktop.io/downloads) installed and running
+- [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) installed
+- Basic familiarity with Kubernetes concepts (pods, deployments, services)
+- An MCP client (VS Code with Copilot is used in this tutorial, but you can use
+ any [supported client](../reference/client-compatibility.mdx))
+
+## Quickstart with Task (TL;DR)
+
+If you want to get up and running quickly and have
+[Task](https://taskfile.dev/installation/) installed, you can use our automated
+setup:
+
+1. Clone the ToolHive repository:
+
+ ```bash
+ git clone https://github.com/stacklok/toolhive.git
+ cd toolhive
+ ```
+
+2. Run the automated setup:
+
+ ```bash
+ task kind-with-toolhive-operator
+ ```
+
+This creates the kind cluster, installs an nginx ingress controller, and deploys
+the latest ToolHive operator image. You should see output indicating successful
+cluster creation and operator deployment. Once complete, skip to
+[Step 3: Create your first MCP server](#step-3-create-your-first-mcp-server) to
+continue with the tutorial.
+
+If you prefer to understand each step or don't have Task installed, continue
+with the manual setup below.
+
+## Step 1: Create a kind cluster
+
+First, create a local Kubernetes cluster using kind. This gives you a safe
+environment to experiment with the ToolHive operator.
+
+Create a cluster named `toolhive`:
+
+```bash
+kind create cluster --name toolhive
+```
+
+Verify your cluster is running:
+
+```bash
+kubectl cluster-info
+```
+
+You should see output similar to this:
+
+```text
+Kubernetes control plane is running at https://127.0.0.1:xxxxx
+CoreDNS is running at https://127.0.0.1:xxxxx/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
+```
+
+This confirms your cluster is running and ready for the ToolHive operator.
+
+:::info[What's happening?]
+
+Kind (Kubernetes in Docker) creates a local Kubernetes cluster using Docker
+containers. This is perfect for development and testing because it's isolated
+from your main system and can be easily deleted when you're done.
+
+:::
+
+## Step 2: Deploy the ToolHive operator
+
+Now deploy the ToolHive operator to your cluster using Helm. The operator will
+watch for MCP server resources and manage their lifecycle automatically.
+
+First, install the operator CRDs:
+
+```bash
+helm upgrade --install toolhive-operator-crds oci://ghcr.io/stacklok/toolhive/toolhive-operator-crds
+```
+
+Then install the operator:
+
+```bash
+helm upgrade --install toolhive-operator oci://ghcr.io/stacklok/toolhive/toolhive-operator -n toolhive-system --create-namespace
+```
+
+Verify that the operator deployed successfully:
+
+```bash
+kubectl get pods -n toolhive-system
+```
+
+You should see output similar to:
+
+```text
+NAME READY STATUS RESTARTS AGE
+toolhive-operator-xxx 1/1 Running 0 30s
+```
+
+If the pod shows "Running" status, your operator is ready to manage MCP servers.
+
+:::info[What's happening?]
+
+The ToolHive operator is a Kubernetes controller that watches for `MCPServer`
+resources. When you create an `MCPServer` resource, the operator automatically
+creates the necessary pods, services, and configurations to run that MCP server
+in your cluster.
+
+:::
+
+## Step 3: Create your first MCP server
+
+Now for the exciting part - create an MCP server using Kubernetes resources.
+You'll deploy the fetch server, which allows AI agents to retrieve web content.
+
+Apply the example `fetch` MCP server from the ToolHive repository:
+
+```bash
+kubectl apply -f https://raw.githubusercontent.com/stacklok/toolhive/refs/heads/main/examples/operator/mcp-servers/mcpserver_fetch.yaml
+```
+
+:::info[What's happening?]
+
+When you create an `MCPServer` resource, the ToolHive operator detects it and
+automatically:
+
+1. Creates a deployment to run the MCP server container
+2. Sets up a service to expose the server
+3. Configures the necessary networking and security settings
+
+:::
+
+Check that your MCP server was created successfully:
+
+```bash
+kubectl get mcpservers -n toolhive-system
+```
+
+You should see:
+
+```text
+NAME STATUS URL AGE
+fetch Running http://mcp-fetch-proxy.toolhive-system.svc.cluster.local:8080 30s
+```
+
+If the status is "Pending", wait a few moments and check again. If it remains
+pending for a long time, see the [troubleshooting section](#troubleshooting) at
+the end of this tutorial.
+
+## Step 4: Test your MCP server
+
+Verify that your MCP server is actually working. First, get the service details:
+
+```bash
+kubectl get service mcp-fetch-proxy -n toolhive-system
+```
+
+Port-forward to access the service locally:
+
+```bash
+kubectl port-forward service/mcp-fetch-proxy -n toolhive-system 8080:8080
+```
+
+In another terminal, test the server:
+
+```bash
+curl http://localhost:8080/health
+```
+
+You should see a response of `OK`.
+
+This confirms your MCP server is running and responding correctly.
+
+:::info[What's happening?]
+
+The ToolHive operator automatically creates a Kubernetes service for each MCP
+server. This service provides a stable network endpoint that other applications
+(like AI agents) can use to communicate with your MCP server.
+
+:::
+
+## Step 5: Connect your AI client to the MCP server
+
+Now that your MCP server is running in Kubernetes, connect it to an AI client
+application. We'll use Visual Studio Code with GitHub Copilot as an example.
+
+Make sure you still have the port-forward running from Step 4. If not, restart
+it in a separate terminal:
+
+```bash
+kubectl port-forward service/mcp-fetch-proxy -n toolhive-system 8080:8080
+```
+
+Configure Visual Studio Code to connect to your MCP server. Open VS Code and
+access your user settings:
+
+1. Open the command palette (Cmd/Ctrl+Shift+P)
+
+2. Type "MCP: Add Server..." and select it
+
+
+
+3. Select "HTTP" as the server type
+
+4. Enter the server URL: `http://localhost:8080/mcp` and press Enter
+
+5. Enter a name for the server (e.g., "fetch") and press Enter
+
+6. Choose "Global" to add the server to your global settings
+
+7. VS Code adds the server and opens the MCP settings file. It should look like
+ this:
+
+ ```json
+ {
+ "servers": {
+ "fetch": {
+ "url": "http://localhost:8080/mcp",
+ "type": "http"
+ }
+ },
+ "inputs": []
+ }
+ ```
+
+To verify the connection, click **Start**. The indicator should change to
+"Running" and show "1 tools".
+
+
+
+
+Now test the connection by asking GitHub Copilot to fetch content from a
+website. Open Copilot Chat in **Agent** mode and ask: "Can you fetch the content
+from https://stacklok.com and summarize it for me?"
+
+
+
+GitHub Copilot should be able to use your Kubernetes-hosted MCP server to
+retrieve the content and provide a summary.
+
+
+
+:::info[What's happening?]
+
+You're manually configuring VS Code to connect to your MCP server running in
+Kubernetes. The port-forward creates a tunnel from your local machine
+(port 8080) to the Kubernetes service, allowing GitHub Copilot to communicate
+with the server using the Streamable HTTP protocol.
+
+:::
+
+## Step 6: Explore operator features
+
+Now that you have a working MCP server, explore some operator features.
+
+View the detailed status of your MCP server:
+
+```bash
+kubectl describe mcpserver fetch -n toolhive-system
+```
+
+This shows you the current state, any events, and configuration details.
+
+Try updating your MCP server's resource limits by editing the resource:
+
+```bash
+kubectl patch mcpserver fetch -n toolhive-system --type='merge' -p '{"spec":{"resources":{"limits":{"memory":"256Mi"}}}}'
+```
+
+You should see output confirming the patch:
+
+```text
+mcpserver.toolhive.stacklok.dev/fetch patched
+```
+
+Check that the pod has been updated with the new resource limits:
+
+```bash
+kubectl get pods -n toolhive-system -l app.kubernetes.io/instance=fetch -o jsonpath='{.items[0].spec.containers[0].resources.limits.memory}'
+```
+
+You should see output showing the updated memory limit:
+
+```text
+256Mi
+```
+
+This demonstrates how the operator automatically updates the underlying pod when
+you modify the MCPServer resource.
+
+## Step 7: Clean up
+
+When you're done experimenting, you can clean up your resources.
+
+Delete the MCP server:
+
+```bash
+kubectl delete mcpserver fetch -n toolhive-system
+```
+
+Verify it's been removed:
+
+```bash
+kubectl get mcpservers -n toolhive-system
+```
+
+You should see:
+
+```text
+No resources found in toolhive-system namespace.
+```
+
+Check that the pods are also gone:
+
+```bash
+kubectl get pods -l app.kubernetes.io/name=fetch -n toolhive-system
+```
+
+You should see:
+
+```text
+No resources found in toolhive-system namespace.
+```
+
+:::info[What's happening?]
+
+When you delete an `MCPServer` resource, the operator automatically cleans up
+all the associated Kubernetes resources (pods, services, etc.). This ensures no
+orphaned resources are left behind.
+
+:::
+
+When you're completely finished, delete the kind cluster:
+
+```bash
+kind delete cluster --name toolhive
+```
+
+:::tip[For Task users]
+
+If you followed the [TL;DR setup](#quickstart-with-task-tldr) using Task, you
+can also run:
+
+```bash
+task kind-destroy
+```
+
+This will fully remove the kind cluster and clean up all associated resources.
+
+:::
+
+## What's next?
+
+Congratulations! You've successfully deployed the ToolHive operator and created
+your first MCP server using Kubernetes resources. You now have a working
+Kubernetes environment where MCP servers are automatically managed by the
+operator.
+
+Here are some next steps to explore:
+
+- Learn about
+ [advanced MCP server configurations](../guides-k8s/run-mcp-k8s.mdx) for
+ production deployments
+- Learn more about [Helm deployment options](../guides-k8s/deploy-operator.mdx)
+ and configuration
+- Integrate MCP servers with your existing Kubernetes applications
+- Try deploying other MCP servers from the ToolHive registry
+
+## Troubleshooting
+
+
+Operator pod not starting
+
+If the operator pod isn't starting, check the logs:
+
+```bash
+kubectl logs -n toolhive-system deployment/toolhive-operator
+```
+
+
+
+
+MCP server stuck in pending state
+
+Check the operator logs to see what's happening:
+
+```bash
+kubectl logs -n toolhive-system deployment/toolhive-operator -f
+```
+
+Also check if there are any resource constraints:
+
+```bash
+kubectl describe mcpserver fetch -n toolhive-system
+```
+
+
+
+
+Can't access MCP server
+
+Verify the service is created and has endpoints:
+
+```bash
+kubectl get service mcp-fetch-proxy -n toolhive-system
+kubectl get endpoints mcp-fetch-proxy -n toolhive-system
+```
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-k8s/rate-limiting.mdx b/versioned_docs/version-1.0/toolhive/guides-k8s/rate-limiting.mdx
new file mode 100644
index 00000000..cf74695f
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-k8s/rate-limiting.mdx
@@ -0,0 +1,202 @@
+---
+title: Rate limiting
+description:
+ Configure per-user and shared rate limits on MCPServer resources to prevent
+ noisy neighbors and protect downstream services.
+---
+
+Configure token bucket rate limits on MCPServer resources to control how many
+tool invocations users can make. Rate limiting prevents individual users from
+monopolizing shared servers and protects downstream services from traffic
+spikes.
+
+ToolHive supports two scopes of rate limiting:
+
+- **Shared** limits cap total requests across all users.
+- **Per-user** limits cap requests independently for each authenticated user.
+
+Both scopes can be applied at the server level and overridden per tool. A
+request must pass all applicable limits to proceed.
+
+:::info[Prerequisites]
+
+Before you begin, ensure you have:
+
+- A Kubernetes cluster with the ToolHive Operator installed
+- Redis deployed in your cluster — rate limiting stores token bucket counters in
+ Redis (see [Redis Sentinel session storage](./redis-session-storage.mdx) for
+ deployment instructions)
+- For per-user limits: authentication enabled on the MCPServer (`oidcConfig`,
+ `oidcConfigRef`, or `externalAuthConfigRef`)
+
+If you need help with these prerequisites, see:
+
+- [Kubernetes quickstart](./quickstart.mdx)
+- [Authentication and authorization](./auth-k8s.mdx)
+
+:::
+
+## How rate limiting works
+
+Rate limits use a **token bucket** algorithm. Each bucket has a capacity
+(`maxTokens`) and a refill period (`refillPeriod`). The bucket starts full and
+each `tools/call` request consumes one token. When the bucket is empty, requests
+are rejected until tokens refill. The refill rate is `maxTokens / refillPeriod`
+tokens per second.
+
+Only `tools/call` requests are rate-limited. Lifecycle methods (`initialize`,
+`ping`) and discovery methods (`tools/list`, `prompts/list`) pass through
+unconditionally.
+
+When a request is rejected, the proxy returns:
+
+- **HTTP 429** with a `Retry-After` header (seconds until a token is available)
+- A **JSON-RPC error** with code `-32029` and `retryAfterSeconds` in the error
+ data
+
+If Redis is unreachable, rate limiting **fails open** and all requests are
+allowed through.
+
+## Configure shared rate limits
+
+Shared limits apply a single token bucket across all users. Use them to cap
+total throughput to protect downstream services.
+
+```yaml title="mcpserver-shared-ratelimit.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: weather-server
+spec:
+ image: ghcr.io/stackloklabs/weather-mcp/server
+ transport: streamable-http
+ sessionStorage:
+ provider: redis
+ address:
+ # highlight-start
+ rateLimiting:
+ shared:
+ maxTokens: 1000
+ refillPeriod: 1m0s
+ # highlight-end
+```
+
+This allows 1,000 total `tools/call` requests per minute across all users.
+
+## Configure per-user rate limits
+
+Per-user limits give each authenticated user their own independent token bucket.
+This prevents a single user from consuming the entire server capacity.
+
+Per-user limits **require authentication** to be enabled. The proxy identifies
+users by the `sub` claim from their JWT token.
+
+```yaml title="mcpserver-peruser-ratelimit.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: weather-server
+spec:
+ image: ghcr.io/stackloklabs/weather-mcp/server
+ transport: streamable-http
+ oidcConfig:
+ type: inline
+ inline:
+ issuer: https://my-idp.example.com
+ audience: my-audience
+ sessionStorage:
+ provider: redis
+ address:
+ # highlight-start
+ rateLimiting:
+ perUser:
+ maxTokens: 100
+ refillPeriod: 1m0s
+ # highlight-end
+```
+
+This allows each user 100 `tools/call` requests per minute independently.
+
+## Combine shared and per-user limits
+
+You can configure both scopes together. A request must pass **all** applicable
+limits. This lets you set a per-user ceiling while also capping total server
+throughput.
+
+```yaml title="mcpserver-combined-ratelimit.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: weather-server
+spec:
+ image: ghcr.io/stackloklabs/weather-mcp/server
+ transport: streamable-http
+ oidcConfig:
+ type: inline
+ inline:
+ issuer: https://my-idp.example.com
+ audience: my-audience
+ sessionStorage:
+ provider: redis
+ address:
+ rateLimiting:
+ # highlight-start
+ shared:
+ maxTokens: 1000
+ refillPeriod: 1m0s
+ perUser:
+ maxTokens: 100
+ refillPeriod: 1m0s
+ # highlight-end
+```
+
+## Add per-tool overrides
+
+Individual tools can have tighter limits than the server default. Per-tool
+limits are enforced **in addition to** server-level limits.
+
+```yaml title="mcpserver-pertool-ratelimit.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: weather-server
+spec:
+ image: ghcr.io/stackloklabs/weather-mcp/server
+ transport: streamable-http
+ oidcConfig:
+ type: inline
+ inline:
+ issuer: https://my-idp.example.com
+ audience: my-audience
+ sessionStorage:
+ provider: redis
+ address:
+ rateLimiting:
+ perUser:
+ maxTokens: 100
+ refillPeriod: 1m0s
+ # highlight-start
+ tools:
+ - name: expensive_search
+ perUser:
+ maxTokens: 10
+ refillPeriod: 1m0s
+ - name: shared_resource
+ shared:
+ maxTokens: 50
+ refillPeriod: 1m0s
+ # highlight-end
+```
+
+In this example:
+
+- Each user can make 100 total tool calls per minute.
+- Each user can make at most 10 `expensive_search` calls per minute (and those
+ also count toward the 100 server-level limit).
+- All users combined can make 50 `shared_resource` calls per minute.
+
+## Next steps
+
+- [Token exchange](./token-exchange-k8s.mdx) to configure token exchange for
+ upstream service authentication
+- [CRD reference](../reference/crd-spec.md) for complete field definitions
diff --git a/versioned_docs/version-1.0/toolhive/guides-k8s/redis-session-storage.mdx b/versioned_docs/version-1.0/toolhive/guides-k8s/redis-session-storage.mdx
new file mode 100644
index 00000000..aaf3e845
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-k8s/redis-session-storage.mdx
@@ -0,0 +1,602 @@
+---
+title: Redis Sentinel session storage
+description:
+ How to deploy Redis Sentinel and configure persistent session storage for the
+ ToolHive embedded authorization server.
+---
+
+Deploy Redis Sentinel and configure it as the session storage backend for the
+ToolHive embedded authorization server. By default, sessions are stored in
+memory, which means upstream tokens are lost when pods restart and users must
+re-authenticate. Redis Sentinel provides persistent storage with automatic
+master discovery, ACL-based access control, and optional failover when replicas
+are configured.
+
+:::info[Prerequisites]
+
+Before you begin, ensure you have:
+
+- A Kubernetes cluster with the ToolHive Operator installed
+- `kubectl` configured to access your cluster
+- Familiarity with the
+ [embedded authorization server](./auth-k8s.mdx#set-up-embedded-authorization-server-authentication)
+ setup
+
+If you need help installing the ToolHive Operator, see the
+[Kubernetes quickstart guide](./quickstart.mdx).
+
+:::
+
+## Deploy Redis Sentinel
+
+Deploy a Redis master and a three-node Sentinel cluster. The following manifests
+create the Redis and Sentinel StatefulSets with ACL authentication and
+persistent storage.
+
+Create the `redis` namespace:
+
+```bash
+kubectl create namespace redis
+```
+
+Save the following manifests to a file called `redis-sentinel.yaml`.
+
+The ACL Secret defines a `toolhive-auth` user with permissions restricted to the
+`thv:auth:*` key pattern that ToolHive uses for session data. An init container
+copies the ACL file into the Redis data directory so it persists across
+restarts.
+
+:::tip[Generate a strong ACL password]
+
+Generate a random password and use it in the ACL Secret and Kubernetes Secret
+below:
+
+```bash
+openssl rand -base64 32
+```
+
+In the ACL entry, the `>` prefix before the password is
+[Redis ACL syntax](https://redis.io/docs/latest/operate/oss_and_stack/management/security/acl/#acl-rules)
+meaning "set this user's password." Replace `YOUR_REDIS_ACL_PASSWORD` with the
+generated value.
+
+:::
+
+```yaml title="redis-sentinel.yaml — Redis master and ACL"
+# --- Redis ACL Secret
+apiVersion: v1
+kind: Secret
+metadata:
+ name: redis-acl
+ namespace: redis
+type: Opaque
+stringData:
+ # highlight-start
+ users.acl: >-
+ user toolhive-auth on >YOUR_REDIS_ACL_PASSWORD ~thv:auth:* &* +GET +SET
+ +SETNX +DEL +EXISTS +EXPIRE +SADD +SREM +SMEMBERS +EVAL +MULTI +EXEC
+ +EVALSHA +PING
+ # highlight-end
+---
+# --- Redis headless Service
+apiVersion: v1
+kind: Service
+metadata:
+ name: redis
+ namespace: redis
+spec:
+ clusterIP: None
+ selector:
+ app: redis
+ ports:
+ - name: redis
+ port: 6379
+---
+# --- Redis master StatefulSet
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ name: redis
+ namespace: redis
+spec:
+ serviceName: redis
+ replicas: 1
+ selector:
+ matchLabels:
+ app: redis
+ template:
+ metadata:
+ labels:
+ app: redis
+ spec:
+ initContainers:
+ - name: init-acl
+ image: redis:7-alpine
+ command: ['cp', '/etc/redis-acl/users.acl', '/data/users.acl']
+ volumeMounts:
+ - name: redis-acl
+ mountPath: /etc/redis-acl
+ - name: redis-data
+ mountPath: /data
+ containers:
+ - name: redis
+ image: redis:7-alpine
+ ports:
+ - containerPort: 6379
+ command:
+ - redis-server
+ - --bind
+ - '0.0.0.0'
+ - --aclfile
+ - /data/users.acl
+ readinessProbe:
+ exec:
+ command: ['redis-cli', 'PING']
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ resources:
+ requests:
+ cpu: 100m
+ memory: 256Mi
+ limits:
+ cpu: 500m
+ memory: 512Mi
+ volumeMounts:
+ - name: redis-data
+ mountPath: /data
+ - name: redis-acl
+ mountPath: /etc/redis-acl
+ readOnly: true
+ volumes:
+ - name: redis-acl
+ secret:
+ secretName: redis-acl
+ volumeClaimTemplates:
+ - metadata:
+ name: redis-data
+ spec:
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: 1Gi
+```
+
+The next section deploys a three-node Sentinel cluster that monitors the Redis
+master and handles automatic failover:
+
+```yaml title="redis-sentinel.yaml — Sentinel cluster (append to same file)"
+# --- Sentinel configuration
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: redis-sentinel-config
+ namespace: redis
+data:
+ sentinel.conf: |
+ sentinel resolve-hostnames yes
+ sentinel announce-hostnames yes
+ # quorum: 2 of 3 sentinels must agree to trigger failover
+ sentinel monitor mymaster redis-0.redis.redis.svc.cluster.local 6379 2
+ sentinel down-after-milliseconds mymaster 5000
+ sentinel failover-timeout mymaster 10000
+ sentinel parallel-syncs mymaster 1
+---
+# --- Sentinel headless Service
+apiVersion: v1
+kind: Service
+metadata:
+ name: redis-sentinel
+ namespace: redis
+spec:
+ clusterIP: None
+ selector:
+ app: redis-sentinel
+ ports:
+ - name: sentinel
+ port: 26379
+---
+# --- Sentinel StatefulSet (3 replicas for quorum)
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ name: redis-sentinel
+ namespace: redis
+spec:
+ serviceName: redis-sentinel
+ replicas: 3
+ selector:
+ matchLabels:
+ app: redis-sentinel
+ template:
+ metadata:
+ labels:
+ app: redis-sentinel
+ spec:
+ initContainers:
+ - name: copy-config
+ image: redis:7-alpine
+ command:
+ ['cp', '/etc/sentinel-ro/sentinel.conf', '/data/sentinel.conf']
+ volumeMounts:
+ - name: sentinel-config-ro
+ mountPath: /etc/sentinel-ro
+ - name: sentinel-data
+ mountPath: /data
+ containers:
+ - name: sentinel
+ image: redis:7-alpine
+ ports:
+ - containerPort: 26379
+ name: sentinel
+ command: ['redis-sentinel', '/data/sentinel.conf']
+ readinessProbe:
+ exec:
+ command: ['redis-cli', '-p', '26379', 'PING']
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ resources:
+ requests:
+ cpu: 100m
+ memory: 128Mi
+ limits:
+ cpu: 200m
+ memory: 256Mi
+ volumeMounts:
+ - name: sentinel-data
+ mountPath: /data
+ volumes:
+ - name: sentinel-config-ro
+ configMap:
+ name: redis-sentinel-config
+ volumeClaimTemplates:
+ - metadata:
+ name: sentinel-data
+ spec:
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: 100Mi
+```
+
+Apply the manifests and wait for all pods to be ready:
+
+```bash
+kubectl apply -f redis-sentinel.yaml
+```
+
+```bash
+kubectl wait --for=condition=ready pod \
+ -l 'app in (redis, redis-sentinel)' \
+ --namespace redis \
+ --timeout=300s
+```
+
+:::warning
+
+The manifests above don't disable the Redis default user, which has full access
+with no password. For production deployments, add `user default off` to the
+`users.acl` entry in the `redis-acl` Secret. If you disable the default user,
+you must also configure Sentinel to authenticate to Redis by adding
+`sentinel auth-user` and `sentinel auth-pass` to the Sentinel ConfigMap, and
+update the readiness probe commands to authenticate.
+
+:::
+
+## Create Kubernetes secrets
+
+Create a Secret in the ToolHive namespace containing the Redis ACL credentials.
+The username and password must match the ACL user defined above:
+
+```bash
+kubectl create secret generic redis-acl-secret \
+ --namespace toolhive-system \
+ --from-literal=username=toolhive-auth \
+ --from-literal=password="YOUR_REDIS_ACL_PASSWORD"
+```
+
+## Configure MCPExternalAuthConfig
+
+Add the `storage` block to your `MCPExternalAuthConfig` resource. The following
+example shows a working configuration with Redis Sentinel storage using Sentinel
+service discovery, which automatically resolves Sentinel endpoints from the
+headless Service deployed above:
+
+```yaml title="embedded-auth-with-redis.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPExternalAuthConfig
+metadata:
+ name: embedded-auth-server
+ namespace: toolhive-system
+spec:
+ type: embeddedAuthServer
+ embeddedAuthServer:
+ issuer: 'https://mcp.example.com'
+ signingKeySecretRefs:
+ - name: auth-server-signing-key
+ key: signing-key
+ hmacSecretRefs:
+ - name: auth-server-hmac-secret
+ key: hmac-key
+ # highlight-start
+ storage:
+ type: redis
+ redis:
+ sentinelConfig:
+ masterName: mymaster
+ sentinelService:
+ name: redis-sentinel
+ namespace: redis
+ aclUserConfig:
+ usernameSecretRef:
+ name: redis-acl-secret
+ key: username
+ passwordSecretRef:
+ name: redis-acl-secret
+ key: password
+ # highlight-end
+ upstreamProviders:
+ - name: google
+ type: oidc
+ oidcConfig:
+ issuerUrl: 'https://accounts.google.com'
+ clientId: ''
+ clientSecretRef:
+ name: upstream-idp-secret
+ key: client-secret
+ scopes:
+ - openid
+ - profile
+ - email
+```
+
+```bash
+kubectl apply -f embedded-auth-with-redis.yaml
+```
+
+### Using explicit Sentinel addresses
+
+:::note
+
+`sentinelAddrs` and `sentinelService` are mutually exclusive. Use
+`sentinelService` when your Sentinel instances run in the same cluster, or
+`sentinelAddrs` when you need to specify exact endpoints.
+
+:::
+
+Instead of service discovery, you can list Sentinel addresses explicitly. This
+is useful when Sentinel instances are in a different namespace or outside the
+cluster:
+
+```yaml title="storage block with sentinelAddrs"
+storage:
+ type: redis
+ redis:
+ sentinelConfig:
+ masterName: mymaster
+ sentinelAddrs:
+ - redis-sentinel-0.redis-sentinel.redis.svc.cluster.local:26379
+ - redis-sentinel-1.redis-sentinel.redis.svc.cluster.local:26379
+ - redis-sentinel-2.redis-sentinel.redis.svc.cluster.local:26379
+ aclUserConfig:
+ usernameSecretRef:
+ name: redis-acl-secret
+ key: username
+ passwordSecretRef:
+ name: redis-acl-secret
+ key: password
+```
+
+For the complete list of storage configuration fields, see the
+[Kubernetes CRD reference](../reference/crd-spec.md#apiv1alpha1authserverstorageconfig).
+
+## Enable TLS
+
+Without TLS, Redis credentials and session tokens travel in plaintext between
+ToolHive and Redis. You should enable TLS for any deployment beyond local
+development.
+
+Configure the `tls` block in your storage config. ToolHive needs the CA
+certificate that signed the Redis server certificate so it can verify the
+connection.
+
+:::note
+
+This step only covers the ToolHive client-side TLS configuration. Your Redis and
+Sentinel instances must also be configured to serve TLS — see the
+[Redis TLS documentation](https://redis.io/docs/latest/operate/oss_and_stack/management/security/encryption/)
+for server-side setup.
+
+:::
+
+### Create a CA certificate Secret
+
+Store your CA certificate in a Secret in the ToolHive namespace:
+
+```bash
+kubectl create secret generic redis-ca-cert \
+ --namespace toolhive-system \
+ --from-file=ca.crt=
+```
+
+### Configure TLS in MCPExternalAuthConfig
+
+Add the `tls` block to the `redis` section of your storage config:
+
+```yaml title="storage block with TLS"
+storage:
+ type: redis
+ redis:
+ sentinelConfig:
+ masterName: mymaster
+ sentinelService:
+ name: redis-sentinel
+ namespace: redis
+ aclUserConfig:
+ usernameSecretRef:
+ name: redis-acl-secret
+ key: username
+ passwordSecretRef:
+ name: redis-acl-secret
+ key: password
+ # highlight-start
+ tls:
+ caCertSecretRef:
+ name: redis-ca-cert
+ key: ca.crt
+ # highlight-end
+```
+
+When you set only `tls`, ToolHive automatically uses the same TLS configuration
+for Sentinel connections. This is the recommended setup when both Redis and
+Sentinel use certificates from the same CA.
+
+### Separate TLS config for Sentinel
+
+If your Sentinel instances use a different CA or require different TLS settings,
+add a `sentinelTls` block:
+
+```yaml title="storage block with separate Sentinel TLS"
+storage:
+ type: redis
+ redis:
+ sentinelConfig:
+ masterName: mymaster
+ sentinelService:
+ name: redis-sentinel
+ namespace: redis
+ aclUserConfig:
+ usernameSecretRef:
+ name: redis-acl-secret
+ key: username
+ passwordSecretRef:
+ name: redis-acl-secret
+ key: password
+ # highlight-start
+ tls:
+ caCertSecretRef:
+ name: redis-ca-cert
+ key: ca.crt
+ sentinelTls:
+ caCertSecretRef:
+ name: sentinel-ca-cert
+ key: ca.crt
+ # highlight-end
+```
+
+When `sentinelTls` is set, ToolHive uses separate TLS configurations for master
+and Sentinel connections. Each connection type uses its own CA certificate for
+verification.
+
+## Verify the integration
+
+After applying the configuration, verify that ToolHive can connect to Redis. The
+examples below use `weather-server-embedded` as the MCPServer name — substitute
+your own.
+
+Check that the MCPServer pod is running:
+
+```bash
+kubectl get pods -n toolhive-system \
+ -l app.kubernetes.io/name=weather-server-embedded
+```
+
+Check the proxy logs for Redis connection messages:
+
+```bash
+kubectl logs -n toolhive-system \
+ -l app.kubernetes.io/name=weather-server-embedded \
+ | grep -i redis
+```
+
+Look for log entries that confirm a successful Redis Sentinel connection. If the
+connection fails, the proxy logs contain error details.
+
+Test the OAuth flow end-to-end by connecting with an MCP client. After
+authenticating, restart the proxy pod and verify that your session persists
+without requiring re-authentication:
+
+```bash
+# Restart the proxy pod
+kubectl rollout restart deployment \
+ -n toolhive-system weather-server-embedded-proxy
+
+# Wait for the new pod to be ready
+kubectl rollout status deployment \
+ -n toolhive-system weather-server-embedded-proxy
+```
+
+If your MCP client can continue making requests without re-authenticating, Redis
+session storage is working correctly.
+
+## Troubleshooting
+
+
+Connection refused or timeout errors
+
+- Verify the Redis Sentinel pods are running: `kubectl get pods -n redis`
+- Check that the Sentinel addresses in your config match the actual pod DNS
+ names: `kubectl get endpoints -n redis`
+- Ensure network policies allow traffic from the `toolhive-system` namespace to
+ the `redis` namespace
+- Verify the `masterName` matches the name in your Sentinel configuration
+ (`mymaster` in the example manifests above)
+
+
+
+
+ACL authentication failures
+
+- Verify the Secret exists and contains the correct credentials:
+ `kubectl get secret redis-acl-secret -n toolhive-system -o yaml`
+- Connect to Redis directly to verify the ACL user exists:
+ ```bash
+ kubectl exec -n redis redis-0 -- redis-cli ACL LIST
+ ```
+- Ensure the ACL user has the required permissions (`~thv:auth:*` key pattern
+ and the commands listed in the ACL Secret)
+
+
+
+
+TLS handshake or certificate errors
+
+- Verify the CA certificate Secret exists in the `toolhive-system` namespace:
+ `kubectl get secret redis-ca-cert -n toolhive-system`
+- Confirm the CA certificate matches the one that signed the Redis server
+ certificate
+- Check proxy logs for TLS-specific errors:
+ ```bash
+ kubectl logs -n toolhive-system \
+ -l app.kubernetes.io/name=weather-server-embedded \
+ | grep -i "tls\|x509\|certificate"
+ ```
+- If using self-signed certificates for testing, you can set
+ `insecureSkipVerify: true` to bypass verification (not recommended for
+ production)
+- When using separate Sentinel TLS, ensure both `tls` and `sentinelTls` are
+ configured with the correct CA certificates for their respective services
+
+
+
+
+Sessions lost after Redis failover
+
+- Check Sentinel logs for failover events:
+ `kubectl logs -n redis -l app=redis-sentinel`
+- Verify that the master is reachable from Sentinel:
+ ```bash
+ kubectl exec -n redis redis-sentinel-0 -- \
+ redis-cli -p 26379 SENTINEL masters
+ ```
+- Ensure Sentinel quorum is met (at least 2 of 3 Sentinel instances must be
+ running)
+
+
+
+## Related information
+
+- [Set up embedded authorization server authentication](./auth-k8s.mdx#set-up-embedded-authorization-server-authentication)
+- [Backend authentication](../concepts/backend-auth.mdx)
+- [Kubernetes CRD reference](../reference/crd-spec.md#apiv1alpha1authserverstorageconfig)
diff --git a/versioned_docs/version-1.0/toolhive/guides-k8s/remote-mcp-proxy.mdx b/versioned_docs/version-1.0/toolhive/guides-k8s/remote-mcp-proxy.mdx
new file mode 100644
index 00000000..f8978fe1
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-k8s/remote-mcp-proxy.mdx
@@ -0,0 +1,962 @@
+---
+title: Proxy remote MCP servers
+description:
+ How to deploy proxies for remote MCP servers in Kubernetes using the ToolHive
+ operator
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+## Overview
+
+The ToolHive operator can deploy proxies for remote MCP servers that are hosted
+externally. This enables you to gain centralized observability, policy
+enforcement, and audit logging for external MCP services without requiring
+changes to the remote servers themselves.
+
+Remote MCP proxies sit between your users and external MCP servers, providing:
+
+- **Authentication**: Validate user tokens using OIDC
+- **Authorization**: Apply Cedar policies based on user identity
+- **Audit logging**: Track all requests with user identity and tool information
+- **Tool filtering**: Control which tools are exposed to users
+- **Telemetry**: Collect metrics and traces for monitoring
+
+```mermaid
+flowchart LR
+ Client["Client"] -->|HTTP with token| Proxy["ToolHive Remote Proxy"]
+ Proxy -->|HTTP| RemoteMCP["Remote MCP Server"]
+
+ subgraph K8s["Kubernetes Cluster"]
+ subgraph NS["toolhive-system"]
+ Operator["ToolHive Operator"]
+ end
+ subgraph NS2["Proxy Namespace"]
+ Proxy
+ end
+ end
+
+ Operator -.->|creates| Proxy
+
+ IDP["Identity Provider (Keycloak, Okta, etc.)"] -.->|validates tokens| Proxy
+```
+
+:::info[Experimental]
+
+The MCPRemoteProxy CRD is still in an alpha state so breaking changes to the
+spec and its capabilities are possible.
+
+:::
+
+## Prerequisites
+
+- A Kubernetes cluster (current and two previous minor versions are supported)
+- Permissions to create resources in the cluster
+- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) configured to communicate
+ with your cluster
+- The ToolHive operator installed in your cluster (see
+ [Deploy the operator](./deploy-operator.mdx))
+- An OIDC identity provider (Keycloak, Okta, Azure AD, etc.)
+- Access to a remote MCP server that supports HTTP transport (SSE or Streamable
+ HTTP)
+
+## Use cases
+
+### Enterprise SaaS MCP servers
+
+Organizations using external MCP services need centralized visibility and
+control over how employees interact with these services. For example, a company
+using a SaaS analytics platform with an MCP interface wants to:
+
+- Monitor which tools employees are calling
+- Apply authorization policies based on user roles
+- Maintain audit logs for compliance
+- Filter or restrict access to sensitive operations
+- Collect centralized telemetry
+
+By deploying a remote MCP proxy in Kubernetes, the company gains all these
+capabilities without modifying the external service.
+
+### Multi-tenant access control
+
+Different teams within an organization may need different levels of access to
+the same remote MCP server. Remote proxies enable you to:
+
+- Deploy separate proxies with different authorization policies
+- Apply team-specific tool filters
+- Maintain separate audit trails per team
+- Scale proxy instances independently based on team usage
+
+## Create a remote MCP proxy
+
+You can create `MCPRemoteProxy` resources in namespaces based on how the
+operator was deployed.
+
+- **Cluster mode (default)**: Create MCPRemoteProxy resources in any namespace
+- **Namespace mode**: Create MCPRemoteProxy resources only in allowed namespaces
+
+See [Deploy the operator](./deploy-operator.mdx#operator-deployment-modes) to
+learn about the different deployment modes.
+
+### Basic configuration
+
+This example creates a remote proxy for a fictional enterprise analytics MCP
+server with OIDC authentication:
+
+```yaml title="analytics-proxy.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRemoteProxy
+metadata:
+ name: analytics-proxy
+ namespace: toolhive-system
+spec:
+ # Remote MCP server URL
+ remoteURL: https://mcp.analytics.example.com
+
+ # Port to expose the proxy on
+ port: 8080
+
+ # Transport method (sse or streamable-http)
+ transport: streamable-http
+
+ # OIDC authentication configuration
+ oidcConfig:
+ type: inline
+ inline:
+ # Your identity provider's issuer URL
+ issuer: https://auth.company.com/realms/production
+ # Expected audience claim in tokens
+ audience: analytics-mcp-proxy
+ # Optional: Client ID if using introspection
+ clientId: analytics-proxy
+
+ # Authorization policies
+ authzConfig:
+ type: inline
+ inline:
+ policies:
+ # Allow all authenticated users to list tools
+ - |
+ permit(
+ principal,
+ action == Action::"list_tools",
+ resource
+ );
+ # Allow users with 'analyst' role to call read-only tools
+ - |
+ permit(
+ principal,
+ action == Action::"call_tool",
+ resource
+ )
+ when {
+ principal has groups &&
+ principal.groups.contains("analyst")
+ };
+
+ # Audit logging
+ audit:
+ enabled: true
+
+ # Resource limits
+ resources:
+ limits:
+ cpu: '500m'
+ memory: 512Mi
+ requests:
+ cpu: 100m
+ memory: 128Mi
+```
+
+Apply the resource:
+
+```bash
+kubectl apply -f analytics-proxy.yaml
+```
+
+:::info[What's happening?]
+
+When you apply an `MCPRemoteProxy` resource, here's what happens:
+
+1. The ToolHive operator detects the new resource (if it's in an allowed
+ namespace)
+2. The operator creates a Deployment running the ToolHive proxy
+3. The operator creates a Service to expose the proxy
+4. The proxy connects to your OIDC provider to fetch JWKS keys for token
+ validation
+5. Clients can now connect through the proxy, which validates tokens, applies
+ policies, and forwards requests to the remote MCP server
+
+:::
+
+### Authentication configuration
+
+The `oidcConfig` field validates incoming tokens from users. The proxy supports
+multiple authentication methods:
+
+
+
+
+Inline OIDC configuration is the simplest approach and works with most identity
+providers:
+
+```yaml {4-6}
+spec:
+ remoteURL: https://mcp.example.com
+
+ oidcConfig:
+ type: inline
+ inline:
+ issuer: https://auth.company.com/realms/production
+ audience: mcp-proxy
+ # Optional: For token introspection
+ clientId: mcp-proxy-client
+ clientSecretRef:
+ name: mcp-proxy-secret
+ key: client-secret
+```
+
+The proxy automatically discovers the JWKS URL from the issuer's OIDC discovery
+endpoint (`/.well-known/openid-configuration`).
+
+
+
+
+For more complex configurations or when you need to share OIDC settings across
+multiple proxies, use a ConfigMap:
+
+```yaml {21-24} title="oidc-config.yaml"
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: company-oidc-config
+ namespace: toolhive-system
+data:
+ issuer: 'https://auth.company.com/realms/production'
+ audience: 'mcp-proxy'
+ clientId: 'mcp-proxy-client'
+---
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRemoteProxy
+metadata:
+ name: analytics-proxy
+ namespace: toolhive-system
+spec:
+ remoteURL: https://mcp.analytics.example.com
+ oidcConfig:
+ type: configMap
+ configMap:
+ name: company-oidc-config
+```
+
+
+
+
+:::warning[Production security]
+
+For production deployments:
+
+- Always use HTTPS for the issuer URL
+- Store client secrets in Kubernetes Secrets, not inline
+- Use certificate validation (set `thvCABundlePath` if using custom CAs)
+- Never set `insecureAllowHTTP: true` in production
+
+:::
+
+### Authorization policies
+
+Authorization policies are written in
+[Cedar policy language](https://www.cedarpolicy.com/) and evaluated for each
+request. Policies can reference claims from the validated JWT token.
+
+This example shows different policy patterns:
+
+```yaml {6-9}
+spec:
+ remoteURL: https://mcp.example.com
+ oidcConfig:
+ # ... OIDC config ...
+
+ authzConfig:
+ type: inline
+ inline:
+ policies:
+ # Allow all authenticated users to list tools
+ - |
+ permit(
+ principal,
+ action == Action::"list_tools",
+ resource
+ );
+
+ # Allow users from specific email domain
+ - |
+ permit(
+ principal,
+ action == Action::"call_tool",
+ resource
+ )
+ when {
+ principal has email &&
+ principal.email like "*@company.com"
+ };
+
+ # Role-based access control
+ - |
+ permit(
+ principal,
+ action == Action::"call_tool",
+ resource
+ )
+ when {
+ principal has groups &&
+ principal.groups.contains("admin")
+ };
+
+ # Deny destructive operations
+ - |
+ forbid(
+ principal,
+ action == Action::"call_tool",
+ resource
+ )
+ when {
+ resource.tool in ["delete_data", "drop_table"]
+ };
+```
+
+Available principal attributes depend on the claims in your JWT tokens. These
+are examples of common JWT claims that can be referenced in policies; actual
+available attributes depend on your identity provider's token configuration:
+
+- `sub` - Subject (User ID)
+- `email` - Email address
+- `name` - Display name
+- `groups` - Group memberships (array)
+- Custom claims from your identity provider
+
+### Tool filtering
+
+Use `MCPToolConfig` to filter or rename tools from the remote server:
+
+```yaml {2,31-32} title="analytics-tools.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPToolConfig
+metadata:
+ name: analytics-tools-filter
+ namespace: toolhive-system
+spec:
+ # Only expose read-only tools
+ toolsFilter:
+ - query_data
+ - get_report
+ - list_dashboards
+ - export_chart
+
+ # Rename tools for clarity
+ toolsOverride:
+ query_data:
+ name: analytics_query_data
+ description: Query analytics data with custom filters
+ get_report:
+ name: analytics_get_report
+ description: Retrieve a generated analytics report
+---
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRemoteProxy
+metadata:
+ name: analytics-proxy
+ namespace: toolhive-system
+spec:
+ remoteURL: https://mcp.analytics.example.com
+ # ... other config ...
+ toolConfigRef:
+ name: analytics-tools-filter
+```
+
+See [Customize tools](./customize-tools.mdx) for more information about tool
+filtering and renaming.
+
+### Token exchange
+
+When your organization has federation with the remote service provider, you can
+configure token exchange to convert company tokens into service-specific tokens:
+
+First, create an `MCPExternalAuthConfig`:
+
+```yaml title="external-auth-config.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPExternalAuthConfig
+metadata:
+ name: analytics-token-exchange
+ namespace: toolhive-system
+spec:
+ type: tokenExchange
+ tokenExchange:
+ tokenURL: https://auth.company.com/protocol/openid-connect/token
+ clientID: token-exchange-client
+ clientSecretRef:
+ name: token-exchange-creds
+ key: client-secret
+ audience: https://mcp.analytics.example.com
+ scopes:
+ - 'analytics:read'
+ - 'analytics:write'
+```
+
+Then reference it in your proxy:
+
+```yaml {11-12}
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRemoteProxy
+metadata:
+ name: analytics-proxy
+ namespace: toolhive-system
+spec:
+ remoteURL: https://mcp.analytics.example.com
+ oidcConfig:
+ # ... Company IDP config ...
+
+ externalAuthConfigRef:
+ name: analytics-token-exchange
+
+ # ... other config ...
+```
+
+Now the proxy exchanges validated company tokens for remote service tokens
+before forwarding requests.
+
+:::tip[AWS services]
+
+For AWS services like the AWS MCP Server, use `type: awsSts` instead of
+`type: tokenExchange`. This exchanges OIDC tokens for temporary AWS credentials
+via `AssumeRoleWithWebIdentity` and signs requests with SigV4. See the
+[AWS STS integration tutorial](../integrations/aws-sts.mdx) for details.
+
+:::
+
+### Inject custom headers
+
+Some remote MCP servers require custom headers for tenant identification, API
+keys, or other purposes. Use the `headerForward` field to inject headers into
+every request forwarded to the remote server.
+
+For non-sensitive values like tenant IDs or correlation headers, use
+`addPlaintextHeaders`:
+
+```yaml {6-9}
+spec:
+ remoteURL: https://mcp.analytics.example.com
+ # ... other config ...
+
+ headerForward:
+ addPlaintextHeaders:
+ X-Tenant-ID: 'tenant-123'
+ X-Correlation-ID: 'corr-abc-def-456'
+```
+
+For sensitive values like API keys, reference Kubernetes Secrets using
+`addHeadersFromSecret`. First, create a Secret containing the header value:
+
+```yaml title="api-key-secret.yaml"
+apiVersion: v1
+kind: Secret
+metadata:
+ name: api-key-secret
+ namespace: toolhive-system
+type: Opaque
+stringData:
+ api-key: 'your-api-key-value'
+```
+
+Then reference the Secret in your MCPRemoteProxy:
+
+```yaml title="analytics-proxy.yaml" {12-17}
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRemoteProxy
+metadata:
+ name: analytics-proxy
+ namespace: toolhive-system
+spec:
+ remoteURL: https://mcp.analytics.example.com
+ # ... other config ...
+
+ headerForward:
+ addHeadersFromSecret:
+ - headerName: 'X-API-Key'
+ valueSecretRef:
+ name: api-key-secret
+ key: api-key
+```
+
+You can combine plaintext and secret-backed headers:
+
+```yaml {6-14}
+spec:
+ remoteURL: https://mcp.analytics.example.com
+ # ... other config ...
+
+ headerForward:
+ addPlaintextHeaders:
+ X-Tenant-ID: 'tenant-123'
+ X-Request-Source: 'toolhive-proxy'
+ addHeadersFromSecret:
+ - headerName: 'X-API-Key'
+ valueSecretRef:
+ name: api-key-secret
+ key: api-key
+```
+
+:::warning[Security considerations]
+
+- Plaintext header values are visible when you inspect the full resource (e.g.,
+ `kubectl get ... -o yaml` or `kubectl describe`). For sensitive values (API
+ keys, tokens), always use `addHeadersFromSecret`.
+- Secret-backed header values are stored in Kubernetes Secrets and resolved at
+ runtime. Only secret references (not actual values) appear in ConfigMaps used
+ internally by ToolHive.
+- Certain headers cannot be configured for security reasons, including `Host`,
+ `Connection`, `Transfer-Encoding`, and proxy-related headers like
+ `X-Forwarded-For`.
+
+:::
+
+## Quick start example
+
+For testing and development, you can use the public MCP specification server:
+
+```yaml title="mcp-spec-proxy.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRemoteProxy
+metadata:
+ name: mcp-spec-proxy
+ namespace: toolhive-system
+spec:
+ remoteURL: https://modelcontextprotocol.io/mcp
+ port: 8080
+ transport: streamable-http
+
+ # For testing - use your IDP's configuration
+ oidcConfig:
+ type: inline
+ inline:
+ issuer: https://auth.company.com/realms/test
+ audience: mcp-test
+
+ # Simple allow-all policy for testing
+ authzConfig:
+ type: inline
+ inline:
+ policies:
+ - |
+ permit(
+ principal,
+ action,
+ resource
+ );
+
+ audit:
+ enabled: true
+
+ resources:
+ limits:
+ cpu: '100m'
+ memory: 128Mi
+ requests:
+ cpu: '50m'
+ memory: 64Mi
+```
+
+Apply it and test:
+
+```bash
+kubectl apply -f mcp-spec-proxy.yaml
+
+# Check status
+kubectl get mcpremoteproxy -n toolhive-system
+
+# Port-forward to test locally
+kubectl port-forward -n toolhive-system svc/mcp-mcp-spec-proxy-remote-proxy 8080:8080
+
+# Get a token from your IDP and test
+curl -X POST http://localhost:8080/mcp \
+ -H "Authorization: Bearer " \
+ -H "Content-Type: application/json" \
+ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
+```
+
+## Expose the proxy externally
+
+To make the proxy accessible from outside the cluster, create an Ingress
+resource:
+
+```yaml title="proxy-ingress.yaml"
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: analytics-proxy-ingress
+ namespace: toolhive-system
+ annotations:
+ # Preserve the path when forwarding
+ nginx.ingress.kubernetes.io/rewrite-target: /
+spec:
+ ingressClassName: nginx
+ rules:
+ - host: analytics-mcp.company.com
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: mcp-analytics-proxy-remote-proxy
+ port:
+ number: 8080
+ # Optional: TLS configuration
+ tls:
+ - hosts:
+ - analytics-mcp.company.com
+ secretName: analytics-proxy-tls
+```
+
+Apply the Ingress:
+
+```bash
+kubectl apply -f proxy-ingress.yaml
+```
+
+Users can now access the proxy at `https://analytics-mcp.company.com`.
+
+## Check remote proxy status
+
+To check the status of your remote proxies:
+
+```bash
+# List all proxies
+kubectl get mcpremoteproxy -n toolhive-system
+
+# Get detailed information
+kubectl describe mcpremoteproxy analytics-proxy -n toolhive-system
+
+# Check proxy logs
+kubectl logs -n toolhive-system -l app.kubernetes.io/instance=analytics-proxy
+```
+
+The status shows:
+
+- **Phase**: Current state (Pending, Ready, Failed, Terminating)
+- **URL**: Internal cluster URL
+- **External URL**: External URL if exposed via Ingress
+- **Conditions**: Detailed status conditions
+
+## Telemetry and observability
+
+### Audit logging
+
+When `audit.enabled: true`, the proxy logs all requests with:
+
+- User identity (from token claims)
+- Request method and parameters
+- Response status and duration
+- Timestamp
+
+Audit logs are structured JSON written to stdout:
+
+```json
+{
+ "loggedAt": "2025-10-29T10:15:30.123Z",
+ "type": "mcp_tools_call",
+ "outcome": "success",
+ "source": "remote-proxy",
+ "component": "analytics-proxy",
+ "subjects": {
+ "user": "jane.doe@company.com",
+ "user_id": "user-123"
+ },
+ "target": {
+ "method": "tools/call",
+ "name": "query_data"
+ },
+ "metadata": {
+ "auditId": "550e8400-e29b-41d4-a716-446655440000",
+ "extra": {
+ "duration_ms": 234,
+ "transport": "http"
+ }
+ }
+}
+```
+
+### Prometheus metrics
+
+Enable Prometheus metrics to monitor proxy health and usage:
+
+```yaml {6-8}
+spec:
+ remoteURL: https://mcp.example.com
+ # ... other config ...
+
+ telemetry:
+ prometheus:
+ enabled: true
+```
+
+Metrics are exposed at `/metrics` and include:
+
+- `toolhive_mcp_requests_total` - Total MCP requests
+- `toolhive_mcp_request_duration_seconds` - Request duration
+- `toolhive_mcp_tool_calls_total` - Tool call operations
+- `toolhive_mcp_active_connections` - Number of active connections
+
+### OpenTelemetry
+
+For distributed tracing and metrics export:
+
+```yaml {6-15}
+spec:
+ remoteURL: https://mcp.example.com
+ # ... other config ...
+
+ telemetry:
+ openTelemetry:
+ enabled: true
+ endpoint: https://otel-collector.company.com:4317
+ serviceName: analytics-mcp-proxy
+ tracing:
+ enabled: true
+ samplingRate: '0.1'
+ metrics:
+ enabled: true
+```
+
+See [Telemetry and metrics](./telemetry-and-metrics.mdx) for more information.
+
+## Use with Virtual MCP Server
+
+MCPRemoteProxy resources can be added to an MCPGroup and discovered by a
+VirtualMCPServer, enabling you to combine remote MCP servers with local
+container-based MCPServer resources into a single unified endpoint.
+
+:::caution[Current limitation]
+
+vMCP can discover MCPRemoteProxy backends in a group, but authentication between
+vMCP and MCPRemoteProxy is not yet fully implemented. Since MCPRemoteProxy
+requires `oidcConfig` to validate incoming requests, and vMCP does not currently
+forward authentication tokens to backends, vMCP cannot communicate with
+MCPRemoteProxy backends that require authentication.
+
+This limitation will be addressed in a future release. For now, MCPRemoteProxy
+works best when accessed directly by clients rather than through vMCP
+aggregation.
+
+:::
+
+### Add remote proxy to a group
+
+To include a remote proxy in an MCPGroup for future vMCP aggregation, add the
+`groupRef` field to your MCPRemoteProxy spec:
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRemoteProxy
+metadata:
+ name: context7-proxy
+ namespace: toolhive-system
+spec:
+ groupRef: my-group # Reference to an MCPGroup
+ remoteURL: https://mcp.context7.com/mcp
+ transport: streamable-http
+ port: 8080
+ oidcConfig:
+ type: inline
+ inline:
+ issuer: https://auth.company.com
+ audience: context7-proxy
+```
+
+### Planned benefits of vMCP aggregation
+
+When vMCP authentication to MCPRemoteProxy is implemented, the following
+benefits will be available:
+
+- **Unified endpoint**: Clients connect to one vMCP URL instead of multiple
+ proxy endpoints
+- **Centralized authentication**: vMCP handles client authentication at the
+ entry point
+- **Tool namespacing**: Tools from remote proxies are automatically prefixed to
+ avoid conflicts with local MCP servers
+- **Unified toolset**: Combine tools from container-based servers and external
+ SaaS MCP services
+
+See [Configure vMCP servers](../guides-vmcp/configuration.mdx) for more vMCP
+configuration options.
+
+## Next steps
+
+See the [Client compatibility](../reference/client-compatibility.mdx) reference
+to learn how to connect to remote MCP proxies using different clients.
+
+Learn how to customize MCP tools using
+[filters and overrides](./customize-tools.mdx).
+
+Discover your deployed MCP servers automatically using the
+[Kubernetes registry](../guides-registry/configuration.mdx#kubernetes-registry)
+feature in the ToolHive Registry Server.
+
+## Related information
+
+- [Kubernetes CRD reference](../reference/crd-spec.md#apiv1alpha1mcpremoteproxy) -
+ Full MCPRemoteProxy specification
+- [Deploy the operator](./deploy-operator.mdx) - Install the ToolHive operator
+- [Run MCP servers in Kubernetes](./run-mcp-k8s.mdx) - Deploy local MCP servers
+
+## Troubleshooting
+
+
+Proxy pod in CrashLoopBackOff
+
+If the proxy pod is restarting continuously:
+
+```bash
+# Check pod status
+kubectl get pods -n toolhive-system -l app.kubernetes.io/instance=analytics-proxy
+
+# Check pod logs
+kubectl logs -n toolhive-system -l app.kubernetes.io/instance=analytics-proxy
+```
+
+Common causes:
+
+- **Invalid OIDC configuration**: Verify the issuer URL is accessible and
+ returns valid OIDC discovery metadata
+- **Certificate validation issues**: If using a custom CA, ensure
+ `thvCABundlePath` is set correctly
+- **Resource limits**: Check if the pod has sufficient CPU and memory
+
+
+
+
+401 Unauthorized errors
+
+If clients receive 401 errors when calling the proxy:
+
+```bash
+# Check proxy logs for authentication errors
+kubectl logs -n toolhive-system -l app.kubernetes.io/instance=analytics-proxy | grep -i "auth"
+```
+
+Common causes:
+
+- **Missing audience claim**: Ensure tokens include the expected `aud` claim
+- **Token expired**: Check token expiration time
+- **Issuer mismatch**: Verify token `iss` claim matches `oidcConfig.issuer`
+- **JWKS fetch failure**: Check proxy can reach the OIDC provider's JWKS
+ endpoint
+
+Verify your token claims:
+
+```bash
+# Decode JWT payload (requires jq)
+# This handles base64url and missing padding
+echo "" | cut -d. -f2 | tr '_-' '/+' | awk '{l=length($0)%4; if(l>0) printf "%s", substr("====",1,4-l); print $0}' | base64 -d 2>/dev/null | jq
+```
+
+:::note
+
+JWTs use base64url encoding and may omit padding characters (`=`). The command
+above converts base64url to standard base64 and adds padding if needed. If you
+get an "invalid input" error, check your shell and base64 version, or manually
+add padding to the payload.
+
+:::
+
+
+
+
+Remote server unreachable
+
+If the proxy cannot connect to the remote MCP server:
+
+```bash
+# Check proxy logs
+kubectl logs -n toolhive-system -l app.kubernetes.io/instance=analytics-proxy
+
+# Test connectivity from the pod
+kubectl exec -n toolhive-system -- \
+ curl -v https://mcp.analytics.example.com
+```
+
+Common causes:
+
+- **Network policies**: Check if network policies allow egress to the remote
+ server
+- **DNS resolution**: Verify the remote URL resolves correctly
+- **Firewall rules**: Ensure the cluster can reach the remote server
+- **Certificate issues**: If using HTTPS, verify certificates are valid
+
+
+
+
+Tool filtering not working
+
+If tool filtering isn't being applied:
+
+```bash
+# Check if MCPToolConfig exists
+kubectl get mcptoolconfig -n toolhive-system
+
+# Verify the reference in MCPRemoteProxy
+kubectl get mcpremoteproxy analytics-proxy -n toolhive-system -o yaml | \
+ grep -A2 toolConfigRef
+```
+
+Ensure:
+
+- The MCPToolConfig exists in the same namespace as the MCPRemoteProxy
+- The `toolConfigRef.name` matches the MCPToolConfig name
+- The MCPRemoteProxy status shows the config is applied
+
+
+
+
+Token exchange failing
+
+If token exchange is configured but not working:
+
+```bash
+# Check for token exchange errors in logs
+kubectl logs -n toolhive-system -l app.kubernetes.io/instance=analytics-proxy | \
+ grep -i "token.*exchange"
+```
+
+Verify:
+
+- The MCPExternalAuthConfig exists and is correctly configured
+- The client secret is valid and stored in a Kubernetes Secret
+- The token exchange endpoint is reachable from the proxy
+- The company IDP is configured for token exchange (RFC 8693)
+
+
+
+
+Getting more debug information
+
+For additional debugging:
+
+```bash
+# Get all resources related to your proxy
+kubectl get all -n toolhive-system -l app.kubernetes.io/instance=analytics-proxy
+
+# Export MCPRemoteProxy for inspection
+kubectl get mcpremoteproxy analytics-proxy -n toolhive-system -o yaml
+
+# Check operator logs
+kubectl logs -n toolhive-system -l app.kubernetes.io/name=toolhive-operator
+
+# Check events
+kubectl get events -n toolhive-system --sort-by='.lastTimestamp' | \
+ grep analytics-proxy
+```
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-k8s/run-mcp-k8s.mdx b/versioned_docs/version-1.0/toolhive/guides-k8s/run-mcp-k8s.mdx
new file mode 100644
index 00000000..339d6f9c
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-k8s/run-mcp-k8s.mdx
@@ -0,0 +1,689 @@
+---
+title: Run MCP servers in Kubernetes
+description: How to deploy MCP servers in Kubernetes using the ToolHive operator
+---
+
+## Prerequisites
+
+- A Kubernetes cluster (current and two previous minor versions are supported)
+- Permissions to create resources in the cluster
+- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) configured to communicate
+ with your cluster
+- The ToolHive operator installed in your cluster (see
+ [Deploy the operator](./deploy-operator.mdx))
+
+## Overview
+
+The ToolHive operator deploys MCP servers in Kubernetes by creating proxy pods
+that manage the actual MCP server containers.
+
+:::tip
+
+If you need to build a container image for an MCP server that doesn't already
+have one available, see the
+[Build MCP containers](../guides-cli/build-containers.mdx) guide to learn how to
+quickly create container images using the ToolHive CLI.
+
+:::
+
+### High-level architecture
+
+This diagram shows the basic relationship between components. The ToolHive
+operator watches for `MCPServer` resources and automatically creates the
+necessary infrastructure to run your MCP servers securely within the cluster.
+
+```mermaid
+flowchart LR
+ Client["Client"] -->|connects| Proxy["ToolHive Proxy/Runner"]
+ Proxy --> MCP["MCP Server"]
+
+ subgraph K8s["Kubernetes Cluster"]
+ subgraph NS["toolhive-system"]
+ Operator["ToolHive Operator"]
+ end
+ subgraph NS2["MCP Server Namespace"]
+ Proxy
+ MCP
+ end
+ end
+
+ Operator -.->|creates| Proxy
+ Proxy -.->|creates| MCP
+```
+
+### STDIO transport flow
+
+For MCP servers using STDIO transport, the proxy directly attaches to the MCP
+server pod's standard input/output streams.
+
+```mermaid
+flowchart LR
+ subgraph Proxy["Created by Operator"]
+ direction TB
+ ProxyService["SVC: ToolHive-Proxy"]
+ ProxyPod["POD: ToolHive-Proxy"]
+ ProxyService --> ProxyPod
+ end
+
+ subgraph MCP["Created by Proxy"]
+ direction TB
+ MCPPod["POD: MCP Server"]
+ end
+
+ Client["Client"] -->|HTTP/SSE| Proxy
+ Proxy -->|Attaches/STDIO| MCP
+```
+
+:::info[STDIO Transport Limitation]
+
+MCP servers using STDIO transport support only a single client connection at a
+time. If you need to support multiple users or concurrent client connections,
+use SSE (Server-Sent Events) or Streamable HTTP transport instead.
+
+:::
+
+### Streamable HTTP and SSE transport flow
+
+For MCP servers using Server-Sent Events (SSE) or Streamable HTTP transport, the
+proxy creates both a pod and a headless service. This allows direct HTTP/SSE or
+HTTP/Streamable HTTP communication between the proxy and MCP server while
+maintaining network isolation and service discovery.
+
+```mermaid
+flowchart LR
+ subgraph Proxy["Created by Operator"]
+ direction TB
+ ProxyService["SVC: ToolHive-Proxy"]
+ ProxyPod["POD: ToolHive-Proxy"]
+ ProxyService --> ProxyPod
+ end
+
+ subgraph MCP["Created by Proxy"]
+ direction TB
+ MCPService["SVC: MCP-Headless"]
+ MCPPod["POD: MCP Server"]
+ end
+
+ Client["Client"] -->|HTTP| Proxy
+ Proxy -->|HTTP| MCP
+ MCPService --> MCPPod
+```
+
+## Create an MCP server
+
+You can create `MCPServer` resources in namespaces based on how the operator was
+deployed.
+
+- **Cluster mode (default)**: Create MCPServer resources in any namespace
+- **Namespace mode**: Create MCPServer resources only in allowed namespaces
+
+See [Deploy the operator](./deploy-operator.mdx#operator-deployment-modes) to
+learn about the different deployment modes.
+
+To create an MCP server, define an `MCPServer` resource and apply it to your
+cluster. This minimal example creates the
+[`osv` MCP server](https://github.com/StacklokLabs/osv-mcp) which queries the
+[Open Source Vulnerability (OSV) database](https://osv.dev/) for vulnerability
+information.
+
+```yaml title="my-mcpserver.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: osv
+ namespace: my-namespace # Update with your namespace
+spec:
+ image: ghcr.io/stackloklabs/osv-mcp/server
+ transport: streamable-http
+ mcpPort: 8080
+ proxyPort: 8080
+ resources:
+ limits:
+ cpu: '100m'
+ memory: '128Mi'
+ requests:
+ cpu: '50m'
+ memory: '64Mi'
+```
+
+Apply the resource:
+
+```bash
+kubectl apply -f my-mcpserver.yaml
+```
+
+:::info[What's happening?]
+
+When you apply an `MCPServer` resource, here's what happens:
+
+1. The ToolHive operator detects the new resource (if it's in an allowed
+ namespace)
+2. The operator automatically creates the necessary RBAC resources in the target
+ namespace:
+ - A ServiceAccount with the same name as the MCPServer
+ - A Role with minimal permissions for StatefulSets, Services, Pods, and Pod
+ logs/attach operations
+ - A RoleBinding that connects the ServiceAccount to the Role
+3. The operator creates a new Deployment containing a ToolHive proxy pod and
+ service to handle client connections
+4. The proxy creates the actual `MCPServer` pod containing your specified
+ container image
+5. For STDIO transport, the proxy attaches directly to the pod; for SSE and
+ Streamable HTTP transport, a headless service is created for direct pod
+ communication
+6. Clients can now connect through the service → proxy → MCP server chain to use
+ the tools and resources (note: external clients will need an ingress
+ controller or similar mechanism to access the service from outside the
+ cluster)
+
+:::
+
+For more examples of `MCPServer` resources, see the
+[example MCP server manifests](https://github.com/stacklok/toolhive/tree/main/examples/operator/mcp-servers)
+in the ToolHive repo.
+
+## Automatic RBAC management
+
+The ToolHive operator automatically handles RBAC (Role-Based Access Control) for
+each MCPServer instance, providing better security isolation and multi-tenant
+support. Here's what the operator creates automatically:
+
+- **ServiceAccount**: A dedicated ServiceAccount with the same name as your
+ MCPServer
+- **Role**: A namespace-scoped Role with minimal permissions for:
+ - StatefulSets (create, get, list, watch, update, patch, delete)
+ - Services (create, get, list, watch, update, patch, delete)
+ - Pods (get, list, watch)
+ - Pod logs and attach operations (get, list)
+- **RoleBinding**: Connects the ServiceAccount to the Role
+
+This approach provides:
+
+- Each MCPServer operates with its own minimal set of permissions
+- No manual RBAC setup required
+- Better security isolation between different MCPServer instances
+- Support for multi-tenant deployments across different namespaces
+
+## Customize server settings
+
+You can customize the MCP server by adding additional fields to the `MCPServer`
+resource. The full specification is available in the
+[Kubernetes CRD reference](../reference/crd-spec.md#apiv1alpha1mcpserver).
+
+Below are some common configurations.
+
+### Customize the MCP server pod
+
+You can customize the MCP server pod that gets created by the proxy using the
+`podTemplateSpec` field. This gives you full control over the pod specification,
+letting you set security contexts, resource limits, node selectors, and other
+pod-level configurations.
+
+The `podTemplateSpec` field follows the standard Kubernetes
+[`PodTemplateSpec`](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-template-v1/#PodTemplateSpec)
+format, so you can use any valid pod specification options.
+
+This example sets resource limits.
+
+```yaml {14-15} title="my-mcpserver-custom-pod.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: fetch
+ namespace: development # Can be any namespace
+spec:
+ image: ghcr.io/stackloklabs/gofetch/server
+ transport: streamable-http
+ mcpPort: 8080
+ proxyPort: 8080
+ podTemplateSpec:
+ spec:
+ containers:
+ - name: mcp # This name must be "mcp"
+ resources: # These resources apply to the MCP container
+ limits:
+ cpu: '500m'
+ memory: '512Mi'
+ requests:
+ cpu: '100m'
+ memory: '128Mi'
+ resources: # These resources apply to the proxy container
+ limits:
+ cpu: '100m'
+ memory: '128Mi'
+ requests:
+ cpu: '50m'
+ memory: '64Mi'
+```
+
+:::info[Container name requirement]
+
+When customizing containers in `podTemplateSpec`, you must use `name: mcp` for
+the main container. This ensures the proxy can properly manage the MCP server
+process.
+
+:::
+
+### Run a server with secrets
+
+When your MCP servers require authentication tokens or other secrets, ToolHive
+supports multiple secrets management methods to fit your existing
+infrastructure. Choose the method that best suits your needs:
+
+
+
+
+ToolHive can reference existing Kubernetes secrets to inject sensitive data into
+your MCP server pods as environment variables. This example demonstrates how to
+pass a GitHub personal access token to the `github` MCP server.
+
+First, create the secret. The secret must exist in the same namespace as your
+MCP server and the key must match what you specify in the `MCPServer` resource.
+
+```bash
+kubectl -n production create secret generic github-token --from-literal=token=
+```
+
+Next, define the `MCPServer` resource to reference the secret:
+
+```yaml {10-13} title="my-mcpserver-with-secrets.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: github
+ namespace: production # Can be any namespace
+spec:
+ image: ghcr.io/github/github-mcp-server
+ transport: stdio
+ proxyPort: 8080
+ secrets:
+ - name: github-token
+ key: token
+ targetEnvName: GITHUB_PERSONAL_ACCESS_TOKEN
+```
+
+Finally, apply the MCPServer resource:
+
+```bash
+kubectl apply -f my-mcpserver-with-secrets.yaml
+```
+
+
+
+
+[External Secrets Operator](https://external-secrets.io/) is a Kubernetes
+operator that integrates external secret management systems and syncs secrets
+into Kubernetes as native resources. This example demonstrates how to use
+ESO-managed secrets with your MCP server.
+
+:::note
+
+When you use the External Secrets Operator, your MCP server definition will look
+the same as the Kubernetes-native example. This is because the External Secrets
+Operator creates standard Kubernetes secrets from external sources.
+
+:::
+
+First, create a secret using the
+[ExternalSecret resource](https://external-secrets.io/latest/api/externalsecret/).
+The exact configuration depends on your external secret management system. The
+secret must exist in the same namespace as your MCP server and the key must
+match what you specify in the `MCPServer` resource.
+
+Next, define the `MCPServer` resource to reference the secret:
+
+```yaml {10-13} title="my-mcpserver-with-secrets-eso.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: github
+ namespace: production # Can be any namespace
+spec:
+ image: ghcr.io/github/github-mcp-server
+ transport: stdio
+ proxyPort: 8080
+ secrets:
+ - name: github-token
+ key: token
+ targetEnvName: GITHUB_PERSONAL_ACCESS_TOKEN
+```
+
+Finally, apply the MCPServer resource:
+
+```bash
+kubectl apply -f my-mcpserver-with-secrets-eso.yaml
+```
+
+
+
+
+HashiCorp Vault provides multiple integration methods for Kubernetes
+environments:
+
+1. [Vault Sidecar Agent Injector](https://developer.hashicorp.com/vault/docs/deploy/kubernetes/injector),
+ which injects a sidecar container into your pod to fetch and renew secrets
+2. [Vault Secrets Operator](https://developer.hashicorp.com/vault/docs/deploy/kubernetes/vso),
+ which creates Kubernetes secrets from Vault secrets (similar to the External
+ Secrets Operator)
+3. [Vault CSI Provider](https://developer.hashicorp.com/vault/docs/deploy/kubernetes/csi),
+ which mounts secrets directly into your pod as files
+
+ToolHive supports the first two methods. When you use the Vault Secrets
+Operator, your MCP server definition will look the same as the Kubernetes-native
+example because the Vault Secrets Operator creates standard Kubernetes secrets
+from Vault.
+
+The Vault Sidecar Agent Injector requires additional configuration in your
+`MCPServer` resource to add the required annotations. For a complete example,
+see the [HashiCorp Vault integration tutorial](../integrations/vault.mdx).
+
+
+
+
+### Mount a volume
+
+You can mount volumes into the MCP server pod to provide persistent storage or
+access to data. This is useful for MCP servers that need to read/write files or
+access large datasets.
+
+To do this, add a standard `volumes` field to the `podTemplateSpec` in the
+`MCPServer` resource and a `volumeMounts` section in the container
+specification. Here's an example that mounts a persistent volume claim (PVC) to
+the `/projects` path in the Filesystem MCP server. The PVC must already exist in
+the same namespace as the MCPServer.
+
+```yaml {12-15,19-22} title="my-mcpserver-with-volume.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: filesystem
+ namespace: data-processing # Can be any namespace
+spec:
+ image: docker.io/mcp/filesystem
+ transport: stdio
+ proxyPort: 8080
+ podTemplateSpec:
+ spec:
+ volumes:
+ - name: my-mcp-data
+ persistentVolumeClaim:
+ claimName: my-mcp-data-claim
+ containers:
+ - name: mcp
+ # ... other container settings ...
+ volumeMounts:
+ - mountPath: /projects/my-mcp-data
+ name: my-mcp-data
+ readOnly: true
+```
+
+## Check MCP server status
+
+To check the status of your MCP servers in a specific namespace:
+
+```bash
+kubectl -n get mcpservers
+```
+
+To check MCP servers across all namespaces:
+
+```bash
+kubectl get mcpservers --all-namespaces
+```
+
+The status, URL, and age of each MCP server is displayed.
+
+For more details about a specific MCP server:
+
+```bash
+kubectl -n describe mcpserver
+```
+
+## Next steps
+
+Learn how to [connect clients to your MCP servers](./connect-clients.mdx) from
+outside the cluster using Ingress or Gateway API, or from applications running
+within the cluster. See the
+[Client compatibility](../reference/client-compatibility.mdx) reference for
+information about supported MCP clients.
+
+Learn how to customize MCP tools using
+[filters and overrides](./customize-tools.mdx).
+
+Collect telemetry data from your MCP servers by following the
+[Telemetry and metrics](./telemetry-and-metrics.mdx) guide. Configure audit
+logging by following the [Set up logging](./logging.mdx) guide.
+
+Discover your deployed MCP servers automatically using the
+[Kubernetes registry](../guides-registry/configuration.mdx#kubernetes-registry)
+feature in the ToolHive Registry Server.
+
+## Related information
+
+- [Kubernetes CRD reference](../reference/crd-spec.md#apiv1alpha1mcpserver) -
+ Reference for the `MCPServer` Custom Resource Definition (CRD)
+- [Deploy the operator](./deploy-operator.mdx) - Install the ToolHive operator
+- [Build MCP containers](../guides-cli/build-containers.mdx) - Create custom MCP
+ server container images
+
+## Troubleshooting
+
+
+MCPServer resource not creating pods
+
+If your `MCPServer` resource is created but no pods appear, first ensure you
+created the `MCPServer` resource in an allowed namespace. If the operator runs
+in namespace mode and you didn't include the namespace in the
+`allowedNamespaces` list, the operator ignores the resource. Check the
+operator's configuration:
+
+```bash
+helm get values toolhive-operator -n toolhive-system
+```
+
+Check the `operator.rbac.scope` and `operator.rbac.allowedNamespaces`
+properties. If the operator runs in `namespace` mode, add the namespace where
+you created the `MCPServer` to the `allowedNamespaces` list. See
+[Operator deployment modes](./deploy-operator.mdx#operator-deployment-modes).
+
+If the operator runs in `cluster` mode (default) or the `MCPServer` is in an
+allowed namespace, check the operator logs and resource status:
+
+```bash
+# Check MCPServer status
+kubectl -n describe mcpserver
+
+# Check operator logs
+kubectl -n toolhive-system logs -l app.kubernetes.io/name=toolhive-operator
+
+# Verify the operator is running
+kubectl -n toolhive-system get pods -l app.kubernetes.io/name=toolhive-operator
+```
+
+Other common causes include:
+
+- **Operator not running**: Ensure the ToolHive operator is deployed and running
+- **Invalid image reference**: Verify the container image exists and is
+ accessible
+- **RBAC issues**: The operator automatically creates RBAC resources, but check
+ for cluster-level permission issues
+- **Resource quotas**: Check if namespace resource quotas prevent pod creation
+
+
+
+
+MCP server pod fails to start
+
+If the MCP server pod is created but fails to start or is in `CrashLoopBackOff`:
+
+```bash
+# Check pod status
+kubectl -n get pods
+
+# Describe the failing pod
+kubectl -n describe pod
+
+# Check pod logs
+kubectl -n logs -c mcp
+```
+
+Common causes include:
+
+- **Image pull errors**: Verify the container image is accessible and the image
+ name is correct
+- **Missing secrets**: Ensure required secrets exist and are properly referenced
+- **Resource constraints**: Check if the pod has sufficient CPU and memory
+ resources
+- **Permission issues**: Verify the security context and RBAC permissions are
+ correctly configured
+- **Invalid arguments**: Check if the `args` field contains valid arguments for
+ the MCP server
+
+
+
+
+Proxy pod connection issues
+
+If the proxy pod is running but clients cannot connect:
+
+```bash
+# Check proxy pod status
+kubectl -n get pods -l app.kubernetes.io/instance=
+
+# Check proxy logs
+kubectl -n logs -l app.kubernetes.io/instance=
+
+# Verify service is created
+kubectl -n get services
+```
+
+Common causes include:
+
+- **Service not created**: Ensure the proxy service exists and has the correct
+ selectors
+- **Port configuration**: Verify the `port` field matches the MCP server's
+ listening port
+- **Transport mismatch**: Ensure the `transport` field
+ (stdio/sse/streamable-http) matches the MCP server's capabilities
+- **Network policies**: Check if network policies are blocking communication
+
+
+
+
+Secret mounting issues
+
+If secrets are not being properly mounted or environment variables are missing:
+
+```bash
+# Check if secret exists
+kubectl -n get secret
+
+# Verify secret content
+kubectl -n describe secret
+
+# Check environment variables in the pod
+kubectl -n exec -c mcp -- env | grep
+```
+
+Common causes include:
+
+- **Secret doesn't exist**: Create the secret in the correct namespace
+- **Wrong key name**: Ensure the `key` field matches the actual key in the
+ secret
+- **Namespace mismatch**: Secrets must be in the same namespace as the
+ `MCPServer`
+- **Permission issues**: The operator automatically creates the necessary RBAC
+ resources, but verify the ServiceAccount has access to read secrets
+
+
+
+
+Volume mounting problems
+
+If persistent volumes or other volumes are not mounting correctly:
+
+```bash
+# Check PVC status
+kubectl -n get pvc
+
+# Describe the PVC
+kubectl -n describe pvc
+
+# Check volume mounts in the pod
+kubectl -n describe pod
+```
+
+Common causes include:
+
+- **PVC not bound**: Ensure the PersistentVolumeClaim is bound to a
+ PersistentVolume
+- **Namespace mismatch**: The PVC must be in the same namespace as the MCPServer
+- **Storage class issues**: Verify the storage class exists and is available
+- **Access mode conflicts**: Check that the access mode is compatible with your
+ setup
+- **Mount path conflicts**: Ensure mount paths don't conflict with existing
+ directories
+
+
+
+
+Resource limit issues
+
+If pods are being killed due to resource constraints:
+
+```bash
+# Check resource usage
+kubectl -n top pods
+
+# Check for resource limit events
+kubectl -n get events --sort-by='.lastTimestamp'
+
+# Describe the pod for resource information
+kubectl -n describe pod
+```
+
+Solutions:
+
+- **Increase resource limits**: Adjust `resources.limits` in the `MCPServer`
+ spec
+- **Optimize resource requests**: Set appropriate `resources.requests` values
+- **Check node capacity**: Ensure cluster nodes have sufficient resources
+- **Review resource quotas**: Check namespace resource quotas and limits
+
+
+
+
+Debugging connectivity
+
+To test connectivity between components:
+
+```bash
+# Port-forward to test direct access to the proxy
+kubectl -n port-forward service/ 8080:8080
+
+# Test the connection locally
+curl http://localhost:8080/health
+
+# Check service endpoints
+kubectl -n get endpoints
+```
+
+
+
+
+Getting more debug information
+
+For additional debugging information:
+
+```bash
+# Get all resources related to your MCP server
+kubectl -n get all -l app.kubernetes.io/instance=
+
+# Check operator events
+kubectl -n get events --field-selector involvedObject.kind=MCPServer
+
+# Export MCPServer resource for inspection
+kubectl -n get mcpserver -o yaml
+```
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-k8s/telemetry-and-metrics.mdx b/versioned_docs/version-1.0/toolhive/guides-k8s/telemetry-and-metrics.mdx
new file mode 100644
index 00000000..5fb2d452
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-k8s/telemetry-and-metrics.mdx
@@ -0,0 +1,286 @@
+---
+title: Telemetry and metrics
+description: How to enable OpenTelemetry (metrics and traces) and Prometheus
+ instrumentation for ToolHive MCP servers inside of Kubernetes using the
+ ToolHive Operator
+---
+
+ToolHive includes built-in instrumentation using OpenTelemetry, which gives you
+comprehensive observability for your MCP server interactions. You can export
+traces and metrics to popular observability backends like Jaeger, Honeycomb,
+Datadog, and Grafana Cloud, or expose Prometheus metrics directly.
+
+## What you can monitor
+
+ToolHive's telemetry captures detailed information about MCP interactions
+including traces, metrics, and performance data. For a comprehensive overview of
+the telemetry architecture, metrics collection, and monitoring capabilities, see
+the [observability overview](../concepts/observability.mdx).
+
+## Enable telemetry
+
+You can enable telemetry when deploying an MCP server by specifying Telemetry
+configuration in the MCPServer or MCPRemoteProxy custom resource.
+
+This example runs the Fetch MCP server and exports traces to a deployed instance
+of the [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/):
+
+```yaml {12}
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer # or MCPRemoteProxy
+metadata:
+ name: gofetch
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stackloklabs/gofetch/server
+ transport: streamable-http
+ proxyPort: 8080
+ mcpPort: 8080
+ # ... other spec fields ...
+ telemetry:
+ openTelemetry:
+ enabled: true
+ endpoint: otel-collector-opentelemetry-collector.monitoring.svc.cluster.local:4318
+ serviceName: mcp-fetch-server
+ insecure: true
+ metrics:
+ enabled: true
+ tracing:
+ enabled: true
+ samplingRate: '0.05'
+ prometheus:
+ enabled: true
+```
+
+The `spec.telemetry.openTelemetry.endpoint` will be the OpenTelemetry collector
+that is deployed inside of your infrastructure, the
+`spec.telemetry.openTelemetry.serviceName` will be what you can use to identify
+your MCP server in your observability stack.
+
+### Export metrics to an OTLP endpoint
+
+If you want to enable ToolHive to export metrics to your OTel collector, you can
+enable the `spec.telemetry.openTelemetry.metrics.enabled` flag.
+
+### Export traces to an OTLP endpoint
+
+If you want to enable ToolHive to export tracing information, you can enable the
+`spec.telemetry.openTelemetry.tracing.enabled` flag.
+
+You can also set the sampling rate of your traces by setting the
+`spec.telemetry.openTelemetry.tracing.sampleRate` option to a number between 0
+and 1.0. By default this will be `0.05` which equates to 5% of all requests.
+
+:::note
+
+The `spec.telemetry.openTelemetry.endpoint` is provided as a hostname and
+optional port, without a scheme or path (e.g., use `api.honeycomb.io` or
+`api.honeycomb.io:443`, not `https://api.honeycomb.io`). ToolHive automatically
+uses HTTPS unless `--otel-insecure` is specified.
+
+:::
+
+By default, the service name is set to `toolhive-mcp-proxy`, and the sampling
+rate is `0.05` (5%).
+
+:::tip[Recommendation]
+
+Set the `spec.telemetry.openTelemetry.serviceName` flag to a meaningful name for
+each MCP server. This helps you identify the server in your observability
+backend.
+
+:::
+
+### Enable Prometheus metrics
+
+You can expose Prometheus-style metrics at `/metrics` on the main transport port
+for local scraping by enabling the `spec.telemetry.prometheus.enabled` flag.
+
+To access the metrics, you can use `curl` or any Prometheus-compatible scraper.
+The metrics are available at `http://:/metrics`, where `` is
+resolvable address of the ToolHive ProxyRunner fronting your MCP server pod and
+`` is the port of which the ProxyRunner service is configured to expose
+for traffic.
+
+### Dual export
+
+You can export to both an OTLP endpoint and expose Prometheus metrics
+simultaneously.
+
+The `MCPServer` example at the top of this page has dual export enabled.
+
+## Observability backends
+
+ToolHive can export telemetry data to many different observability backends. It
+supports exporting traces and metrics to any backend that implements the OTLP
+protocol. Some common examples are listed below, but specific configurations
+will vary based on your environment and requirements.
+
+### OpenTelemetry Collector (recommended)
+
+The OpenTelemetry Collector is a vendor-agnostic way to receive, process and
+export telemetry data. It supports many backend services, scalable deployment
+options, and advanced processing capabilities.
+
+```mermaid
+graph LR
+ A[ToolHive] -->|traces & metrics| B[OpenTelemetry Collector]
+ B --> C[AWS CloudWatch]
+ B --> D[Splunk]
+ B --> E[New Relic]
+ B <--> F[Prometheus]
+ B --> G[Other OTLP backends]
+```
+
+You can run the OpenTelemetry Collector inside of a Kubernetes cluster, follow
+the
+[OpenTelemetry Collector documentation](https://opentelemetry.io/docs/collector/)
+for more information.
+
+To export data to a local OpenTelemetry Collector, set your OTLP endpoint to the
+OTLP http receiver port (default is `4318`):
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: gofetch
+ namespace: toolhive-system
+spec:
+ ...
+ ...
+ telemetry:
+ openTelemetry:
+ enabled: true
+ endpoint: otel-collector-opentelemetry-collector.monitoring.svc.cluster.local:4318
+ serviceName: mcp-fetch-server
+ insecure: true
+ metrics:
+ enabled: true
+```
+
+### Prometheus
+
+To collect metrics using Prometheus, run your MCP server with the
+`spec.telemetry.prometheus.enabled` flag enabled and add the following to your
+Prometheus configuration:
+
+```yaml title="prometheus.yml"
+scrape_configs:
+ - job_name: 'toolhive-mcp-proxy'
+ static_configs:
+ - targets: [':']
+ scrape_interval: 15s
+ metrics_path: /metrics
+```
+
+You can add multiple MCP servers to the `targets` list. Replace
+`` with the ProxyRunner SVC name and
+`` with the port number exposed by the SVC.
+
+### Jaeger
+
+[Jaeger](https://www.jaegertracing.io) is a popular open-source distributed
+tracing system. You can run it inside of a Kubernetes cluster in order to store
+tracing telemetry data exported by the ToolHive proxy.
+
+You can export traces to Jaeger by setting the OTLP endpoint to an OpenTelemetry
+collector, and then configuring the collector to export tracing data to Jaeger.
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: gofetch
+ namespace: toolhive-system
+spec:
+ ...
+ ...
+ telemetry:
+ openTelemetry:
+ enabled: true
+ endpoint: otel-collector-opentelemetry-collector.monitoring.svc.cluster.local:4318
+ serviceName: mcp-fetch-server
+ insecure: true
+ tracing:
+ enabled: true
+```
+
+Inside of your OpenTelemetry collector configuration.
+
+```yaml
+config:
+ receivers:
+ otlp:
+ protocols:
+ grpc:
+ endpoint: 0.0.0.0:4317
+ http:
+ endpoint: 0.0.0.0:4318
+
+ exporters:
+ otlp/jaeger:
+ endpoint: http://jaeger-all-in-one-collector.monitoring:4317
+
+ service:
+ pipelines:
+ traces:
+ receivers: [otlp]
+ processors: [batch]
+ exporters: [otlp/jaeger]
+```
+
+### Honeycomb
+
+Coming soon.
+
+You'll need your Honeycomb API key, which you can find in your
+[Honeycomb account settings](https://ui.honeycomb.io/account).
+
+### Datadog
+
+Datadog has [multiple options](https://docs.datadoghq.com/opentelemetry/) for
+collecting OpenTelemetry data:
+
+- The
+ [**OpenTelemetry Collector**](https://docs.datadoghq.com/opentelemetry/setup/collector_exporter/)
+ is recommended for existing OpenTelemetry users or users wanting a
+ vendor-neutral solution.
+
+- The [**Datadog Agent**](https://docs.datadoghq.com/opentelemetry/setup/agent)
+ is recommended for existing Datadog users.
+
+### Grafana Cloud
+
+Coming soon.
+
+## Performance considerations
+
+### Sampling rates
+
+Adjust sampling rates based on your environment:
+
+- **Development**: `spec.telemetry.openTelemetry.tracing.samplingRate: 1.0`
+ (100% sampling)
+- **Production**: `spec.telemetry.openTelemetry.tracing.samplingRate 0.01` (1%
+ sampling for high-traffic systems)
+- **Default**: `spec.telemetry.openTelemetry.tracing.samplingRate 0.05` (5%
+ sampling)
+
+### Network overhead
+
+Telemetry adds minimal overhead when properly configured:
+
+- Use appropriate sampling rates for your traffic volume
+- Monitor your observability backend costs and adjust sampling accordingly
+
+## Related information
+
+- Tutorial:
+ [Collect telemetry for MCP workloads](../integrations/opentelemetry.mdx) -
+ Step-by-step guide to set up a local observability stack
+- [Telemetry and monitoring concepts](../concepts/observability.mdx) - Overview
+ of ToolHive's observability architecture
+- [Kubernetes CRD reference](../reference/crd-spec.md#apiv1alpha1mcpserver) -
+ Reference for the `MCPServer` Custom Resource Definition (CRD)
+- [Deploy the operator](./deploy-operator.mdx) - Install the ToolHive operator
diff --git a/versioned_docs/version-1.0/toolhive/guides-k8s/token-exchange-k8s.mdx b/versioned_docs/version-1.0/toolhive/guides-k8s/token-exchange-k8s.mdx
new file mode 100644
index 00000000..1befe9ff
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-k8s/token-exchange-k8s.mdx
@@ -0,0 +1,266 @@
+---
+title: Configure token exchange for backend authentication
+description:
+ How to set up token exchange so MCP servers can authenticate to backend
+ services in Kubernetes using the ToolHive Operator.
+---
+
+This guide shows you how to configure token exchange in Kubernetes, which allows
+MCP servers to authenticate to backend APIs using short-lived, properly scoped
+tokens instead of embedded secrets.
+
+For conceptual background on how token exchange works, see
+[Backend authentication](../concepts/backend-auth.mdx). For CLI-based setup, see
+[Configure token exchange](../guides-cli/token-exchange.mdx).
+
+## Prerequisites
+
+Before you begin, make sure you have:
+
+- Kubernetes cluster with RBAC enabled
+- ToolHive Operator installed (see
+ [Deploy the ToolHive Operator](./deploy-operator.mdx))
+- `kubectl` access to your cluster
+- An identity provider that supports
+ [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693) token exchange (such
+ as Okta, Auth0, or Keycloak)
+- A backend service configured to accept tokens from your identity provider
+- Familiarity with
+ [Authentication and authorization in Kubernetes](./auth-k8s.mdx)
+
+## Configure your identity provider
+
+Token exchange requires your identity provider to issue tokens for the backend
+service when presented with a valid MCP server token. This involves:
+
+- Registering a token exchange client with credentials
+- Defining audience and scopes for the backend service
+- Creating access policies that permit token exchange
+
+For detailed IdP configuration steps, see
+[Configure your identity provider](../guides-cli/token-exchange.mdx#configure-your-identity-provider)
+in the CLI guide.
+
+## Create the token exchange configuration
+
+### Step 1: Create a Secret for client credentials
+
+Store the OAuth client secret that ToolHive uses to authenticate when performing
+token exchange:
+
+```yaml title="token-exchange-secret.yaml"
+apiVersion: v1
+kind: Secret
+metadata:
+ name: token-exchange-secret
+ namespace: toolhive-system
+type: Opaque
+stringData:
+ client-secret: ''
+```
+
+```bash
+kubectl apply -f token-exchange-secret.yaml
+```
+
+### Step 2: Create the MCPExternalAuthConfig resource
+
+Create an `MCPExternalAuthConfig` resource that defines the token exchange
+parameters:
+
+```yaml title="token-exchange-config.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPExternalAuthConfig
+metadata:
+ name: backend-token-exchange
+ namespace: toolhive-system
+spec:
+ type: tokenExchange
+ tokenExchange:
+ tokenUrl: ''
+ audience: ''
+ clientId: ''
+ clientSecretRef:
+ name: token-exchange-secret
+ key: client-secret
+ scopes:
+ - ''
+```
+
+```bash
+kubectl apply -f token-exchange-config.yaml
+```
+
+### Configuration reference
+
+| Field | Description |
+| ----------------- | -------------------------------------------------------------- |
+| `tokenUrl` | Your identity provider's token exchange endpoint |
+| `audience` | Target audience for the exchanged token (your backend service) |
+| `clientId` | Client ID for ToolHive to authenticate to the IdP |
+| `clientSecretRef` | Reference to the Secret containing the client secret |
+| `scopes` | Scopes to request for the backend service |
+
+## MCP server requirements
+
+The MCP server that ToolHive fronts must accept a per-request authentication
+token. Specifically, the server should:
+
+- Read the access token from the `Authorization: Bearer` header
+- Use this token to authenticate to the backend service
+- Not rely on hardcoded secrets or environment variables for backend
+ authentication
+
+ToolHive injects the exchanged token into each request, so the MCP server
+receives a fresh, properly scoped token for every call.
+
+## Deploy an MCP server with token exchange
+
+Create an `MCPServer` resource that references the token exchange configuration:
+
+```yaml title="mcpserver-token-exchange.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: my-mcp-server
+ namespace: toolhive-system
+spec:
+ image:
+ transport: streamable-http
+ proxyPort: 8080
+ # Reference the token exchange configuration
+ externalAuthConfigRef:
+ name: backend-token-exchange
+ # OIDC configuration for validating incoming client tokens
+ oidcConfig:
+ type: inline
+ inline:
+ issuer: ''
+ audience: ''
+ jwksUrl: ''
+```
+
+```bash
+kubectl apply -f mcpserver-token-exchange.yaml
+```
+
+The `externalAuthConfigRef` tells ToolHive to use the token exchange
+configuration you created earlier. The `oidcConfig` validates incoming client
+tokens before performing the exchange.
+
+## Verify the configuration
+
+To confirm token exchange is working:
+
+1. Check the MCPServer status:
+
+ ```bash
+ kubectl get mcpserver -n toolhive-system my-mcp-server
+ ```
+
+2. Optionally, expose the server outside the cluster using an Ingress or Gateway
+
+3. Connect to the MCP server with a client that supports authentication
+
+4. Make a tool call that requires backend access
+
+5. Check the proxy logs for successful token exchange:
+
+ ```bash
+ kubectl logs -n toolhive-system -l app.kubernetes.io/name=my-mcp-server
+ ```
+
+You can also verify by examining your identity provider's logs for successful
+token exchange requests, or by checking audit logs on your backend service to
+confirm requests arrive with the correct user identity and scopes.
+
+## Example: Okta configuration
+
+This example shows a complete configuration using Okta for token exchange.
+
+### Secret
+
+```yaml title="okta-secret.yaml"
+apiVersion: v1
+kind: Secret
+metadata:
+ name: okta-token-exchange-secret
+ namespace: toolhive-system
+type: Opaque
+stringData:
+ client-secret: 'your-okta-client-secret'
+```
+
+### MCPExternalAuthConfig
+
+```yaml title="okta-token-exchange.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPExternalAuthConfig
+metadata:
+ name: okta-backend-exchange
+ namespace: toolhive-system
+spec:
+ type: tokenExchange
+ tokenExchange:
+ tokenUrl: 'https://dev-123456.okta.com/oauth2/aus9876543210/v1/token'
+ audience: 'backend-api'
+ clientId: '0oa0987654321fedcba'
+ clientSecretRef:
+ name: okta-token-exchange-secret
+ key: client-secret
+ scopes:
+ - 'api:read'
+ - 'api:write'
+```
+
+### MCPServer
+
+```yaml title="mcpserver-okta.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: my-backend-server
+ namespace: toolhive-system
+spec:
+ image: your-mcp-server:latest
+ transport: streamable-http
+ proxyPort: 8080
+ externalAuthConfigRef:
+ name: okta-backend-exchange
+ oidcConfig:
+ type: inline
+ # Set resourceUrl to the external URL if exposing outside the cluster
+ resourceUrl: 'https://my-backend-server.example.com'
+ inline:
+ issuer: 'https://dev-123456.okta.com/oauth2/aus1234567890'
+ audience: 'mcp-server'
+ jwksUrl: 'https://dev-123456.okta.com/oauth2/aus1234567890/v1/keys'
+```
+
+Key points in this example:
+
+- **Two authorization servers**: The `issuer` in `oidcConfig` (`aus1234567890`)
+ validates incoming client tokens. The `tokenUrl` in `MCPExternalAuthConfig`
+ uses a different authorization server (`aus9876543210`) that issues tokens for
+ the backend API.
+- **Audience transformation**: Client tokens arrive with audience `mcp-server`.
+ ToolHive exchanges them for tokens with audience `backend-api`, which the
+ backend service expects.
+- **Scope transformation**: The original token has MCP-specific scopes, while
+ the exchanged token has backend-specific scopes (`api:read`, `api:write`). The
+ user's identity is preserved, but the permissions are transformed for the
+ target service.
+
+## Related information
+
+- [Backend authentication](../concepts/backend-auth.mdx) - conceptual overview
+ of token exchange and federation
+- [Configure token exchange (CLI)](../guides-cli/token-exchange.mdx) - CLI-based
+ setup
+- [Authentication and authorization](./auth-k8s.mdx) - basic auth setup for MCP
+ servers in Kubernetes
+- [CRD specification](../reference/crd-spec.md) - complete CRD reference
+ including MCPExternalAuthConfig
+- [AWS STS integration](../integrations/aws-sts.mdx) - for AWS services,
+ ToolHive has built-in STS support using `MCPExternalAuthConfig` with
+ `type: awsSts`
diff --git a/versioned_docs/version-1.0/toolhive/guides-mcp/_template.mdx b/versioned_docs/version-1.0/toolhive/guides-mcp/_template.mdx
new file mode 100644
index 00000000..cbf8ba8e
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-mcp/_template.mdx
@@ -0,0 +1,60 @@
+---
+title: SERVER_NAME MCP server guide
+sidebar_label: SERVER_NAME
+description: Using the SERVER_NAME MCP server with ToolHive for PURPOSE.
+last_update:
+ author: YOUR_GITHUB_USERNAME
+ date: YYYY-MM-DD
+---
+
+## Overview
+
+A brief overview of the MCP server, its purpose, and key features. Link to the
+official documentation.
+
+## Metadata
+
+
+
+## Usage
+
+
+
+
+UI instructions go here. Only include a screenshot if it adds value, such as
+showing a unique configuration option or feature.
+
+
+
+
+CLI instructions go here, with multiple usage examples specific to the MCP
+server.
+
+```bash
+thv run ...
+```
+
+If appropriate, include guidance for using network isolation and/or a custom
+permission profile.
+
+
+
+
+Kubernetes manifest and instructions go here
+
+```yaml title="SERVER_NAME.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+```
+
+
+
+
+## Sample prompts
+
+Provide sample prompts that users can use to interact with the MCP server.
+
+## Recommended practices
+
+- Include some recommended practices for using the MCP server safely and
+ effectively
diff --git a/versioned_docs/version-1.0/toolhive/guides-mcp/context7.mdx b/versioned_docs/version-1.0/toolhive/guides-mcp/context7.mdx
new file mode 100644
index 00000000..c4139cf1
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-mcp/context7.mdx
@@ -0,0 +1,179 @@
+---
+title: Context7 MCP server guide
+sidebar_label: Context7
+description: Using the Context7 MCP server with ToolHive for up-to-date documentation.
+last_update:
+ author: danbarr
+ date: 2026-02-10
+---
+
+## Overview
+
+The [Context7 MCP server](https://github.com/upstash/context7) provides
+up-to-date, version-specific documentation and code examples straight from the
+source for any library or framework. Instead of relying on outdated training
+data, Context7 fetches current documentation and API references directly into
+your LLM's context, ensuring you get working code examples and accurate
+information.
+
+Context7 eliminates common issues like hallucinated APIs that don't exist,
+outdated code patterns, and generic answers based on old package versions. It
+supports thousands of popular libraries including Next.js, React, MongoDB,
+Supabase, and many more.
+
+Learn more at [context7.com](https://context7.com) and view the
+[project documentation](https://github.com/upstash/context7) for additional
+details.
+
+## Metadata
+
+
+
+## Usage
+
+While Context7 works without an API key, registering at
+[context7.com/dashboard](https://context7.com/dashboard) provides:
+
+- Higher rate limits
+- Priority access during peak usage
+- Better performance for frequent queries
+
+
+
+
+**Remote MCP server**
+
+If you have a Context7 account, you can select the `context7-remote` MCP server
+to connect to the hosted service with dynamic OAuth authentication. Your browser
+will open to authorize the server to access your Context7 account.
+
+**Local MCP server**
+
+Select the `context7` MCP server in the ToolHive registry.
+
+The server works without authentication for basic usage, but you can optionally
+add an API key in the **Secrets** section for higher rate limits.
+
+:::tip[Security tip]
+
+Enable outbound network filtering on the **Network Isolation** tab to restrict
+the server's network access using the default profile contained in the registry.
+
+:::
+
+
+
+
+If you have a Context7 account, you can run the remote MCP server with dynamic
+OAuth authentication. Your browser will open to authorize the server to access
+your Context7 account.
+
+```bash
+thv run context7-remote
+```
+
+Alternatively, run the local containerized MCP server with the default
+configuration (you don't need an API key for basic usage):
+
+```bash
+thv run context7
+```
+
+Enable [network isolation](../guides-cli/network-isolation.mdx) using the
+default profile from the registry to restrict the server's network access:
+
+```bash
+thv run --isolate-network context7
+```
+
+If you have a Context7 API key for higher rate limits, create a secret named
+`context7` containing your API key and run the server with the `--secret` flag:
+
+```bash
+thv secret set context7
+thv run --secret context7,target=CONTEXT7_API_KEY context7
+```
+
+Combine API key with network isolation:
+
+```bash
+thv run --secret context7,target=CONTEXT7_API_KEY --isolate-network context7
+```
+
+
+
+
+For basic usage without authentication:
+
+```yaml title="context7.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: context7
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stacklok/dockyard/npx/context7:2.1.1
+ transport: stdio
+ proxyPort: 8080
+```
+
+Apply the manifest to your cluster:
+
+```bash
+kubectl apply -f context7.yaml
+```
+
+If you have a Context7 API key for higher rate limits, create a Kubernetes
+secret containing your key:
+
+```bash
+kubectl -n toolhive-system create secret generic context7-api-key --from-literal=token=
+```
+
+Then add a `secrets` section to the manifest file above to pass the API key as
+an environment variable:
+
+```yaml title="context7-with-auth.yaml"
+spec:
+ # ...
+ secrets:
+ - name: context7-api-key
+ key: token
+ targetEnvName: CONTEXT7_API_KEY
+```
+
+
+
+
+## Sample prompts
+
+Here are practical prompts you can use with the Context7 MCP server:
+
+- "Create a Next.js middleware that checks for a valid JWT in cookies and
+ redirects unauthenticated users to `/login`. Use context7"
+- "Show me how to set up MongoDB connection pooling with the latest MongoDB
+ Node.js driver. Use context7"
+- "Configure a Supabase client with TypeScript types for a user authentication
+ system. Use context7"
+- "Create a React component using the latest Tailwind CSS utility classes for a
+ responsive navigation bar. Use context7"
+- "Show me how to implement server-side rendering with the current version of
+ Nuxt.js. Use context7"
+- "Configure Redis caching with the Upstash Redis SDK for serverless functions.
+ Use context7"
+
+## Recommended practices
+
+- Include `use context7` in your prompts to automatically fetch current
+ documentation for the libraries you're working with.
+- When you know the exact library, specify it directly using the Context7 ID
+ format, for example: `use library /supabase/supabase for api and docs`
+- Consider setting up a rule in your MCP client to automatically invoke Context7
+ for code-related queries, eliminating the need to manually add `use context7`
+ to each prompt.
+- Use the `topic` parameter when requesting documentation to focus on specific
+ areas like "routing", "authentication", or "deployment".
+- Register for an API key at
+ [context7.com/dashboard](https://context7.com/dashboard) if you plan to make
+ frequent requests or need higher rate limits.
+- Enable network isolation to restrict the server's outbound access.
diff --git a/versioned_docs/version-1.0/toolhive/guides-mcp/fetch.mdx b/versioned_docs/version-1.0/toolhive/guides-mcp/fetch.mdx
new file mode 100644
index 00000000..b9dc2226
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-mcp/fetch.mdx
@@ -0,0 +1,111 @@
+---
+title: Fetch MCP server guide
+sidebar_label: Fetch
+description: Using the Fetch MCP server with ToolHive to retrieve website data.
+last_update:
+ author: danbarr
+ date: 2026-02-10
+---
+
+## Overview
+
+A simple, lightweight MCP server that retrieves data from a website to add
+real-time context to an AI agent workflow.
+
+[GoFetch](https://github.com/StacklokLabs/gofetch) is a Go implementation of the
+original
+[Fetch MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch)
+with improved performance and security, built by Stacklok.
+
+## Metadata
+
+
+
+## Usage
+
+
+
+
+Select the `fetch` MCP server in the ToolHive registry. No additional
+configuration is required to run it.
+
+By default, it can access any website. To restrict its network access,
+[enable **network isolation**](../guides-ui/network-isolation.mdx) and enter the
+allowed hosts and ports.
+
+
+
+
+Run with the default configuration:
+
+```bash
+thv run fetch
+```
+
+To control which website resources the server can access, create a custom
+permission profile:
+
+```json title="fetch-profile.json"
+{
+ "network": {
+ "outbound": {
+ "insecure_allow_all": false,
+ "allow_host": [
+ "host.docker.internal",
+ "intranet.example.com",
+ ".googleapis.com"
+ ],
+ "allow_port": [443]
+ }
+ }
+}
+```
+
+Then run the server with the profile and
+[enable network isolation](../guides-cli/network-isolation.mdx):
+
+```bash
+thv run --isolate-network --permission-profile fetch-profile.json fetch
+```
+
+
+
+
+Create a Kubernetes manifest to deploy the Fetch MCP server:
+
+```yaml title="fetch.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: fetch
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stackloklabs/gofetch/server:1.0.3
+ transport: streamable-http
+ mcpPort: 8080
+ proxyPort: 8080
+```
+
+Apply the manifest to your Kubernetes cluster:
+
+```bash
+kubectl apply -f fetch.yaml
+```
+
+
+
+
+## Sample prompts
+
+Here are some sample prompts you can use to interact with the Fetch MCP server:
+
+- "Fetch the latest news from `https://news.ycombinator.com`"
+- "Get the current weather for `https://weather.com`"
+- "Retrieve the latest blog posts from `https://example.com/blog`"
+
+## Recommended practices
+
+- Use network isolation to restrict the server's outbound network access to the
+ specific hosts and ports required for your use case.
+- Enable [telemetry](../guides-cli/telemetry-and-metrics.mdx) to monitor tool
+ usage including URL access for security and auditing purposes.
diff --git a/versioned_docs/version-1.0/toolhive/guides-mcp/filesystem.mdx b/versioned_docs/version-1.0/toolhive/guides-mcp/filesystem.mdx
new file mode 100644
index 00000000..f739a60d
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-mcp/filesystem.mdx
@@ -0,0 +1,175 @@
+---
+title: Filesystem MCP server guide
+sidebar_label: Filesystem
+description: Using the Filesystem MCP server with ToolHive for file access.
+last_update:
+ author: danbarr
+ date: 2026-02-10
+---
+
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+
+## Overview
+
+The
+[Filesystem MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem)
+provides access to the local filesystem, allowing AI agents to read and write
+files as part of their workflows.
+
+:::note
+
+Since most AI agent host applications like IDEs already have access to your
+working directory, this MCP server is primarily useful for access to files
+outside your working directory, headless environments where the host application
+does not provide filesystem access, or for demonstrating MCP capabilities.
+
+:::
+
+## Metadata
+
+
+
+## Usage
+
+
+
+
+Select the `filesystem` MCP server in the ToolHive registry.
+
+In the **Storage volumes** section,
+[add local files or folders](../guides-ui/run-mcp-servers.mdx#volumes) to expose
+to the MCP server. In the drop-down, choose whether to mount the volume as
+read-only or read-write.
+
+:::note
+
+By default, the server expects files to be located in `/projects`. If you use a
+different container path, you must update the command arguments to replace
+`/projects` with your custom path.
+
+:::
+
+
+
+:::tip[Security tip]
+
+Since the server does not require any network access,
+[enable **network isolation**](../guides-ui/network-isolation.mdx) and do not
+add any hosts or ports to completely restrict its outbound network access.
+
+:::
+
+
+
+
+[Mount a directory](../guides-cli/filesystem-access.mdx) from the host
+filesystem to the MCP server using the default container path:
+
+```bash
+thv run --volume /path/to/host/directory:/projects filesystem
+```
+
+:::note
+
+By default, the server expects files to be located in `/projects`. If you use a
+different container path, you must update the command arguments to replace
+`/projects` with your custom path.
+
+:::
+
+Mount multiple files or directories by repeating the `--volume` flag. This
+example mounts a directory under `/projects` and a file under `/data`, and
+updates the command arguments accordingly:
+
+```bash
+thv run \
+ --volume /path/to/host/directory1:/projects/dir1 \
+ --volume /path/to/host/file.txt:/data/file.txt:ro \
+ filesystem -- /projects /data
+```
+
+:::tip
+
+Since the server does not require any network access, add the
+`--isolate-network --permission-profile none` flags to completely restrict its
+outbound network access (see the
+[network isolation guide](../guides-cli/network-isolation.mdx)).
+
+:::
+
+
+
+
+Create a Kubernetes manifest to deploy the Filesystem MCP server with a
+[persistent volume](../guides-k8s/run-mcp-k8s.mdx#mount-a-volume).
+
+Update the `podTemplateSpec` section to include your specific volume claim and
+mount path:
+
+```yaml {14-17,20-23} title="filesystem.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: filesystem
+ namespace: toolhive-system
+spec:
+ image: mcp/filesystem:1.0.2
+ transport: stdio
+ proxyPort: 8080
+ args:
+ - '/projects' # Update if you use a different mountPath below
+ podTemplateSpec:
+ spec:
+ volumes:
+ - name: my-mcp-data
+ persistentVolumeClaim:
+ claimName: my-mcp-data-claim
+ containers:
+ - name: mcp
+ volumeMounts:
+ - mountPath: /projects/my-mcp-data
+ name: my-mcp-data
+ readOnly: true
+```
+
+:::note
+
+If you change the mount path from `/projects`, you must also update the `args`
+section to include the path.
+
+:::
+
+Apply the manifest to your Kubernetes cluster:
+
+```bash
+kubectl apply -f filesystem.yaml
+```
+
+
+
+
+## Sample prompts
+
+Here are some sample prompts you can use to interact with the Filesystem MCP
+server:
+
+- "List all files in the `/projects` directory"
+- "Read the contents of the file `/projects/example.txt`"
+- "Write 'Hello, World!' to the file `/projects/hello.txt`"
+
+## Recommended practices
+
+- Mount only the directories or files required for your AI agent's tasks to
+ minimize resource usage and improve performance.
+- Use read-only mounts for directories or files that do not need to be modified
+ by the AI agent to prevent accidental changes.
+- Enable network isolation to restrict the server's outbound network access,
+ since the filesystem MCP server does not require any network connectivity.
diff --git a/versioned_docs/version-1.0/toolhive/guides-mcp/github.mdx b/versioned_docs/version-1.0/toolhive/guides-mcp/github.mdx
new file mode 100644
index 00000000..fff3e1e0
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-mcp/github.mdx
@@ -0,0 +1,211 @@
+---
+title: GitHub MCP server guide
+sidebar_label: GitHub
+description: Using the GitHub MCP server with ToolHive for repository management.
+last_update:
+ author: danbarr
+ date: 2026-02-10
+---
+
+## Overview
+
+The official [GitHub MCP server](https://github.com/github/github-mcp-server)
+provides access to the GitHub API, allowing AI agents to interact with GitHub
+repositories, issues, pull requests, and more.
+
+## Metadata
+
+
+
+## Usage
+
+
+
+
+Select the `github` MCP server in the ToolHive registry. In the **Secrets**
+section, add your GitHub personal access token to authenticate with the GitHub
+API, or select an existing secret that contains the token.
+
+Review the optional environment variables to customize the server's behavior.
+For example, you might want to limit the active toolsets or enable read-only
+mode. Refer to the
+[documentation](https://github.com/github/github-mcp-server?tab=readme-ov-file#tool-configuration)
+for the current list of toolsets.
+
+:::tip[Security tip]
+
+Enable outbound network filtering on the **Network Isolation** tab to restrict
+the server's network access using the default profile contained in the registry.
+
+:::
+
+:::info[GitHub Enterprise]
+
+If you're working with a GitHub Enterprise instance, enter the instance URL in
+the `GITHUB_HOST` environment variable and update the network isolation settings
+to allow access to the enterprise domain.
+
+:::
+
+
+
+
+Run with the default configuration. ToolHive will prompt you to enter your
+GitHub personal access token:
+
+```bash
+thv run github
+```
+
+Create a secret named `github` containing your GitHub personal access token and
+run the server with the `--secret` flag:
+
+```bash
+thv secret set github
+thv run --secret github,target=GITHUB_PERSONAL_ACCESS_TOKEN github
+```
+
+Or, use the GitHub CLI to populate the secret with your token:
+
+```bash
+gh auth token | thv secret set github
+thv run --secret github,target=GITHUB_PERSONAL_ACCESS_TOKEN github
+```
+
+Enable [network isolation](../guides-cli/network-isolation.mdx) using the
+default profile from the registry (appropriate for `github.com`) to restrict the
+server's network access:
+
+```bash
+thv run --isolate-network github
+```
+
+Limit the active toolsets (useful to avoid context overload) and enable
+read-only mode. Refer to the
+[documentation](https://github.com/github/github-mcp-server?tab=readme-ov-file#tool-configuration)
+for the current list of toolsets.
+
+```bash
+thv run -e GITHUB_TOOLSETS=repos,issues,pull_requests -e GITHUB_READ_ONLY=1 github
+```
+
+Enable the MCP server's dynamic tool discovery feature (currently in beta):
+
+```bash
+thv run -e GITHUB_DYNAMIC_TOOLSETS=1 github
+```
+
+:::info[GitHub Enterprise]
+
+Create a custom permission profile for your GitHub Enterprise instance:
+
+```json title="github-enterprise-profile.json"
+{
+ "network": {
+ "outbound": {
+ "insecure_allow_all": false,
+ "allow_host": ["github.your-enterprise.com"],
+ "allow_port": [443]
+ }
+ }
+}
+```
+
+Then run the server with the profile:
+
+```bash
+thv run \
+ -e GITHUB_HOST=https://github.your-enterprise.com \
+ --isolate-network --permission-profile github-enterprise-profile.json \
+ github
+```
+
+:::
+
+
+
+
+Create a Kubernetes secret containing your GitHub personal access token:
+
+```bash
+kubectl -n toolhive-system create secret generic github-token --from-literal=token=
+```
+
+Create a Kubernetes manifest to deploy the GitHub MCP server using your secret:
+
+```yaml {10-14} title="github.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: github
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/github/github-mcp-server:v0.31.0
+ transport: stdio
+ proxyPort: 8080
+ secrets:
+ - name: github-token
+ key: token
+ targetEnvName: GITHUB_PERSONAL_ACCESS_TOKEN
+```
+
+Apply the manifest to your Kubernetes cluster:
+
+```bash
+kubectl apply -f github.yaml
+```
+
+To customize the server's behavior, add environment variables to the `spec`
+section of your manifest. For example, to limit the active toolsets or enable
+read-only mode, add:
+
+```yaml title="github.yaml"
+spec:
+ # ...
+ env:
+ - name: GITHUB_TOOLSETS
+ value: 'repos,issues,pull_requests'
+ - name: GITHUB_READ_ONLY
+ value: '1'
+```
+
+Refer to the
+[documentation](https://github.com/github/github-mcp-server?tab=readme-ov-file#tool-configuration)
+for the current list of toolsets.
+
+:::info[GitHub Enterprise]
+
+If you're working with a GitHub Enterprise instance, add the `GITHUB_HOST`
+environment variable to the `spec` section of your manifest:
+
+```yaml
+spec:
+ # ...
+ env:
+ - name: GITHUB_HOST
+ value: 'https://github.your-enterprise.com'
+```
+
+:::
+
+
+
+
+## Sample prompts
+
+Here are some sample prompts you can use to interact with the GitHub MCP server:
+
+- "List all repositories for the organization `my-org`"
+- "Create a new issue in the repository `my-org/my-repo` with the title 'Bug
+ report' and the body 'There is a bug in the code'"
+- "Get the latest pull requests for the repository `my-org/my-repo`"
+
+## Recommended practices
+
+- Scope your GitHub personal access token to the minimum permissions required
+ for your use case.
+- Regularly rotate your GitHub personal access token and update the secret in
+ ToolHive.
+- Enable network isolation to restrict the server's outbound network access.
+- Limit the active toolsets to reduce context overload and improve performance,
+ or use dynamic tool discovery if supported by your client.
diff --git a/versioned_docs/version-1.0/toolhive/guides-mcp/grafana.mdx b/versioned_docs/version-1.0/toolhive/guides-mcp/grafana.mdx
new file mode 100644
index 00000000..5b6066e2
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-mcp/grafana.mdx
@@ -0,0 +1,234 @@
+---
+title: Grafana MCP server guide
+sidebar_label: Grafana
+description:
+ Using the Grafana MCP server with ToolHive for dashboard management,
+ datasource queries, alerting, and incident response.
+last_update:
+ author: danbarr
+ date: 2026-02-10
+---
+
+## Overview
+
+The official [Grafana MCP server](https://github.com/grafana/mcp-grafana)
+provides comprehensive access to your Grafana instance and its surrounding
+ecosystem. With support for dashboards, datasource queries (Prometheus, Loki,
+Pyroscope), alerting, incident management, Grafana OnCall, and Sift
+investigations, this server enables AI agents to interact with your entire
+observability stack.
+
+The server works with both local Grafana instances and Grafana Cloud, making it
+ideal for tasks like troubleshooting production issues, analyzing metrics and
+logs, managing dashboards, and coordinating incident response.
+
+## Metadata
+
+
+
+## Usage
+
+You'll need a Grafana service account token to authenticate with the Grafana
+API. The token must have permissions for the Grafana features you want to access
+(such as dashboards, datasources, or alerting). Refer to the
+[Grafana service account documentation](https://grafana.com/docs/grafana/latest/administration/service-accounts/)
+for details on creating tokens and configuring permissions.
+
+
+
+
+Select the `grafana` MCP server in the ToolHive registry.
+
+In the **Secrets** section, add your Grafana service account token or select an
+existing secret that contains the token.
+
+In the **Environment Variables** section, configure the connection to your
+Grafana instance:
+
+- `GRAFANA_URL`: Your Grafana instance URL (for example, `http://localhost:3000`
+ for local instances or `https://myinstance.grafana.net` for Grafana Cloud)
+- `GRAFANA_ORG_ID` (optional): The numeric organization ID if your Grafana
+ instance has multiple organizations
+
+:::tip[Security tip]
+
+Enable outbound network filtering on the **Network Isolation** tab to restrict
+the server's network access. Update the allowed hosts to match your Grafana
+instance domain.
+
+:::
+
+
+
+
+Create a secret containing your Grafana service account token:
+
+```bash
+thv secret set grafana-token
+```
+
+Run the server with your Grafana instance URL and the secret:
+
+```bash
+thv run \
+ -e GRAFANA_URL=http://localhost:3000 \
+ --secret grafana-token,target=GRAFANA_SERVICE_ACCOUNT_TOKEN \
+ grafana
+```
+
+For Grafana Cloud, use your cloud instance URL:
+
+```bash
+thv run \
+ -e GRAFANA_URL=https://myinstance.grafana.net \
+ --secret grafana-token,target=GRAFANA_SERVICE_ACCOUNT_TOKEN \
+ grafana
+```
+
+Enable [network isolation](../guides-cli/network-isolation.mdx) to restrict the
+server's network access. Create a permission profile with your Grafana instance
+domain:
+
+```json title="grafana-profile.json"
+{
+ "network": {
+ "outbound": {
+ "insecure_allow_all": false,
+ "allow_host": ["myinstance.grafana.net"],
+ "allow_port": [443]
+ }
+ }
+}
+```
+
+Then run with the custom profile:
+
+```bash
+thv run \
+ -e GRAFANA_URL=https://myinstance.grafana.net \
+ --secret grafana-token,target=GRAFANA_SERVICE_ACCOUNT_TOKEN \
+ --isolate-network --permission-profile grafana-profile.json \
+ grafana
+```
+
+If your Grafana instance has multiple organizations, add the `GRAFANA_ORG_ID`
+environment variable with the numeric organization ID (for example,
+`-e GRAFANA_ORG_ID=2`).
+
+:::tip[Debug mode]
+
+Add the `--` separator followed by `-debug` to enable detailed logging of HTTP
+requests and responses:
+
+```bash
+thv run \
+ -e GRAFANA_URL=http://localhost:3000 \
+ --secret grafana-token,target=GRAFANA_SERVICE_ACCOUNT_TOKEN \
+ -- -debug
+```
+
+:::
+
+
+
+
+Create a Kubernetes secret containing your Grafana service account token:
+
+```bash
+kubectl -n toolhive-system create secret generic grafana-token \
+ --from-literal=token=
+```
+
+Create a Kubernetes manifest to deploy the Grafana MCP server, specifying your
+Grafana instance URL and referencing the secret. This example uses a Grafana
+instance running in an `observability` namespace in the cluster:
+
+```yaml title="grafana.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: grafana
+ namespace: toolhive-system
+spec:
+ image: docker.io/grafana/mcp-grafana:0.11.2
+ transport: streamable-http
+ mcpPort: 8000
+ proxyPort: 8080
+ args:
+ - --transport
+ - streamable-http
+ env:
+ - name: GRAFANA_URL
+ value: 'http://grafana.observability.svc.cluster.local:3000'
+ secrets:
+ - name: grafana-token
+ key: token
+ targetEnvName: GRAFANA_SERVICE_ACCOUNT_TOKEN
+```
+
+Apply the manifest to your Kubernetes cluster:
+
+```bash
+kubectl apply -f grafana.yaml
+```
+
+For Grafana Cloud, update the `GRAFANA_URL` in the manifest:
+
+```yaml
+spec:
+ env:
+ - name: GRAFANA_URL
+ value: 'https://myinstance.grafana.net'
+```
+
+If your Grafana instance has multiple organizations, add the `GRAFANA_ORG_ID`
+environment variable with the numeric organization ID:
+
+```yaml
+spec:
+ env:
+ - name: GRAFANA_URL
+ value: 'http://grafana.observability.svc.cluster.local:3000'
+ - name: GRAFANA_ORG_ID
+ value: '2'
+```
+
+
+
+
+## Sample prompts
+
+Here are some sample prompts you can use to interact with the Grafana MCP
+server:
+
+- "Show me all dashboards related to Kubernetes monitoring"
+- "Query the Prometheus datasource for CPU usage over the last hour for the
+ `api-service` pod"
+- "Get the recent alerts that are currently firing"
+- "List all open incidents and show me details for the most recent one"
+- "Find error patterns in the logs from the `production` namespace using Loki"
+- "Who is currently on call for the backend team schedule?"
+- "Create a new incident titled 'High memory usage on production cluster' with
+ severity critical"
+- "Show me the panel queries from the 'API Performance' dashboard"
+- "Get label values for the `namespace` label from the Loki datasource"
+- "List all Sift investigations from the past week"
+
+## Recommended practices
+
+- Create service accounts with least-privilege permissions. Use fine-grained
+ RBAC scopes to limit access to only the datasources, dashboards, and features
+ required for your specific use case.
+- Regularly rotate service account tokens and update the secrets in ToolHive.
+- Enable network isolation to restrict the server's outbound network access to
+ your Grafana instance domain only.
+- For dashboards with large JSON configurations, use the `get_dashboard_summary`
+ or `get_dashboard_property` tools to minimize context window usage instead of
+ retrieving the full dashboard with `get_dashboard_by_uid`.
+- When working with multi-organization setups, always specify the
+ `GRAFANA_ORG_ID` to ensure operations target the correct organization.
+- Enable [telemetry](../guides-cli/telemetry-and-metrics.mdx) to monitor API
+ calls and track which Grafana resources are being accessed.
+- For production deployments, consider using the debug mode temporarily to
+ troubleshoot connection or permission issues, but disable it once everything
+ is working correctly.
diff --git a/versioned_docs/version-1.0/toolhive/guides-mcp/k8s.mdx b/versioned_docs/version-1.0/toolhive/guides-mcp/k8s.mdx
new file mode 100644
index 00000000..b2d2ee46
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-mcp/k8s.mdx
@@ -0,0 +1,225 @@
+---
+title: Kubernetes MCP server guide
+sidebar_label: Kubernetes (MKP)
+description: Using the Kubernetes (MKP) MCP server with ToolHive for cluster management.
+last_update:
+ author: danbarr
+ date: 2026-02-10
+---
+
+## Overview
+
+An MCP server that interacts with Kubernetes clusters, enabling you to manage
+and automate tasks within your Kubernetes environment.
+
+This server is based on the
+[MKP (Model Kontext Protocol)](https://github.com/StacklokLabs/mkp) project,
+which is a native Go implementation of an MCP server for Kubernetes that
+directly interacts with the Kubernetes API.
+
+## Metadata
+
+
+
+## Usage
+
+
+
+
+Select the `k8s` MCP server in the ToolHive registry. The server requires
+Kubernetes cluster access, so you'll need to configure authentication.
+
+In the **Volumes** section, mount your kubeconfig file to provide cluster
+access. The most common setup is:
+
+- **Host path**: `/home/$USER/.kube` (your local kubeconfig directory)
+- **Container path**: `/home/nonroot/.kube`
+- **Read only access** (recommended for security)
+
+Alternatively, if you have a specific kubeconfig file, mount it directly:
+
+- **Host path**: `/path/to/your/kubeconfig`
+- **Container path**: `/home/nonroot/.kube/config`
+- **Read only access**
+
+:::note[Write Operations]
+
+By default, the server runs in read-only mode. To enable write operations (like
+applying resources), add the `--read-write=true` argument in the **Command
+arguments** section. Use with caution in production environments.
+
+:::
+
+:::tip[Security tip]
+
+Enable outbound network filtering on the **Network Isolation** tab to restrict
+the server's network access to your Kubernetes cluster endpoints only. Add your
+cluster's API server host and port (usually 443 or 6443) to the allowed list.
+
+:::
+
+
+
+
+Run with the default configuration, mounting your local `.kube` directory as
+read-only:
+
+```bash
+thv run --volume $HOME/.kube:/home/nonroot/.kube:ro k8s
+```
+
+Mount a specific kubeconfig file:
+
+```bash
+thv run --volume /path/to/kubeconfig:/home/nonroot/.kube/config:ro k8s
+```
+
+Enable write operations (create/update/delete resources) with the `--read-write`
+flag:
+
+```bash
+thv run --volume $HOME/.kube:/home/nonroot/.kube:ro \
+ k8s -- --read-write=true
+```
+
+Disable resource discovery to reduce context size in large clusters:
+
+```bash
+thv run --volume $HOME/.kube:/home/nonroot/.kube:ro \
+ --arg --serve-resources=false \
+ k8s
+```
+
+Enable [network isolation](../guides-cli/network-isolation.mdx) to restrict
+network access to your Kubernetes cluster only. Create a custom permission
+profile:
+
+```json title="k8s-cluster-profile.json"
+{
+ "network": {
+ "outbound": {
+ "insecure_allow_all": false,
+ "allow_host": ["your-cluster-endpoint.com", "kubernetes.default.svc"],
+ "allow_port": [443, 6443]
+ }
+ }
+}
+```
+
+Then run with network isolation:
+
+```bash
+thv run --volume $HOME/.kube:/home/nonroot/.kube:ro \
+ --isolate-network --permission-profile k8s-cluster-profile.json \
+ k8s
+```
+
+
+
+
+This example follows the recommended practice of running the MKP MCP server
+inside the cluster it's managing. A service account is used for authentication.
+
+Create a Kubernetes manifest to deploy the MKP server. This example creates a
+service account with `cluster-admin` permissions for simplicity. In production,
+create a custom ClusterRole with only the minimum permissions required.
+
+```yaml title="mkp.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: mkp
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stackloklabs/mkp/server:0.2.4
+ transport: streamable-http
+ mcpPort: 8080
+ proxyPort: 8080
+ serviceAccount: mkp-sa
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: mkp-sa
+ namespace: toolhive-system
+---
+# NOTE: This ClusterRoleBinding uses cluster-admin for example purposes only.
+# In production, you should create a custom ClusterRole with the minimum
+# permissions required by your MCP server instead of using cluster-admin.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: mkp-sa-cluster-admin
+subjects:
+ - kind: ServiceAccount
+ name: mkp-sa
+ namespace: toolhive-system
+roleRef:
+ kind: ClusterRole
+ name: cluster-admin
+ apiGroup: rbac.authorization.k8s.io
+```
+
+Apply the manifest to your Kubernetes cluster:
+
+```bash
+kubectl apply -f mkp.yaml
+```
+
+To customize the server's behavior, add CLI arguments to the `spec` section of
+your manifest. For example, to enable write operations:
+
+```yaml title="mkp.yaml"
+spec:
+ # ...
+ args:
+ - '--read-write=true'
+```
+
+:::info[Configure tools]
+
+To filter or rename the tools exposed by your MCP server on Kubernetes, use the
+MCPToolConfig CRD and reference it from your MCPServer with the `toolConfigRef`
+field. See
+[Configure tools for MCP servers on Kubernetes](../guides-k8s/customize-tools.mdx).
+
+:::
+
+
+
+
+## Sample prompts
+
+Here are some sample prompts you can use to interact with the Kubernetes MCP
+server:
+
+- "List all pods in the default namespace"
+- "Show me the status of all deployments across all namespaces"
+- "Get the logs from the pod named `nginx-pod` in the default namespace"
+- "Create a new deployment with 3 replicas of nginx in the production namespace"
+- "Show me all services in the kube-system namespace"
+- "List all nodes in the cluster and show their resource usage"
+- "Get the YAML definition of the deployment named `web-app` in the staging
+ namespace"
+- "Show me all persistent volume claims that are not bound"
+- "Apply this ConfigMap to the development namespace"
+
+## Recommended practices
+
+- **Use read-only mode by default**: Only enable write operations
+ (`--read-write=true`) when necessary and in controlled environments.
+- **Implement proper RBAC**: When using service accounts, follow the principle
+ of least privilege and grant only the minimum permissions required.
+- **Enable network isolation**: Restrict network access to your Kubernetes API
+ endpoints only, especially in production environments.
+- **Monitor resource usage**: The server includes built-in rate limiting, but
+ monitor your cluster's API server load when using with AI agents.
+- **Disable resource discovery in large clusters**: Use
+ `--serve-resources=false` to reduce context size and improve performance in
+ clusters with many resources.
+- **Secure kubeconfig files**: When mounting kubeconfig files, always use
+ read-only mounts and ensure proper file permissions.
+- **Use namespace scoping**: When possible, limit the server's access to
+ specific namespaces rather than cluster-wide permissions.
+- **Enable audit logging**: Configure Kubernetes audit logging to track API
+ calls made by the MCP server for security monitoring.
diff --git a/versioned_docs/version-1.0/toolhive/guides-mcp/notion-remote.mdx b/versioned_docs/version-1.0/toolhive/guides-mcp/notion-remote.mdx
new file mode 100644
index 00000000..62dd7b47
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-mcp/notion-remote.mdx
@@ -0,0 +1,212 @@
+---
+title: Notion MCP server guide
+sidebar_label: Notion
+description:
+ Using the Notion remote MCP server with ToolHive to access your Notion
+ workspace.
+last_update:
+ author: danbarr
+ date: 2026-02-10
+---
+
+## Overview
+
+The official [Notion remote MCP server](https://developers.notion.com/docs/mcp)
+provides access to your Notion workspace through the Notion API, allowing AI
+agents to search, read, create, and manage pages, databases, and other content
+in your Notion workspace.
+
+This is a remote MCP server that uses the **Auto-Discovered** authorization
+method, meaning ToolHive handles OAuth authentication automatically with minimal
+configuration required.
+
+## Metadata
+
+
+
+## Usage
+
+
+
+
+Select the `notion-remote` MCP server in the ToolHive registry.
+
+The server is preconfigured with the following settings:
+
+- **Server URL**: `https://mcp.notion.com/mcp`
+- **Transport**: Streamable HTTP
+- **Authorization method**: Auto-Discovered
+
+In the **Callback port** field, enter the port that ToolHive should use to
+listen for the OAuth callback. This can be any available port on your machine.
+
+When you install the server, ToolHive:
+
+1. Discovers the OAuth endpoints automatically
+2. Registers a new OAuth client with Notion
+3. Opens your browser for authentication
+
+After you authorize access in your browser, the remote MCP server appears in
+your server list with a "Running" status.
+
+
+
+
+Run the Notion remote MCP server with the default configuration:
+
+```bash
+thv run notion-remote
+```
+
+When you run the server, ToolHive:
+
+1. Discovers the OAuth endpoints automatically
+2. Registers a new OAuth client with Notion
+3. Opens your browser for authentication
+
+Customize the OAuth callback port if the default port (`8777`) is unavailable:
+
+```bash
+thv run --remote-auth-callback-port 50051 notion-remote
+```
+
+After successful authentication, the server will be available to your configured
+MCP clients.
+
+To restart the server (which triggers a new OAuth authentication flow):
+
+```bash
+thv restart notion-remote
+```
+
+
+
+
+:::note
+
+The ToolHive Kubernetes operator does not currently support remote MCP servers
+using dynamic OAuth authentication. Instead, you can run the
+[local Notion MCP server](https://github.com/makenotion/notion-mcp-server) in
+Kubernetes using a static integration key.
+
+:::
+
+Create an integration in Notion to obtain an authentication token by following
+the instructions in the MCP server's
+[README](https://github.com/makenotion/notion-mcp-server?tab=readme-ov-file#installation).
+
+Create a Kubernetes secret containing your Notion integration token
+("`ntn_****`"):
+
+```bash
+kubectl -n toolhive-system create secret generic notion-token --from-literal=token=
+```
+
+Create a Kubernetes manifest to deploy the Notion MCP server using your secret:
+
+```yaml title="notion.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: notion
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stacklok/dockyard/npx/notion:2.1.0
+ transport: stdio
+ port: 8080
+ permissionProfile:
+ type: builtin
+ name: network
+ secrets:
+ - name: notion-token
+ key: token
+ targetEnvName: NOTION_TOKEN
+```
+
+Apply the manifest to your Kubernetes cluster:
+
+```bash
+kubectl apply -f notion.yaml
+```
+
+
+
+
+## Sample prompts
+
+Here are some sample prompts you can use to interact with the Notion MCP server:
+
+- "Search my Notion workspace for pages about 'project planning'"
+- "Create a new page in my Notion workspace titled 'Meeting Notes' with today's
+ date"
+- "Find all database entries in my Task List database where status is 'To Do'"
+- "Update the page titled 'Weekly Report' with a summary of this week's
+ accomplishments"
+- "List all pages in my 'Engineering' teamspace"
+- "Create a new Notion database for tracking customer feedback with columns for
+ name, date, and feedback text"
+- "Add a comment to the page titled 'Q4 Planning' with my feedback"
+- "Search for pages created by john@example.com in the last 30 days"
+
+## Recommended practices
+
+- **Scope your access**: When authenticating, review the permissions requested
+ and only grant access to the workspaces you need for your use case.
+- **Test with read operations**: Start with search and fetch operations to
+ familiarize yourself with the server's capabilities before making changes to
+ your workspace.
+- **Use specific searches**: Instead of broad workspace searches, narrow down
+ results by searching within specific pages, databases, or teamspaces for
+ better performance.
+- **Understand the data model**: Notion has a specific hierarchy (workspaces,
+ teamspaces, pages, databases, data sources). Understanding this structure
+ helps craft more effective prompts.
+- **Be cautious with updates**: The MCP server can modify and delete content in
+ your Notion workspace. Always review changes before applying them to important
+ pages or databases.
+- **Handle authentication errors**: If you see authentication errors, restart
+ the server to trigger a new OAuth flow: `thv restart notion-remote`
+
+## Troubleshooting
+
+
+OAuth authentication fails
+
+If OAuth authentication fails or times out:
+
+1. Ensure the callback port is not blocked by a firewall
+2. Check that your browser allows pop-ups from ToolHive
+3. Try restarting the server with `thv restart notion-remote`
+
+
+
+Server shows "Running" but tools don't work
+
+The server may have lost authentication. Restart the server to re-authenticate:
+
+```bash
+thv restart notion-remote
+```
+
+
+
+Can't find content in search results
+
+- Notion search uses semantic search, which may return different results than
+ exact keyword matching
+- Try using more specific search terms or filtering by page, database, or
+ teamspace
+- Check that you have access to the content you're searching for (permissions
+ may limit results)
+
+
+
+Rate limits
+
+Notion's API has rate limits. If you encounter rate limit errors:
+
+- Reduce the frequency of requests
+- Use more specific queries to reduce the amount of data retrieved
+- Wait for the rate limit to reset (typically a few minutes)
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-mcp/osv.mdx b/versioned_docs/version-1.0/toolhive/guides-mcp/osv.mdx
new file mode 100644
index 00000000..50e65b7a
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-mcp/osv.mdx
@@ -0,0 +1,121 @@
+---
+title: OSV MCP server guide
+sidebar_label: Open Source Vulnerabilities (OSV)
+description: Using the Open Source Vulnerabilities database (OSV) MCP server with ToolHive.
+last_update:
+ author: danbarr
+ date: 2026-02-10
+---
+
+## Overview
+
+The [OSV MCP server](https://github.com/StacklokLabs/osv-mcp) provides access to
+the [Open Source Vulnerabilities database](https://osv.dev), which aggregates
+vulnerability data from multiple sources including GitHub Security Advisories,
+PyPA, RustSec, and many others. This server enables AI agents to:
+
+- Query vulnerabilities for specific package versions or Git commits
+- Perform batch vulnerability queries across multiple packages
+- Retrieve detailed vulnerability information by OSV ID
+- Access comprehensive vulnerability data including severity, affected versions,
+ and remediation guidance
+
+The server supports various package ecosystems including npm, PyPI, Go modules,
+Maven, NuGet, and more, making it an essential tool for security analysis and
+dependency management workflows.
+
+## Metadata
+
+
+
+## Usage
+
+
+
+
+Select the `osv` MCP server in the ToolHive registry.
+
+The OSV MCP server does not require any additional configuration or secrets. It
+communicates directly with the public OSV API at `api.osv.dev`.
+
+:::tip[Security tip]
+
+Enable outbound network filtering on the **Network Isolation** tab to restrict
+the server's network access using the default profile contained in the registry.
+
+:::
+
+
+
+
+Run with the default configuration:
+
+```bash
+thv run osv
+```
+
+Enable [network isolation](../guides-cli/network-isolation.mdx) using the
+default profile from the registry to restrict the server's network access to
+only the OSV API:
+
+```bash
+thv run --isolate-network osv
+```
+
+
+
+
+Create a Kubernetes manifest to deploy the OSV MCP server:
+
+```yaml title="osv.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: osv
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stackloklabs/osv-mcp/server:0.1.0
+ transport: streamable-http
+ mcpPort: 8080
+ proxyPort: 8080
+```
+
+Apply the manifest to your Kubernetes cluster:
+
+```bash
+kubectl apply -f osv.yaml
+```
+
+
+
+
+## Sample prompts
+
+Here are some sample prompts you can use to interact with the OSV MCP server:
+
+- "Check if the npm package lodash version 4.17.15 has any known
+ vulnerabilities"
+- "Query vulnerabilities for the Python package jinja2 version 2.4.1 and the Go
+ module github.com/gin-gonic/gin version 1.6.3"
+- "Get detailed information about vulnerability GHSA-vqj2-4v8m-8vrq including
+ affected versions and remediation steps"
+- "Check for vulnerabilities in commit hash
+ 6879efc2c1596d11a6a6ad296f80063b558d5e0f"
+- "Scan these packages for vulnerabilities: express@4.17.1, react@16.13.0, and
+ django@3.0.5"
+- "Look up vulnerability CVE-2021-44228 and provide details about affected
+ packages and severity"
+
+## Recommended practices
+
+- **Use batch queries** when checking multiple packages to reduce API calls and
+ improve performance. The batch query tool can handle multiple packages in a
+ single request.
+- **Enable network isolation** to restrict the server's network access to only
+ the OSV API endpoints, improving security posture.
+- **Specify ecosystems accurately** (npm, PyPI, Go, Maven, etc.) to ensure
+ accurate vulnerability matching and reduce false positives.
+- **Use package URLs (PURLs)** when available for more precise package
+ identification across different ecosystems and registries.
+- **Monitor rate limits** when performing large-scale vulnerability scans to
+ avoid overwhelming the OSV API service.
diff --git a/versioned_docs/version-1.0/toolhive/guides-mcp/playwright.mdx b/versioned_docs/version-1.0/toolhive/guides-mcp/playwright.mdx
new file mode 100644
index 00000000..2c27e147
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-mcp/playwright.mdx
@@ -0,0 +1,273 @@
+---
+title: Playwright MCP server guide
+sidebar_label: Playwright
+description: Using the Playwright MCP server with ToolHive for browser automation.
+last_update:
+ author: danbarr
+ date: 2026-02-10
+---
+
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+
+## Overview
+
+The official
+[Playwright MCP server](https://github.com/microsoft/playwright-mcp) provides
+browser automation capabilities using [Playwright](https://playwright.dev). This
+server enables AI agents to interact with web pages through structured
+accessibility snapshots, bypassing the need for screenshots or visually-tuned
+models.
+
+Key features include:
+
+- **Fast and lightweight**: Uses Playwright's accessibility tree instead of
+ pixel-based input
+- **LLM-friendly**: Operates purely on structured data without requiring vision
+ models
+- **Deterministic tool application**: Avoids ambiguity common with
+ screenshot-based approaches
+- **Multi-browser support**: Works with Chrome, Firefox, Safari (WebKit), and
+ Edge
+- **Tab management**: Create, switch between, and manage multiple browser tabs
+- **Persistent sessions**: Maintains login state and browser data between
+ interactions
+
+## Metadata
+
+
+
+## Usage
+
+The Playwright MCP server supports numerous command-line arguments to customize
+its behavior. Common options include:
+
+- **Custom viewport**: Set `--viewport-size` to specify browser dimensions
+ (e.g., `1920,1080`)
+- **Device emulation**: Use `--device` to emulate mobile devices (e.g.,
+ `"iPhone 15"`)
+- **Network isolation**: Configure `--allowed-origins` and `--blocked-origins`
+ to control which websites the browser can access
+- **Output directory**: Specify `--output-dir` to save screenshots, PDFs, and
+ other files to a custom location; mount a host directory for persistence (see
+ examples below)
+
+Refer to the
+[Playwright MCP server documentation](https://github.com/microsoft/playwright-mcp?tab=readme-ov-file#configuration)
+for the full list of configuration options and their descriptions.
+
+:::note[Limitations]
+
+The containerized version of Playwright only supports the Chromium browser in
+headless mode.
+
+:::
+
+
+
+
+Select the `playwright` MCP server in the ToolHive registry. The server runs
+with default settings that work for most use cases.
+
+To customize the server's behavior, add command-line arguments in the **Command
+arguments** section.
+
+To save browser output files like screenshots and traces, mount a host directory
+in the **Storage volumes** section and add the `--output-dir `
+command argument as shown in the screenshot below.
+
+
+
+
+
+
+Run with the default configuration using Chromium in headless mode:
+
+```bash
+thv run playwright
+```
+
+Emulate a mobile device for responsive testing:
+
+```bash {2}
+thv run playwright -- --device "iPhone 15"
+```
+
+Restrict access to specific domains:
+
+```bash {2}
+thv run playwright -- --allowed-origins "example.com;trusted-site.com"
+```
+
+Mount a host directory (e.g., `~/playwright-output`) to save browser output
+files like screenshots and traces:
+
+```bash {3,5}
+mkdir ~/playwright-output
+thv run \
+ --volume ~/playwright-output:/browser-output playwright \
+ -- --output-dir /browser-output --save-trace --save-session
+```
+
+You can run multiple instances of the server with different configurations by
+giving each a unique name:
+
+```bash {1,5}
+thv run --name playwright-desktop playwright \
+ -- --device "Desktop Chrome" --viewport-size 1920,1080
+
+thv run --name playwright-iphone playwright \
+ -- --device "iPhone 15"
+```
+
+
+
+
+Create a basic Kubernetes manifest to deploy the Playwright MCP server using the
+Streamable HTTP transport:
+
+```yaml title="playwright.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: playwright
+ namespace: toolhive-system
+spec:
+ image: mcr.microsoft.com/playwright/mcp:v0.0.68
+ transport: streamable-http
+ mcpPort: 8931
+ proxyPort: 8080
+ args:
+ - '--port'
+ - '8931'
+ - '--host'
+ - '0.0.0.0'
+ - '--allowed-hosts'
+ - '*'
+```
+
+:::note[Important]
+
+Don't remove the `--port 8931`, `--host 0.0.0.0`, or `--allowed-hosts "*"`
+arguments. They are required to run the server using Streamable HTTP.
+
+:::
+
+Apply the manifest to your Kubernetes cluster:
+
+```bash
+kubectl apply -f playwright.yaml
+```
+
+For production deployments with network restrictions, add the
+`--allowed-origins` argument with a list of trusted domains:
+
+```yaml {18-19} title="playwright-restricted.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: playwright
+ namespace: toolhive-system
+spec:
+ image: mcr.microsoft.com/playwright/mcp:v0.0.68
+ transport: streamable-http
+ mcpPort: 8931
+ proxyPort: 8080
+ args:
+ - '--port'
+ - '8931'
+ - '--host'
+ - '0.0.0.0'
+ - '--allowed-hosts'
+ - '*'
+ - '--allowed-origins'
+ - 'example.com;trusted-domain.org'
+```
+
+Mount a persistent volume to save browser output files like screenshots and
+traces:
+
+```yaml {18-19,24-27,30-33} title="playwright-with-volume.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: playwright
+ namespace: toolhive-system
+spec:
+ image: mcr.microsoft.com/playwright/mcp:v0.0.68
+ transport: streamable-http
+ mcpPort: 8931
+ proxyPort: 8080
+ args:
+ - '--port'
+ - '8931'
+ - '--host'
+ - '0.0.0.0'
+ - '--allowed-hosts'
+ - '*'
+ - '--output-dir'
+ - '/browser-output'
+ - '--save-trace'
+ - '--save-session'
+ podTemplateSpec:
+ spec:
+ volumes:
+ - name: playwright-output
+ persistentVolumeClaim:
+ claimName: playwright-output-claim
+ containers:
+ - name: mcp
+ volumeMounts:
+ - mountPath: /browser-output
+ name: playwright-output
+ readOnly: false
+```
+
+
+
+
+## Sample prompts
+
+Here are some sample prompts you can use to interact with the Playwright MCP
+server:
+
+- "Navigate to https://toolhive.dev and take a screenshot of the homepage"
+- "Go to the login page, fill in the username field with 'testuser' and password
+ field with 'password123', then click the login button"
+- "Open https://news.ycombinator.com and get the titles of the top 5 stories"
+- "Navigate to a form on https://httpbin.org/forms/post, fill it out with sample
+ data, and submit it"
+- "Go to https://github.com/microsoft/playwright and check if there are any
+ console errors on the page"
+- "Take a full-page screenshot of https://example.com and save it as
+ 'homepage.png'"
+- "Navigate to an e-commerce site, search for 'laptop', and capture information
+ about the first product"
+- "Open multiple tabs, navigate to different websites in each, and then switch
+ between them"
+
+## Recommended practices
+
+- **Use accessibility-first interactions**: The server works best when you
+ describe elements by their accessible names, labels, or text content rather
+ than visual characteristics.
+- **Leverage browser profiles**: For workflows requiring authentication, use
+ persistent profiles or storage state files to maintain login sessions.
+- **Enable network restrictions**: Use `--allowed-origins` and
+ `--blocked-origins` to limit which domains the browser can access, especially
+ in production environments.
+- **Monitor resource usage**: Browser automation can be resource-intensive;
+ consider using headless mode and limiting concurrent sessions.
+- **Handle dynamic content**: Use the `browser_wait_for` tool when dealing with
+ content that loads asynchronously.
+- **Organize output files**: Specify a custom output directory to keep
+ screenshots, PDFs, and traces organized.
+- **Test responsively**: Use device emulation to test how web applications
+ behave on different screen sizes and devices.
diff --git a/versioned_docs/version-1.0/toolhive/guides-registry/authentication.mdx b/versioned_docs/version-1.0/toolhive/guides-registry/authentication.mdx
new file mode 100644
index 00000000..b98377b2
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-registry/authentication.mdx
@@ -0,0 +1,552 @@
+---
+title: Authentication configuration
+description:
+ How to configure authentication for the ToolHive Registry Server with
+ anonymous and OAuth modes
+---
+
+The Registry server provides secure-by-default authentication, defaulting to
+OAuth mode to protect your registry. You can configure authentication to fit
+different deployment scenarios, from development environments to production
+deployments with enterprise identity providers.
+
+## Authentication modes
+
+The server supports two authentication modes configured via the required `auth`
+section in your configuration file:
+
+- **OAuth** (default): Secure authentication using JWT tokens from identity
+ providers
+- **Anonymous**: No authentication (development/testing only)
+
+:::info[Secure by default]
+
+The server defaults to **OAuth mode** when no explicit auth configuration is
+provided. This secure-by-default posture ensures your registry is protected
+unless you explicitly choose anonymous mode for development scenarios.
+
+:::
+
+## OAuth authentication
+
+OAuth mode (the default) validates access tokens from identity providers. The
+server supports two token formats:
+
+- **JWT tokens**: Validated locally using the provider's public keys (JWKS
+ endpoint). This is the most common format for modern identity providers.
+- **Opaque tokens**: Validated via token introspection (RFC 7662) by querying
+ the provider's introspection endpoint. Use this when your provider issues
+ non-JWT tokens.
+
+This enables enterprise authentication with providers like Keycloak, Auth0,
+Okta, Azure AD, Kubernetes service accounts, or any OAuth-compliant service.
+
+### Basic OAuth configuration
+
+```yaml title="config-oauth.yaml"
+auth:
+ mode: oauth
+ oauth:
+ resourceUrl: https://registry.example.com
+ providers:
+ - name: keycloak
+ issuerUrl: https://keycloak.example.com/realms/mcp
+ audience: registry-api
+```
+
+### OAuth configuration fields
+
+| Field | Type | Required | Default | Description |
+| ----------------- | -------- | -------- | ----------------------------------------- | -------------------------------------------------- |
+| `mode` | string | Yes | `oauth` | Authentication mode (`oauth` or `anonymous`) |
+| `resourceUrl` | string | Yes | - | The URL of the registry resource being protected |
+| `realm` | string | No | `mcp-registry` | OAuth realm identifier |
+| `scopesSupported` | []string | No | `[mcp-registry:read, mcp-registry:write]` | OAuth scopes advertised in the discovery endpoint |
+| `publicPaths` | []string | No | `[]` | Additional paths accessible without authentication |
+| `providers` | array | Yes | - | List of OAuth/OIDC identity providers |
+
+### Provider configuration fields
+
+| Field | Type | Required | Description |
+| ------------------ | ------ | -------- | ----------------------------------------------------------------------- |
+| `name` | string | Yes | Provider identifier for logging and monitoring |
+| `issuerUrl` | string | Yes | OAuth/OIDC issuer URL (e.g., `https://keycloak.example.com/realms/mcp`) |
+| `audience` | string | Yes | Expected audience claim in the access token |
+| `jwksUrl` | string | No | JWKS endpoint URL (skips OIDC discovery if specified) |
+| `introspectionUrl` | string | No | Token introspection endpoint URL for opaque token validation |
+| `clientId` | string | No | OAuth client ID (for authenticated introspection requests) |
+| `clientSecretFile` | string | No | Path to file containing client secret (for authenticated introspection) |
+| `caCertPath` | string | No | Path to CA certificate for TLS verification |
+| `authTokenFile` | string | No | Path to token file for authenticating JWKS/introspection requests |
+| `allowPrivateIP` | bool | No | Allow connections to private IP addresses (for in-cluster use) |
+
+### Complete OAuth configuration example
+
+```yaml title="config-oauth-complete.yaml"
+auth:
+ mode: oauth
+ oauth:
+ resourceUrl: https://registry.example.com
+ realm: mcp-registry
+ scopesSupported:
+ - mcp-registry:read
+ - mcp-registry:write
+ publicPaths:
+ - /custom-health
+ - /metrics
+ providers:
+ - name: keycloak-prod
+ issuerUrl: https://keycloak.example.com/realms/production
+ audience: registry-api
+ clientId: registry-client
+ clientSecretFile: /etc/secrets/keycloak-secret
+ caCertPath: /etc/ssl/certs/keycloak-ca.crt
+ - name: keycloak-staging
+ issuerUrl: https://keycloak.example.com/realms/staging
+ audience: registry-api-staging
+```
+
+### Opaque token configuration
+
+When your identity provider issues opaque (non-JWT) tokens, configure the
+`introspectionUrl` field to enable token introspection:
+
+```yaml title="config-opaque-tokens.yaml"
+auth:
+ mode: oauth
+ oauth:
+ resourceUrl: https://registry.example.com
+ providers:
+ - name: google
+ issuerUrl: https://accounts.google.com
+ introspectionUrl: https://oauth2.googleapis.com/tokeninfo
+ audience: 407408718192.apps.googleusercontent.com
+```
+
+The server automatically detects the token format and uses the appropriate
+validation method—attempting JWT validation first and falling back to token
+introspection if needed.
+
+## Kubernetes authentication
+
+For Kubernetes deployments, you can configure OAuth to validate service account
+tokens. This provides automatic, zero-config authentication for workloads
+running in the cluster.
+
+### Kubernetes provider configuration
+
+```yaml title="config-k8s-auth.yaml"
+auth:
+ mode: oauth
+ oauth:
+ resourceUrl: https://registry.example.com
+ providers:
+ - name: kubernetes
+ issuerUrl: https://kubernetes.default.svc.cluster.local
+ jwksUrl: https://kubernetes.default.svc/openid/v1/jwks
+ audience: registry-server
+ caCertPath: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ authTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
+ allowPrivateIP: true
+```
+
+:::info[Kubernetes-specific configuration]
+
+- **issuerUrl**: Use `https://kubernetes.default.svc.cluster.local` to match the
+ `iss` claim in Kubernetes service account tokens.
+- **jwksUrl**: Specify the JWKS endpoint directly to skip OIDC discovery.
+- **authTokenFile**: The server uses this token to authenticate when fetching
+ the JWKS from the Kubernetes API server.
+- **allowPrivateIP**: Required for in-cluster communication with the API server.
+
+:::
+
+### How Kubernetes authentication works
+
+1. Workloads mount projected service account tokens with a specific audience
+2. Clients send these tokens in the `Authorization: Bearer ` header
+3. The server validates tokens using the Kubernetes API server's public keys
+4. Only tokens with the correct audience (e.g., `registry-server`) are accepted
+
+### Kubernetes deployment example
+
+When deploying in Kubernetes, the service account CA certificate is
+automatically mounted:
+
+```yaml title="deployment-k8s-auth.yaml"
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: registry-api
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: registry-api
+ template:
+ metadata:
+ labels:
+ app: registry-api
+ spec:
+ serviceAccountName: registry-api
+ containers:
+ - name: registry-api
+ image: ghcr.io/stacklok/thv-registry-api:latest
+ args:
+ - serve
+ - --config=/etc/registry/config.yaml
+ volumeMounts:
+ - name: config
+ mountPath: /etc/registry/config.yaml
+ subPath: config.yaml
+ readOnly: true
+ # Service account token and CA cert are mounted automatically by Kubernetes
+ volumes:
+ - name: config
+ configMap:
+ name: registry-api-config
+```
+
+The service account token and CA certificate are automatically mounted at:
+
+- Token: `/var/run/secrets/kubernetes.io/serviceaccount/token`
+- CA cert: `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`
+
+### Client workload example
+
+Clients that need to authenticate with the registry should mount a projected
+service account token with the correct audience:
+
+```yaml title="client-workload.yaml"
+apiVersion: v1
+kind: Pod
+metadata:
+ name: registry-client
+spec:
+ serviceAccountName: my-client-sa
+ containers:
+ - name: client
+ image: my-client-image
+ volumeMounts:
+ - name: registry-token
+ mountPath: /var/run/secrets/registry
+ readOnly: true
+ volumes:
+ - name: registry-token
+ projected:
+ sources:
+ - serviceAccountToken:
+ audience: registry-server
+ expirationSeconds: 3600
+ path: token
+```
+
+The client reads the token from `/var/run/secrets/registry/token` and includes
+it in the `Authorization: Bearer ` header when making requests to the
+registry.
+
+## Provider-specific examples
+
+### Keycloak
+
+```yaml
+auth:
+ mode: oauth
+ oauth:
+ resourceUrl: https://registry.example.com
+ providers:
+ - name: keycloak
+ issuerUrl: https://keycloak.example.com/realms/YOUR_REALM
+ audience: registry-api
+```
+
+The `issuerUrl` should point to your Keycloak realm. The realm name is part of
+the URL path.
+
+### Auth0
+
+```yaml
+auth:
+ mode: oauth
+ oauth:
+ resourceUrl: https://registry.example.com
+ providers:
+ - name: auth0
+ issuerUrl: https://YOUR_DOMAIN.auth0.com/
+ audience: https://registry.example.com
+```
+
+For Auth0, the `issuerUrl` is your Auth0 domain. The `audience` should match the
+API identifier configured in your Auth0 dashboard.
+
+### Azure AD
+
+```yaml
+auth:
+ mode: oauth
+ oauth:
+ resourceUrl: https://registry.example.com
+ providers:
+ - name: azure-ad
+ issuerUrl: https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0
+ audience: api://YOUR_CLIENT_ID
+```
+
+For Azure AD, the `issuerUrl` includes your tenant ID, and the `audience`
+typically uses the `api://` scheme with your application's client ID.
+
+### Okta
+
+```yaml
+auth:
+ mode: oauth
+ oauth:
+ resourceUrl: https://registry.example.com
+ providers:
+ - name: okta
+ issuerUrl: https://YOUR_DOMAIN.okta.com/oauth2/default
+ audience: api://default
+```
+
+For Okta, the `issuerUrl` points to your Okta authorization server. Use
+`/oauth2/default` for the default authorization server or
+`/oauth2/YOUR_AUTH_SERVER_ID` for custom servers.
+
+## Anonymous authentication
+
+Anonymous mode disables authentication entirely, allowing unrestricted access to
+all registry endpoints. This is only suitable for development, testing, or
+internal deployments where authentication is handled at a different layer (e.g.,
+network policies, VPN, or reverse proxy).
+
+### Anonymous configuration
+
+```yaml title="config-anonymous.yaml"
+auth:
+ mode: anonymous
+```
+
+:::danger[No access control]
+
+Anonymous mode provides **no access control**. Only use it in trusted
+environments or when other security measures are in place. **Do not use
+anonymous mode in production.**
+
+:::
+
+### Anonymous use cases
+
+- Local development and testing
+- Internal deployments behind corporate firewalls
+- Read-only public registries
+- Environments with external authentication (reverse proxy, API gateway)
+
+## Default public paths
+
+The following endpoints are **always accessible without authentication**,
+regardless of the auth mode:
+
+- `/health` - Health check endpoint
+- `/readiness` - Readiness probe endpoint
+- `/version` - Version information
+- `/.well-known/*` - OAuth discovery endpoints (RFC 9728)
+
+You can configure additional public paths using the `publicPaths` field in your
+OAuth configuration. See the
+[Registry API reference](../reference/registry-api.mdx) for complete endpoint
+documentation.
+
+## RFC 9728 OAuth discovery
+
+The server implements [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728.html)
+for OAuth Protected Resource Metadata, enabling clients to automatically
+discover authentication requirements.
+
+### Discovery endpoint
+
+Clients can discover the server's OAuth configuration at:
+
+```text
+GET /.well-known/oauth-protected-resource
+```
+
+### Example discovery response
+
+```json
+{
+ "resource": "https://registry.example.com",
+ "authorization_servers": [
+ "https://keycloak.example.com/realms/production",
+ "https://keycloak.example.com/realms/staging"
+ ],
+ "scopes_supported": ["mcp-registry:read", "mcp-registry:write"],
+ "bearer_methods_supported": ["header"],
+ "resource_documentation": "https://docs.example.com/registry"
+}
+```
+
+This allows OAuth clients to automatically configure themselves without manual
+setup, improving interoperability and reducing configuration errors.
+
+When a request fails authentication, the server returns a `WWW-Authenticate`
+header that includes a link to the discovery endpoint, helping clients locate
+the authentication requirements.
+
+## Testing authentication
+
+### Using curl with a bearer token
+
+```bash
+TOKEN="your-jwt-token-here"
+
+curl -H "Authorization: Bearer $TOKEN" \
+ https://registry.example.com/default/v0.1/servers
+```
+
+### Using kubectl with Kubernetes service accounts
+
+Use `kubectl create token` to generate a token with the correct audience:
+
+```bash
+# Create a token with the registry-server audience
+TOKEN=$(kubectl create token \
+ -n \
+ --audience=registry-server)
+
+# Make authenticated request
+curl -H "Authorization: Bearer $TOKEN" \
+ https://registry.example.com/registry/v0.1/servers
+```
+
+:::tip[Projected tokens vs kubectl create token]
+
+For automated workloads, use projected service account tokens (see
+[Client workload example](#client-workload-example)). The `kubectl create token`
+command is useful for manual testing and debugging.
+
+:::
+
+### Testing token validation
+
+To verify your token is valid:
+
+1. Decode the JWT at [jwt.io](https://jwt.io/) (don't paste production tokens!)
+2. Check the `iss` (issuer) matches your configured `issuerUrl`
+3. Check the `aud` (audience) matches your configured `audience`
+4. Check the `exp` (expiration) is in the future
+
+## Choosing an authentication mode
+
+| Mode | Security | Complexity | Best for |
+| --------- | -------- | ---------- | ----------------------------------------------- |
+| OAuth | High | Medium | Production deployments, enterprise environments |
+| Anonymous | None | None | Development, testing, internal trusted networks |
+
+**Recommendations:**
+
+- **Production deployments**: Always use OAuth mode with your organization's
+ identity provider
+- **Kubernetes deployments**: Use OAuth with Kubernetes provider for automatic
+ authentication
+- **Development/testing**: Use anonymous mode for local development only
+- **Public read-only registries**: Use OAuth mode with rate limiting; avoid
+ anonymous in production
+
+## Security considerations
+
+### Token validation
+
+All OAuth providers validate:
+
+- Token expiration (`exp` claim)
+- Audience claim (`aud`) matches configuration
+- Issuer (`iss`) matches the configured provider
+
+For JWT tokens, signature verification uses the provider's public keys (fetched
+from the issuer's JWKS endpoint). For opaque tokens, the server queries the
+configured `introspectionUrl` to validate the token.
+
+### HTTPS requirements
+
+Always use HTTPS in production to protect tokens in transit:
+
+```yaml
+auth:
+ mode: oauth
+ oauth:
+ resourceUrl: https://registry.example.com # Use HTTPS
+ providers:
+ - issuerUrl: https://keycloak.example.com/realms/mcp # Use HTTPS
+```
+
+### Token storage
+
+- Never log or persist JWT tokens in plaintext
+- Use short-lived tokens when possible (e.g., 1 hour)
+- Implement token refresh flows for long-running clients
+- Rotate client secrets regularly if using `clientSecretFile`
+
+### Custom CA certificates
+
+If your identity provider uses a custom CA certificate, specify the `caCertPath`
+in your provider configuration:
+
+```yaml
+providers:
+ - name: internal-keycloak
+ issuerUrl: https://keycloak.internal.example.com/realms/mcp
+ audience: registry-api
+ caCertPath: /etc/ssl/certs/internal-ca.crt
+```
+
+## Troubleshooting
+
+### 401 Unauthorized errors
+
+If you receive 401 Unauthorized responses:
+
+1. **Verify the token is valid**: Check expiration, issuer, and audience claims
+2. **Check the Authorization header**: Must be `Authorization: Bearer `
+3. **Verify issuer URL**: Must exactly match the `iss` claim in your token
+4. **Check audience**: The `aud` claim must match your configured `audience`
+5. **Review server logs**: Look for specific validation error messages
+
+### Token validation errors
+
+If you see authentication failures in server logs:
+
+1. **Issuer URL accessibility**: Verify the server can reach the issuer's JWKS
+ endpoint (typically `${issuerUrl}/.well-known/openid-configuration`)
+2. **Network connectivity**: Check DNS resolution and firewall rules
+3. **CA certificates**: If using custom CAs, ensure `caCertPath` is correct
+4. **Token expiration**: Ensure your token hasn't expired (check `exp` claim)
+
+### Provider connectivity issues
+
+If the server cannot connect to the OAuth provider:
+
+1. Verify network connectivity to the issuer URL from the server
+2. Check DNS resolution for the provider's domain
+3. Ensure firewall rules allow outbound HTTPS (port 443) to the provider
+4. For Kubernetes providers, verify the API server is reachable at
+ `https://kubernetes.default.svc`
+5. Check CA certificate configuration if using self-signed certificates
+
+### Multiple providers not working
+
+If tokens from some providers work but others don't:
+
+1. Verify each provider's configuration independently using curl
+2. Check that audience claims differ between providers if needed (or are
+ intentionally the same)
+3. Ensure issuer URLs are correct and include the full path (including realm for
+ Keycloak)
+4. Review server logs to identify which specific provider validation is failing
+5. Test each provider's JWKS endpoint accessibility:
+ `curl ${issuerUrl}/.well-known/openid-configuration`
+
+## Next steps
+
+- [Configure database storage](./database.mdx) for production deployments
+- [Deploy the server](./deployment.mdx) with authentication enabled
+- [Configure registry sources](./configuration.mdx) for your environment
diff --git a/versioned_docs/version-1.0/toolhive/guides-registry/authorization.mdx b/versioned_docs/version-1.0/toolhive/guides-registry/authorization.mdx
new file mode 100644
index 00000000..a51a3964
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-registry/authorization.mdx
@@ -0,0 +1,429 @@
+---
+title: Authorization
+description:
+ Configure role-based access control and claims-based authorization for the
+ Registry Server
+---
+
+The Registry server provides a claims-based authorization model that controls
+who can manage sources, registries, and entries. Authorization builds on top of
+[authentication](./authentication.mdx) — you need OAuth authentication enabled
+before configuring authorization.
+
+## How authorization works
+
+When a client accesses registry data, the server checks three layers in order:
+
+1. **Registry claims** (access gate) — can the caller access this registry at
+ all? If the registry has claims and the caller's JWT doesn't satisfy them,
+ the server returns `403 Forbidden` before any data is returned.
+2. **Entry claims** (visibility filter) — which entries can the caller see? Each
+ entry can carry claims inherited from its source (for synced sources) or set
+ explicitly (via publish payload or Kubernetes annotation). Only entries whose
+ claims the caller's JWT satisfies are included in the response.
+3. **Role checks** (admin operations) — can the caller perform this write
+ operation? Publishing, deleting, and managing sources/registries require
+ specific [roles](#configure-roles).
+
+When a caller makes an API request, the server:
+
+1. Extracts the caller's claims from their JWT token
+2. Resolves the caller's roles based on those claims and the `authz`
+ configuration
+3. Checks whether the caller's role permits the operation
+4. Checks whether the caller's claims satisfy the resource's claims
+
+```mermaid
+flowchart LR
+ JWT["JWT token"] --> Claims["Extract claims"]
+ Claims --> Roles["Resolve roles"]
+ Roles --> RoleCheck{"Role\npermitted?"}
+ RoleCheck -->|Yes| ClaimCheck{"Claims\nsatisfied?"}
+ RoleCheck -->|No| Deny403["403 Forbidden"]
+ ClaimCheck -->|Yes| Allow["Allow"]
+ ClaimCheck -->|No| Deny403
+```
+
+## Configure roles
+
+Define roles in the `auth.authz.roles` section of your configuration file. Each
+role maps to a list of claim rules — if a caller's JWT claims match any rule in
+the list, they are granted that role.
+
+```yaml title="config-authz.yaml"
+auth:
+ mode: oauth
+ oauth:
+ resourceUrl: https://registry.example.com
+ providers:
+ - name: keycloak
+ issuerUrl: https://keycloak.example.com/realms/mcp
+ audience: registry-api
+ # highlight-start
+ authz:
+ roles:
+ superAdmin:
+ - role: 'super-admin'
+ manageSources:
+ - org: 'acme'
+ role: 'admin'
+ manageRegistries:
+ - org: 'acme'
+ role: 'admin'
+ manageEntries:
+ - role: 'writer'
+ # highlight-end
+```
+
+### Available roles
+
+| Role | Grants access to |
+| ------------------ | ------------------------------------------------------------- |
+| `superAdmin` | All operations; bypasses all claim checks |
+| `manageSources` | Create, update, delete, and list sources via the admin API |
+| `manageRegistries` | Create, update, delete, and list registries via the admin API |
+| `manageEntries` | Publish and delete MCP server versions and skills |
+
+### Role rule matching
+
+Each role is defined as a list of claim maps. A caller is granted the role if
+their JWT claims match **any** map in the list (OR logic). Within a single map,
+**all** key-value pairs must match (AND logic).
+
+```yaml title="Example: grant manageSources to org admins OR platform leads"
+authz:
+ roles:
+ manageSources:
+ # Rule 1: any admin in the acme org
+ - org: 'acme'
+ role: 'admin'
+ # Rule 2: anyone with the platform-lead role
+ - role: 'platform-lead'
+```
+
+Claim values can be strings or arrays. When a JWT claim is an array (for
+example, `role: ["admin", "writer"]`), the server checks whether any element
+matches the required value. A rule with `role: "admin"` would match this JWT
+because `"admin"` is one of the array elements.
+
+### Super-admin role
+
+The `superAdmin` role bypasses **all** claim checks across the entire server. A
+super-admin can:
+
+- Access any registry regardless of its claims
+- See all entries regardless of source or entry claims
+- Manage any source or registry, even those with claims outside their JWT
+- Publish and delete entries without claim validation
+
+Use this role sparingly and only for platform operators who need unrestricted
+access.
+
+## Configure claims on sources and registries
+
+Claims are key-value pairs attached to sources and registries in your
+configuration file. They act as access boundaries — only callers whose JWT
+claims satisfy the resource's claims can access it.
+
+### Source claims
+
+For synced sources (Git, API, File), claims on a source are **inherited by all
+entries** during sync. Every MCP server or skill ingested from that source
+carries the source's claims.
+
+For Kubernetes and managed sources, source claims control who can manage the
+source via the admin API but are **not** inherited by entries. Kubernetes
+entries get claims from the
+[`authz-claims` annotation](./configuration.mdx#per-entry-claims-via-annotation)
+on each CRD. Managed source entries get claims from the publish request payload.
+
+```yaml title="config-source-claims.yaml"
+sources:
+ - name: platform-tools
+ format: upstream
+ git:
+ repository: https://github.com/acme/platform-tools.git
+ branch: main
+ path: registry.json
+ syncPolicy:
+ interval: '30m'
+ # highlight-start
+ claims:
+ org: 'acme'
+ team: 'platform'
+ # highlight-end
+
+ - name: data-tools
+ format: upstream
+ git:
+ repository: https://github.com/acme/data-tools.git
+ branch: main
+ path: registry.json
+ syncPolicy:
+ interval: '30m'
+ claims:
+ org: 'acme'
+ team: 'data'
+```
+
+With this configuration:
+
+- Entries from `platform-tools` are visible only to callers with `org: "acme"`
+ **and** `team: "platform"` in their JWT
+- Entries from `data-tools` are visible only to callers with `org: "acme"`
+ **and** `team: "data"` in their JWT
+- A super-admin sees all entries regardless of claims
+
+### Registry claims
+
+Claims on a registry act as an **access gate** for the consumer API. Before
+returning any data from a registry's endpoints, the server checks that the
+caller's JWT claims satisfy the registry's claims.
+
+```yaml title="config-registry-claims.yaml"
+registries:
+ - name: platform
+ sources: ['platform-tools']
+ # highlight-start
+ claims:
+ org: 'acme'
+ team: 'platform'
+ # highlight-end
+
+ - name: public
+ sources: ['community-tools']
+ # No claims — accessible to all authenticated users
+```
+
+A caller who requests `GET /platform/v0.1/servers` must have JWT claims that
+include `org: "acme"` and `team: "platform"`. Otherwise, the server returns
+`403 Forbidden`.
+
+### Claim containment
+
+The server uses **containment** (superset check) for claim validation: the
+caller's claims must be a superset of the resource's claims. For example:
+
+| Resource claims | Caller JWT claims | Result |
+| --------------------------------- | --------------------------------- | ------- |
+| `{org: "acme"}` | `{org: "acme", team: "platform"}` | Allowed |
+| `{org: "acme", team: "platform"}` | `{org: "acme"}` | Denied |
+| `{}` (no claims) | `{org: "acme"}` | Allowed |
+| `{org: "acme"}` | `{org: "contoso"}` | Denied |
+
+Registries and sources with no claims are accessible to all authenticated
+callers.
+
+:::warning[Entries without claims are invisible when authorization is enabled]
+
+Entries with no claims are visible in anonymous mode and
+[auth-only mode](#auth-only-mode), but **invisible** when full authorization is
+enabled. The per-entry filter requires both sides to have claims for a match —
+entries without claims are filtered out. To make entries visible, attach claims
+to the source (for synced sources) or to individual entries (via the publish
+payload or the
+[`authz-claims` annotation](./configuration.mdx#per-entry-claims-via-annotation)
+for Kubernetes sources).
+
+:::
+
+## Claims on published entries
+
+When you publish an MCP server version or skill to a managed source, you can
+attach claims to the entry. The server enforces two rules:
+
+1. **Publish claims must be a subset of the publisher's JWT claims.** Every
+ claim key in the publish request must exist in the publisher's JWT with a
+ matching value. For example, if your JWT has
+ `{org: "acme", team: "platform"}`, you can publish entries with
+ `{org: "acme"}`, `{org: "acme", team: "platform"}`, or no claims at all — but
+ not with `{org: "contoso"}` (a value your JWT doesn't have).
+
+2. **Subsequent versions must have the same claims as the first.** Once you
+ publish the first version of an entry with specific claims, all future
+ versions must carry the exact same claims. This prevents accidentally
+ narrowing or broadening visibility across versions.
+
+```bash title="Publish a server with claims"
+curl -X POST \
+ https://registry.example.com/default/v0.1/publish \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "my-server",
+ "version": "1.0.0",
+ "url": "https://mcp.example.com/my-server",
+ "description": "Team-scoped MCP server",
+ "claims": {
+ "org": "acme",
+ "team": "platform"
+ }
+ }'
+```
+
+## Admin API claim scoping
+
+When authorization is enabled, the admin API endpoints for managing sources and
+registries are also scoped by claims:
+
+- **List sources/registries**: Only returns resources whose claims the caller's
+ JWT satisfies.
+- **Get source/registry by name**: Returns `404 Not Found` (not `403`) when the
+ caller's claims don't match — this prevents information disclosure about
+ resources the caller cannot access.
+- **List source/registry entries**: `GET /v1/sources/{name}/entries` and
+ `GET /v1/registries/{name}/entries` return the raw entries (servers and skills
+ with versions and claims) in a source or registry. These endpoints follow the
+ same claim scoping — the parent source or registry must be accessible to the
+ caller.
+- **Create source/registry**: The request claims must be a subset of the
+ caller's JWT claims.
+- **Update/delete source/registry**: The caller's JWT claims must satisfy the
+ existing resource's claims.
+
+## Auth-only mode
+
+When OAuth authentication is enabled but the `auth.authz` block is omitted from
+your configuration, the server runs in **auth-only mode**. In this mode:
+
+- Callers must still authenticate with a valid JWT token
+- All claims-based filtering is disabled — every authenticated caller sees all
+ entries regardless of source or registry claims
+- Role checks are not enforced (no roles are resolved)
+- The server logs a warning at startup:
+ `Authorization not configured, per-entry claims filtering disabled (auth-only mode)`
+
+Auth-only mode is useful when you need identity verification without
+multi-tenant visibility controls — for example, a single-team deployment where
+all authenticated users should have the same access.
+
+To enable full authorization, add the `auth.authz` block with
+[role definitions](#configure-roles).
+
+## Anonymous mode
+
+When authentication is set to `anonymous`, all authorization checks are
+bypassed. There are no JWT claims to validate, so all sources, registries, and
+entries are accessible without restriction. This is suitable for development and
+testing environments only.
+
+## Check your identity and permissions
+
+Use the `GET /v1/me` endpoint to verify your authenticated identity and resolved
+roles:
+
+```bash
+curl -H "Authorization: Bearer $TOKEN" \
+ https://registry.example.com/v1/me
+```
+
+```json title="Example response"
+{
+ "subject": "user@example.com",
+ "roles": ["manageSources", "manageEntries"]
+}
+```
+
+This is useful for debugging authorization issues — you can confirm which roles
+your JWT grants and whether the expected claims are present. The endpoint
+returns `401 Unauthorized` in anonymous mode since there is no identity to
+report.
+
+## Complete example
+
+This example shows a multi-team setup with full RBAC and claims-based scoping:
+
+```yaml title="config-multi-tenant.yaml"
+sources:
+ - name: platform-tools
+ format: upstream
+ git:
+ repository: https://github.com/acme/platform-tools.git
+ branch: main
+ path: registry.json
+ syncPolicy:
+ interval: '30m'
+ claims:
+ org: 'acme'
+ team: 'platform'
+
+ - name: data-tools
+ format: upstream
+ git:
+ repository: https://github.com/acme/data-tools.git
+ branch: main
+ path: registry.json
+ syncPolicy:
+ interval: '30m'
+ claims:
+ org: 'acme'
+ team: 'data'
+
+ - name: shared
+ managed: {}
+
+registries:
+ - name: platform
+ sources: ['platform-tools', 'shared']
+ claims:
+ org: 'acme'
+ team: 'platform'
+
+ - name: data
+ sources: ['data-tools', 'shared']
+ claims:
+ org: 'acme'
+ team: 'data'
+
+auth:
+ mode: oauth
+ oauth:
+ resourceUrl: https://registry.example.com
+ providers:
+ - name: keycloak
+ issuerUrl: https://keycloak.example.com/realms/mcp
+ audience: registry-api
+ authz:
+ roles:
+ superAdmin:
+ - role: 'super-admin'
+ manageSources:
+ - org: 'acme'
+ role: 'admin'
+ manageRegistries:
+ - org: 'acme'
+ role: 'admin'
+ manageEntries:
+ - role: 'writer'
+```
+
+With this configuration:
+
+- **Platform team members** (JWT with `org: "acme"`, `team: "platform"`) can
+ access the `platform` registry and see entries from `platform-tools` and
+ `shared`.
+- **Data team members** (JWT with `org: "acme"`, `team: "data"`) can access the
+ `data` registry and see entries from `data-tools` and `shared`.
+- **Writers** (JWT with `role: "writer"`) can publish to the `shared` managed
+ source.
+- **Admins** (JWT with `org: "acme"`, `role: "admin"`) can manage sources and
+ registries within the `acme` org.
+- **Super-admins** (JWT with `role: "super-admin"`) can access and manage
+ everything.
+
+Entries published to the `shared` source without claims are visible through any
+registry that includes it, subject only to the registry-level claims gate. To
+restrict visibility further, attach claims when
+[publishing entries](#claims-on-published-entries).
+
+## Next steps
+
+- [Configure authentication](./authentication.mdx) to set up OAuth providers
+- [Configure sources and registries](./configuration.mdx) to set up your data
+ sources
+- [Manage skills](./skills.mdx) to publish and discover reusable skills
+
+## Related information
+
+- [Registry server introduction](./intro.mdx) - architecture and features
+ overview
diff --git a/versioned_docs/version-1.0/toolhive/guides-registry/configuration.mdx b/versioned_docs/version-1.0/toolhive/guides-registry/configuration.mdx
new file mode 100644
index 00000000..60f2be00
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-registry/configuration.mdx
@@ -0,0 +1,527 @@
+---
+title: Configuration
+description: How to configure the ToolHive Registry Server with registry and sync policies
+---
+
+All configuration is done via YAML files. The server requires a `--config` flag
+pointing to a YAML configuration file.
+
+## Configuration file structure
+
+```yaml title="config.yaml"
+# Registry name/identifier (optional, defaults to "default")
+registryName: my-registry
+
+# Registries configuration (required, can have multiple registries)
+registries:
+ - name: toolhive
+ # Data format: upstream or toolhive (see below)
+ format: upstream
+
+ # Git repository configuration
+ git:
+ repository: https://github.com/stacklok/toolhive-catalog.git
+ branch: main
+ path: pkg/catalog/toolhive/data/official-registry.json
+
+ # Per-registry automatic sync policy (required for synced registries)
+ syncPolicy:
+ # Sync interval (e.g., "30m", "1h", "24h")
+ interval: '30m'
+
+ # Optional: Per-registry server filtering
+ filter:
+ names:
+ include: ['official/*']
+ exclude: ['*/deprecated']
+ tags:
+ include: ['production']
+ exclude: ['experimental']
+
+# Authentication configuration (required)
+# See authentication.mdx for detailed configuration options
+auth:
+ mode: anonymous
+
+# Database configuration (required)
+database:
+ host: localhost
+ port: 5432
+ user: registry
+ database: registry
+ sslMode: require
+ maxOpenConns: 25
+ maxIdleConns: 5
+ connMaxLifetime: '5m'
+ maxMetaSize: 262144
+ # Optional: dynamic authentication (alternative to pgpass)
+ # dynamicAuth:
+ # awsRdsIam:
+ # region: us-east-1
+```
+
+## Command-line flags
+
+| Flag | Description | Required | Default |
+| ------------- | ------------------------------------------- | -------- | ------- |
+| `--config` | Path to YAML configuration file | Yes | - |
+| `--address` | Server listen address | No | `:8080` |
+| `--auth-mode` | Override auth mode (`anonymous` or `oauth`) | No | - |
+
+## Registry data formats
+
+The `format` field specifies the JSON schema format for the registry data:
+
+- **`upstream`**: The official
+ [upstream MCP registry format](../reference/registry-schema-upstream.mdx),
+ used by the MCP Registry API and recommended for new registries.
+- **`toolhive`**: The
+ [ToolHive-native format](../reference/registry-schema-toolhive.mdx), used by
+ the built-in ToolHive registry.
+
+## Registries
+
+The server supports five registry types, each with its own configuration
+options. You can configure multiple registries in a single server instance.
+
+### Git repository source
+
+Clone and sync from Git repositories. Ideal for version-controlled registries.
+
+:::note
+
+When the registry server clones a Git repository, it stores the local copy in
+`/data`. Mounting a persistent volume there is optional but recommended for
+production deployments. Without it, the registry server re-clones the repository
+on every container restart, adding startup latency and increasing network usage.
+
+:::
+
+```yaml title="config-git.yaml"
+registries:
+ - name: toolhive
+ format: toolhive
+ git:
+ repository: https://github.com/stacklok/toolhive-catalog.git
+ branch: main
+ path: pkg/catalog/toolhive/data/registry.json
+ syncPolicy:
+ interval: '30m'
+```
+
+**Configuration options:**
+
+- `repository` (required): Git repository URL
+- `branch` (optional): Branch name to use (defaults to `main`)
+- `tag` (optional): Tag name to pin to a specific version
+- `commit` (optional): Commit SHA to pin to a specific commit
+- `path` (optional): Path to the registry file within the repository (defaults
+ to `registry.json`)
+- `auth` (optional): Authentication for private repositories (see below)
+
+:::tip
+
+You can use `branch`, `tag`, or `commit` to pin to a specific version. If
+multiple are specified, `commit` takes precedence over `tag`, which takes
+precedence over `branch`.
+
+:::
+
+#### Private repository authentication
+
+To access private Git repositories, configure the `auth` section with your
+credentials:
+
+```yaml title="config-git-private.yaml"
+registries:
+ - name: private-registry
+ format: toolhive
+ git:
+ repository: https://github.com/my-org/private-registry.git
+ branch: main
+ path: registry.json
+ # highlight-start
+ auth:
+ username: oauth2
+ passwordFile: /secrets/git/token
+ # highlight-end
+ syncPolicy:
+ interval: '30m'
+```
+
+**Authentication options:**
+
+- `auth.username` (required with `passwordFile`): Git username for HTTP Basic
+ authentication. For GitHub and GitLab, use `oauth2` as the username when
+ authenticating with a personal access token (PAT).
+- `auth.passwordFile` (required with `username`): Absolute path to a file
+ containing the Git password or token. Whitespace is trimmed from the file
+ content.
+
+:::warning
+
+Both `username` and `passwordFile` must be specified together. If only one is
+provided, the configuration will fail validation.
+
+:::
+
+**Using with Kubernetes secrets:**
+
+In Kubernetes deployments, mount a secret containing your Git token and
+reference the mount path:
+
+```yaml title="registry-deployment.yaml"
+apiVersion: v1
+kind: Secret
+metadata:
+ name: git-credentials
+type: Opaque
+stringData:
+ token:
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: registry-server
+spec:
+ # Other deployment fields omitted, refer to the Deployment in Kubernetes guide
+ template:
+ spec:
+ containers:
+ - name: registry
+ volumeMounts:
+ - name: git-credentials
+ mountPath: /secrets/git
+ readOnly: true
+ - name: data
+ mountPath: /data
+ readOnly: false
+ volumes:
+ - name: git-credentials
+ secret:
+ secretName: git-credentials
+ items:
+ - key: token
+ path: token
+ - name: data
+ emptyDir: {}
+```
+
+### API endpoint source
+
+Sync from upstream MCP Registry APIs. Supports federation and aggregation
+scenarios.
+
+```yaml title="config-api.yaml"
+registries:
+ - name: mcp-upstream
+ format: upstream
+ api:
+ endpoint: https://registry.modelcontextprotocol.io
+ syncPolicy:
+ interval: '1h'
+```
+
+**Configuration options:**
+
+- `endpoint` (required): Base URL of the upstream MCP Registry API (without
+ path)
+- `format` (required): Must be `upstream` for API sources
+
+:::note
+
+The server automatically appends the appropriate API paths (`/v0.1/servers`,
+etc.) to the endpoint URL.
+
+:::
+
+### File source
+
+Read from filesystem. Ideal for local development and testing.
+
+```yaml title="config-file.yaml"
+registries:
+ - name: local
+ format: upstream
+ file:
+ path: /data/registry.json
+ syncPolicy:
+ interval: '15m'
+```
+
+Alternatively, the source can be a custom URL.
+
+```yaml title="config-file.yaml"
+registries:
+ - name: remote
+ format: upstream
+ file:
+ url: https://www.example.com/registry.json
+ timeout: 5s
+ syncPolicy:
+ interval: '15m'
+```
+
+The fields `file` and `url` are mutually exclusive.
+
+**Configuration options:**
+
+- `path` (required): Path to the registry file on the filesystem. Mutually
+ exclusive with `url`
+- `url` (required): URL is the HTTP/HTTPS URL to fetch the registry file from.
+ Mutually exclusive with `file`
+- `timeout` (optional): The timeout for HTTP requests when using URL, defaults
+ to 30s, ignored when `file` is specified
+
+### Managed registry
+
+API-managed registry for directly publishing and deleting MCP servers via the
+API. Does not sync from external sources.
+
+```yaml title="config-managed.yaml"
+registries:
+ - name: internal
+ format: upstream
+ managed: {}
+```
+
+**Configuration options:**
+
+- `managed` (required): Empty object to indicate managed registry type
+- No sync policy required (managed registries are updated via API, not synced)
+
+**Supported operations:**
+
+- `POST /{registryName}/v0.1/publish` - Publish new server versions
+- `DELETE /{registryName}/v0.1/servers/{name}/versions/{version}` - Delete
+ versions
+- `GET` operations for listing and retrieving servers
+- [Skills management](./skills.mdx) via the extensions API
+
+See the [Registry API reference](../reference/registry-api.mdx) for complete
+endpoint documentation and request/response examples.
+
+### Kubernetes registry
+
+Discovers MCP servers from running Kubernetes deployments. Automatically creates
+registry entries for deployed MCP servers in your cluster.
+
+:::note[Operator-managed registry]
+
+When using the ToolHive operator, a single Kubernetes registry named `default`
+is automatically created and managed. You cannot configure additional Kubernetes
+registries through the `MCPRegistry` CR or configuration file, as only one
+Kubernetes registry instance is supported per Registry Server instance.
+
+The configuration example below is for reference when running the Registry
+Server standalone (without the operator).
+
+:::
+
+By default, the Registry server discovers resources in all namespaces
+(cluster-wide). You can restrict discovery to specific namespaces using the
+`THV_REGISTRY_WATCH_NAMESPACE` environment variable. See
+[Workload discovery](./deploy-manual.mdx#workload-discovery) for configuration
+details and RBAC requirements.
+
+```yaml title="config-kubernetes.yaml"
+registries:
+ - name: default
+ format: toolhive
+ kubernetes: {}
+```
+
+**Configuration options:**
+
+- `kubernetes` (required): Empty object to indicate Kubernetes registry type
+- No sync policy required (Kubernetes registries query live deployments
+ on-demand)
+- Only one Kubernetes registry is supported per Registry Server instance
+
+:::info[How does it work?]
+
+Kubernetes workload discovery works by looking for annotations in a specific set
+of workloads. The types being watched are
+[`MCPServer`](../guides-k8s/run-mcp-k8s.mdx),
+[`MCPRemoteProxy`](../guides-k8s/remote-mcp-proxy.mdx), and
+[`VirtualMCPServer`](../guides-vmcp/configuration.mdx).
+
+The Registry server will receive events when a resource among those listed above
+is annotated with the following annotations:
+
+```yaml {7-16}
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: my-mcp-server
+ namespace: toolhive-system
+ annotations:
+ toolhive.stacklok.dev/registry-export: 'true'
+ toolhive.stacklok.dev/registry-title: 'Code Analysis Server'
+ toolhive.stacklok.dev/registry-url: 'https://mcp.example.com/servers/my-mcp-server'
+ toolhive.stacklok.dev/registry-description: |
+ Production MCP server for code analysis
+ toolhive.stacklok.dev/tool-definitions:
+ '[{"name":"analyze_code","description":"Analyze code for potential
+ issues","inputSchema":{"type":"object","properties":{"file":{"type":"string","description":"Path
+ to the file to
+ analyze"},"rules":{"type":"array","items":{"type":"string"},"description":"List
+ of rule IDs to check"}},"required":["file"]}}]'
+spec:
+ # ... MCP server spec
+```
+
+| Annotation | Required | Description |
+| -------------------------------------------- | -------- | --------------------------------------------------------------------------------- |
+| `toolhive.stacklok.dev/registry-export` | Yes | Must be `"true"` to include in registry |
+| `toolhive.stacklok.dev/registry-url` | Yes | The external endpoint URL for this server |
+| `toolhive.stacklok.dev/registry-description` | Yes | Description text displayed in registry listings |
+| `toolhive.stacklok.dev/registry-title` | No | Human-friendly display name for the registry entry (overrides the generated name) |
+| `toolhive.stacklok.dev/tools` | No | JSON array of tool name strings (e.g., `["get_weather","get_forecast"]`) |
+| `toolhive.stacklok.dev/tool-definitions` | No | JSON array of tool definitions with MCP tool metadata (name, description, schema) |
+
+**Tool definitions format:**
+
+The `tool-definitions` annotation accepts a JSON array containing tool metadata
+that follows the MCP specification. Each tool definition can include:
+
+- `name` (required): Tool identifier
+- `description`: Human-readable description of what the tool does
+- `inputSchema`: JSON Schema describing the tool's input parameters
+- `outputSchema`: JSON Schema describing the tool's output format
+- `annotations`: Additional metadata about the tool
+
+The Registry server validates JSON syntax only. Schema validation is performed
+by the operator during resource reconciliation. If the annotation contains
+invalid JSON, a warning is logged and the tool definitions are omitted from the
+registry, but the server is still registered normally.
+
+Tool definitions appear in the Registry API response under the server's registry
+URL within the publisher-provided metadata:
+
+```json
+{
+ "_meta": {
+ "io.modelcontextprotocol.registry/publisher-provided": {
+ "io.github.stacklok": {
+ "https://mcp.example.com/servers/my-mcp-server": {
+ "tool_definitions": [...]
+ }
+ }
+ }
+ }
+}
+```
+
+The registry URL from the `registry-url` annotation becomes a key in the JSON
+structure.
+
+This feature requires the Registry server to be granted access to those
+resources via a Service Account, check the details in the
+[deployment section](./deploy-manual.mdx#workload-discovery).
+
+:::
+
+**Use cases:**
+
+- Discover MCP servers deployed via ToolHive Operator
+- Automatically expose running MCP servers to end users
+- Separate MCP server development from user consumption
+
+## Sync policy
+
+Configure automatic synchronization of registry data on a per-registry basis.
+Only applicable to synced registries (Git, API, File). Not required for managed
+or Kubernetes registries.
+
+```yaml
+registries:
+ - name: toolhive
+ git:
+ repository: https://github.com/stacklok/toolhive-catalog.git
+ branch: main
+ path: pkg/catalog/toolhive/data/registry.json
+ syncPolicy:
+ # Sync interval (e.g., "30m", "1h", "24h")
+ interval: '30m'
+```
+
+The `interval` field specifies how often the server should fetch updates from
+the registry. Use Go duration format (e.g., `"30m"`, `"1h"`, `"24h"`).
+
+:::note
+
+Sync policy is per-registry and must be specified within each registry
+configuration. Managed and Kubernetes registries do not require sync policies.
+
+:::
+
+## Server filtering
+
+Optionally filter which servers are exposed through the API on a per-registry
+basis. Only applicable to synced registries (Git, API, File).
+
+```yaml
+registries:
+ - name: toolhive
+ git:
+ repository: https://github.com/stacklok/toolhive-catalog.git
+ branch: main
+ path: pkg/catalog/toolhive/data/registry.json
+ syncPolicy:
+ interval: '30m'
+ filter:
+ names:
+ include: ['official/*']
+ exclude: ['*/deprecated']
+ tags:
+ include: ['production']
+ exclude: ['experimental']
+```
+
+**Filter options:**
+
+- `names.include`: List of name patterns to include (supports wildcards)
+- `names.exclude`: List of name patterns to exclude (supports wildcards)
+- `tags.include`: List of tags that servers must have
+- `tags.exclude`: List of tags that servers must not have
+
+:::note
+
+Filters are per-registry and specified within each registry configuration.
+
+:::
+
+## Authentication configuration
+
+The server supports multiple authentication modes to fit different deployment
+scenarios. All authentication configuration is done via the `auth` section in
+your configuration file.
+
+Available modes:
+
+- **Anonymous**: No authentication (development/testing)
+- **OAuth with Kubernetes**: Service account token validation
+- **OAuth with generic providers**: Integration with Keycloak, Auth0, Okta, etc.
+
+See the [Authentication configuration](./authentication.mdx) guide for detailed
+information on configuring each mode.
+
+## Database configuration
+
+The server requires a PostgreSQL database for storing registry state and
+metadata. See the [Database configuration](./database.mdx) guide for detailed
+information.
+
+## Telemetry configuration
+
+The server supports OpenTelemetry for comprehensive observability, including
+distributed tracing and metrics collection. See the
+[Telemetry and metrics](./telemetry-metrics.mdx) guide for detailed information.
+
+## Next steps
+
+- [Configure authentication](./authentication.mdx) for secure access
+- [Configure telemetry](./telemetry-metrics.mdx) for metrics and tracing
+- [Deploy the server](./deployment.mdx) with your configuration
+- [Configure database storage](./database.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-registry/database.mdx b/versioned_docs/version-1.0/toolhive/guides-registry/database.mdx
new file mode 100644
index 00000000..24d19b1b
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-registry/database.mdx
@@ -0,0 +1,310 @@
+---
+title: Database configuration
+description:
+ How to configure PostgreSQL database storage and migrations for the ToolHive
+ Registry server
+---
+
+The Registry server requires a PostgreSQL database for storing registry state
+and metadata. This enables persistence across restarts and provides a foundation
+for advanced features.
+
+## Configuration
+
+### Basic database configuration
+
+```yaml title="config.yaml"
+database:
+ host: localhost
+ port: 5432
+ user: registry
+ database: registry
+ sslMode: require
+ maxOpenConns: 25
+ maxIdleConns: 5
+ connMaxLifetime: '5m'
+ maxMetaSize: 262144
+```
+
+### Configuration fields
+
+| Field | Type | Required | Default | Description |
+| ----------------- | ------ | -------- | --------- | -------------------------------------------------------------------------------------------------- |
+| `host` | string | Yes | - | Database server hostname or IP address |
+| `port` | int | Yes | - | Database server port |
+| `user` | string | Yes | - | Database username for normal operations |
+| `migrationUser` | string | No | `user` | Database username for running migrations (should have elevated privileges) |
+| `database` | string | Yes | - | Database name |
+| `sslMode` | string | No | `require` | SSL mode (`disable`, `require`, `verify-ca`, `verify-full`) |
+| `maxOpenConns` | int | No | `25` | Maximum number of open connections to the database |
+| `maxIdleConns` | int | No | `5` | Maximum number of idle connections in the pool |
+| `connMaxLifetime` | string | No | `5m` | Maximum lifetime of a connection (e.g., "1h", "30m") |
+| `maxMetaSize` | int | No | `262144` | Maximum allowed size in bytes for publisher-provided metadata extensions (256 KB) |
+| `dynamicAuth` | object | No | - | Dynamic authentication configuration (see [Dynamic authentication](#dynamic-authentication) below) |
+
+\* Password configuration is required but has multiple sources (see Password
+Security and Dynamic authentication below)
+
+## Password security
+
+The server supports secure password management with separate credentials for
+migrations and normal operations. This follows the principle of least privilege
+by using elevated privileges only when necessary.
+
+Password configuration is done using a
+[Postgres Password File](https://www.postgresql.org/docs/current/libpq-pgpass.html)
+and exporting the `PGPASSFILE` environment variable.
+
+### Recommended setup
+
+For production deployments, use separate database users:
+
+1. **Application user** (`user`): Limited privileges for normal operations
+ - SELECT, INSERT, UPDATE, DELETE on application tables
+ - No schema modification privileges
+
+2. **Migration user** (`migrationUser`): Elevated privileges for migrations
+ - CREATE, ALTER, DROP on schemas and tables
+ - Used only during migration operations
+
+### Example configuration with separate users
+
+```yaml title="config-production.yaml"
+database:
+ host: db.example.com
+ port: 5432
+ user: db_app
+ migrationUser: db_migrator
+ database: registry
+ sslMode: verify-full
+```
+
+Store passwords in a pgpass file with restricted permissions:
+
+```bash
+# Create pgpass file (recommended location: /etc/secrets/pgpassfile)
+echo "db.example.com:5432:registry:db_app:app_password" > /etc/secrets/pgpassfile
+echo "db.example.com:5432:registry:db_migrator:migrator_password" >> /etc/secrets/pgpassfile
+
+# Mandatory: restrict permissions to 0600, will be ignored otherwise
+chmod 600 /etc/secrets/pgpassfile
+```
+
+**Using the pgpass file:**
+
+Set the `PGPASSFILE` environment variable when running the server:
+
+```bash
+# For standalone server
+export PGPASSFILE=/etc/secrets/pgpassfile
+thv-registry-api serve --config config.yaml
+
+# For Docker/Kubernetes
+# Set the PGPASSFILE environment variable in your deployment configuration
+# See deployment.mdx for examples
+```
+
+:::tip
+
+The pgpass file format is: `hostname:port:database:username:password`
+
+You can use wildcards (`*`) for any field except password. For example:
+
+- `*:5432:*:db_app:app_password` - matches any host or database
+- `localhost:*:registry:db_app:app_password` - matches any port
+
+See the
+[PostgreSQL documentation](https://www.postgresql.org/docs/current/libpq-pgpass.html)
+for more details.
+
+:::
+
+You can find more details about user creation and initial configuration in this
+[test file](https://github.com/stacklok/toolhive-registry-server/blob/301ccf4e3ad13daad28be7b669d8e5fca337ec14/cmd/thv-registry-api/app/serve_test.go#L56-L103).
+
+## Dynamic authentication
+
+As an alternative to pgpass files, the server supports dynamic credential
+generation for cloud-hosted databases.
+
+### AWS RDS IAM authentication
+
+When running on AWS, you can authenticate to RDS using IAM credentials instead
+of static passwords. The server generates short-lived authentication tokens
+using the IAM role attached to the workload.
+
+```yaml title="config-aws-rds.yaml"
+database:
+ host: my-database.123456789.us-east-1.rds.amazonaws.com
+ port: 5432
+ user: my_app_user
+ database: registry
+ sslMode: require
+ dynamicAuth:
+ awsRdsIam:
+ region: us-east-1
+```
+
+**Configuration options:**
+
+- `dynamicAuth.awsRdsIam.region` (required): The AWS region of the RDS instance.
+ Set to `detect` to automatically determine the region from the EC2 instance
+ metadata service (IMDS).
+
+:::note
+
+Dynamic authentication replaces pgpass files. The server generates
+authentication tokens automatically before each connection.
+
+:::
+
+## Database migrations
+
+The server uses database migrations to manage schema changes. Migrations run
+automatically on startup, but you can also run them manually.
+
+### Automatic migrations
+
+By default, the server runs migrations automatically when it starts:
+
+1. Connects to the database using the migration user credentials
+2. Checks the current migration version
+3. Applies any pending migrations
+4. Switches to the application user for normal operations
+
+This ensures the database schema is always up to date.
+
+### Manual migrations
+
+You can run migrations manually using the CLI:
+
+#### Run migrations
+
+```bash
+thv-registry-api migrate up --config config.yaml [--yes]
+```
+
+The `--yes` flag skips the confirmation prompt.
+
+#### Rollback migrations
+
+```bash
+thv-registry-api migrate down --config config.yaml --num-steps N [--yes]
+```
+
+The `--num-steps` parameter specifies how many migration steps to roll back.
+
+### Migration user privileges
+
+The migration user needs the following privileges:
+
+- CREATE, ALTER, DROP on the target database
+- Ability to create and modify tables, indexes, and other schema objects
+- SELECT, INSERT, UPDATE, DELETE on the migration tracking table
+
+Example SQL to create a migration user:
+
+```sql
+DO $$
+DECLARE
+ migrator_user TEXT := 'db_migrator';
+ migrator_password TEXT := 'migrator_password';
+ db_name TEXT := 'registry';
+BEGIN
+ EXECUTE format('CREATE USER %I WITH PASSWORD %L', migrator_user, migrator_password);
+ EXECUTE format('GRANT CONNECT ON DATABASE %I TO %I', db_name, migrator_user);
+ EXECUTE format('GRANT CREATE ON SCHEMA public TO %I', migrator_user);
+ EXECUTE format('GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO %I', migrator_user);
+END
+$$;
+```
+
+### Application user privileges
+
+The application user needs limited privileges for normal operations:
+
+- SELECT, INSERT, UPDATE, DELETE on application tables
+- No schema modification privileges
+
+Example SQL to create an application user:
+
+```sql
+DO $$
+DECLARE
+ app_user TEXT := 'db_app';
+ app_password TEXT := 'app_password';
+ db_name TEXT := 'registry';
+BEGIN
+ CREATE ROLE toolhive_registry_server;
+ EXECUTE format('CREATE USER %I WITH PASSWORD %L', app_user, app_password);
+ EXECUTE format('GRANT toolhive_registry_server TO %I', app_user);
+ EXECUTE format('GRANT CONNECT ON DATABASE %I TO %I', db_name, app_user);
+END
+$$;
+```
+
+## SSL/TLS configuration
+
+Configure SSL/TLS for secure database connections:
+
+- `disable`: No SSL (not recommended for production)
+- `require`: Require SSL (default)
+- `verify-ca`: Require SSL and verify CA certificate
+- `verify-full`: Require SSL and verify both CA and server hostname
+
+For production, use `verify-full`:
+
+```yaml
+database:
+ sslMode: verify-full
+```
+
+## Connection pooling
+
+Tune connection pool settings for your workload:
+
+```yaml
+database:
+ maxOpenConns: 25 # Maximum open connections
+ maxIdleConns: 5 # Maximum idle connections
+ connMaxLifetime: '5m' # Maximum connection lifetime
+```
+
+**Guidelines:**
+
+- `maxOpenConns`: Set based on your database server's connection limits
+- `maxIdleConns`: Typically 20-25% of `maxOpenConns`
+- `connMaxLifetime`: Set to less than your database server's connection timeout
+
+## Troubleshooting
+
+### Connection errors
+
+If you encounter connection errors:
+
+1. Verify database credentials are correct
+2. Check network connectivity to the database server
+3. Ensure the database server allows connections from your host
+4. Verify SSL/TLS configuration matches your database server settings
+
+### Migration errors
+
+If migrations fail:
+
+1. Check that the migration user has sufficient privileges
+2. Verify the database exists and is accessible
+3. Check migration logs for specific error messages
+4. Ensure no other processes are modifying the schema concurrently
+
+### Permission errors
+
+If you see permission errors during normal operations:
+
+1. Verify the application user has the required privileges
+2. Check that migrations completed successfully
+3. Ensure the application user can access all required tables
+
+## Next steps
+
+- [Deploy the server](./deployment.mdx) with database configuration
+- [Configure data sources](./configuration.mdx) to populate the registry
diff --git a/versioned_docs/version-1.0/toolhive/guides-registry/deploy-manual.mdx b/versioned_docs/version-1.0/toolhive/guides-registry/deploy-manual.mdx
new file mode 100644
index 00000000..c5a4164d
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-registry/deploy-manual.mdx
@@ -0,0 +1,361 @@
+---
+title: Deploy manually
+description: How to deploy the ToolHive Registry Server in Kubernetes using raw manifests
+---
+
+:::info
+
+For an alternative deployment approach using Kubernetes custom resources, see
+[Deploy with the ToolHive Operator](./deploy-operator.mdx).
+
+:::
+
+Below is an example Kubernetes Deployment configuring the ToolHive Registry
+Server to expose a single static registry based on a Git repository.
+
+This example assumes that a Postgres database is available at `db.example.com`
+and the necessary users for migration and application execution are configured
+and able to connect to a `registry` database. It also assumes that you have a
+keycloak instance configured to act as identity provider.
+
+All resources are created in the `toolhive-system` namespace. This namespace
+must exist before applying the deployment.
+
+For further details about user grants read the
+[Migration user privileges](./database.mdx#migration-user-privileges) and
+[Application user privileges](./database.mdx#application-user-privileges)
+sections.
+
+{/* prettier-ignore */}
+```yaml title="deployment.yaml"
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: registry-api
+ namespace: toolhive-system
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: registry-api
+ template:
+ metadata:
+ labels:
+ app: registry-api
+ spec:
+ initContainers:
+ - name: pgpass-fixer
+ image: alpine:3
+ command:
+ - /bin/sh
+ - -c
+ - cp /cfg/* /thv/ && chmod 0600 /thv/pgpass && chown 65532:65532 /thv/pgpass
+ volumeMounts:
+ - name: thv
+ mountPath: /thv
+ - name: config
+ mountPath: /cfg/config.yaml
+ subPath: config.yaml
+ - name: pgpass
+ mountPath: /cfg/pgpass
+ subPath: pgpass
+ containers:
+ - name: registry-api
+ image: ghcr.io/stacklok/thv-registry-api:latest
+ args:
+ - serve
+ - --config=/thv/config.yaml
+ env:
+ - name: PGPASSFILE
+ value: /thv/pgpass
+ ports:
+ - containerPort: 8080
+ name: http
+ volumeMounts:
+ - name: thv
+ mountPath: /thv
+ readOnly: true
+ livenessProbe:
+ httpGet:
+ path: /health
+ port: 8080
+ initialDelaySeconds: 30
+ periodSeconds: 10
+ readinessProbe:
+ httpGet:
+ path: /readiness
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ volumes:
+ - name: thv
+ emptyDir: {}
+ - name: config
+ configMap:
+ name: registry-api-config
+ items:
+ - key: config.yaml
+ path: config.yaml
+ - name: pgpass
+ secret:
+ secretName: registry-api-pgpass
+ items:
+ - key: pgpass
+ path: pgpass
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: registry-api-config
+ namespace: toolhive-system
+data:
+ config.yaml: |
+ registryName: my-registry
+ registries:
+ - name: git-registry
+ format: toolhive
+ git:
+ repository: https://github.com/stacklok/toolhive-catalog.git
+ branch: main
+ path: pkg/catalog/toolhive/data/registry.json
+ syncPolicy:
+ interval: "15m"
+ auth:
+ mode: oauth
+ oauth:
+ resourceUrl: https://registry.example.com
+ providers:
+ - name: keycloak
+ issuerUrl: https://keycloak.example.com/realms/mcp
+ audience: registry-api
+ database:
+ host: db.example.com
+ port: 5432
+ user: db_app
+ migrationUser: db_migrator
+ database: registry
+ sslMode: verify-full
+---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: registry-api-pgpass
+ namespace: toolhive-system
+type: Opaque
+stringData:
+ pgpass: |
+ db.example.com:5432:registry:db_app:app_password
+ db.example.com:5432:registry:db_migrator:migrator_password
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: registry-api
+ namespace: toolhive-system
+spec:
+ selector:
+ app: registry-api
+ ports:
+ - port: 80
+ targetPort: 8080
+ protocol: TCP
+ type: ClusterIP
+```
+
+Apply the deployment:
+
+```bash
+kubectl apply -f deployment.yaml
+```
+
+## Workload discovery
+
+Kubernetes workload discovery works by looking for annotations in a specific set
+of workloads. The types being watched are
+[`MCPServer`](../guides-k8s/run-mcp-k8s.mdx),
+[`MCPRemoteProxy`](../guides-k8s/remote-mcp-proxy.mdx), and
+[`VirtualMCPServer`](../guides-vmcp/configuration.mdx).
+
+By default, the Registry server discovers resources in all namespaces
+(cluster-wide). You can restrict discovery to specific namespaces by setting the
+`THV_REGISTRY_WATCH_NAMESPACE` environment variable to a comma-separated list of
+namespace names in your deployment:
+
+```yaml
+env:
+ - name: THV_REGISTRY_WATCH_NAMESPACE
+ value: toolhive-system,production
+```
+
+When `THV_REGISTRY_WATCH_NAMESPACE` is set, only resources in the specified
+namespaces are discovered. When unset, the server watches all namespaces.
+
+Both RBAC options below use the same ClusterRole for workload discovery and a
+separate namespace-scoped Role for leader election. The difference is how the
+ClusterRole is bound.
+
+### Cluster-wide discovery (default)
+
+For cluster-wide discovery, apply the following resources:
+
+{/* prettier-ignore */}
+```yaml title="registry-rbac-cluster.yaml"
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ labels:
+ toolhive.stacklok.io/registry-name: registry-api
+ name: registry-api
+ namespace: toolhive-system
+---
+# Manager role for workload discovery (ToolHive CRDs + services)
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ labels:
+ toolhive.stacklok.io/registry-name: registry-api
+ name: registry-api-manager
+rules:
+ - apiGroups:
+ - toolhive.stacklok.dev
+ resources:
+ - mcpservers
+ - mcpremoteproxies
+ - virtualmcpservers
+ verbs:
+ - get
+ - list
+ - watch
+ - apiGroups:
+ - ''
+ resources:
+ - services
+ verbs:
+ - get
+ - list
+ - watch
+---
+# Leader election role (namespace-scoped, always required)
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ labels:
+ toolhive.stacklok.io/registry-name: registry-api
+ name: registry-api-leader-election
+ namespace: toolhive-system
+rules:
+ - apiGroups:
+ - ''
+ resources:
+ - configmaps
+ verbs:
+ - get
+ - list
+ - watch
+ - create
+ - update
+ - patch
+ - delete
+ - apiGroups:
+ - coordination.k8s.io
+ resources:
+ - leases
+ verbs:
+ - get
+ - list
+ - watch
+ - create
+ - update
+ - patch
+ - delete
+ - apiGroups:
+ - ''
+ resources:
+ - events
+ verbs:
+ - create
+ - patch
+---
+# Leader election binding (always namespace-scoped)
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ labels:
+ toolhive.stacklok.io/registry-name: registry-api
+ name: registry-api-leader-election
+ namespace: toolhive-system
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: registry-api-leader-election
+subjects:
+ - kind: ServiceAccount
+ name: registry-api
+ namespace: toolhive-system
+---
+# Cluster-wide binding for the manager role
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ labels:
+ toolhive.stacklok.io/registry-name: registry-api
+ name: registry-api-manager
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: registry-api-manager
+subjects:
+ - kind: ServiceAccount
+ name: registry-api
+ namespace: toolhive-system
+```
+
+### Namespace-scoped discovery
+
+When `THV_REGISTRY_WATCH_NAMESPACE` is set, use the same ClusterRole but bind it
+with a RoleBinding in each watched namespace instead of a ClusterRoleBinding.
+Create one RoleBinding per namespace:
+
+{/* prettier-ignore */}
+```yaml title="registry-rbac-namespace.yaml"
+# Use the same ServiceAccount, ClusterRole, and leader election
+# Role/RoleBinding from the cluster-wide example above.
+# Replace the ClusterRoleBinding with one RoleBinding per namespace:
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ labels:
+ toolhive.stacklok.io/registry-name: registry-api
+ name: registry-api-manager
+ namespace: toolhive-system # repeat for each watched namespace
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: registry-api-manager
+subjects:
+ - kind: ServiceAccount
+ name: registry-api
+ namespace: toolhive-system
+```
+
+### Applying the service account
+
+Apply the service account to the registry server deployment in the
+`spec.template.spec` section:
+
+```yaml
+spec:
+ template:
+ spec:
+ serviceAccountName: registry-api
+```
+
+:::tip
+
+If you run multiple Registry Server instances in the same namespace, set the
+`THV_REGISTRY_LEADER_ELECTION_ID` environment variable to a unique value for
+each instance to avoid leader election lease conflicts. The Helm chart handles
+this automatically.
+
+:::
diff --git a/versioned_docs/version-1.0/toolhive/guides-registry/deploy-operator.mdx b/versioned_docs/version-1.0/toolhive/guides-registry/deploy-operator.mdx
new file mode 100644
index 00000000..a75704ad
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-registry/deploy-operator.mdx
@@ -0,0 +1,730 @@
+---
+title: Deploy with the ToolHive Operator
+description:
+ How to deploy the ToolHive Registry Server in Kubernetes using the ToolHive
+ Operator
+---
+
+## Prerequisites
+
+- A Kubernetes cluster (current and two previous minor versions are supported)
+- Permissions to create resources in the cluster
+- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) configured to communicate
+ with your cluster
+- The ToolHive operator installed in your cluster (see
+ [Deploy the operator](../guides-k8s/deploy-operator.mdx))
+- A PostgreSQL database
+
+## Overview
+
+The ToolHive operator deploys the Registry server in Kubernetes by creating
+`MCPRegistry` resources. Alternatively, you can deploy the Registry Server
+manually by following the [manual deployment instructions](./deploy-manual.mdx).
+
+### High-level architecture
+
+This diagram shows the basic relationship between components. The ToolHive
+operator watches for `MCPRegistry` resources and automatically creates the
+necessary infrastructure to run the Registry server.
+
+```mermaid
+flowchart LR
+ subgraph K8s["Kubernetes Cluster"]
+ direction LR
+ subgraph NS["toolhive-system"]
+ Operator["ToolHive Operator"]
+ end
+ subgraph NS2["Registry Namespace"]
+ Registry["Registry Server"]
+ DB[("PostgreSQL")]
+ end
+ subgraph Sources["Registry Sources"]
+ Git["Git Repository"]
+ CM["ConfigMap"]
+ PVC["PVC"]
+ API["Upstream API"]
+ end
+ end
+
+ Operator -.->|creates| Registry
+ Sources -->|sync| Registry
+ Registry --> DB
+```
+
+## Create a registry
+
+You can create `MCPRegistry` resources in the namespaces where the ToolHive
+Operator is deployed.
+
+See
+[Deploy the operator](../guides-k8s/deploy-operator.mdx#operator-deployment-modes)
+to learn about the different deployment modes.
+
+To create a registry, define an `MCPRegistry` resource and apply it to your
+cluster. This minimal example creates a registry that syncs from the ToolHive
+Git repository.
+
+```yaml title="my-registry.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRegistry
+metadata:
+ name: my-registry
+ namespace: my-namespace # Update with your namespace
+spec:
+ displayName: My MCP Registry
+ auth:
+ mode: anonymous
+ registries:
+ - name: toolhive
+ format: toolhive
+ git:
+ repository: https://github.com/stacklok/toolhive-catalog.git
+ branch: main
+ path: pkg/catalog/toolhive/data/registry.json
+ syncPolicy:
+ interval: '30m'
+```
+
+Apply the resource:
+
+```bash
+kubectl apply -f my-registry.yaml
+```
+
+:::info[What's happening?]
+
+When you apply an `MCPRegistry` resource, here's what happens:
+
+1. The ToolHive operator detects the new resource (if it's in an allowed
+ namespace)
+2. The operator creates the necessary RBAC resources in the target namespace
+3. The operator creates a Deployment containing the Registry server pod and
+ service
+4. The Registry server syncs data from the configured sources
+5. The Registry API becomes available at the service endpoint
+
+:::
+
+## Configuring source Registries
+
+The `MCPRegistry` resource supports multiple registry source types. You can
+configure one or more of them. Each type is mutually exclusive within a single
+registry configuration.
+
+### Git repository source
+
+Clone and sync from Git repositories. Ideal for version-controlled registries.
+
+```yaml {9-14} title="registry-git.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRegistry
+metadata:
+ name: git-registry
+spec:
+ auth:
+ mode: anonymous
+ registries:
+ - name: toolhive
+ format: toolhive
+ git:
+ repository: https://github.com/stacklok/toolhive-catalog.git
+ branch: main
+ path: pkg/catalog/toolhive/data/registry.json
+ syncPolicy:
+ interval: '30m'
+```
+
+**Git source fields:**
+
+| Field | Required | Description |
+| ------------ | -------- | ----------------------------------------------------- |
+| `repository` | Yes | Git repository URL (HTTP/HTTPS/SSH) |
+| `branch` | No | Branch name (mutually exclusive with `tag`, `commit`) |
+| `tag` | No | Tag name (mutually exclusive with `branch`, `commit`) |
+| `commit` | No | Commit SHA (mutually exclusive with `branch`, `tag`) |
+| `path` | No | Path to registry file (default: `registry.json`) |
+
+:::tip
+
+You can use `branch`, `tag`, or `commit` to pin to a specific version. If
+multiple are specified, `commit` takes precedence over `tag`, which takes
+precedence over `branch`.
+
+:::
+
+### ConfigMap source
+
+Read from a Kubernetes ConfigMap. Ideal for registry data managed within the
+cluster.
+
+```yaml {7-11} title="registry-configmap.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRegistry
+metadata:
+ name: configmap-registry
+spec:
+ auth:
+ mode: anonymous
+ registries:
+ - name: local
+ format: upstream
+ configMapRef:
+ name: registry-data
+ key: registry.json
+ syncPolicy:
+ interval: '15m'
+```
+
+The ConfigMap must exist in the same namespace as the `MCPRegistry` resource.
+
+### PersistentVolumeClaim source
+
+Read from a PersistentVolumeClaim. Useful for large registry files or shared
+storage scenarios.
+
+```yaml {7-10} title="registry-pvc.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRegistry
+metadata:
+ name: pvc-registry
+spec:
+ auth:
+ mode: anonymous
+ registries:
+ - name: shared
+ format: upstream
+ pvcRef:
+ claimName: registry-data-pvc
+ path: registries/production.json
+ syncPolicy:
+ interval: '1h'
+```
+
+**PVC source fields:**
+
+| Field | Required | Description |
+| ----------- | -------- | ----------------------------------------------------------- |
+| `claimName` | Yes | Name of the PersistentVolumeClaim |
+| `path` | No | Path to registry file within PVC (default: `registry.json`) |
+
+The PVC must exist in the same namespace as the `MCPRegistry` resource.
+
+### API source
+
+Sync from an upstream MCP Registry API. Supports federation and aggregation
+scenarios.
+
+```yaml {7-10} title="registry-api.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRegistry
+metadata:
+ name: api-registry
+spec:
+ auth:
+ mode: anonymous
+ registries:
+ - name: upstream
+ format: upstream
+ api:
+ endpoint: https://registry.example.com
+ syncPolicy:
+ interval: '1h'
+```
+
+The controller automatically appends the appropriate API paths to the endpoint
+URL.
+
+### Configure synchronization
+
+Each registry source can have its own sync policy that controls automatic
+synchronization.
+
+```yaml
+syncPolicy:
+ interval: '30m' # Go duration format: "1h", "30m", "24h"
+```
+
+### Filter registry content
+
+You can filter which servers are exposed through the API using name and tag
+patterns.
+
+```yaml {13-20} title="registry-filtered.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRegistry
+metadata:
+ name: filtered-registry
+spec:
+ auth:
+ mode: anonymous
+ registries:
+ - name: toolhive
+ format: toolhive
+ git:
+ repository: https://github.com/stacklok/toolhive-catalog.git
+ branch: main
+ path: pkg/catalog/toolhive/data/registry.json
+ filter:
+ names:
+ include:
+ - 'official/*'
+ exclude:
+ - '*/deprecated'
+ tags:
+ include:
+ - production
+ exclude:
+ - experimental
+ syncPolicy:
+ interval: '30m'
+```
+
+## Configure database storage
+
+Configure PostgreSQL database storage for the Registry server.
+
+```yaml {17-32} title="registry-with-database.yaml"
+apiVersion: v1
+kind: Secret
+metadata:
+ name: registry-api-db-passwords
+type: Opaque
+stringData:
+ db-password: app_password
+ migration-password: migrator_password
+---
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRegistry
+metadata:
+ name: production-registry
+spec:
+ auth:
+ mode: anonymous
+ databaseConfig:
+ host: postgres.database.svc.cluster.local
+ port: 5432
+ user: db_app
+ migrationUser: db_migrator
+ dbAppUserPasswordSecretRef:
+ name: registry-api-db-passwords
+ key: db-password
+ dbMigrationUserPasswordSecretRef:
+ name: registry-api-db-passwords
+ key: migration-password
+ database: registry
+ sslMode: verify-full
+ maxOpenConns: 25
+ maxIdleConns: 5
+ connMaxLifetime: '30m'
+ registries:
+ - name: toolhive
+ format: toolhive
+ git:
+ repository: https://github.com/stacklok/toolhive-catalog.git
+ branch: main
+ path: pkg/catalog/toolhive/data/registry.json
+ syncPolicy:
+ interval: '30m'
+```
+
+**Database configuration fields:**
+
+| Field | Default | Description |
+| ---------------------------------- | ------------- | ------------------------------------------------- |
+| `host` | `postgres` | Database server hostname |
+| `port` | `5432` | Database server port |
+| `user` | `db_app` | Application user (SELECT, INSERT, UPDATE, DELETE) |
+| `migrationUser` | `db_migrator` | Migration user (CREATE, ALTER, DROP) |
+| `dbAppUserPasswordSecretRef` | `` | Password of application user |
+| `dbMigrationUserPasswordSecretRef` | `` | Password of migration user |
+| `database` | `registry` | Database name |
+| `sslMode` | `prefer` | SSL mode (disable, prefer, require, verify-full) |
+| `maxOpenConns` | `10` | Maximum open connections |
+| `maxIdleConns` | `2` | Maximum idle connections |
+| `connMaxLifetime` | `30m` | Maximum connection lifetime |
+
+:::tip
+
+Credentials are internally configured using a pgpass file mounted as a secret.
+
+:::
+
+## Configure authentication
+
+You can configure authentication using the `authConfig` field in your
+`MCPRegistry` resource.
+
+### Authentication modes
+
+| Mode | Description | Use case |
+| ----------- | ----------------------------------------------- | ---------------------------- |
+| `oauth` | Validates access tokens from identity providers | Production deployments |
+| `anonymous` | No authentication required | Development and testing only |
+
+:::info[Secure by default]
+
+Configuring an authentication mode is mandatory, if you're not interested you
+can set it to `anonymous`.
+
+:::
+
+### OAuth authentication
+
+OAuth mode validates JWT tokens from one or more identity providers. Configure
+providers in the `authConfig.oauth.providers` array.
+
+```yaml title="registry-oauth.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRegistry
+metadata:
+ name: registry
+ namespace: toolhive-system
+spec:
+ displayName: 'Authenticated MCP Server Registry'
+ authConfig:
+ mode: oauth
+ oauth:
+ providers:
+ - name: kubernetes
+ issuerUrl: https://kubernetes.default.svc.cluster.local
+ jwksUrl: https://kubernetes.default.svc/openid/v1/jwks
+ audience: registry-server
+ caCertPath: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ authTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
+ allowPrivateIP: true
+ registries:
+ - name: toolhive
+ format: toolhive
+ git:
+ repository: https://github.com/stacklok/toolhive-catalog.git
+ branch: main
+ path: pkg/catalog/toolhive/data/registry.json
+```
+
+### OAuth configuration fields
+
+| Field | Required | Default | Description |
+| ----------------- | -------- | ----------------------------------------- | -------------------------------------------------- |
+| `mode` | No | `oauth` | Authentication mode (`oauth` or `anonymous`) |
+| `resourceUrl` | No | - | URL identifying this protected resource (RFC 9728) |
+| `realm` | No | `mcp-registry` | Protection space identifier for WWW-Authenticate |
+| `scopesSupported` | No | `[mcp-registry:read, mcp-registry:write]` | OAuth scopes supported by this resource |
+
+### Provider configuration fields
+
+| Field | Required | Description |
+| ------------------ | -------- | ----------------------------------------------------------------------- |
+| `name` | Yes | Unique identifier for this provider (for logging and monitoring) |
+| `issuerUrl` | Yes | OIDC issuer URL (e.g., `https://accounts.google.com`) |
+| `audience` | Yes | Expected audience claim in the access token |
+| `jwksUrl` | No | JWKS endpoint URL (skips OIDC discovery if specified) |
+| `clientId` | No | OAuth client ID for token introspection |
+| `clientSecretFile` | No | Path to file containing the client secret |
+| `caCertPath` | No | Path to CA certificate for TLS verification |
+| `authTokenFile` | No | Path to token file for authenticating to OIDC/JWKS endpoints |
+| `introspectionUrl` | No | Token introspection endpoint URL for opaque token validation (RFC 7662) |
+| `allowPrivateIP` | No | Allow connections to private IP addresses (required for in-cluster) |
+
+### Kubernetes service account authentication
+
+For in-cluster deployments, you can configure OAuth to validate Kubernetes
+service account tokens:
+
+```yaml title="registry-k8s-auth.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRegistry
+metadata:
+ name: registry
+spec:
+ authConfig:
+ mode: oauth
+ oauth:
+ providers:
+ - name: kubernetes
+ issuerUrl: https://kubernetes.default.svc.cluster.local
+ jwksUrl: https://kubernetes.default.svc/openid/v1/jwks
+ audience: registry-server
+ caCertPath: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ authTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
+ allowPrivateIP: true
+```
+
+:::tip[Kubernetes provider settings]
+
+- **issuerUrl**: for most Kubernetes distributions,
+ `https://kubernetes.default.svc.cluster.local` is the correct value to match
+ the `iss` claim in Kubernetes service account tokens.
+- **jwksUrl**: Specify directly to skip OIDC discovery (the Kubernetes API
+ server doesn't support standard discovery).
+- **allowPrivateIP**: Required for in-cluster communication with the API server.
+
+:::
+
+### Multiple providers
+
+You can configure multiple OAuth providers to accept tokens from different
+identity sources:
+
+```yaml title="registry-multi-provider.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRegistry
+metadata:
+ name: registry
+spec:
+ authConfig:
+ mode: oauth
+ oauth:
+ providers:
+ # Kubernetes service accounts (in-cluster workloads)
+ - name: kubernetes
+ issuerUrl: https://kubernetes.default.svc.cluster.local
+ jwksUrl: https://kubernetes.default.svc/openid/v1/jwks
+ audience: registry-server
+ caCertPath: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ authTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
+ allowPrivateIP: true
+ # External identity provider
+ - name: okta
+ issuerUrl: https://YOUR_DOMAIN.okta.com/oauth2/default
+ audience: registry
+```
+
+The server validates tokens against each provider in order until one succeeds.
+
+### Anonymous authentication
+
+For development and testing, you can disable authentication entirely:
+
+```yaml title="registry-anonymous.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRegistry
+metadata:
+ name: registry
+spec:
+ authConfig:
+ mode: anonymous
+ registries:
+ - name: toolhive
+ format: toolhive
+ git:
+ repository: https://github.com/stacklok/toolhive-catalog.git
+ branch: main
+ path: pkg/catalog/toolhive/data/registry.json
+```
+
+:::danger[No access control]
+
+Anonymous mode provides **no access control**. Only use it in trusted
+environments or when other security measures are in place. **Do not use
+anonymous mode in production.**
+
+:::
+
+For detailed information about authentication configuration, including
+provider-specific examples for Keycloak, Auth0, Azure AD, and Okta, see the
+[Authentication configuration](./authentication.mdx) guide.
+
+## Customize the Registry server pod
+
+You can customize the Registry server pod using the `podTemplateSpec` field.
+This gives you full control over the pod specification.
+
+```yaml {6-15} title="registry-custom-pod.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRegistry
+metadata:
+ name: custom-registry
+spec:
+ podTemplateSpec:
+ spec:
+ containers:
+ - name: registry-api # This name must be "registry-api"
+ resources:
+ limits:
+ cpu: '500m'
+ memory: '512Mi'
+ requests:
+ cpu: '100m'
+ memory: '128Mi'
+ auth:
+ mode: anonymous
+ registries:
+ - name: toolhive
+ format: toolhive
+ git:
+ repository: https://github.com/stacklok/toolhive-catalog.git
+ branch: main
+ path: pkg/catalog/toolhive/data/registry.json
+ syncPolicy:
+ interval: '30m'
+```
+
+:::info[Container name requirement]
+
+When customizing containers in `podTemplateSpec`, you must use
+`name: registry-api` for the main container to ensure the operator can properly
+manage the Registry server. The container name is hardcoded to avoid conflict
+issues with user provided containers. Mandating a container name on the Operator
+side explicitly tells the Operator it is the main registry server container and
+any other containers provided by the use are sidecars/init containers.
+
+:::
+
+## Check registry status
+
+To check the status of your registries in a specific namespace:
+
+```bash
+kubectl -n get mcpregistries
+```
+
+To check registries across all namespaces:
+
+```bash
+kubectl get mcpregistries --all-namespaces
+```
+
+The status displays the phase, message, and age of each registry.
+
+For more details about a specific registry:
+
+```bash
+kubectl -n describe mcpregistry
+```
+
+### Registry phases
+
+| Phase | Description |
+| ------------- | -------------------------------------- |
+| `Pending` | The registry is being initialized |
+| `Ready` | The registry is ready and operational |
+| `Syncing` | The registry is currently syncing data |
+| `Failed` | The registry has encountered an error |
+| `Terminating` | The registry is being deleted |
+
+## Next steps
+
+Learn how to configure authentication for the Registry server in the
+[Authentication configuration](./authentication.mdx) guide.
+
+Configure additional registry sources and filtering options in the
+[Configuration](./configuration.mdx) guide.
+
+Discover your deployed MCP servers automatically using the
+[Kubernetes registry](./configuration.mdx#kubernetes-registry) feature. Note
+that the operator automatically creates a single Kubernetes registry named
+`default` - you cannot configure additional Kubernetes registries.
+
+## Related information
+
+- [Kubernetes CRD reference](../reference/crd-spec.md#apiv1alpha1mcpregistry) -
+ Reference for the `MCPRegistry` Custom Resource Definition (CRD)
+- [Deploy the operator](../guides-k8s/deploy-operator.mdx) - Install the
+ ToolHive operator
+- [Database configuration](./database.mdx) - Configure PostgreSQL storage
+
+## Troubleshooting
+
+
+MCPRegistry resource not creating pods
+
+If your `MCPRegistry` resource is created but no pods appear:
+
+1. Ensure you created the `MCPRegistry` resource in an allowed namespace
+1. Check the operator's configuration:
+
+```bash
+helm get values toolhive-operator -n toolhive-system
+```
+
+1. Check the MCPRegistry status and operator logs:
+
+```bash
+# Check MCPRegistry status
+kubectl -n describe mcpregistry
+
+# Check operator logs
+kubectl -n toolhive-system logs -l app.kubernetes.io/name=toolhive-operator
+
+# Verify the operator is running
+kubectl -n toolhive-system get pods -l app.kubernetes.io/name=toolhive-operator
+```
+
+Common causes include:
+
+- **Operator not running**: Ensure the ToolHive operator is deployed and running
+- **RBAC issues**: Check for cluster-level permission issues
+- **Resource quotas**: Check if namespace resource quotas prevent pod creation
+
+
+
+
+Registry stuck in Pending or Syncing phase
+
+If the registry is stuck in `Pending` or `Syncing` phase:
+
+```bash
+# Check registry status
+kubectl -n describe mcpregistry
+
+# Check registry pod logs
+kubectl -n logs -l app.kubernetes.io/instance=
+```
+
+Common causes include:
+
+- **Git repository inaccessible**: Verify the repository URL is correct and
+ accessible
+- **ConfigMap/PVC doesn't exist**: Ensure referenced resources exist in the same
+ namespace
+- **Network policies**: Check if network policies are blocking external access
+- **Invalid registry file format**: Verify the registry JSON file is valid
+
+
+
+
+Database connection errors
+
+If you see database connection errors:
+
+```bash
+# Check registry pod logs
+kubectl -n logs -l app.kubernetes.io/instance=
+```
+
+Common causes include:
+
+- **Database not reachable**: Verify database host and port are correct
+- **Invalid credentials**: Check that pgpass file is properly mounted
+- **SSL configuration mismatch**: Verify `sslMode` matches your database
+ configuration
+- **Permission issues**: Ensure database users have required privileges
+
+
+
+
+Sync failures
+
+If synchronization is failing:
+
+```bash
+# Check sync status
+kubectl -n get mcpregistry -o jsonpath='{.status.syncStatus}'
+
+# Trigger manual sync to see immediate errors
+kubectl annotate mcpregistry \
+ toolhive.stacklok.dev/sync-trigger="$(date +%s)" \
+ --overwrite
+
+# Check logs
+kubectl -n logs -l app.kubernetes.io/instance=
+```
+
+Common causes include:
+
+- **Source unavailable**: Git repository, API endpoint, or file is inaccessible
+- **Invalid JSON format**: Registry file contains invalid JSON
+- **Format mismatch**: The `format` field doesn't match the actual data format
+- **Filter too restrictive**: Filters may be excluding all servers
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-registry/deployment.mdx b/versioned_docs/version-1.0/toolhive/guides-registry/deployment.mdx
new file mode 100644
index 00000000..8a93e939
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-registry/deployment.mdx
@@ -0,0 +1,34 @@
+---
+title: Deploy the Registry Server
+description: Deployment options for the ToolHive Registry Server in Kubernetes
+---
+
+The Registry server can be deployed in Kubernetes using three methods. Choose
+the one that fits your environment:
+
+| Method | Description |
+| ------------------------------------------------ | --------------------------------------------------------------- |
+| [ToolHive Operator](#toolhive-operator) | Manage the Registry Server lifecycle through `MCPRegistry` CRDs |
+| [Helm](#helm) | Deploy using the Registry Server Helm chart |
+| [Manual manifests](#manual-kubernetes-manifests) | Deploy directly using raw Kubernetes manifests |
+
+## ToolHive Operator
+
+Deploy and manage the Registry server using `MCPRegistry` custom resources. The
+ToolHive Operator watches for these resources and creates the necessary
+infrastructure automatically.
+
+See [Deploy with the ToolHive Operator](./deploy-operator.mdx) for a complete
+guide.
+
+## Helm
+
+A Helm chart is available for the Registry Server. Documentation for this
+deployment method is coming soon.
+
+## Manual Kubernetes manifests
+
+Deploy the Registry Server directly using raw Kubernetes manifests. This
+approach gives you full control over the deployment configuration.
+
+See [Deploy manually](./deploy-manual.mdx) for instructions.
diff --git a/versioned_docs/version-1.0/toolhive/guides-registry/index.mdx b/versioned_docs/version-1.0/toolhive/guides-registry/index.mdx
new file mode 100644
index 00000000..cd771ac8
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-registry/index.mdx
@@ -0,0 +1,20 @@
+---
+title: ToolHive Registry Server
+description:
+ How-to guides for using the ToolHive Registry Server to discover and access
+ MCP servers and skills.
+---
+
+import DocCardList from '@theme/DocCardList';
+
+## Introduction
+
+The ToolHive Registry Server is an implementation of the official
+[Model Context Protocol (MCP) Registry API specification](https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/api/generic-registry-api.md).
+It provides a standardized REST API for discovering and accessing MCP servers
+from multiple backend sources, including Kubernetes clusters, Git repositories,
+API endpoints, and local files.
+
+## Contents
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-registry/intro.mdx b/versioned_docs/version-1.0/toolhive/guides-registry/intro.mdx
new file mode 100644
index 00000000..b096652a
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-registry/intro.mdx
@@ -0,0 +1,110 @@
+---
+title: Overview
+sidebar_label: Introduction
+description:
+ How to use the ToolHive Registry server to discover and access MCP servers and
+ skills
+---
+
+The ToolHive Registry server is a standards-compliant implementation of the MCP
+Registry API specification. It provides a REST API for discovering and accessing
+MCP servers from multiple backend sources.
+
+## Overview
+
+The Registry server aggregates MCP server metadata from various sources and
+exposes it through a standardized API. When you start the server, it:
+
+1. Loads configuration from a YAML file
+2. Runs database migrations automatically
+3. Immediately fetches registry data from the configured sources
+4. Starts background sync coordinator for automatic updates (for synced
+ registries)
+5. Serves MCP Registry API endpoints on the configured address
+
+```mermaid
+flowchart LR
+ subgraph Sources["Registry sources"]
+ direction LR
+ Managed["Managed registry"]
+ API["Upstream registry"]
+ Kubernetes["Kubernetes cluster"]
+ Git["Git repository"]
+ File["Local file"]
+ end
+ subgraph Server["Registry server"]
+ direction TB
+ Config["Configuration"]
+ Sync["Sync manager"]
+ Storage["Storage layer"]
+ APIHandler["API handler"]
+ end
+ subgraph Clients["Clients"]
+ direction LR
+ ToolHive["ToolHive"]
+ MCPClient["MCP clients"]
+ end
+ Sources -->|sync| Server
+ Config --> Sync
+ Sync --> Storage
+ Storage --> APIHandler
+ Server -->|REST API| Clients
+```
+
+## Features
+
+- **Standards-compliant**: Implements the official MCP Registry API
+ specification
+- **Multiple registry sources**: Git repositories, API endpoints, local files,
+ managed registries, and Kubernetes discovery
+- **Automatic synchronization**: Background sync with configurable intervals and
+ retry logic for Git, API, and File sources
+- **Container-ready**: Designed for deployment in Kubernetes clusters, but can
+ be deployed anywhere
+- **Flexible deployment**: Works standalone or as part of ToolHive
+ infrastructure
+- **Production-ready**: Built-in health checks, graceful shutdown, and sync
+ status persistence
+- **Kubernetes-aware**: Automatic discovery of MCP servers deployed via ToolHive
+ Operator
+- **Skills registry**: Publish and discover reusable
+ [skills](../concepts/skills.mdx) through the extensions API
+
+## Registry sources
+
+The server supports five registry source types:
+
+1. **Managed Registry** - A fully-managed MCP Registry
+ - Ideal for private repositories
+ - Automatically exposes entries following upstream MCP Registry format
+ - Supports adding new MCP servers via `/publish` endpoint
+
+2. **Upstream Registry** - Sync from upstream MCP Registry APIs
+ - Supports federation and aggregation scenarios
+ - Format conversion from upstream to ToolHive format
+ - Does not support publishing
+
+3. **Kubernetes Cluster** - Automatically creates registry entries for running
+ workloads
+ - Ideal to quickly grant access to running MCP servers to knowledge workers
+ - Useful for bigger organizations where MCP server developers differ from
+ users
+ - Does not support publishing
+
+4. **Git Repository** - Clone and sync from Git repositories
+ - Supports branch, tag, or commit pinning
+ - Ideal for version-controlled registries
+ - Does not support publishing
+
+5. **Local File** - Read from filesystem
+ - Ideal for local development and testing
+ - Supports mounted volumes in containers
+ - Does not support publishing
+
+## Next steps
+
+- [Configure registry sources](./configuration.mdx) to set up your registry
+- [Deploy the server](./deployment.mdx) in your environment
+- [Manage skills](./skills.mdx) to publish and discover reusable skills
+- [View the API reference](../reference/registry-api.mdx) for endpoint
+ documentation
diff --git a/versioned_docs/version-1.0/toolhive/guides-registry/skills.mdx b/versioned_docs/version-1.0/toolhive/guides-registry/skills.mdx
new file mode 100644
index 00000000..850c2e72
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-registry/skills.mdx
@@ -0,0 +1,202 @@
+---
+title: Manage skills
+description:
+ How to publish, list, retrieve, and delete skills using the ToolHive Registry
+ server extensions API.
+---
+
+The Registry server provides an extensions API for managing
+[skills](../concepts/skills.mdx). This guide covers the full lifecycle:
+publishing, listing, retrieving, and deleting skills.
+
+## Prerequisites
+
+- A running Registry server with at least one **managed** registry configured
+ (skills can only be published to managed registries)
+- `curl` or another HTTP client
+- If authentication is enabled, a valid bearer token (see
+ [Authentication](./authentication.mdx))
+
+## API base path
+
+All skills endpoints use the following base path:
+
+```text
+/{registryName}/v0.1/x/dev.toolhive/skills
+```
+
+Replace `{registryName}` with the `name` of the registry as defined in your
+[configuration file](./configuration.mdx) (for example, `my-registry` if your
+config has `registries: [{name: my-registry, ...}]`).
+
+## Publish a skill
+
+To publish a new skill version, send a `POST` request with the skill metadata:
+
+```bash title="Publish a skill"
+curl -X POST \
+ https://registry.example.com/my-registry/v0.1/x/dev.toolhive/skills \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer " \
+ -d '{
+ "namespace": "io.github.acme",
+ "name": "code-review",
+ "description": "Performs structured code reviews using best practices",
+ "version": "1.0.0",
+ "status": "active",
+ "title": "Code Review Assistant",
+ "license": "Apache-2.0",
+ "packages": [
+ {
+ "registryType": "git",
+ "url": "https://github.com/acme/skills",
+ "ref": "v1.0.0",
+ "subfolder": "code-review"
+ }
+ ],
+ "repository": {
+ "url": "https://github.com/acme/skills",
+ "type": "git"
+ }
+ }'
+```
+
+**Required fields:** `namespace`, `name`, `description`, `version`
+
+A successful response returns `201 Created` with the published skill. If the
+version already exists, the server returns `409 Conflict`.
+
+The `status` field accepts `active`, `deprecated`, or `archived` (case
+insensitive). The server normalizes status values to uppercase internally.
+
+### Versioning behavior
+
+When you publish a new version, the registry compares it against the current
+latest version. If the new version is newer, the latest pointer updates
+automatically. Publishing an older version (for example, backfilling `0.9.0`
+after `1.0.0` exists) does not change the latest pointer.
+
+## List skills
+
+To list skills in a registry:
+
+```bash title="List all skills"
+curl https://registry.example.com/my-registry/v0.1/x/dev.toolhive/skills
+```
+
+### Query parameters
+
+| Parameter | Type | Default | Description |
+| --------- | ------ | ------- | -------------------------------------------------- |
+| `search` | string | - | Filter by name or description substring |
+| `status` | string | - | Filter by status (comma-separated, e.g., `active`) |
+| `limit` | int | 50 | Maximum results per page (1-100) |
+| `cursor` | string | - | Pagination cursor from a previous response |
+
+### Search example
+
+```bash title="Search for skills by keyword"
+curl "https://registry.example.com/my-registry/v0.1/x/dev.toolhive/skills?search=review&limit=10"
+```
+
+### Response format
+
+```json
+{
+ "skills": [
+ {
+ "namespace": "io.github.acme",
+ "name": "code-review",
+ "description": "Performs structured code reviews using best practices",
+ "version": "1.0.0",
+ "status": "ACTIVE",
+ "title": "Code Review Assistant",
+ "license": "Apache-2.0",
+ "packages": [
+ {
+ "registryType": "git",
+ "url": "https://github.com/acme/skills",
+ "ref": "v1.0.0",
+ "subfolder": "code-review"
+ }
+ ],
+ "repository": {
+ "url": "https://github.com/acme/skills",
+ "type": "git"
+ }
+ }
+ ],
+ "metadata": {
+ "count": 1,
+ "nextCursor": ""
+ }
+}
+```
+
+Use the `nextCursor` value to fetch the next page of results. An empty
+`nextCursor` indicates there are no more pages.
+
+## Retrieve a skill
+
+### Get the latest version
+
+```bash title="Get the latest version of a skill"
+curl https://registry.example.com/my-registry/v0.1/x/dev.toolhive/skills/io.github.acme/code-review
+```
+
+### Get a specific version
+
+```bash title="Get a specific version"
+curl https://registry.example.com/my-registry/v0.1/x/dev.toolhive/skills/io.github.acme/code-review/versions/1.0.0
+```
+
+### List all versions
+
+```bash title="List all versions of a skill"
+curl https://registry.example.com/my-registry/v0.1/x/dev.toolhive/skills/io.github.acme/code-review/versions
+```
+
+## Delete a skill version
+
+To delete a specific version:
+
+```bash title="Delete a skill version"
+curl -X DELETE \
+ https://registry.example.com/my-registry/v0.1/x/dev.toolhive/skills/io.github.acme/code-review/versions/1.0.0 \
+ -H "Authorization: Bearer "
+```
+
+A successful delete returns `204 No Content`. Deleting a non-existent version
+returns `404 Not Found`.
+
+:::warning
+
+Deleting a skill version is permanent. If the deleted version was the latest,
+the latest pointer is not automatically reassigned to a previous version.
+
+:::
+
+## Error responses
+
+The API returns standard HTTP status codes:
+
+| Code | Meaning |
+| ---- | ------------------------------------------------------------- |
+| 400 | Invalid request (missing required fields, invalid parameters) |
+| 401 | Authentication required |
+| 403 | Registry is not a managed registry (read-only registries) |
+| 404 | Registry, skill, or version not found |
+| 409 | Version already exists |
+| 500 | Internal server error |
+
+## What's next
+
+Skill installation via agent clients (such as the ToolHive CLI or IDE
+extensions) is planned for a future release. For now, the registry serves as a
+discovery and distribution layer.
+
+- [Understanding skills](../concepts/skills.mdx) for background on the skills
+ model
+- [Registry server introduction](./intro.mdx) for an overview of the Registry
+ server
+- [Authentication](./authentication.mdx) for configuring API access
diff --git a/versioned_docs/version-1.0/toolhive/guides-registry/telemetry-metrics.mdx b/versioned_docs/version-1.0/toolhive/guides-registry/telemetry-metrics.mdx
new file mode 100644
index 00000000..eb7d3e73
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-registry/telemetry-metrics.mdx
@@ -0,0 +1,185 @@
+---
+title: Telemetry and metrics
+description:
+ How to enable OpenTelemetry metrics and distributed tracing for the ToolHive
+ Registry Server
+---
+
+The ToolHive Registry Server provides comprehensive observability through
+OpenTelemetry (OTel), supporting both distributed tracing and metrics collection
+via OTLP exporters. This enables you to monitor system behavior, diagnose
+issues, and improve performance.
+
+## Architecture overview
+
+The Registry Server exports telemetry data (traces and metrics) via OTLP HTTP to
+an OpenTelemetry Collector, which can then forward to various backends:
+
+```mermaid
+flowchart TB
+ subgraph server["Registry Server"]
+ http["HTTP requests"]
+ sync["Sync metrics"]
+ registry["Registry metrics"]
+
+ http & sync & registry --> otlp["OTLP HTTP"]
+ end
+
+ otlp --> collector["OTel Collector"]
+
+ collector --> tempo["Tempo"]
+ collector --> prometheus["Prometheus"]
+ collector --> grafana["Grafana"]
+```
+
+## Configuration
+
+Add telemetry configuration to your Registry Server configuration file:
+
+```yaml title="config.yaml"
+telemetry:
+ enabled: true
+ serviceName: thv-registry-api
+ serviceVersion: '1.0.0'
+ endpoint: otel-collector:4318
+ insecure: true
+ tracing:
+ enabled: true
+ sampling: 0.05
+ metrics:
+ enabled: true
+```
+
+### Configuration options
+
+| Option | Type | Default | Description |
+| ------------------ | ------ | ------------------ | --------------------------------- |
+| `enabled` | bool | `false` | Enable or disable all telemetry |
+| `serviceName` | string | `thv-registry-api` | Service name in telemetry data |
+| `serviceVersion` | string | `"unknown"` | Service version in telemetry data |
+| `endpoint` | string | `localhost:4318` | OTLP HTTP endpoint (host:port) |
+| `insecure` | bool | `false` | Use insecure connection (no TLS) |
+| `tracing.enabled` | bool | `false` | Enable distributed tracing |
+| `tracing.sampling` | float | `0.05` | Trace sampling ratio (0.0 to 1.0) |
+| `metrics.enabled` | bool | `false` | Enable metrics collection |
+
+:::note
+
+The `endpoint` is provided as a hostname and optional port, without a scheme or
+path (e.g., use `api.honeycomb.io` or `api.honeycomb.io:443`, not
+`https://api.honeycomb.io`). The server automatically uses HTTPS unless
+`insecure: true` is specified.
+
+:::
+
+## Metrics
+
+The Registry Server exposes metrics prefixed with `thv_reg_srv_` for easy
+identification.
+
+### Available metrics
+
+| Metric | Type | Labels | Description |
+| ------------------------------------------- | ------------- | -------------------------------- | ------------------------------ |
+| `thv_reg_srv_http_request_duration_seconds` | Histogram | `method`, `route`, `status_code` | Duration of HTTP requests |
+| `thv_reg_srv_http_requests_total` | Counter | `method`, `route`, `status_code` | Total number of HTTP requests |
+| `thv_reg_srv_http_active_requests` | UpDownCounter | - | Number of in-flight requests |
+| `thv_reg_srv_servers_total` | Gauge | `registry` | Number of servers per registry |
+| `thv_reg_srv_sync_duration_seconds` | Histogram | `registry`, `success` | Duration of sync operations |
+
+### Histogram buckets
+
+The metrics use the following histogram bucket boundaries:
+
+- **HTTP request duration**: 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5,
+ 5, 10 seconds
+- **Sync duration**: 0.1, 0.5, 1, 2.5, 5, 10, 30, 60, 120, 300 seconds
+
+## Distributed tracing
+
+The Registry Server implements distributed tracing across two layers: HTTP
+requests and service operations.
+
+### Trace hierarchy
+
+Traces follow a parent-child hierarchy that shows the complete request flow:
+
+```text
+HTTP Request Span (root)
+└── Service Span (child)
+ └── Database operations with db.system=postgresql
+```
+
+:::note
+
+Background sync operations are monitored through metrics (see the
+`thv_reg_srv_sync_duration_seconds` metric above) rather than distributed
+traces, as they are internal operations without incoming request context.
+
+:::
+
+### HTTP layer spans
+
+All HTTP requests (except health and readiness endpoints) are traced with the
+following attributes:
+
+| Attribute | Type | Description |
+| --------------------------- | ------ | -------------------------------------------- |
+| `http.request.method` | string | HTTP method (GET, POST, etc.) |
+| `http.route` | string | Route pattern (e.g., `/v0.1/servers/{name}`) |
+| `url.path` | string | Actual URL path |
+| `user_agent.original` | string | Client user agent (truncated to 256 chars) |
+| `http.response.status_code` | int | Response status code |
+
+### Service layer spans
+
+Database service operations include these attributes:
+
+| Attribute | Type | Description |
+| ----------------------- | ------ | --------------------------------- |
+| `registry.name` | string | Name of the registry |
+| `server.name` | string | Name of the server |
+| `server.version` | string | Version of the server |
+| `pagination.limit` | int | Page size limit |
+| `pagination.has_cursor` | bool | Whether pagination cursor is used |
+| `result.count` | int | Number of results returned |
+
+### Context propagation
+
+The Registry Server supports W3C Trace Context propagation. Incoming requests
+with `traceparent` headers have their trace context extracted and used as the
+parent for all child spans, enabling distributed tracing across multiple
+services.
+
+## Sampling strategies
+
+Adjust sampling rates based on your environment and traffic volume:
+
+| Environment | Sampling rate | Use case |
+| ----------- | ------------- | -------------------------------------------- |
+| Development | `1.0` | Capture all traces for debugging |
+| Staging | `0.1` | 10% sampling for testing |
+| Production | `0.01 - 0.05` | 1-5% sampling to balance cost and visibility |
+
+:::tip
+
+Start with a higher sampling rate and reduce it as you understand your traffic
+patterns. For high-traffic production environments, even 1% sampling provides
+sufficient data for identifying issues.
+
+:::
+
+## Excluded endpoints
+
+The `/health` and `/readiness` endpoints are intentionally excluded from
+tracing.
+
+These endpoints generate a high volume of nearly identical spans that provide
+minimal diagnostic value while significantly increasing storage costs. HTTP
+metrics still capture latency and error rates for these endpoints.
+
+## Next steps
+
+- [Configure the Registry Server](./configuration.mdx) with your registries
+- [Deploy the server](./deployment.mdx) to your environment
+- [Configure authentication](./authentication.mdx) for secure access
diff --git a/versioned_docs/version-1.0/toolhive/guides-ui/cli-access.mdx b/versioned_docs/version-1.0/toolhive/guides-ui/cli-access.mdx
new file mode 100644
index 00000000..0033e23e
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-ui/cli-access.mdx
@@ -0,0 +1,140 @@
+---
+title: Access the CLI from ToolHive UI
+description:
+ How to use the ToolHive CLI when using the ToolHive UI application for
+ advanced features and terminal-based workflows.
+---
+
+ToolHive UI includes the CLI for advanced users who want terminal access or
+features not yet available in the graphical interface. ToolHive UI automatically
+installs and manages the CLI, so you don't need to install it separately.
+
+## Why use the CLI with ToolHive UI?
+
+While the ToolHive UI covers most common tasks, you might want to use the CLI
+for:
+
+- **Advanced features**: Some features are available in the CLI before they're
+ added to the graphical interface
+- **Scripting and automation**: Integrate MCP server management into local
+ scripts or automated workflows
+- **Personal preference**: If you prefer working in a terminal for certain
+ tasks, the CLI is available without a separate installation
+
+## Use CLI commands
+
+After ToolHive UI installation, you can use the CLI from your terminal:
+
+1. Open a new terminal window to pick up the PATH changes.
+
+1. Verify the CLI is available:
+
+ ```bash
+ thv version
+ ```
+
+1. Run any CLI command:
+
+ ```bash
+ thv list # List running MCP servers
+ thv registry list # Browse available servers
+ thv --help # View all commands
+ ```
+
+For detailed command reference, see the [CLI guides](../guides-cli/index.mdx)
+and [command reference](../reference/cli/thv.md).
+
+## How ToolHive UI manages the CLI
+
+When you install ToolHive UI, it automatically:
+
+1. **Creates a symlink** to its bundled CLI binary:
+ - macOS/Linux: `~/.toolhive/bin/thv`
+ - Windows: `%LOCALAPPDATA%\ToolHive\bin\thv.exe`
+
+1. **Configures your PATH** by adding entries to your shell configuration files
+ (`.bashrc`, `.zshrc`, `config.fish`, or the Windows User PATH)
+
+This ensures the CLI version always matches the ToolHive UI version, preventing
+compatibility issues with the API.
+
+:::note
+
+If you have a standalone CLI installed (via Homebrew, WinGet, or manually), it
+will show a conflict error. See
+[CLI conflict resolution](../guides-cli/install.mdx#cli-conflict-resolution) for
+details.
+
+:::
+
+## The Settings > CLI page
+
+ToolHive UI includes a dedicated settings page to manage the CLI installation.
+Access it from **Settings** (gear icon) > **CLI**.
+
+The page displays:
+
+- CLI installation status and version
+- Symlink location and target path
+- Shell configuration status
+
+Use the **Reinstall** button if the CLI becomes unavailable or the symlink
+breaks (for example, after moving the ToolHive UI application).
+
+## Troubleshooting
+
+
+CLI not found in terminal
+
+If `thv` is not recognized after installing ToolHive UI:
+
+1. **Open a new terminal window**: The PATH changes only take effect in new
+ terminal sessions.
+
+1. **Check the Settings > CLI page**: Verify that the PATH Configuration shows
+ "Valid" status.
+
+1. **Manually source your shell configuration**:
+
+ ```bash
+ # Bash
+ source ~/.bashrc
+
+ # Zsh
+ source ~/.zshrc
+
+ # Fish
+ source ~/.config/fish/config.fish
+ ```
+
+1. **Reinstall the CLI**: Go to Settings > CLI and click **Reinstall**.
+
+
+
+
+Broken symlink after moving ToolHive UI
+
+If you move the ToolHive UI application to a different location, the CLI symlink
+may break. To fix this:
+
+1. Open ToolHive UI from its new location.
+1. Go to Settings > CLI.
+1. Click **Reinstall** to create a new symlink pointing to the correct location.
+
+
+
+
+CLI conflict error when running thv
+
+If you see "CLI conflict detected", you have both ToolHive UI and a standalone
+CLI installed. See
+[CLI conflict resolution](../guides-cli/install.mdx#cli-conflict-resolution) for
+the error message and resolution steps.
+
+
+
+## Related information
+
+- [CLI guides](../guides-cli/index.mdx)
+- [CLI command reference](../reference/cli/thv.md)
+- [CLI conflict resolution](../guides-cli/install.mdx#cli-conflict-resolution)
diff --git a/versioned_docs/version-1.0/toolhive/guides-ui/client-configuration.mdx b/versioned_docs/version-1.0/toolhive/guides-ui/client-configuration.mdx
new file mode 100644
index 00000000..c0d539b9
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-ui/client-configuration.mdx
@@ -0,0 +1,85 @@
+---
+title: Client configuration
+description: How to connect ToolHive to AI clients using the UI.
+---
+
+import ClientIntro from '../_partials/_client-config-intro.mdx';
+
+
+
+## Manage clients
+
+ToolHive automatically discovers supported AI clients installed on your system.
+To choose which clients ToolHive configures, open the **MCP Servers** page and
+click **Manage Clients**. This opens a dialog that lists detected clients with
+on/off toggles. Discovery is dynamic: if you install a new client, ToolHive
+updates the list.
+
+:::tip[Using groups?]
+
+If you organize your MCP servers into groups, you can configure client access
+per group. This lets you control which servers each client can access. See
+[Organize servers into groups](./group-management.mdx) for details.
+
+:::
+
+### Connect a client
+
+To connect a client to ToolHive:
+
+1. Go to **MCP Servers** and click **Manage Clients**.
+2. Turn on the toggle for the client you want to connect.
+3. Click **Save** to apply your changes.
+
+When you connect a client and save, ToolHive configures it to use your running
+MCP servers. Any new servers you start are also added automatically. When you
+stop or remove an MCP server, ToolHive updates the client's configuration to
+remove the server.
+
+### Disconnect a client
+
+To disconnect, open **Manage Clients**, turn off the client's toggle, then click
+**Save**. ToolHive removes the MCP server configurations from that client. The
+client can no longer use ToolHive-managed MCP servers.
+
+### Save your changes
+
+Changes you make in **Manage Clients** are not applied until you click **Save**.
+If you close the dialog or click **Cancel** without saving, your toggle changes
+are discarded.
+
+## Why don't I see my client?
+
+If you don't see your client in **Manage Clients**, ToolHive might not support
+it, or its configuration file might not be in a location ToolHive recognizes.
+
+For a list of supported clients and how ToolHive detects them, see the
+[client compatibility reference](../reference/client-compatibility.mdx).
+
+## Why do I still see a client I uninstalled?
+
+Many clients leave behind configuration files even after you uninstall them.
+ToolHive detects these files and continues to show the client in the **Manage
+Clients** dialog. If you want to remove the client from ToolHive, delete its
+configuration files manually.
+
+## Manual client configuration
+
+For clients that ToolHive doesn't support directly, you can still configure them
+to connect to ToolHive-managed MCP servers using the SSE (Server-Sent Events) or
+Streamable HTTP protocol.
+
+To do this, you need the URL of the MCP server you want to connect to. You can
+get this URL from the **MCP Servers** page in ToolHive. Click the menu (︙) on
+the MCP server card to copy the URL.
+
+You can then configure your client to use this URL. The exact steps depend on
+your client or library, so refer to its documentation for details. See the
+[client configuration reference](../reference/client-compatibility.mdx#manual-configuration)
+for some common examples.
+
+## Related information
+
+- [Client compatibility](../reference/client-compatibility.mdx)
+- [Run MCP servers](./run-mcp-servers.mdx)
+- [Organize servers into groups](./group-management.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-ui/customize-tools.mdx b/versioned_docs/version-1.0/toolhive/guides-ui/customize-tools.mdx
new file mode 100644
index 00000000..220b5bd7
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-ui/customize-tools.mdx
@@ -0,0 +1,201 @@
+---
+title: Customize tools
+description:
+ Select which tools from an MCP server are available to your AI clients using
+ the ToolHive UI.
+---
+
+ToolHive lets you customize which tools from a running MCP server are available
+to your AI clients. You can enable or disable individual tools, and customize
+their names and descriptions. This gives you fine-grained control over the
+capabilities exposed by each server, helping you create focused tool sets and
+reduce complexity for your AI workflows.
+
+## Why customize tools?
+
+Customizing tools helps you:
+
+- **Reduce complexity**: Hide tools that aren't relevant to your workflow,
+ making it easier for AI clients to select the right tool
+- **Improve performance**: Fewer tools mean faster tool selection and reduced
+ token usage in AI interactions
+- **Enhance security**: Limit exposure to only the tools you need, reducing the
+ risk of unintended actions
+- **Create focused environments**: Tailor tool availability to specific tasks or
+ projects
+- **Clarify tool purpose**: Edit tool names and descriptions to make them more
+ understandable for your specific use case
+
+:::info[Registry match required]
+
+Tool customization is only available for MCP servers that match an entry in the
+registry. This includes:
+
+- Servers installed directly from the registry
+- Custom servers with a docker image or URL that matches a registry entry
+
+Custom servers that don't match any registry entry do not support this feature.
+
+:::
+
+## Access tool customization
+
+To customize tools for a running MCP server:
+
+1. Open the **MCP Servers** page.
+1. Find the server you want to customize and click the menu (︙) on its card.
+1. Select **Customize Tools** from the dropdown menu.
+
+The customize tools page displays all available tools from the MCP server, each
+with a toggle switch and description.
+
+:::note[Server must be running]
+
+The MCP server must be running to view and customize tools. If the server is
+stopped when you access the customize tools page, you'll see a message prompting
+you to start the server first.
+
+:::
+
+## Enable or disable tools
+
+On the customize tools page:
+
+1. Review the list of available tools and their descriptions.
+1. Use the toggle switches to enable or disable individual tools:
+ - **Green (enabled)**: The tool is available to AI clients
+ - **Gray (disabled)**: The tool is hidden from AI clients
+1. Click **Apply** to save your changes.
+
+The ToolHive proxy filters out disabled tools when AI clients connect to the
+server. Clients only see and can only call the tools you've enabled.
+
+:::warning[At least one tool required]
+
+You must keep at least one tool enabled. ToolHive prevents you from disabling
+all tools on a server, as this would make the server non-functional.
+
+:::
+
+## Edit tool names and descriptions
+
+You can customize the name and description of any tool to make it more suitable
+for your workflow. This is useful when the default tool name or description is
+unclear, or when you want to adapt the tool's presentation to your specific use
+case.
+
+To edit a tool:
+
+1. On the customize tools page, find the tool you want to edit.
+1. Click the **Edit** button next to the tool.
+1. In the dialog that opens:
+ - Edit the **Tool** field to change the tool's name.
+ - Edit the **Description** field to customize the tool's description.
+ - The original values are shown as helper text for reference.
+ - Leave either field empty to reset it to the original value.
+1. Click **Save** to apply your changes, or **Cancel** to discard them.
+
+Tools with custom names or descriptions are marked with a wrench icon to
+indicate they have custom overrides applied. The icon color indicates the
+status:
+
+- **Orange**: The tool has unsaved custom overrides
+- **Primary color**: The tool has saved custom overrides
+
+Your customizations are preserved even if you disable and re-enable the tool.
+
+:::tip[Unsaved custom overrides]
+
+If you customize a tool's name or description but haven't clicked **Apply** on
+the main page, a tooltip appears indicating "This tool has unsaved custom
+overrides". Click **Apply** to save both your tool selection changes and custom
+overrides.
+
+:::
+
+## How filtering works
+
+When you customize tools:
+
+1. You toggle tools on or off using the switches and optionally edit their names
+ and descriptions.
+1. After clicking **Apply**, your settings are saved to the server
+ configuration.
+1. The ToolHive proxy intercepts tool discovery requests from AI clients.
+1. Only enabled tools appear in the client's tool list, with your custom names
+ and descriptions if you've set them.
+1. If a client attempts to call a disabled tool directly, the proxy blocks the
+ request.
+
+This filtering happens transparently without requiring any changes to your AI
+client configuration. Your clients automatically see the updated tool list with
+customized names and descriptions the next time they connect.
+
+:::tip
+
+If you change tool settings while a client is connected, you may need to restart
+the client session to see the updated tool list. Some clients cache tool lists
+and don't automatically refresh.
+
+:::
+
+## Example workflows
+
+### Focus on specific GitHub operations
+
+If you're using the GitHub MCP server but only need pull request tools:
+
+1. Open **Customize Tools** for the GitHub server.
+1. Disable all tools except those related to pull requests (like
+ `create_pull_request`, `get_pull_request`, `list_pull_requests`).
+1. Your AI clients now see only pull request tools, making it easier to work
+ with GitHub PRs without distraction from issue, branch, or repository tools.
+
+### Create environment-specific tool sets
+
+If you're running the same MCP server in different groups for different
+environments:
+
+1. Copy the server to multiple groups (see
+ [Organize servers into groups](./group-management.mdx)).
+1. Customize tools in each group to match the environment's needs.
+1. For example, enable only read-only tools in a production group, while
+ allowing all tools in a development group.
+
+### Clarify technical tool names
+
+If an MCP server exposes tools with technical or unclear names:
+
+1. Open **Customize Tools** for the server.
+1. Click **Edit** on tools with unclear names.
+1. Change the tool name to something more descriptive (for example, rename
+ `list_commits_test` to `List Commits`).
+1. Update the description to clarify what the tool does in your context.
+1. Click **Save**, then **Apply**.
+1. Your AI clients now see clearer, more intuitive tool names and descriptions.
+
+## Reset to defaults
+
+To restore all tools to their default enabled state and remove custom overrides:
+
+**To reset an individual tool's name or description:**
+
+1. Click the **Edit** button next to the tool.
+1. Clear the **Tool** and **Description** fields (leave them empty).
+1. Click **Save**.
+1. Click **Apply** to save your changes.
+
+The tool name and description revert to their original values, and the wrench
+icon is removed.
+
+**To enable all tools:**
+
+1. Open the customize tools page for the server.
+1. Manually enable all tools using the toggle switches.
+1. Click **Apply** to save your changes.
+
+## Related information
+
+- [Run MCP servers](./run-mcp-servers.mdx)
+- [Organize servers into groups](./group-management.mdx)
+- [Client configuration](./client-configuration.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-ui/group-management.mdx b/versioned_docs/version-1.0/toolhive/guides-ui/group-management.mdx
new file mode 100644
index 00000000..dca3454a
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-ui/group-management.mdx
@@ -0,0 +1,158 @@
+---
+title: Organize servers into groups
+description:
+ How to organize MCP servers into groups and configure client access in the
+ ToolHive UI.
+---
+
+This guide explains how to organize your MCP servers into groups and configure
+which groups your MCP clients can access.
+
+:::tip[New to groups?]
+
+If you're not sure whether groups are right for your use case, see
+[Organizing MCP servers with groups](../concepts/groups.mdx) for an overview of
+when and why to use them.
+
+:::
+
+:::info[What's the default behavior?]
+
+The `default` group always exists and cannot be deleted. When you add an MCP
+server, the `default` group is preselected in the group field unless you choose
+a different group.
+
+:::
+
+## View and navigate groups
+
+The **MCP Servers** page includes a sidebar on the left that lists all your
+groups. The active group is highlighted.
+
+To view servers in a different group, click on the group name in the sidebar.
+The **MCP Servers** page updates to show only the servers in that group.
+
+## Create a group
+
+To create a new group:
+
+1. Open the **MCP Servers** page.
+2. Click **Add a group** in the sidebar.
+3. Enter a unique name for the group.
+4. Click **Create**.
+
+:::note
+
+Group names must be unique. If you try to create a group with a name that
+already exists, you'll see an error message.
+
+:::
+
+## Assign servers to groups
+
+When you install or configure an MCP server, you assign it to a group. All
+server types (registry, custom local, and remote) include a **Group** field in
+their configuration forms.
+
+### Assign a server during installation
+
+When installing a new MCP server from the registry or adding a custom server:
+
+1. Fill in the server configuration.
+2. In the **Group** field, select the group where you want to add the server.
+3. Click **Install server**.
+
+### Copy a server to a different group
+
+You can copy a server to a different group:
+
+1. On the **MCP Servers** page, find the server you want to copy.
+2. Click the menu (︙) on the server card.
+3. Select **Copy server to a group**.
+4. Choose the destination group.
+5. Click **Copy**.
+
+This creates a complete copy of the server in the destination group, preserving
+all configuration including secrets, environment variables, and storage volumes.
+The original server remains in its current group.
+
+## Manage client access per group
+
+You can control which AI clients have access to servers in each group. This lets
+you configure different tool sets for different clients or environments.
+
+To manage client access for a group:
+
+1. Navigate to the group by clicking its name in the sidebar.
+2. Click **Manage Clients**.
+3. Toggle the switches to enable or disable clients for this group.
+4. Click **Save**.
+
+When you enable a client for a group, ToolHive registers that client to access
+all servers in the group. When you disable a client for a group, ToolHive
+unregisters the client from that group.
+
+:::tip
+
+You can enable the same client for multiple groups. The client will have access
+to servers from all groups where it's enabled.
+
+:::
+
+## Delete a group
+
+To delete a group:
+
+1. Navigate to the group by clicking its name in the sidebar.
+2. Click the group actions menu (⋮) in the header.
+3. Select **Delete group**.
+4. Confirm the deletion in the dialog.
+
+:::warning
+
+Deleting a group permanently removes all servers in that group. This action
+cannot be undone. If you want to preserve the servers, copy them to another
+group before deleting.
+
+:::
+
+You cannot delete the `default` group.
+
+## Example workflows
+
+### Separate development and production servers
+
+1. Create two groups: "development" and "production".
+2. Install your MCP servers, assigning development tools to the "development"
+ group and production tools to the "production" group.
+3. Configure your primary AI client (like GitHub Copilot) to access only the
+ "development" group for day-to-day coding work.
+4. Use a different client or manually configure clients to access the
+ "production" group when needed.
+
+### Create environment-specific tool sets
+
+If you're running the same MCP server in different groups for different
+environments:
+
+1. Copy the server to multiple groups (see
+ [Copy a server to a different group](#copy-a-server-to-a-different-group)).
+2. Customize tools in each group to match the environment's needs (see
+ [Customize tools](./customize-tools.mdx)).
+3. For example, enable only read-only tools in a production group, while
+ allowing all tools in a development group.
+
+### Project-based organization
+
+1. Create groups for each project: "webapp-frontend", "webapp-backend", and
+ "mobile-app".
+2. Install project-specific MCP servers in each group (for example, React tools
+ in "webapp-frontend", database tools in "webapp-backend").
+3. Configure different AI clients or workspaces to access the appropriate groups
+ for each project.
+
+## Related information
+
+- [Run MCP servers](./run-mcp-servers.mdx)
+- [Client configuration](./client-configuration.mdx)
+- [Secrets management](./secrets-management.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-ui/index.mdx b/versioned_docs/version-1.0/toolhive/guides-ui/index.mdx
new file mode 100644
index 00000000..11b55324
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-ui/index.mdx
@@ -0,0 +1,46 @@
+---
+title: Using ToolHive
+description: How-to guides for using the ToolHive UI to run and manage MCP servers.
+---
+
+import DocCardList from '@theme/DocCardList';
+
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+
+## Introduction
+
+The ToolHive UI is a desktop application that makes it easy to run and manage
+Model Context Protocol (MCP) servers on your local machine. It provides a
+user-friendly interface to discover, deploy, and manage MCP servers with
+security and ease of use built in.
+
+It's designed for anyone who wants to run MCP servers without needing to
+understand the complexities of Docker or command-line tools. Whether you're a
+developer, researcher, or just curious about MCP servers, ToolHive provides a
+simple way to get started.
+
+
+
+
+We strive to make ToolHive intuitive and easy to use. If we've missed the mark
+on something, [let us know](https://discord.gg/stacklok)!
+
+:::tip[Advanced users]
+
+ToolHive UI includes and manages the CLI automatically for terminal access and
+advanced features. See [Access the CLI from ToolHive UI](./cli-access.mdx) for
+details.
+
+:::
+
+## Contents
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-ui/install.mdx b/versioned_docs/version-1.0/toolhive/guides-ui/install.mdx
new file mode 100644
index 00000000..5fdaa1e7
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-ui/install.mdx
@@ -0,0 +1,269 @@
+---
+title: Install ToolHive
+description: How to install the ToolHive UI application.
+---
+
+This guide walks you through installing, upgrading, and managing the ToolHive
+desktop application.
+
+
+
+
+## Prerequisites
+
+Before installing ToolHive, make sure your system meets these requirements:
+
+- **Operating systems**:
+ - macOS (Apple silicon or Intel)
+ - Windows 10/11 (x64)
+ - Linux (x86_64/amd64)
+- **Container runtime**:
+ - Docker / Docker Desktop
+ - Podman / Podman Desktop
+ - Colima with Docker runtime
+ - Rancher Desktop with the dockerd/moby runtime (experimental)
+
+ToolHive requires minimal CPU, memory, and disk space. The exact requirements
+depend on how many MCP servers you run and the resources they use.
+
+## Install ToolHive
+
+Select your operating system to see the installation instructions.
+
+
+
+
+Download the latest ToolHive installer for
+[Apple silicon](https://github.com/stacklok/toolhive-studio/releases/latest/download/ToolHive-arm64.dmg)
+or
+[Intel-based](https://github.com/stacklok/toolhive-studio/releases/latest/download/ToolHive-x64.dmg)
+Macs and open the DMG file.
+
+Copy the ToolHive app to your Applications folder. You can then open it from
+your Applications folder, Launchpad, or using Spotlight search.
+
+
+
+
+Download the latest
+[ToolHive installer](https://github.com/stacklok/toolhive-studio/releases/latest/download/ToolHive.Setup.exe)
+and run the setup executable.
+
+After installation, you can find ToolHive in your Start menu or on your desktop.
+
+:::info[Important]
+
+The first time you run ToolHive, you may be prompted to allow firewall access.
+If you don't allow this, ToolHive won't be able to run MCP servers.
+
+:::
+
+
+
+
+1. Download the appropriate RPM or DEB package for your distribution from the
+ [ToolHive UI releases page](https://github.com/stacklok/toolhive-studio/releases/latest)
+
+2. Use your package manager to install the downloaded package:
+ - For RPM-based distributions (like Fedora or Red Hat Enterprise Linux):
+
+ ```bash
+ sudo rpm -i ToolHive--1.x86_64.rpm
+ ```
+
+ - For DEB-based distributions (like Ubuntu or Debian):
+
+ ```bash
+ sudo dpkg -i toolhive__amd64.deb
+ ```
+
+For other Linux distributions, download the
+[binary tarball](https://github.com/stacklok/toolhive-studio/releases/latest/download/toolhive-studio-linux-x64.tar.gz)
+and extract it, then run the `ToolHive` binary directly.
+
+
+
+
+## System tray icon
+
+When you close the ToolHive application window, it continues running in the
+background so your MCP servers remain available. ToolHive installs a system tray
+icon for quick access. You can use it to:
+
+- Enable or disable **Start on login**
+- Show or hide the ToolHive application window
+- Quit ToolHive, which stops all running MCP servers
+
+## Application settings
+
+Open the ToolHive settings screen from the gear icon (⚙️) in the application
+window. The settings screen allows you to configure various options:
+
+- **Display theme**: Choose between light and dark themes for the application.
+ ToolHive matches your system theme by default.
+- **Start on login**: Automatically start ToolHive when you log in to your
+ system. MCP servers that were running when you quit ToolHive are restarted
+ automatically.
+- **Error reporting**: Enable or disable error reporting and telemetry data
+ collection.
+- **Skip quit confirmation**: Skip the MCP server shutdown confirmation dialog
+ when quitting ToolHive.
+
+From the settings screen, you can also view version information and download the
+application log file for troubleshooting.
+
+## Upgrade ToolHive
+
+ToolHive automatically checks for updates. When a new version is available,
+you'll see a notification in the application. During the upgrade, ToolHive stops
+all running MCP servers, updates the application, and then restarts itself and
+the MCP servers.
+
+You can also manually install updates by downloading the latest installer for
+your operating system from the
+[ToolHive UI releases page](https://github.com/stacklok/toolhive-studio/releases/latest)
+and running it. The installer will upgrade your existing ToolHive installation
+to the latest version. See the [Install ToolHive](#install-toolhive) section for
+direct download links.
+
+## File locations
+
+ToolHive stores its configuration and data files in several locations depending
+on your operating system:
+
+
+
+
+- The `~/Library/Application Support/ToolHive` directory contains:
+ - Configuration files and application data for the ToolHive UI
+ - MCP server logs and configurations (`logs/` and `runconfigs/` directories)
+ - Your encrypted secrets store (`secrets_encrypted` file)
+ - ToolHive CLI/API configuration file (`config.yaml`)
+- The main UI application log is located at `~/Library/Logs/ToolHive/main.log`
+
+Since macOS is not case sensitive, the `~/Library/Application Support/ToolHive`
+directory is shared by the UI and CLI if you have both installed.
+
+
+
+
+- The `%LOCALAPPDATA%\ToolHive` directory contains:
+ - Application executables and installation logs
+ - MCP server logs and configurations (`logs/` and `runconfigs/` directories)
+ - Your encrypted secrets store (`secrets_encrypted` file)
+ - ToolHive CLI/API configuration file (`config.yaml`)
+- The `%APPDATA%\ToolHive` directory contains:
+ - Configuration files and application data for the ToolHive UI
+- The main UI application log is located at `%APPDATA%\ToolHive\logs\main.log`
+
+Since Windows is not case sensitive, the `%LOCALAPPDATA%\ToolHive` directory is
+shared by the UI and CLI if you have both installed.
+
+
+
+
+- The `~/.config/ToolHive` directory contains:
+ - Configuration files and application data for the ToolHive UI
+- The `~/.config/toolhive` directory contains (note the case sensitivity):
+ - MCP server logs and configurations (`logs/` and `runconfigs/` directories)
+ - Your encrypted secrets store (`secrets_encrypted` file)
+ - ToolHive CLI/API configuration file (`config.yaml`)
+- The main UI application log is located at `~/.config/ToolHive/logs/main.log`
+
+Since Linux is case sensitive, the `~/.config/ToolHive` and `~/.config/toolhive`
+directories are separate. However, the ToolHive UI and CLI share the same
+configuration file and secrets store to support coexistence.
+
+
+
+
+You can also download the application log file from the **Settings** screen (⚙️)
+in the ToolHive UI.
+
+## Telemetry and error reporting
+
+ToolHive uses [Sentry](https://sentry.io/welcome/) for error tracking and
+performance monitoring to help us identify and fix issues, improve stability,
+and enhance the user experience. This telemetry is enabled by default. You can
+disable this by turning off the **Error reporting** option in the settings
+screen (⚙️) if you prefer not to share this data.
+
+ToolHive collects the following information:
+
+- Error reports and crash logs
+- Performance metrics
+- Usage patterns
+
+This data is anonymized and does not include any personally identifiable
+information. It helps us understand how ToolHive is used and identify areas for
+improvement. Review the
+[Stacklok privacy policy](https://www.iubenda.com/privacy-policy/29074746) and
+[Terms of Service](https://stacklok.com/terms-of-service) for more details.
+
+## Next steps
+
+Now that you have ToolHive installed, you can start using it to run and manage
+MCP servers. See [Run MCP servers](./run-mcp-servers.mdx) to get started.
+
+:::tip[CLI access for advanced users]
+
+ToolHive UI includes the CLI for terminal access and advanced features. See
+[Access the CLI from ToolHive UI](./cli-access.mdx) to learn more.
+
+:::
+
+## Related information
+
+- Quickstart: [Getting started with the ToolHive UI](./quickstart.mdx)
+- [Client configuration](./client-configuration.mdx)
+- [Secrets management](./secrets-management.mdx)
+
+## Troubleshooting
+
+
+Connection Refused error on startup
+
+If you see a "Connection Refused" error when starting ToolHive, your container
+runtime (Docker, Podman, or Colima) is likely not installed, not running, or not
+configured correctly.
+
+Follow the instructions in the error message to install or start your container
+runtime. For example, if you're using Docker Desktop, make sure it's running and
+that the Docker daemon is active.
+
+If the retry button doesn't work, restart ToolHive.
+
+
+
+
+No system tray icon on Linux
+
+Recent versions of Fedora Linux and other distributions have removed the
+AppIndicator extension from their default installations. ToolHive requires this
+extension for the system tray icon to work properly.
+
+On Fedora, install the `gnome-shell-extension-appindicator` package:
+
+```bash
+sudo dnf install gnome-shell-extension-appindicator
+```
+
+You'll need to log out and log back in to activate the extension.
+
+Alternatively, install the
+[Extension Manager](https://github.com/mjakeman/extension-manager) app. It's
+available as a native package in many distributions, or you can install it from
+[Flathub](https://flathub.org/apps/com.mattjakeman.ExtensionManager). Then, use
+Extension Manager to install the
+[AppIndicator](https://extensions.gnome.org/extension/615/appindicator-support/)
+extension (listed as "AppIndicator and KStatusNotifierItem Support").
+
+The ToolHive icon should now appear in your system tray.
+
+
+
+### Other issues
+
+For other installation issues, check the
+[GitHub issues page](https://github.com/stacklok/toolhive-studio/issues) or join
+the [Discord community](https://discord.gg/stacklok).
diff --git a/versioned_docs/version-1.0/toolhive/guides-ui/mcp-optimizer.mdx b/versioned_docs/version-1.0/toolhive/guides-ui/mcp-optimizer.mdx
new file mode 100644
index 00000000..e62e03c1
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-ui/mcp-optimizer.mdx
@@ -0,0 +1,120 @@
+---
+title: Optimize MCP tool usage
+description: Enable the MCP Optimizer to enhance tool selection and reduce token usage.
+---
+
+## Overview
+
+The ToolHive MCP Optimizer acts as an intelligent intermediary between AI
+clients and MCP servers. It provides tool discovery, unified access to multiple
+MCP servers through a single endpoint, and intelligent routing of requests to
+appropriate MCP tools.
+
+:::info[Status]
+
+The MCP Optimizer is currently experimental. If you try it out, please share
+your feedback on the [Stacklok Discord community](https://discord.gg/stacklok).
+
+:::
+
+Learn more about the MCP Optimizer, its benefits, and how it works in the
+tutorial:
+[Reduce token usage with MCP Optimizer](../tutorials/mcp-optimizer.mdx).
+
+## Enable MCP Optimizer
+
+:::note[Limitations]
+
+MCP Optimizer does not currently work on Linux hosts or with Podman Desktop on
+Windows. Supported configurations:
+
+- macOS with Docker Desktop, Podman Desktop, or Rancher Desktop
+- Windows with Docker Desktop or Rancher Desktop
+
+MCP Optimizer also does not currently work with MCP servers that have network
+isolation enabled.
+
+:::
+
+To enable the MCP Optimizer in the ToolHive UI:
+
+1. Open the **Settings** (⚙️) screen and enable **MCP Optimizer** under
+ **Experimental Features**
+2. Click the **Configure** button on the notification that pops up, or go to the
+ main **MCP Servers** screen and click **MCP Optimizer** in the left sidebar
+3. Select the group that contains the MCP servers you want to optimize and click
+ **Apply Changes**
+
+ToolHive automatically updates clients that were registered with the selected
+group to use the MCP Optimizer. If you want to connect a new client, go to the
+group which is enabled for optimization and use the **Manage Clients** button to
+register it.
+
+For best results, connect your client to only the optimized group. If you
+connect it to multiple groups, ensure there is no overlap in MCP servers between
+the groups to avoid unpredictable behavior.
+
+:::info[What's happening?]
+
+When you enable MCP Optimizer, ToolHive automatically creates an internal group
+and runs the `mcp-optimizer` MCP server in that group.
+
+The MCP Optimizer server discovers the MCP servers in the selected group and
+builds an index of their tools for intelligent routing. Automatic polling keeps
+the index up to date as servers are added or removed from the optimized group.
+
+ToolHive also disconnects your AI clients from the original MCP server group and
+reconnects them to the MCP Optimizer group.
+
+:::
+
+## Customize MCP Optimizer settings
+
+To update the MCP Optimizer's default settings, click the **Advanced** menu and
+select **Customize MCP Optimizer configuration**. This opens a form where you
+can modify the `mcp-optimizer` MCP server's configuration.
+
+:::note
+
+Changes to the MCP Optimizer configuration might cause the feature to stop
+working correctly. Only modify these settings if you understand their
+implications.
+
+Generally, you should only need to change the Docker image tag and the
+environment variables detailed below.
+
+:::
+
+You can customize the MCP Optimizer's behavior using the following environment
+variables:
+
+- `MAX_TOOLS_TO_RETURN`: Number of tools to return from `find_tool` (default:
+ `8`)
+- `TOOL_DISTANCE_THRESHOLD`: Distance threshold for tool similarity (default:
+ `1.0`)
+- `MAX_TOOL_RESPONSE_TOKENS`: Maximum number of tokens to return from
+ `call_tool` (default: `None`)
+- `WORKLOAD_POLLING_INTERVAL`: Polling interval for running MCP servers, in
+ seconds (default: `60`)
+- `REGISTRY_POLLING_INTERVAL`: Polling interval for ToolHive registry, in
+ seconds (default: `86400`, 24 hours)
+
+## Disable MCP Optimizer
+
+To disable the MCP Optimizer, open the **Settings** (⚙️) screen and disable the
+**MCP Optimizer** option under the **Experimental Features** section.
+
+ToolHive stops and removes the MCP Optimizer server and reconnects your clients
+to the original MCP server group.
+
+## Troubleshooting
+
+If you encounter issues while using the MCP Optimizer, check the logs for
+debugging information. On the **MCP Optimizer** page, click the **Advanced**
+menu and select **MCP Optimizer logs**.
+
+## Related information
+
+- Tutorial:
+ [Reduce token usage with MCP Optimizer](../tutorials/mcp-optimizer.mdx)
+- [Organize MCP servers into groups](./group-management.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-ui/network-isolation.mdx b/versioned_docs/version-1.0/toolhive/guides-ui/network-isolation.mdx
new file mode 100644
index 00000000..89c53566
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-ui/network-isolation.mdx
@@ -0,0 +1,101 @@
+---
+title: Network isolation
+description: How to configure network isolation for MCP servers in ToolHive.
+---
+
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+
+Most MCP servers require network access to function properly—for example, to
+access APIs, download data, or communicate with other services. However,
+malicious or misconfigured servers can also exfiltrate sensitive data or
+download unwanted content.
+
+When you install an MCP server in ToolHive, you can optionally enable _network
+isolation_. This feature restricts the MCP server's network access to only the
+resources you specify.
+
+:::note
+
+Network isolation currently supports HTTP and HTTPS connections only. Other
+protocols are not supported.
+
+:::
+
+## Enabling network isolation
+
+Network isolation is available for local MCP servers installed from the registry
+or custom servers. It is not available for remote MCP servers, which are hosted
+and outside of ToolHive.
+
+During the MCP server installation, select the **Network isolation** tab in the
+configuration form. Click the toggle to enable it.
+
+When you enable network isolation, any safe default configuration defined in the
+registry is pre-loaded in the form. You can accept these defaults or customize
+the settings to specify which hosts and ports the MCP server is allowed to
+access:
+
+- **Allowed hosts**:\
+ A list of hostnames or IP addresses that the MCP server is allowed to access.
+ This can include APIs, data sources, or other services that the MCP server
+ needs to function properly.
+
+ :::tip
+
+ To allow access to all subdomains under a specific domain, add a leading
+ period (`.`) in front of the hostname. For example, to allow access to all
+ subdomains of `github.com`, enter `.github.com` in the allowed hosts list.
+
+ :::
+
+- **Allowed ports**:\
+ A list of ports that the MCP server is allowed to use for outgoing
+ connections. This can help prevent the MCP server from accessing unauthorized
+ services or resources. For example, port 443 is the default port for HTTPS
+ connections.
+
+:::info[Important]
+
+If you do not specify any allowed hosts or ports, the MCP server will not be
+able to access any external resources, including the internet. This can be
+useful for MCP servers that do not require network access or for testing
+purposes.
+
+:::
+
+## Example configuration
+
+The configuration pictured below allows the MCP server to access
+`api.github.com` and all subdomains of `githubusercontent.com` on port 443
+(HTTPS):
+
+
+
+
+### Accessing other workloads on the same container network
+
+To allow an MCP server to access other workloads on the same network, you need
+to configure network isolation to include the appropriate hostnames and ports.
+This is commonly needed when your MCP server needs to communicate with
+databases, APIs, or other services that are running on your local host during
+development.
+
+For example, in Docker environments, you can add `host.docker.internal` to
+access services on the host. `host.docker.internal` is a special hostname
+provided by Docker that resolves to the host machine's IP address from within
+containers.
+
+- **Allowed hosts**: `host.docker.internal`
+- **Allowed ports**: `3000`
+
+## Related information
+
+- [Run MCP servers](./run-mcp-servers.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-ui/playground.mdx b/versioned_docs/version-1.0/toolhive/guides-ui/playground.mdx
new file mode 100644
index 00000000..fe681cce
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-ui/playground.mdx
@@ -0,0 +1,266 @@
+---
+title: Test MCP servers
+description:
+ Use the playground feature to test and validate MCP servers directly in the
+ ToolHive UI with AI model providers.
+---
+
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+
+ToolHive's playground lets you test and validate MCP servers directly in the UI
+without requiring additional client setup. This streamlined testing environment
+helps you quickly evaluate functionality and behavior before deploying MCP
+servers to production environments.
+
+## Key capabilities
+
+### Instant testing of MCP servers
+
+Configure your AI model providers, select your MCP servers and tools, and begin
+testing immediately in the desktop app. The playground eliminates the friction
+of setting up external AI clients just to validate that your MCP servers work
+correctly.
+
+### Detailed interaction logs
+
+See tool details, parameters, and execution results directly in the UI, ensuring
+full visibility into tool performance and responses. Every interaction is
+logged, making it easy to understand exactly what your MCP servers are doing and
+how they respond to requests.
+
+### Integrated ToolHive management
+
+The playground includes a built-in MCP server that lets you manage your other
+MCP servers directly through natural language commands. You can list servers,
+check their status, start or stop them, and perform other management tasks using
+conversational AI.
+
+## Getting started
+
+To start using the playground:
+
+1. **Access the playground**: Click the **Playground** tab in the ToolHive UI
+ navigation bar.
+
+2. **Configure provider settings**: Click **Provider Settings** to set up access
+ to AI model providers:
+ - **OpenAI**: Enter your OpenAI API key to use GPT models
+ - **Anthropic**: Add your Anthropic API key for Claude models
+ - **Google**: Configure Google AI API key for Gemini models
+ - **xAI**: Set up xAI API key for Grok models
+ - **Ollama**: Enter the server URL to connect to your local Ollama instance
+ (default: `http://localhost:11434`)
+ - **LM Studio**: Enter the server URL from the **Developer** section in LM
+ Studio where you started the local server (default:
+ `http://localhost:1234`)
+ - **OpenRouter**: Add OpenRouter API key for access to multiple model
+ providers
+
+3. **Select MCP tools**: Click the tools icon to manage which MCP servers and
+ tools are available in the playground.
+ - View all your running MCP servers
+ - Enable or disable specific tools from each server
+ - Search and filter tools by name or functionality
+ - The `toolhive mcp` server is included by default, providing management
+ capabilities
+
+
+
+ :::tip
+
+ For more control over tool availability, use
+ [Customize tools](./customize-tools.mdx) to permanently configure which tools
+ are enabled for each registry server. The playground tool selection is
+ temporary and only affects your testing session.
+
+ :::
+
+4. **Start testing**: Begin chatting with your chosen AI model. The model will
+ have access to all enabled MCP tools and can execute them based on your
+ requests.
+
+## Using the playground
+
+### Testing MCP server functionality
+
+Use the playground to validate that your MCP servers work as expected:
+
+```text
+Can you list all my MCP servers and show their current status?
+```
+
+The AI will use the `list_servers` tool from the ToolHive MCP server to provide
+a comprehensive overview of your server status.
+
+
+
+Or test that an individual MCP tool is working as expected:
+
+```text
+Use the GitHub MCP server to search for recent issues in the microsoft/vscode repository
+```
+
+If you have the GitHub MCP server running, the AI will execute the appropriate
+GitHub API calls and return formatted results.
+
+### Managing servers through conversation
+
+The ToolHive desktop app automatically starts a dedicated MCP server
+(`toolhive mcp`) that orchestrates ToolHive operations through natural language
+commands. This approach provides several key benefits:
+
+- **Unified interface**: Manage your MCP infrastructure using the same
+ conversational AI interface you use for testing
+- **Contextual operations**: The AI understands your current server state and
+ can make intelligent decisions about which servers to start, stop, or
+ troubleshoot
+- **Reduced complexity**: No need to switch between the chat interface and
+ traditional UI controls—everything can be done through conversation
+- **Audit trail**: All management operations are logged in the same transparent
+ way as tool executions, providing clear visibility into what actions were
+ taken
+
+Take advantage of these integrated ToolHive management tools:
+
+```text
+Start the fetch MCP server for me
+```
+
+```text
+Stop all unhealthy MCP servers
+```
+
+```text
+Show me the logs for the meta-mcp server
+```
+
+### Validating tool responses
+
+The playground shows detailed information about each tool execution:
+
+- **Tool name and description**: What tool was called and its purpose
+- **Input parameters**: The exact parameters passed to the tool
+- **Execution status**: Whether the tool succeeded or failed
+- **Response data**: The complete response from the tool
+- **Timing information**: How long the tool took to execute
+
+This visibility helps you understand exactly how your MCP servers are behaving
+and identify any issues with tool implementation or configuration.
+
+## Recommended practices
+
+### Provider security
+
+- Use dedicated API keys for testing that have appropriate rate limits
+- Regularly rotate API keys used in development environments
+- Consider using API keys with restricted permissions for testing purposes
+- When using local providers like Ollama or LM Studio, ensure the server URLs
+ are only accessible on your local network to prevent unauthorized access
+
+### Server management
+
+- Start only the MCP servers you need for testing to improve performance
+- Use the playground to validate new server configurations before connecting
+ them to production AI clients
+- Test different combinations of tools to understand how they work together
+
+### Testing workflow
+
+1. **Isolated testing**: Test individual MCP servers one at a time to validate
+ their functionality
+2. **Integration testing**: Enable multiple servers to test how they work
+ together
+3. **Performance validation**: Monitor tool execution times and responses under
+ different loads
+4. **Error handling**: Intentionally trigger error conditions to validate proper
+ error handling
+
+## Troubleshooting
+
+### Common issues
+
+
+Provider not working
+
+If your provider isn't working:
+
+1. **For API key-based providers** (OpenAI, Anthropic, Google, xAI, OpenRouter):
+ - Verify the API key is correct and has proper permissions
+ - Check that you have sufficient quota or credits with the provider
+ - Ensure the API key hasn't expired or been revoked
+ - Try creating a new API key from the provider's dashboard
+
+2. **For local providers** (Ollama, LM Studio):
+ - Verify the server is running and accessible at the configured URL
+ - Check that the server URL is correct and includes the port number
+ - Ensure no firewall or network settings are blocking the connection
+ - For LM Studio, confirm you started the server in the **Developer** section
+
+
+
+
+MCP tools not appearing
+
+If your MCP server tools aren't showing up:
+
+1. Verify the MCP server is running (check the **MCP Servers** page)
+2. Click the tools icon and ensure the server's tools are enabled
+3. Try restarting the MCP server if it shows as unhealthy
+4. Check the server logs for any error messages
+
+
+
+
+Tool execution failing
+
+If tools are failing to execute:
+
+1. Check the tool's parameter requirements in the audit log
+2. Verify any required secrets or environment variables are configured
+3. Ensure the MCP server has necessary permissions (network access, file system
+ access)
+4. Review the server logs for detailed error information
+
+
+
+## Next steps
+
+- Learn about [client configuration](./client-configuration.mdx) to connect
+ ToolHive to external AI applications
+- Set up [secrets management](./secrets-management.mdx) for secure handling of
+ API keys and tokens
+- Explore [network isolation](./network-isolation.mdx) for enhanced security
+ when testing untrusted MCP servers
+- Browse the [registry](./registry.mdx) to discover new MCP servers to test in
+ the playground
+
+## Related information
+
+- [Run MCP servers](./run-mcp-servers.mdx)
+- [Install ToolHive](./install.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-ui/quickstart.mdx b/versioned_docs/version-1.0/toolhive/guides-ui/quickstart.mdx
new file mode 100644
index 00000000..89071826
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-ui/quickstart.mdx
@@ -0,0 +1,162 @@
+---
+title: 'Quickstart: ToolHive UI'
+sidebar_label: Quickstart
+description:
+ A step-by-step guide to installing the ToolHive desktop application and
+ running your first MCP server.
+---
+
+In this tutorial, you'll install the ToolHive desktop application and run your
+first MCP server. By the end, you'll have a working MCP server that can fetch
+content from websites and be used by AI applications like GitHub Copilot or
+Cursor.
+
+## What you'll learn
+
+- How to install ToolHive on your system
+- How to find available MCP servers
+- How to run an MCP server
+- How to use the server with an AI client application
+
+## Prerequisites
+
+Before starting this tutorial, make sure you have:
+
+- [Docker](https://docs.docker.com/get-docker/) or
+ [Podman](https://podman-desktop.io/downloads) or
+ [Colima](https://github.com/abiosoft/colima) installed and running
+- A [supported MCP client](../reference/client-compatibility.mdx) like GitHub
+ Copilot in VS Code, Cursor, Claude Code, and more
+
+## Step 1: Install the ToolHive UI
+
+First, download and run the ToolHive installer for your system.
+
+- **macOS**: download the DMG installer for
+ [Apple silicon](https://github.com/stacklok/toolhive-studio/releases/latest/download/ToolHive-arm64.dmg)
+ or
+ [Intel-based](https://github.com/stacklok/toolhive-studio/releases/latest/download/ToolHive-x64.dmg)
+ Macs and copy the ToolHive application to your Applications folder
+- **Windows**: download and run the
+ [installer](https://github.com/stacklok/toolhive-studio/releases/latest/download/ToolHive.Setup.exe)
+
+- **Linux**: download the RPM or DEB package from the
+ [releases page](https://github.com/stacklok/toolhive-studio/releases/latest)
+ and install it using your package manager.
+
+For more detailed installation instructions, see the
+[installing ToolHive](../guides-ui/install.mdx) guide.
+
+## Step 2: Find an MCP server to run
+
+On the initial splash screen, click **Browse registry**, or open the
+**Registry** page from the top menu bar. This page lists the MCP servers in
+ToolHive's built-in registry.
+
+:::info[What is this?]
+
+ToolHive maintains a curated registry of MCP servers that have been verified to
+work correctly. The registry includes information about what each server does
+and how to use it.
+
+:::
+
+For this tutorial, you'll use the "fetch" server, which is a simple MCP server
+that lets AI agents get the contents of a website. Click on the "fetch" server
+to start the installation process.
+
+## Step 3: Install the Fetch MCP server
+
+The Fetch MCP server doesn't require any special configuration, so you can
+install it with the default settings. Click the **Install server** button to
+start the installation.
+
+Once the server is installed, it will appear on the **MCP Servers** page.
+
+:::info[What's happening?]
+
+ToolHive downloads the container image for the fetch server, creates a container
+with the appropriate security settings, and starts the server. It also sets up a
+proxy process that lets your AI agent communicate with the server.
+
+:::
+
+## Step 4: Connect an AI client
+
+On the **Clients** page, you'll see the supported AI clients that ToolHive can
+manage for you. When you connect a client, ToolHive configures it to use the MCP
+servers you have installed.
+
+Click the toggle switch to connect the client you want to use.
+
+:::info[What's happening?]
+
+When you connect a supported client, ToolHive automatically configures it to use
+MCP servers that you install. This means you don't have to manually configure
+the client each time you run an MCP server.
+
+:::
+
+## Step 5: Use the MCP server with your client
+
+Now that your MCP server is running and your client is connected to ToolHive,
+you can use the MCP server's tools. Open your client and ask the AI to fetch
+content from a website. Note that you might need to restart your client for the
+changes to take effect.
+
+For example, try asking: "Can you fetch the content from https://toolhive.dev
+and summarize it for me?"
+
+The AI should be able to use the Fetch MCP server to retrieve the content and
+provide a summary.
+
+:::info[What's happening?]
+
+When you ask the AI agent to fetch content, the large language model (LLM)
+determines that it needs external data. It discovers the fetch tool provided by
+your MCP server, instructs the agent to execute the tool with the URL you
+specified, then receives and processes the webpage content to create a summary.
+
+:::
+
+## What's next?
+
+Congratulations! You've successfully installed ToolHive and run your first MCP
+server. Here are some next steps to explore:
+
+- Learn more about MCP concepts in the
+ [MCP primer guide](../concepts/mcp-primer.mdx)
+- Try [running more MCP servers](../guides-ui/run-mcp-servers.mdx) from the
+ registry or from custom sources
+- Learn about [secrets management](../guides-ui/secrets-management.mdx) for MCP
+ servers that require authentication
+- Learn how to
+ [manually configure clients](../reference/client-compatibility.mdx#manual-configuration)
+ that ToolHive doesn't automatically configure
+
+## Troubleshooting
+
+
+Server fails to start
+
+If the server fails to start, check:
+
+- Is Docker, Podman, or Colima running?
+- Do you have internet access to pull the container image?
+
+
+
+
+Client can't use the server
+
+If your AI client application can't use the server:
+
+- Make sure your client is connected to ToolHive (see Step 4)
+- Restart your client to pick up the new configuration
+- Verify the server is running on the **MCP Servers** page
+- Disconnect and reconnect the client in ToolHive to refresh the configuration
+
+
+
+If you encounter any problems, please join our
+[Discord community](https://discord.gg/stacklok) for help.
diff --git a/versioned_docs/version-1.0/toolhive/guides-ui/registry.mdx b/versioned_docs/version-1.0/toolhive/guides-ui/registry.mdx
new file mode 100644
index 00000000..f7ac4fcb
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-ui/registry.mdx
@@ -0,0 +1,147 @@
+---
+title: Explore the registry
+description: How to use the built-in or custom registry to find MCP servers.
+---
+
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+
+ToolHive includes a built-in registry of MCP servers with verified
+configurations that meet a
+[minimum quality standard](../concepts/registry-criteria.mdx), allowing you to
+discover and deploy high-quality tools effortlessly. You can browse the
+registry, select servers, and run them securely through the user interface.
+
+You can also configure ToolHive to use a custom registry instead. This is useful
+for organizations that want to maintain their own private registry of MCP
+servers.
+
+## Find MCP servers
+
+The ToolHive interface includes a dedicated **Registry** section in the main
+menu. This section lets you explore the various MCP servers available in either
+the built-in or custom registry.
+
+
+
+The registry interface displays a list of available servers. You can browse
+through the list, use search functionality, and click on any server to view
+detailed information before installing it.
+
+## View server details
+
+Click on any server in the registry to view detailed information including:
+
+- **Server name and description**: Full description of what the server does
+- **Tier badge**: Shows whether the server is **Official** or **Community**
+- **Transport type**: Shows the communication method (e.g., stdio)
+- **Popularity**: Star count and download statistics
+- **Provenance**: Security verification status (e.g., "Provenance signed by
+ Sigstore")
+- **Tools listed**: Names of all available tools the server provides
+- **Action buttons**: **Install server** and **GitHub** (if repository
+ available)
+
+This detailed view helps you understand the server's purpose and capabilities
+before adding it to your ToolHive installation.
+
+## Registry groups
+
+Registry groups allow you to organize related MCP servers and run them together
+as a cohesive unit. This is particularly useful for creating team-specific
+toolkits, workflow-based server collections, or environment-specific
+configurations.
+
+The default ToolHive registry contains only individual servers. To use groups,
+configure a [custom registry](../tutorials/custom-registry.mdx) that defines
+them.
+
+### Key characteristics
+
+- **Optional**: Groups are entirely optional. You only need a custom registry
+ with groups if you want to install multiple servers together
+- **Independent server definitions**: Each group contains complete server
+ configurations, not references to top-level servers
+- **Self-contained**: Groups can define servers with the same names as
+ individual servers but with different configurations
+- **Flexible membership**: The same server can appear in multiple groups with
+ different configurations
+
+### Browse and view groups
+
+When you use a custom registry that includes groups, they appear in the registry
+grid alongside individual servers. Click on any group to view its name,
+description, and a list of all included servers with their descriptions. From
+the group details page, you can click **Install group** to begin installation.
+
+### Install a group
+
+To install a group:
+
+1. Click **Install group** to open the wizard
+2. Configure each server following the same process as
+ [installing an individual server](./run-mcp-servers.mdx#configure-the-server)
+3. Click **Next** after each server, or **Finish** on the last one
+
+The wizard guides you through each server one at a time, showing your progress
+(for example, "Installing server 1 of 3"). After installation, the group appears
+in the **MCP Servers** page where you can
+[manage the servers together](./group-management.mdx).
+
+## Registry settings
+
+To configure your registry, ToolHive provides a dedicated **Registry** settings
+section in **Settings** → **Registry** with three options:
+
+- **Default Registry** - Use ToolHive's built-in verified registry
+- **Remote Registry (URL)** - Specify a custom URL for a remote registry hosted
+ on a web server
+- **Local Registry (File Path)** - Specify a local file path to a JSON registry
+ file on your system
+
+When you select **Local Registry (File Path)**, an additional **Registry File
+Path** field appears where you need to provide the absolute path to your local
+JSON registry file (for example: `/path/myregistry/db.json`). Click **Save** to
+apply your configuration.
+
+For detailed information on creating a custom registry, see the
+[custom registry tutorial](../tutorials/custom-registry.mdx).
+
+
+
+## Next steps
+
+After exploring the registry and finding servers you want to use:
+
+- **Install servers**: Click on servers from the registry to install and
+ configure them for your environment
+- **Customize tools**: Selectively enable or disable tools from installed
+ registry servers to create focused tool sets
+- **Manage running servers**: Use the **MCP Servers** section to start, stop,
+ and monitor your installed servers
+- **Create custom registries**: Set up your own private registry for
+ organization-specific servers
+
+## Related information
+
+- [Install ToolHive UI](./install.mdx) - Get started with ToolHive
+- [Run MCP servers](./run-mcp-servers.mdx) - Install and manage servers from the
+ registry
+- [Custom registry tutorial](../tutorials/custom-registry.mdx) - Create your own
+ MCP server registry
+- [Registry criteria](../concepts/registry-criteria.mdx) - Quality standards for
+ built-in registry servers
diff --git a/versioned_docs/version-1.0/toolhive/guides-ui/run-mcp-servers.mdx b/versioned_docs/version-1.0/toolhive/guides-ui/run-mcp-servers.mdx
new file mode 100644
index 00000000..e2d1afe1
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-ui/run-mcp-servers.mdx
@@ -0,0 +1,517 @@
+---
+title: Run MCP servers
+description: How to install and run MCP servers in the ToolHive UI.
+---
+
+import RemoteAuthExamples from '../_partials/_remote-mcp-auth-examples.mdx';
+import Heading from '@theme/Heading';
+
+ToolHive makes it easy to run and manage Model Context Protocol (MCP) servers.
+This guide walks you through installing MCP servers from the registry, using
+Docker images, or from source packages.
+
+## Install from the registry
+
+ToolHive includes a built-in registry of MCP servers that meet a
+[minimum quality standard](../concepts/registry-criteria.mdx), allowing you to
+discover and deploy high-quality tools effortlessly. Simply select one from the
+list to run it securely with just a few clicks.
+
+### Select an MCP server
+
+{/* prettier-ignore */}
+To install an MCP server (local
+or remote {/* prettier-ignore */} ) from the registry:
+
+1. Open the **Registry** page from the menu bar.
+1. Browse or search for the MCP server you want to install.
+1. Click on the desired MCP server to open its details page. Here you can review
+ more information about the server like its:
+ - Description
+ - Available tools
+ - Tier (community or official)
+ - Popularity (GitHub stars)
+ - A link to the GitHub repository for more details and usage documentation
+1. Click the **Install server** button to start the installation process.
+
+### Configure the server
+
+
+
+
+ {' '}Local MCP
+ >
+ }
+ default>
+
+**Local MCP servers** run directly on your machine using your container runtime.
+
+The configuration form is pre-filled with defaults from the registry. Enter the
+remaining required information and adjust any optional settings as needed:
+
+1. **Server name**: A unique name for the MCP server.\
+ This defaults to the MCP server's name in the registry. If you want to run
+ multiple instances of the same MCP server, give each instance a unique name.
+
+1. **Group**: The group where this server will be added. [Optional]\
+ Select an existing group or keep the default. Groups help you organize
+ servers and control client access. See
+ [Organize servers into groups](./group-management.mdx) for details.
+
+1. **Command arguments** (optional):\
+ Enter any command-line arguments needed to run the MCP server. These might be
+ needed to pass application-specific parameters to the MCP server. Refer to
+ the MCP server's documentation for details.
+
+ :::info
+
+ We've made every effort to include sensible defaults in the registry for a
+ one-click experience, but some servers may require additional command-line
+ arguments to function correctly.
+
+ :::
+
+1. **Storage volumes** [Optional]:\
+ Map files or folders from your local host to the MCP server. See
+ [Mount host files and folders](#volumes). [Optional]
+
+1. **Secrets** and **environment variables** expected by the server are
+ populated from the registry automatically. Required values are marked with an
+ asterisk (\*).
+ 1. **Secrets**: Enter a value to create a new secret or select an existing
+ secret to map to the environment variable. Secrets are stored securely and
+ can be used by the MCP server without exposing them in plaintext.
+ 2. **Environment variables**: Enter the value in the input field alongside
+ the variable name. These are typically used for configuration options that
+ do not require sensitive data.
+
+1. **Network isolation** [Optional]:\
+ Enable network isolation to restrict the MCP server's network access. This
+ enhances security by limiting the server's ability to communicate with
+ external networks. See the [Network isolation](./network-isolation.mdx) guide
+ for details.
+
+:::note
+
+Refer to the MCP server's documentation for the required configuration
+information, permissions, and authentication details. A link to the GitHub
+repository is provided on each server's details page.
+
+:::
+
+
+
+
+ {' '}Remote MCP
+ >
+ }>
+
+**Remote MCP servers** are hosted services that you connect to.
+
+The configuration form is pre-filled with defaults from the registry. Enter the
+remaining required information and adjust any optional settings as needed:
+
+1. A unique **name** for the MCP server. [Required]
+1. **Group**: The group where this server will be added. [Optional]\
+ Select an existing group or keep the default. Groups help you organize
+ servers and control client access. See
+ [Organize servers into groups](./group-management.mdx) for details.
+1. The **URL** of the remote MCP server. [Required]
+1. The **transport protocol** that the MCP server uses. [Required]\
+ ToolHive supports server-sent events (SSE) and Streamable HTTP (default) for
+ real-time communication. The protocol must match what the MCP server
+ supports.
+1. **Authorization method**: Choose how ToolHive should authenticate with the
+ remote server.\
+ The default is **Auto-Discovered**. Use this option for MCP servers that
+ fully implement the MCP authorization spec including dynamic client
+ registration (RFC7591) or for servers that do not require authentication.
+ ToolHive automatically:
+ - Discovers OAuth/OIDC endpoints
+ - Registers a new OAuth client
+ - Obtains and manages client credentials
+ - Handles token lifecycle automatically
+
+ For MCP servers that require manual configuration, ToolHive supports OAuth2
+ and OIDC authentication. Obtain the necessary information from the MCP
+ server's documentation or administrator.
+
+ **OAuth2 authentication options:**
+ - **Authorize URL**: The URL where users are redirected to authenticate and
+ authorize access to the MCP server. [Required]
+ - **Token URL**: The URL where your application exchanges the authorization
+ code for access tokens. [Required]
+ - **Client ID**: Your application's identifier registered with the OAuth
+ provider. [Required]
+ - **Client secret**: The secret key that proves your application's identity.
+ Enter a value to create a new secret or select an existing secret from the
+ provider. Secrets are stored securely and can be used by the MCP server
+ without exposing them in plaintext. See
+ [Secrets management](./secrets-management.mdx) for details. [Optional]
+ - **Scopes**: List of permissions your application is requesting. [Optional]
+ - **PKCE**: Enable Proof Key for Code Exchange (RFC 7636) for enhanced
+ security without requiring a client secret. [Optional]
+
+ **OIDC authentication options:**
+ - **Issuer URL**: The base URL of the OIDC provider. [Required]
+ - **Client ID**: Your application's identifier registered with the OIDC
+ provider. [Required]
+ - **Client secret**: The secret key that proves your application's identity.
+ Enter a value to create a new secret or select an existing secret from the
+ provider. Secrets are stored securely and can be used by the MCP server
+ without exposing them in plaintext. See
+ [Secrets management](./secrets-management.mdx) for details. [Optional]
+ - **PKCE**: Enable Proof Key for Code Exchange (RFC 7636) for enhanced
+ security without requiring a client secret. [Optional]
+
+1. The **callback port** for the authentication redirect. [Optional]
+
+1. **Custom headers** [Optional]:\
+ Add custom HTTP headers to inject into requests to the remote MCP server.
+ - **Plaintext headers**: Add static key-value pairs for headers like
+ `X-Tenant-ID` or correlation IDs. These values are stored and transmitted
+ in plaintext.
+ - **Headers from secrets**: For sensitive data like API keys, add headers
+ whose values are retrieved from ToolHive's secrets manager. Enter a header
+ name and either select an existing secret or enter a new value to create
+ one.
+
+Click **Install server** to connect to the remote MCP server.
+
+
+View examples of remote MCP authentication configuration
+
+
+
+
+
+
+
+
+### Start the MCP server
+
+Click **Install server** to install and start the MCP server. ToolHive downloads
+the Docker image, creates a container, and starts it with the specified
+configuration.
+
+Once the server is running, you can see its status on the **MCP Servers** page.
+Each server's card includes:
+
+- The server name
+- Its status (Running or Stopped) with a toggle button to start or stop it
+- A menu (︙) that includes the server's URL for AI clients that need manual
+ configuration, and options to:
+ - Open the server's GitHub repository
+ - View the server's logs
+ - Remove the server
+
+See [Manage MCP servers](#manage-mcp-servers) below for more details on server
+states.
+
+## Install a custom MCP server
+
+You're not limited to the MCP servers in the registry. You can run remote MCP
+servers by providing a URL, or your own local custom MCP servers using Docker
+images or source packages.
+
+### Configure the server
+
+
+
+
+ {' '}Custom local MCP
+ >
+ }
+ default>
+
+On the **MCP Servers** page, click **Add an MCP server**, then choose **Add
+custom local server** in the drop-down menu. Or if this is your first MCP
+server, on the introductory screen.
+
+In the **Custom MCP server** dialog, choose [Docker image](#from-a-docker-image)
+or [Package manager](#from-a-source-package).
+
+{/* prettier-ignore */}
+From a Docker image
+
+Select the **Docker image** option. This allows you to run any MCP server that
+is available as a Docker image in a remote registry or locally on your system.
+
+On the configuration form, enter:
+
+1. A unique **name** for the MCP server. [Required]
+
+1. **Group**: The group where this server will be added. [Optional]\
+ Select an existing group or keep the default. Groups help you organize
+ servers and control client access. See
+ [Organize servers into groups](./group-management.mdx) for details.
+
+1. The **transport protocol** that the MCP server uses. [Required]\
+ ToolHive supports standard input/output (`stdio`), server-sent events (SSE),
+ and Streamable HTTP. The protocol must match what the MCP server supports.
+
+1. The **target port** to expose from the container (SSE or Streamable HTTP
+ transports only). [Optional]\
+ If the MCP server uses a specific port, enter it here. If not specified,
+ ToolHive will use a random port that is exposed to the container with the
+ `MCP_PORT` and `FASTMCP_PORT` environment variables.
+
+1. The Docker **image name** and tag (e.g., `my-mcp-server:latest`). [Required]\
+ You can use any valid Docker image, including those hosted on Docker Hub or
+ other registries.
+
+1. **Command-line arguments** needed to run the MCP server. [Optional]\
+ These are specific to the MCP server and might include transport options or
+ application-specific parameters. Refer to the MCP server's documentation for
+ details.
+
+1. Any **secrets** or **environment variables** required by the MCP server.
+ [Optional]\
+ These might include API tokens, configuration options, or other sensitive
+ data.
+ - Secrets are mapped to an environment variable. Enter the variable name and
+ select an existing secret or enter a value to create a new one.
+ - For non-sensitive environment variables, enter the name and value directly.
+
+1. **Storage volumes** [Optional]:\
+ Map files or folders from your local host to the MCP server. See
+ [Mount host files and folders](#volumes). [Optional]
+
+1. **Network isolation** [Optional]:\
+ Enable network isolation to restrict the MCP server's network access. This
+ enhances security by limiting the server's ability to communicate with
+ external networks. See the [Network isolation](./network-isolation.mdx) guide
+ for details.
+
+Click **Install server** to create and start the MCP server container.
+
+{/* prettier-ignore */}
+From a source package
+
+Select the **Package manager** option. This allows you to run an MCP server from
+a source package.
+
+ToolHive downloads the MCP server's source package and builds a Docker image
+on-the-fly. The following package formats are supported:
+
+- Node.js-based MCP servers using npm
+- Python-based MCP servers available on PyPI, using the `uv` package manager
+- Go-based MCP servers available on GitHub
+
+On the configuration form, enter:
+
+1. A unique **name** for the MCP server. [Required]
+
+1. **Group**: The group where this server will be added. [Optional]\
+ Select an existing group or keep the default. Groups help you organize
+ servers and control client access. See
+ [Organize servers into groups](./group-management.mdx) for details.
+
+1. The **transport protocol** that the MCP server uses. [Required]\
+ ToolHive supports standard input/output (`stdio`), server-sent events (SSE),
+ and Streamable HTTP. The protocol must match what the MCP server supports.
+
+1. The **target port** to expose from the container (SSE or Streamable HTTP
+ transports only). [Optional]\
+ If the MCP server uses a specific port, enter it here. If not specified,
+ ToolHive will use a random port that is exposed to the container with the
+ `MCP_PORT` and `FASTMCP_PORT` environment variables.
+
+1. The package **protocol** (`npx`, `uvx`, or `go`). [Required]
+
+1. The **name** of the package to run. [Required]
+ 1. For `npx`, use the [npm](https://www.npmjs.com/) package name and version,
+ e.g. `my-mcp-package@latest`
+ 2. For `uvx`, use the [PyPI](https://pypi.org/) package name and version,
+ e.g. `my-mcp-package@latest`
+ 3. For `go`, use the GitHub repository URL with full path to the `main`
+ package and version, e.g.
+ `go://github.com/orgname/my-mcp-server/cmd/server@latest`
+
+1. **Command-line arguments** needed to run the MCP server. [Optional]\
+ These are specific to the MCP server and might include transport options or
+ application-specific parameters. Refer to the MCP server's documentation for
+ details.
+
+1. Any **secrets** or **environment variables** required by the MCP server.
+ [Optional]\
+ These might include API tokens, configuration options, or other sensitive
+ data.
+ - Secrets are mapped to an environment variable. Enter the variable name and
+ select an existing secret or enter a value to create a new one.
+ - For non-sensitive environment variables, enter the name and value directly.
+
+1. **Storage volumes** [Optional]:\
+ Map files or folders from your local host to the MCP server. See
+ [Mount host files and folders](#volumes). [Optional]
+
+1. **Network isolation** [Optional]:\
+ Enable network isolation to restrict the MCP server's network access. This
+ enhances security by limiting the server's ability to communicate with
+ external networks. See the [Network isolation](./network-isolation.mdx) guide
+ for details.
+
+Click **Install server** to create and start the MCP server container.
+
+
+
+
+ {' '}Custom remote MCP
+ >
+ }>
+
+On the **MCP Servers** page, click **Add an MCP server**, then choose **Add a
+remote MCP server** in the drop-down menu.
+
+On the configuration form, enter:
+
+1. A unique **name** for the MCP server. [Required]
+2. **Group**: The group where this server will be added. [Optional]\
+ Select an existing group or keep the default. Groups help you organize
+ servers and control client access. See
+ [Organize servers into groups](./group-management.mdx) for details.
+3. The **URL** of the remote MCP server. [Required]
+4. The **transport protocol** that the MCP server uses. [Required]\
+ ToolHive supports server-sent events (SSE) and Streamable HTTP (default) for
+ real-time communication. The protocol must match what the MCP server
+ supports.
+5. **Authorization method**: Choose how ToolHive should authenticate with the
+ remote server.\
+ The default is **Auto-Discovered**. Use this option for MCP servers that
+ fully implement the MCP authorization spec including dynamic client
+ registration (RFC7591) or for servers that do not require authentication.
+ ToolHive automatically:
+ - Discovers OAuth/OIDC endpoints
+ - Registers a new OAuth client
+ - Obtains and manages client credentials
+ - Handles token lifecycle automatically
+
+ For MCP servers that require manual configuration, ToolHive supports OAuth2
+ and OIDC authentication. Obtain the necessary information from the MCP
+ server's documentation or administrator.
+
+ **OAuth2 authentication options:**
+ - **Authorize URL**: The URL where users are redirected to authenticate and
+ authorize access to the MCP server. [Required]
+ - **Token URL**: The URL where your application exchanges the authorization
+ code for access tokens. [Required]
+ - **Client ID**: Your application's identifier registered with the OAuth
+ provider. [Required]
+ - **Client secret**: The secret key that proves your application's identity.
+ Enter a value to create a new secret or select an existing secret from the
+ provider. Secrets are stored securely and can be used by the MCP server
+ without exposing them in plaintext. See
+ [Secrets management](./secrets-management.mdx) for details. [Optional]
+ - **Scopes**: List of permissions your application is requesting. [Optional]
+
+ **OIDC authentication options:**
+ - **Issuer URL**: The base URL of the OIDC provider. [Required]
+ - **Client ID**: Your application's identifier registered with the OIDC
+ provider. [Required]
+ - **Client secret**: The secret key that proves your application's identity.
+ Enter a value to create a new secret or select an existing secret from the
+ provider. Secrets are stored securely and can be used by the MCP server
+ without exposing them in plaintext. See
+ [Secrets management](./secrets-management.mdx) for details. [Optional]
+ - **PKCE**: Enable Proof Key for Code Exchange (RFC 7636) for enhanced
+ security without requiring a client secret. [Optional]
+
+6. The **callback port** for the authentication redirect. [Optional]
+
+7. **Custom headers** [Optional]:\
+ Add custom HTTP headers to inject into requests to the remote MCP server.
+ - **Plaintext headers**: Add static key-value pairs for headers like
+ `X-Tenant-ID` or correlation IDs. These values are stored and transmitted
+ in plaintext.
+ - **Headers from secrets**: For sensitive data like API keys, add headers
+ whose values are retrieved from ToolHive's secrets manager. Enter a header
+ name and either select an existing secret or enter a new value to create
+ one.
+
+Click **Install server** to connect to the remote MCP server.
+
+
+View examples of remote MCP authentication configuration
+
+
+
+
+
+
+
+
+## Mount host files and folders \{#volumes}
+
+Some MCP servers need access to files on your machine. You can mount host paths
+directly in the UI.
+
+1. In the server **Install / Configure** dialog, scroll to **Storage volumes**.
+2. Use the **first row** to create your mount:
+ - **Host path** — choose a file or folder on your computer.
+ - **Container path** — where it should appear inside the server (for example,
+ `/data`).
+ - By default, mounts are in read-write mode. If you want your volume mount to
+ be **Read-only**, select the "Read-only access" option from the drop-down.
+3. If you need additional mounts, click **Add a volume** and repeat.
+4. Click **Install server** to start the server with your volume(s).
+
+This applies to both registry-installed servers and custom servers (Docker image
+or source package).
+
+## Manage MCP servers
+
+On the **MCP Servers** page, you can manage your installed MCP servers:
+
+- **Start/Stop**: Use the toggle button to start or stop the MCP server. When
+ you stop a server, it remains in the list but is no longer running. ToolHive
+ removes the server from connected AI clients while stopped.
+
+Click the menu (︙) on the server card to access these options:
+
+- **Copy URL** (⧉): Copy the MCP server's URL to your clipboard. This URL is
+ used by AI clients to connect to the MCP server.
+- **Edit configuration**: Modify the server's settings, such as command
+ arguments, environment variables, secrets, storage volumes, and network
+ isolation. The dialog is pre-filled with your existing configuration, so you
+ only need to change the specific settings you want to update.
+- **GitHub repository**: View the MCP server's source code and documentation on
+ GitHub. (Only available for servers installed from the registry using the
+ default name.)
+- **Logs**: View the server's output. For local servers, this shows the
+ container logs. For remote servers, it shows logs from the proxy process.
+- **Remove**: Stop and remove the MCP server from ToolHive. This deletes the
+ container and any associated configuration, so use with caution.
+- **Copy server to a group**: Duplicate the server configuration to a different
+ group. See [Organize servers into groups](./group-management.mdx) for details.
+
+When you quit the application, ToolHive prompts you to stop all running MCP
+servers. The running servers are recorded and ToolHive restarts them
+automatically the next time you launch the application.
+
+## Next steps
+
+- Connect ToolHive to AI clients like GitHub Copilot or Cursor using the
+ [client configuration guide](./client-configuration.mdx).
+- Customize which tools are available from registry servers using the
+ [customize tools guide](./customize-tools.mdx).
+- Learn more about [secrets management](./secrets-management.mdx) to securely
+ manage API tokens and other sensitive data.
+- Test your MCP servers using the [playground](./playground.mdx) to validate
+ functionality and behavior with different models.
diff --git a/versioned_docs/version-1.0/toolhive/guides-ui/secrets-management.mdx b/versioned_docs/version-1.0/toolhive/guides-ui/secrets-management.mdx
new file mode 100644
index 00000000..2047284e
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-ui/secrets-management.mdx
@@ -0,0 +1,62 @@
+---
+title: Secrets management
+description: How to securely manage API tokens and other sensitive data in the ToolHive UI.
+---
+
+Many MCP servers need secrets like API tokens, connection strings, and other
+sensitive parameters. ToolHive provides built-in secrets management so you can
+manage these values securely without exposing them in plaintext configuration
+files.
+
+ToolHive encrypts secrets using a randomly generated password that is stored in
+your operating system's secure keyring.
+
+You can add new secrets on the **Secrets** page or during MCP server
+installation.
+
+:::note
+
+The ToolHive UI does not currently support the 1Password secrets provider. If
+you have configured the 1Password secrets provider using the ToolHive CLI, the
+UI will automatically update your configuration to use the built-in encrypted
+provider instead.
+
+:::
+
+## Enter secrets during MCP installation
+
+When you install an MCP server from the registry, any required secrets are
+listed on the configuration form with their corresponding environment variable.
+
+When you add a custom MCP server, add secrets by entering the environment
+variable name that the MCP server expects.
+
+To set the secret value, you can:
+
+- Select an existing secret to populate its value in the configuration form.
+- Enter a value in the input box. ToolHive creates a new secret with a name
+ matching the environment variable.
+
+## Manage secrets
+
+Your ToolHive secrets are managed on the **Secrets** page. Here you can:
+
+- Click the **Add secret** button to create a new secret. Enter a friendly name
+ for the secret and its value.
+- Expand the menu (︙) next to an existing secret to:
+ - Update the secret value
+ - Delete the secret
+
+:::warning
+
+If you delete a secret that is in use by an MCP server, the server will continue
+running but you will not be able to restart it if stopped. You'll need to remove
+the server and reinstall it with the required secrets, or add the secret back
+using the same name.
+
+:::
+
+## Related information
+
+- [Run MCP servers](./run-mcp-servers.mdx)
+- [Client configuration](./client-configuration.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-vmcp/audit-logging.mdx b/versioned_docs/version-1.0/toolhive/guides-vmcp/audit-logging.mdx
new file mode 100644
index 00000000..3ff13e1e
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-vmcp/audit-logging.mdx
@@ -0,0 +1,410 @@
+---
+title: Audit logging
+description:
+ Configure audit logging for Virtual MCP Server to meet security compliance
+ requirements and track MCP operations.
+---
+
+Virtual MCP Server (vMCP) provides comprehensive audit logging for all MCP
+operations. Audit logs enable security teams to meet compliance requirements,
+investigate incidents, and maintain operational visibility.
+
+## Overview
+
+The audit logging system captures structured JSON events for every MCP protocol
+operation, including tool calls, resource reads, prompt requests, and composite
+workflow executions. The implementation can help organizations address
+audit-related compliance requirements for enterprise security.
+
+## Enable audit logging
+
+Configure audit logging in the VirtualMCPServer resource using the
+`spec.config.audit` field:
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: my-vmcp
+ namespace: toolhive-system
+spec:
+ config:
+ groupRef: my-group
+ # highlight-start
+ audit:
+ enabled: true
+ component: vmcp-production
+ includeRequestData: true
+ includeResponseData: false
+ maxDataSize: 4096
+ # highlight-end
+```
+
+### Configuration options
+
+| Field | Description | Default |
+| --------------------- | ------------------------------------------------ | ------------- |
+| `enabled` | Enable audit logging | `false` |
+| `component` | Component name in audit events | `vmcp-server` |
+| `eventTypes` | Specific event types to log (empty = all events) | `[]` (all) |
+| `excludeEventTypes` | Event types to exclude from logging | `[]` |
+| `includeRequestData` | Include request payloads in audit logs | `false` |
+| `includeResponseData` | Include response payloads in audit logs | `false` |
+| `maxDataSize` | Maximum payload size in bytes | `1024` |
+| `logFile` | File path for audit logs (empty = stdout) | `""` (stdout) |
+
+## Audit event types
+
+vMCP logs several categories of events:
+
+### MCP protocol operations
+
+Standard MCP protocol interactions are logged with these event types:
+
+- `mcp_initialize`: MCP connection initialization
+- `mcp_tool_call`: Tool invocation
+- `mcp_tools_list`: List available tools
+- `mcp_resource_read`: Read a resource
+- `mcp_resources_list`: List available resources
+- `mcp_prompt_get`: Get a prompt
+- `mcp_prompts_list`: List available prompts
+- `mcp_notification`: MCP notifications
+- `mcp_completion`: Completion requests
+- `mcp_roots_list_changed`: Root list changed notifications
+
+### Connection and transport events
+
+Connection establishment events:
+
+- `sse_connection`: SSE transport connection established (SSE connections are
+ logged separately due to their long-lived nature; other transports like
+ streamable HTTP use standard MCP protocol events)
+- `mcp_ping`: Health check pings
+
+### Logging events
+
+MCP logging protocol events:
+
+- `mcp_logging`: Logging messages from MCP servers
+
+### Composite workflow operations
+
+Composite tools (multi-step workflows) generate additional audit events:
+
+- `vmcp_workflow_started`: Workflow execution begins
+- `vmcp_workflow_completed`: Workflow completes successfully
+- `vmcp_workflow_failed`: Workflow fails
+- `vmcp_workflow_timed_out`: Workflow exceeds timeout
+- `vmcp_workflow_step_started`: Individual step begins
+- `vmcp_workflow_step_completed`: Individual step completes
+- `vmcp_workflow_step_failed`: Individual step fails
+- `vmcp_workflow_step_skipped`: Conditional step is skipped
+
+### Fallback event types
+
+Generic event types for unrecognized requests:
+
+- `mcp_request`: Generic MCP request when specific type cannot be determined
+- `http_request`: Generic HTTP request (non-MCP)
+
+## Filter audit events
+
+By default, all event types are logged. You can filter events using `eventTypes`
+(allowlist) and `excludeEventTypes` (blocklist):
+
+```yaml
+spec:
+ config:
+ audit:
+ enabled: true
+ eventTypes:
+ - mcp_initialize
+ - mcp_tool_call
+ - vmcp_workflow_started
+ - vmcp_workflow_completed
+ - vmcp_workflow_failed
+ excludeEventTypes:
+ - mcp_ping
+```
+
+Use `eventTypes` to capture only specific operations. Use `excludeEventTypes` to
+filter out high-frequency events like health checks.
+
+:::info
+
+When both fields are specified, `excludeEventTypes` takes precedence. Events
+matching the exclusion list are never logged, even if they appear in
+`eventTypes`.
+
+:::
+
+## Payload logging
+
+By default, audit logs capture metadata about operations but not the actual
+request and response payloads. For forensic analysis or debugging, you can
+enable payload capture:
+
+```yaml
+spec:
+ config:
+ audit:
+ enabled: true
+ includeRequestData: true
+ includeResponseData: true
+ maxDataSize: 16384 # Truncate payloads larger than 16KB
+```
+
+The `maxDataSize` field controls the maximum size of captured payloads to
+prevent log bloat. Payloads exceeding this limit are truncated.
+
+:::warning
+
+Request and response payloads may contain sensitive data. Review your
+organization's data handling policies before enabling payload logging in
+production environments.
+
+:::
+
+## Audit log format
+
+Each audit event is a structured JSON object with these fields:
+
+```json
+{
+ "time": "2025-02-02T15:45:30.123456789Z",
+ "level": "INFO+2",
+ "msg": "audit_event",
+ "audit_id": "a3f2b8d1-4c5e-6789-abcd-ef0123456789",
+ "type": "mcp_tool_call",
+ "logged_at": "2025-02-02T15:45:30.123456Z",
+ "outcome": "success",
+ "component": "vmcp-production",
+ "source": {
+ "type": "network",
+ "value": "10.0.1.50",
+ "extra": {
+ "user_agent": "Claude/1.0"
+ }
+ },
+ "subjects": {
+ "user": "alice@company.com",
+ "user_id": "sub-alice-123",
+ "client_name": "Claude Desktop",
+ "client_version": "1.0.0"
+ },
+ "target": {
+ "endpoint": "/mcp",
+ "method": "tools/call",
+ "type": "tool",
+ "name": "github_create_pr"
+ },
+ "metadata": {
+ "extra": {
+ "duration_ms": 234,
+ "transport": "http",
+ "backend_name": "github"
+ }
+ },
+ "data": {
+ "request": {
+ "title": "Add new feature",
+ "base": "main"
+ }
+ }
+}
+```
+
+### Field descriptions
+
+| Field | Description |
+| ----------- | --------------------------------------------------------- |
+| `time` | Timestamp when the log was generated |
+| `level` | Log level (INFO+2 for audit events) |
+| `msg` | Always "audit_event" for audit logs |
+| `audit_id` | Unique identifier for this audit event |
+| `type` | Event classification (what happened) |
+| `logged_at` | UTC timestamp when the event occurred |
+| `outcome` | Result (success, failure, denied, error) |
+| `component` | System component that generated the event |
+| `source` | Request origin (IP address, user agent) |
+| `subjects` | Identity information (user, client) |
+| `target` | Resource or operation targeted |
+| `metadata` | Additional context (duration_ms, transport, backend_name) |
+| `data` | Optional request and response payloads |
+
+## User identity in audit logs
+
+The audit system extracts user identity from OIDC authentication tokens into the
+subjects field (shown in the audit log format above).
+
+The user field is populated using this fallback order from token claims:
+
+1. `name` claim (full name)
+2. `preferred_username` claim (username)
+3. `email` claim (email address)
+4. `"anonymous"` (when authentication is disabled)
+
+The `user_id` field contains the OIDC `sub` claim (subject identifier).
+
+:::note
+
+When using anonymous authentication (not recommended for production), audit logs
+show `"user": "anonymous"` with no `user_id`. This may not meet compliance
+requirements for user identification.
+
+:::
+
+## Configure output destination
+
+### Log to stdout (default)
+
+By default, audit logs are written to standard output, which integrates with
+Kubernetes log collection:
+
+```yaml
+spec:
+ config:
+ audit:
+ enabled: true
+ # logFile not specified = stdout
+```
+
+This approach works well with log aggregation systems like Fluentd, Fluent Bit,
+or cloud provider log collectors (CloudWatch, Google Cloud Logging, Azure
+Monitor).
+
+### Log to a file
+
+To persist audit logs to a file, configure a persistent volume:
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: my-vmcp
+ namespace: toolhive-system
+spec:
+ config:
+ audit:
+ enabled: true
+ logFile: /var/log/audit/vmcp.log
+ podTemplateSpec:
+ spec:
+ volumes:
+ - name: audit-logs
+ persistentVolumeClaim:
+ claimName: vmcp-audit-pvc
+ containers:
+ - name: vmcp
+ volumeMounts:
+ - name: audit-logs
+ mountPath: /var/log/audit
+```
+
+:::info
+
+File-based audit logs are written with secure permissions (`0600`) that allow
+read/write access only to the file owner (the user the vMCP container runs as).
+
+To access these logs from persistent volumes or log collection sidecars, ensure:
+
+- The vMCP container user is explicitly configured via
+ `podTemplateSpec.spec.securityContext.runAsUser`
+- Log collection sidecars run as the same user, or
+- Use `podTemplateSpec.spec.securityContext.fsGroup` to enable group-based
+ access
+
+For most deployments, using the default stdout logging is simpler and integrates
+better with Kubernetes log collection systems.
+
+:::
+
+## Configuration patterns
+
+### Security compliance
+
+For environments requiring comprehensive audit trails:
+
+```yaml
+spec:
+ config:
+ audit:
+ enabled: true
+ component: vmcp-production
+ # No eventTypes filter = log all events (comprehensive)
+ excludeEventTypes:
+ - mcp_ping # Exclude health checks (not audit-relevant)
+ includeRequestData: true
+ includeResponseData: true
+ maxDataSize: 16384
+ logFile: /var/log/vmcp/audit.log
+```
+
+### Performance-optimized
+
+For high-throughput environments, log only critical events:
+
+```yaml
+spec:
+ config:
+ audit:
+ enabled: true
+ component: vmcp-high-throughput
+ eventTypes:
+ - mcp_tool_call
+ - vmcp_workflow_failed
+ includeRequestData: false
+ includeResponseData: false
+```
+
+## Query and analyze audit logs
+
+### Search for specific operations
+
+Query logs for a specific user's tool calls:
+
+```bash
+kubectl logs -n toolhive-system -l app.kubernetes.io/instance=my-vmcp \
+ | jq 'select(.type == "mcp_tool_call" and .subjects.user == "alice@company.com")'
+```
+
+### Analyze workflow failures
+
+Find all failed workflows in the last hour:
+
+```bash
+kubectl logs -n toolhive-system -l app.kubernetes.io/instance=my-vmcp --since=1h \
+ | jq 'select(.type == "vmcp_workflow_failed")'
+```
+
+### Track backend usage
+
+Count tool calls per backend MCP server:
+
+```bash
+kubectl logs -n toolhive-system -l app.kubernetes.io/instance=my-vmcp \
+ | jq -r 'select(.type == "mcp_tool_call") | .metadata.extra.backend_name' \
+ | sort | uniq -c
+```
+
+## Integrate with log collection systems
+
+When audit logs are written to stdout (the default), they integrate with
+standard Kubernetes log collection infrastructure. Your existing log collectors
+(Fluentd, Fluent Bit, Filebeat, Splunk forwarders) can parse the JSON audit
+events and route them to your observability backend.
+
+For detailed configuration examples and best practices for setting up log
+collection with Fluentd, Filebeat, Splunk, and other systems, see the
+[Kubernetes logging guide](../guides-k8s/logging.mdx#set-up-log-collection).
+
+## Related information
+
+- [Authentication](./authentication.mdx) - Configure client and backend
+ authentication for user identity in audit logs
+- [Telemetry and metrics](./telemetry-and-metrics.mdx) - Monitor vMCP
+ performance with OpenTelemetry
+- [Observability concepts](../concepts/observability.mdx) - Overview of
+ ToolHive's observability architecture
+- [Kubernetes logging guide](../guides-k8s/logging.mdx) - Configure logging for
+ MCP servers in Kubernetes
diff --git a/versioned_docs/version-1.0/toolhive/guides-vmcp/authentication.mdx b/versioned_docs/version-1.0/toolhive/guides-vmcp/authentication.mdx
new file mode 100644
index 00000000..4a2c9036
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-vmcp/authentication.mdx
@@ -0,0 +1,164 @@
+---
+title: Authentication
+description: Configure client and backend authentication for vMCP.
+---
+
+Virtual MCP Server (vMCP) implements a two-boundary authentication model that
+separates client and backend authentication, giving you centralized control over
+access while supporting diverse backend requirements.
+
+## Two-boundary authentication model
+
+```mermaid
+flowchart LR
+ subgraph Boundary1[" "]
+ direction TB
+ Client[MCP Client]
+ B1Label["**Boundary 1** Client → vMCP"]
+ end
+
+ subgraph vMCP["Virtual MCP Server (vMCP)"]
+ Auth[Token validation]
+ Backend[Backend auth]
+ end
+
+ subgraph Boundary2[" "]
+ direction TB
+ B2Label["**Boundary 2** vMCP → Backend APIs"]
+ GitHub[GitHub API]
+ Jira[Jira API]
+ end
+
+ Client -->|"vMCP-scoped token"| Auth
+ Auth --> Backend
+ Backend -->|"Backend-scoped token"| GitHub
+ Backend -->|"Backend-scoped token"| Jira
+```
+
+**Boundary 1 (Incoming):** Clients authenticate to vMCP using OAuth 2.1
+authorization as defined in the
+[MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization).
+This is your organization's identity layer.
+
+**Boundary 2 (Outgoing):** vMCP obtains appropriate credentials for each
+backend. Each backend API receives a token or credential scoped to its
+requirements.
+
+## Incoming authentication
+
+Configure how clients authenticate to vMCP.
+
+### Anonymous (development only)
+
+No authentication required:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ incomingAuth:
+ type: anonymous
+```
+
+:::warning
+
+Do not use `anonymous` authentication in production environments. This setting
+disables all access control, allowing anyone to use the vMCP without
+credentials.
+
+:::
+
+### OIDC authentication
+
+Validate tokens from an external identity provider:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ incomingAuth:
+ type: oidc
+ oidcConfig:
+ type: inline
+ inline:
+ issuer: https://auth.example.com
+ clientId:
+ audience: vmcp
+```
+
+When using an identity provider that issues opaque OAuth tokens, add a
+`clientSecretRef` referencing a Kubernetes Secret to enable token introspection:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ incomingAuth:
+ type: oidc
+ oidcConfig:
+ type: inline
+ inline:
+ issuer: https://auth.example.com
+ clientId:
+ audience: vmcp
+ clientSecretRef:
+ name: oidc-client-secret
+ key: clientSecret
+```
+
+Create the Secret:
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+ name: oidc-client-secret
+ namespace: toolhive-system
+type: Opaque
+stringData:
+ clientSecret:
+```
+
+### Kubernetes service account tokens
+
+Authenticate using Kubernetes service account tokens for in-cluster clients:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ incomingAuth:
+ type: oidc
+ oidcConfig:
+ type: kubernetes
+ kubernetes:
+ audience: toolhive
+```
+
+This configuration uses the Kubernetes API server as the OIDC issuer and
+validates service account tokens. The defaults work for most clusters:
+
+- **issuer**: `https://kubernetes.default.svc` (auto-detected)
+- **audience**: `toolhive` (configurable)
+
+## Outgoing authentication
+
+Configure how vMCP authenticates to backend MCP servers.
+
+### Discovery mode
+
+When using discovery mode, vMCP checks each backend MCPServer's
+`externalAuthConfigRef` to determine how to authenticate. If a backend has no
+auth config, vMCP connects without authentication.
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ outgoingAuth:
+ source: discovered
+```
+
+This is the recommended approach for most deployments. Backends that don't
+require authentication work automatically, while backends with
+`externalAuthConfigRef` configured use their specified authentication method.
+
+See
+[Configure token exchange for backend authentication](../guides-k8s/token-exchange-k8s.mdx)
+for details on using service account token exchange for backend authentication.
+
+## Related information
+
+- [Authentication framework concepts](../concepts/auth-framework.mdx)
+- [VirtualMCPServer configuration](./configuration.mdx)
+- [Token exchange in Kubernetes](../guides-k8s/token-exchange-k8s.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-vmcp/backend-discovery.mdx b/versioned_docs/version-1.0/toolhive/guides-vmcp/backend-discovery.mdx
new file mode 100644
index 00000000..9a9fb586
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-vmcp/backend-discovery.mdx
@@ -0,0 +1,734 @@
+---
+title: Backend discovery modes
+description: Choose between discovered and inline backend discovery for Virtual MCP Server.
+---
+
+Virtual MCP Server (vMCP) supports two backend discovery modes, allowing you to
+optimize for either operational convenience (discovered mode with declarative
+backend management) or security (inline mode with minimal permissions).
+
+## Overview
+
+The deployment mode is configured via the `spec.outgoingAuth.source` field in
+the VirtualMCPServer resource.
+
+| Mode | Backend Discovery | RBAC Requirements | K8s API Access |
+| ------------ | ------------------------- | -------------------------------------- | ------------------ |
+| `discovered` | Runtime from K8s API | Namespace-scoped read + status updates | Yes |
+| `inline` | Inline from configuration | Minimal (status updates only) | No (except status) |
+
+:::note[Design rationale]
+
+Backend discovery is configured via `outgoingAuth.source` because the
+authentication strategy and backend source are tightly coupled:
+
+- **Discovered backends** (`source: discovered`) are found at runtime by
+ querying the Kubernetes API and can reference MCPExternalAuthConfig resources
+ for their authentication
+- **Inline backends** (`source: inline`) are defined in the configuration and
+ require their authentication to be explicitly configured in
+ `outgoingAuth.backends`
+
+This coupling ensures authentication configuration matches the backend discovery
+method, preventing misconfigurations.
+
+:::
+
+### When to use discovered mode
+
+Choose discovered mode when:
+
+- Backends change frequently and you want declarative management via Kubernetes
+ resources
+- Centralized authentication via MCPExternalAuthConfig is preferred
+- Namespace-scoped read permissions are acceptable
+
+### When to use inline mode
+
+Choose inline mode when:
+
+- Security or compliance requires minimal permissions and attack surface
+- Backend configuration is stable and changes infrequently
+- Explicit control over all backend details is required (zero-trust, air-gapped
+ environments)
+
+### Trade-offs comparison
+
+| Consideration | Discovered Mode | Inline Mode |
+| ------------------------------ | ------------------------------------------------ | ----------------------------------------- |
+| Backend management | Declarative (K8s resources) | Explicit (in configuration) |
+| Configuration changes | Add/remove resources without vMCP config changes | Update vMCP config and restart |
+| RBAC permissions | Namespace read access | Minimal (status updates only) |
+| Attack surface | Larger (K8s API access) | Smaller (no backend discovery API access) |
+| Auth management | Centralized (MCPExternalAuthConfig) | Duplicated in YAML |
+| Individual backend pod updates | Supported without vMCP changes | Requires vMCP awareness |
+
+## Discovered mode
+
+Discovered mode queries the Kubernetes API at runtime to find backend MCP
+servers. This is the default mode.
+
+### Discovered mode configuration
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: my-vmcp
+ namespace: toolhive-system
+spec:
+ config:
+ groupRef: my-group
+ incomingAuth:
+ type: anonymous
+ outgoingAuth:
+ source: discovered # Discover backends at runtime (default)
+```
+
+### How it works
+
+When vMCP starts in discovered mode:
+
+1. **Group verification**: Verifies the referenced MCPGroup exists
+2. **Workload discovery**: Queries all MCPServer and MCPRemoteProxy resources in
+ the group
+3. **Backend conversion**: For each workload:
+ - Extracts service URL and transport type
+ - Resolves authentication from `externalAuthConfigRef` if configured
+ - Adds metadata labels
+4. **Capability querying**: Calls each backend's `initialize` method to discover
+ available tools, resources, and prompts
+5. **Status updates**: Reports backend health in the VirtualMCPServer status
+
+### Discovered mode RBAC
+
+The operator automatically creates the required RBAC resources (ServiceAccount,
+Role, and RoleBinding) when you create a VirtualMCPServer resource.
+
+For reference, the vMCP service account needs read access to:
+
+- `configmaps`, `secrets`: Read OIDC configs and auth secrets
+- `mcpgroups`: Verify group exists and list members
+- `mcpservers`, `mcpremoteproxies`: Discover backend workloads
+- `mcpexternalauthconfigs`: Resolve authentication configurations
+- `mcptoolconfigs`: Resolve tool filtering and renaming
+- `virtualmcpservers/status`: Update status with discovered backends
+
+### Runtime updates
+
+When backend resources are added, modified, or removed in the group:
+
+1. The change does NOT automatically trigger vMCP to rediscover backends
+2. vMCP continues using the backend list from startup
+3. To pick up changes, restart the vMCP pod:
+
+```bash
+kubectl rollout restart deployment vmcp-my-vmcp -n toolhive-system
+```
+
+## Inline mode
+
+Inline mode uses pre-configured backends defined in the VirtualMCPServer
+resource. This eliminates the need for Kubernetes API access (except status
+updates), reducing the attack surface.
+
+### Inline mode configuration
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: my-vmcp
+ namespace: toolhive-system
+spec:
+ config:
+ groupRef: my-group
+ # highlight-start
+ backends:
+ - name: github-mcp
+ url: http://github-mcp.toolhive-system.svc.cluster.local:8080
+ transport: sse
+ - name: fetch-mcp
+ url: http://fetch-mcp.toolhive-system.svc.cluster.local:8080
+ transport: streamable-http
+ # highlight-end
+ incomingAuth:
+ type: anonymous
+ outgoingAuth:
+ # highlight-start
+ source: inline # Use inline backend configuration
+ # highlight-end
+ backends:
+ github-mcp:
+ type: external_auth_config_ref
+ externalAuthConfigRef:
+ name: github-token-config
+```
+
+:::note
+
+The `groupRef` field is required in both discovered and inline modes. In inline
+mode, the group reference is used for organizational purposes and status
+reporting, even though backends are defined inline rather than discovered from
+the group.
+
+:::
+
+:::info
+
+Backend authentication in `outgoingAuth.backends` uses references to
+[MCPExternalAuthConfig](../reference/crd-spec.md#apiv1alpha1mcpexternalauthconfig)
+resources, not inline configuration. Create the MCPExternalAuthConfig resource
+first, then reference it by name. See the
+[Authentication guide](./authentication.mdx) for complete examples.
+
+:::
+
+### Backend configuration
+
+Each backend in `spec.config.backends` requires:
+
+| Field | Description | Required |
+| ----------- | -------------------------------------------------------- | -------- |
+| `name` | Backend identifier (must match auth config keys) | Yes |
+| `url` | Backend MCP server URL (must be `http://` or `https://`) | Yes |
+| `transport` | MCP transport protocol (`sse` or `streamable-http`) | Yes |
+| `metadata` | Custom labels for the backend | No |
+
+### Inline mode RBAC
+
+Inline mode requires minimal permissions:
+
+- `virtualmcpservers/status`: Update status (only permission needed)
+
+The operator still creates RBAC resources for status updates, but the vMCP pod
+does not query the Kubernetes API for backend discovery.
+
+## Verify backend status
+
+### Check VirtualMCPServer status
+
+View discovered backends and their health:
+
+```bash
+kubectl get virtualmcpserver my-vmcp -n toolhive-system -o yaml
+```
+
+The status includes:
+
+```yaml
+status:
+ phase: Ready # Pending|Ready|Degraded|Failed
+ backendCount: 2
+ discoveredBackends:
+ - name: github-mcp
+ url: http://github-mcp.toolhive-system.svc.cluster.local:8080
+ status: ready
+ authConfigRef: github-token-config
+ authType: token_exchange
+ lastHealthCheck: '2025-02-02T15:30:00Z'
+ message: Healthy
+ circuitBreakerState: closed
+ circuitLastChanged: '2025-02-02T10:00:00Z'
+ consecutiveFailures: 0
+ - name: fetch-mcp
+ url: http://fetch-mcp.toolhive-system.svc.cluster.local:8080
+ status: ready
+ authType: unauthenticated
+ lastHealthCheck: '2025-02-02T15:30:00Z'
+ message: Healthy
+ consecutiveFailures: 0
+```
+
+### Query the status endpoint
+
+vMCP exposes an unauthenticated `/status` HTTP endpoint for operational
+monitoring:
+
+```bash
+kubectl port-forward -n toolhive-system svc/vmcp-my-vmcp 4483:4483
+curl http://localhost:4483/status
+```
+
+Response format:
+
+```json
+{
+ "backends": [
+ {
+ "name": "github-mcp",
+ "health": "healthy",
+ "transport": "sse",
+ "auth_type": "token_exchange"
+ },
+ {
+ "name": "fetch-mcp",
+ "health": "healthy",
+ "transport": "streamable-http",
+ "auth_type": "unauthenticated"
+ }
+ ],
+ "healthy": true,
+ "version": "v1.2.3",
+ "group_ref": "my-group"
+}
+```
+
+**Health states:**
+
+| State | Description | CRD Status |
+| ----------------- | ------------------------------------------------------------------------------------------------------------ | ------------- |
+| `healthy` | Backend responds to health checks successfully and quickly (under 5s) | `ready` |
+| `degraded` | Health checks succeed but response times exceed 5 seconds (slow), or backend recently recovered from failure | `degraded` |
+| `unhealthy` | Backend not responding or health checks timing out (timeout controlled by `healthCheckTimeout`, default 10s) | `unavailable` |
+| `unauthenticated` | Authentication to backend failed (internal tracking only) | `unavailable` |
+| `unknown` | Health check not yet performed (initial state) | `unknown` |
+
+:::note[Status terminology]
+
+The `/status` HTTP endpoint uses internal health values (`healthy`, `degraded`,
+`unhealthy`, `unauthenticated`, `unknown`) for debugging.
+
+The VirtualMCPServer CRD uses user-facing status values (`ready`, `degraded`,
+`unavailable`, `unknown`) as shown in the "CRD Status" column above.
+
+Note: `unauthenticated` is tracked separately for diagnostics but represents an
+authentication failure reason, not a distinct health state—it maps to
+`unavailable` in the CRD.
+
+:::
+
+:::info[Unauthenticated access]
+
+The `/status` endpoint is unauthenticated for operator consumption. It exposes
+operational metadata but does not include secrets, tokens, internal URLs, or
+request data.
+
+:::
+
+:::tip
+
+To configure health check intervals, timeouts, thresholds, and circuit breaker
+settings, see the
+[Operational configuration](./configuration.mdx#operational-configuration)
+section.
+
+:::
+
+## Switch deployment modes
+
+Switching between modes requires updating the VirtualMCPServer resource and
+restarting the vMCP pod.
+
+### From discovered to inline
+
+1. List current backends to capture their configuration:
+
+ ```bash
+ kubectl get virtualmcpserver my-vmcp -n toolhive-system \
+ -o json | jq '.status.discoveredBackends'
+ ```
+
+2. Update the VirtualMCPServer to inline mode:
+
+ ```yaml
+ spec:
+ config:
+ groupRef: my-group
+ backends:
+ - name: github-mcp
+ url: http://github-mcp.toolhive-system.svc.cluster.local:8080
+ transport: sse
+ # Add all backends from status.discoveredBackends
+ outgoingAuth:
+ source: inline
+ ```
+
+3. The operator automatically restarts the vMCP pod with the new configuration
+
+4. Optionally reduce RBAC permissions by removing read access to MCPServer and
+ MCPRemoteProxy resources (keep status update permissions)
+
+### From inline to discovered
+
+1. Ensure backend MCPServer and MCPRemoteProxy resources exist in the group
+
+2. Update the VirtualMCPServer to discovered mode:
+
+ ```yaml
+ spec:
+ config:
+ groupRef: my-group
+ # Remove backends array
+ outgoingAuth:
+ source: discovered
+ ```
+
+3. Verify RBAC permissions are configured (operator creates them automatically)
+
+4. The operator automatically restarts the vMCP pod with the new configuration
+
+5. Check status to verify backends were discovered:
+
+ ```bash
+ kubectl get virtualmcpserver my-vmcp -n toolhive-system \
+ -o json | jq '.status.discoveredBackends'
+ ```
+
+## Complete example
+
+Here's a complete example showing all required resources for discovered mode
+with authentication:
+
+```yaml
+---
+# 1. Create the MCPGroup
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPGroup
+metadata:
+ name: engineering-tools
+ namespace: toolhive-system
+spec:
+ description: Engineering team MCP servers
+
+---
+# 2. Create authentication config for GitHub backend
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPExternalAuthConfig
+metadata:
+ name: github-token-config
+ namespace: toolhive-system
+spec:
+ type: tokenExchange
+ tokenExchange:
+ tokenUrl: https://oauth.example.com/token
+ clientId: github-mcp-client
+ clientSecretRef:
+ name: github-oauth-secret
+ key: client-secret
+ audience: github-api
+
+---
+# 3. Create backend MCPServer
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: github-mcp
+ namespace: toolhive-system
+spec:
+ groupRef: engineering-tools
+ image: ghcr.io/example/github-mcp-server:v1.2.3
+ transport: sse
+ externalAuthConfigRef:
+ name: github-token-config
+
+---
+# 4. Create VirtualMCPServer (discovered mode)
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: engineering-vmcp
+ namespace: toolhive-system
+spec:
+ config:
+ groupRef: engineering-tools
+ incomingAuth:
+ type: oidc
+ oidc:
+ issuer: https://auth.company.com
+ audience: engineering-vmcp
+ outgoingAuth:
+ source: discovered # Discovers github-mcp and its auth config
+```
+
+Apply all resources:
+
+```bash
+kubectl apply -f vmcp-complete-example.yaml
+```
+
+Verify backends were discovered:
+
+```bash
+kubectl get virtualmcpserver engineering-vmcp -n toolhive-system \
+ -o json | jq '.status.discoveredBackends'
+```
+
+## Troubleshooting
+
+
+Backends not appearing in status
+
+**Symptoms:**
+
+- `status.discoveredBackends` is empty or missing backends
+- `status.backendCount` is 0 or lower than expected
+
+**Possible causes and solutions:**
+
+1. **MCPGroup not in Ready state**
+
+ ```bash
+ kubectl get mcpgroup my-group -n toolhive-system
+ ```
+
+ Wait for the group to reach Ready state before starting vMCP.
+
+2. **Backend resources not referencing the correct group**
+
+ ```bash
+ kubectl get mcpserver,mcpremoteproxy -n toolhive-system \
+ -o custom-columns=NAME:.metadata.name,GROUP:.spec.groupRef
+ ```
+
+ Ensure all backends have `spec.groupRef` matching the VirtualMCPServer's
+ `spec.config.groupRef`.
+
+3. **vMCP pod not restarted after backend changes**
+
+ Backend changes require a pod restart to be discovered:
+
+ ```bash
+ kubectl rollout restart deployment vmcp-my-vmcp -n toolhive-system
+ ```
+
+4. **RBAC permissions missing (discovered mode)**
+
+ Check the vMCP service account has required permissions:
+
+ ```bash
+ kubectl get role -n toolhive-system | grep vmcp
+ kubectl describe role vmcp-my-vmcp -n toolhive-system
+ ```
+
+ The operator should create these automatically. If missing, delete and
+ recreate the VirtualMCPServer resource.
+
+
+
+
+Backends showing as unavailable
+
+**Symptoms:**
+
+- `status.discoveredBackends[].status` is `unavailable` or `unknown`
+- `/status` endpoint shows `health: unhealthy`
+
+**Possible causes and solutions:**
+
+1. **Backend pod not running**
+
+ ```bash
+ kubectl get pods -n toolhive-system -l app.kubernetes.io/name=my-backend
+ ```
+
+ Check backend pod logs for errors:
+
+ ```bash
+ kubectl logs -n toolhive-system deployment/my-backend
+ ```
+
+2. **Backend service not accessible**
+
+ Test connectivity from vMCP pod:
+
+ ```bash
+ kubectl exec -n toolhive-system deployment/vmcp-my-vmcp -- \
+ wget -O- http://my-backend:8080/health
+ ```
+
+3. **Authentication failing**
+
+ Check vMCP logs for auth errors:
+
+ ```bash
+ kubectl logs -n toolhive-system deployment/vmcp-my-vmcp | grep ERROR
+ ```
+
+ Common auth issues:
+ - Invalid OIDC configuration in MCPExternalAuthConfig
+ - Expired or invalid client secrets
+ - Token exchange endpoint unreachable
+
+4. **Backend returning errors on initialize**
+
+ The backend may be misconfigured or failing to start properly. Check backend
+ logs and ensure it responds correctly to MCP `initialize` requests.
+
+
+
+
+RBAC permission errors
+
+**Symptoms:**
+
+- vMCP logs show `forbidden` or `unauthorized` errors
+- Backends not being discovered in discovered mode
+
+**Error examples:**
+
+```text
+Failed to list MCPServers: mcpservers.toolhive.stacklok.dev is forbidden:
+User "system:serviceaccount:toolhive-system:vmcp-my-vmcp" cannot list
+resource "mcpservers"
+```
+
+**Solutions:**
+
+1. **Verify service account and role binding exist**
+
+ ```bash
+ kubectl get serviceaccount vmcp-my-vmcp -n toolhive-system
+ kubectl get role vmcp-my-vmcp -n toolhive-system
+ kubectl get rolebinding vmcp-my-vmcp -n toolhive-system
+ ```
+
+2. **Check role permissions**
+
+ ```bash
+ kubectl describe role vmcp-my-vmcp -n toolhive-system
+ ```
+
+ Required permissions for discovered mode:
+ - `configmaps`, `secrets`: get, list, watch
+ - `mcpgroups`, `mcpservers`, `mcpremoteproxies`: get, list, watch
+ - `mcpexternalauthconfigs`, `mcptoolconfigs`: get, list, watch
+ - `virtualmcpservers/status`: update, patch
+
+3. **Recreate RBAC resources**
+
+ If RBAC resources are missing or incorrect, delete and recreate the
+ VirtualMCPServer:
+
+ ```bash
+ kubectl delete virtualmcpserver my-vmcp -n toolhive-system
+ kubectl apply -f my-vmcp.yaml
+ ```
+
+ The operator will recreate all RBAC resources automatically.
+
+
+
+
+Mode switching issues
+
+**Symptoms:**
+
+- vMCP pod fails to start after switching modes
+- Configuration validation errors
+
+**Switching from discovered to inline:**
+
+Ensure you define `spec.config.backends[]` before changing `source` to `inline`:
+
+```yaml
+spec:
+ config:
+ backends: [] # ❌ Empty array will fail validation
+```
+
+**Switching from inline to discovered:**
+
+Remove the `spec.config.backends[]` array when switching to discovered mode:
+
+```yaml
+spec:
+ config:
+ backends: [...] # ❌ Should be removed in discovered mode
+```
+
+
+
+
+Health check failures
+
+**Symptoms:**
+
+- `/status` endpoint shows backends as `degraded` or `unhealthy`
+- Intermittent backend availability
+
+**Possible causes:**
+
+1. **Backend service overloaded or slow**
+
+ Health checks timeout after 10 seconds (default `healthCheckTimeout`). If
+ backends are slow to respond, they'll be marked unhealthy even if functional.
+
+2. **Network issues between vMCP and backends**
+
+ Check network policies and service mesh configuration that might block or
+ slow connections.
+
+3. **Backend requires authentication for initialize**
+
+ Ensure `externalAuthConfigRef` is properly configured if the backend requires
+ authentication.
+
+
+
+
+Configuration validation errors
+
+**Missing groupRef:**
+
+```text
+Error: spec.config.groupRef is required
+```
+
+**Fix:** Add `spec.config.groupRef` referencing an existing MCPGroup.
+
+**Invalid backend URL in inline mode:**
+
+```text
+Error: spec.config.backends[0].url must start with http:// or https://
+```
+
+**Fix:** Ensure backend URLs use proper scheme:
+
+```yaml
+backends:
+ - name: my-backend
+ url: http://my-backend.default.svc.cluster.local:8080 # Valid
+ # url: my-backend:8080 # Invalid
+```
+
+**Missing backends array in inline mode:**
+
+```text
+Error: spec.config.backends is required when outgoingAuth.source is "inline"
+```
+
+**Fix:** Define at least one backend in `spec.config.backends` when using inline
+mode.
+
+**Invalid transport protocol:**
+
+```text
+Error: spec.config.backends[0].transport must be "sse" or "streamable-http"
+```
+
+**Fix:** Use only supported transport protocols:
+
+```yaml
+backends:
+ - name: my-backend
+ transport: sse # Valid
+ # transport: stdio # Invalid for inline backends
+```
+
+**Referenced MCPExternalAuthConfig not found:**
+
+```text
+Error: MCPExternalAuthConfig "github-token-config" not found in namespace "toolhive-system"
+```
+
+**Fix:** Create the MCPExternalAuthConfig resource before referencing it, or
+remove the auth reference.
+
+
+
+## Related information
+
+- [Configure vMCP servers](./configuration.mdx)
+- [Authentication](./authentication.mdx)
+- [VirtualMCPServer CRD specification](../reference/crd-spec.md#apiv1alpha1virtualmcpserver)
diff --git a/versioned_docs/version-1.0/toolhive/guides-vmcp/composite-tools.mdx b/versioned_docs/version-1.0/toolhive/guides-vmcp/composite-tools.mdx
new file mode 100644
index 00000000..6656d962
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-vmcp/composite-tools.mdx
@@ -0,0 +1,637 @@
+---
+title: Composite tools and workflows
+description: Create multi-step workflows that span multiple backend MCP servers.
+---
+
+Composite tools let you define multi-step workflows that execute across multiple
+backend MCP servers with parallel execution, conditional logic, approval gates,
+and error handling.
+
+## Overview
+
+A composite tool combines multiple backend tool calls into a single workflow.
+When a client calls a composite tool, vMCP orchestrates the execution across
+backend MCP servers, handling dependencies and collecting results.
+
+## Key capabilities
+
+- **Parallel execution**: Independent steps run concurrently; dependent steps
+ wait for their prerequisites
+- **Template expansion**: Dynamic arguments using step outputs
+- **Elicitation**: Request user input mid-workflow (approval gates, choices)
+- **Error handling**: Configurable abort, continue, or retry behavior
+- **Timeouts**: Workflow and per-step timeout configuration
+
+:::info
+
+Elicitation (user prompts during workflow execution) is defined in the CRD but
+has not been extensively tested. Test thoroughly in non-production environments
+first.
+
+:::
+
+## Configuration location
+
+Composite tools are defined in the VirtualMCPServer resource under
+`spec.config.compositeTools`:
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: my-vmcp
+spec:
+ incomingAuth:
+ type: anonymous
+ config:
+ groupRef: my-tools
+ # ... other configuration ...
+ compositeTools:
+ - name: my_workflow
+ description: A multi-step workflow
+ parameters:
+ # Input parameters (JSON Schema)
+ steps:
+ # Workflow steps
+```
+
+For complex, reusable workflows, you can also reference external
+`VirtualMCPCompositeToolDefinition` resources using
+`spec.config.compositeToolRefs`.
+
+## Simple example
+
+Here's a composite tool that searches arXiv for papers on a topic and reads the
+top result. This example assumes you have an MCPServer resource named `arxiv` in
+a group that your vMCP server references, and that you're using the default
+[conflict resolution strategy](./tool-aggregation.mdx#conflict-resolution-strategies)
+and prefix format (`_`):
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ compositeTools:
+ - name: research_topic
+ description: Search arXiv for papers and read the top result
+ parameters:
+ type: object
+ properties:
+ query:
+ type: string
+ description: Research topic to search for
+ required:
+ - query
+ steps:
+ # Step 1: Search arXiv for papers matching the query
+ - id: search
+ tool: arxiv_search_papers
+ arguments:
+ query: '{{.params.query}}'
+ max_results: 1
+ # Step 2: Download the paper (required before reading)
+ # Note: fromJson is needed when the MCP server returns JSON as text
+ # rather than structured content. This is common for servers that
+ # don't fully support MCP's structuredContent field.
+ - id: download
+ tool: arxiv_download_paper
+ arguments:
+ paper_id: '{{(index (fromJson .steps.search.output.text).papers 0).id}}'
+ dependsOn: [search]
+ # Step 3: Read the downloaded paper content
+ - id: read
+ tool: arxiv_read_paper
+ arguments:
+ paper_id: '{{(index (fromJson .steps.search.output.text).papers 0).id}}'
+ dependsOn: [download]
+```
+
+**What's happening:**
+
+1. **Parameters**: Define the workflow inputs (`query` for the research topic)
+2. **Step 1 (search)**: Calls `arxiv_search_papers` with the query from
+ parameters using template syntax `{{.params.query}}`
+3. **Step 2 (download)**: Waits for search (`dependsOn: [search]`), then
+ downloads the paper. The `fromJson` function parses the JSON text returned by
+ the server, and `index` accesses the first paper's ID.
+4. **Step 3 (read)**: Waits for download, then reads the paper content.
+
+When a client calls this composite tool, vMCP executes all three steps in
+sequence and returns the paper content.
+
+## Structured content vs JSON text
+
+MCP servers can return data in two ways:
+
+- **Structured content**: Data is in `structuredContent` and can be accessed
+ directly: `{{.steps.stepid.output.field}}`
+- **JSON text**: Data is returned as a JSON string in the `text` field and
+ requires parsing: `{{(fromJson .steps.stepid.output.text).field}}`
+
+The arxiv-mcp-server in this example uses JSON text, so we use `fromJson`. Check
+your backend's response format to determine which approach to use.
+
+## Use cases
+
+### Incident investigation
+
+Gather data from multiple monitoring systems in parallel:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ compositeTools:
+ - name: investigate_incident
+ description: Gather incident data from multiple sources in parallel
+ parameters:
+ type: object
+ properties:
+ incident_id:
+ type: string
+ required:
+ - incident_id
+ steps:
+ # These steps run in parallel (no dependencies)
+ - id: get_logs
+ tool: logging_search_logs
+ arguments:
+ query: 'incident_id={{.params.incident_id}}'
+ timerange: '1h'
+ - id: get_metrics
+ tool: monitoring_get_metrics
+ arguments:
+ filter: 'error_rate'
+ timerange: '1h'
+ - id: get_alerts
+ tool: pagerduty_list_alerts
+ arguments:
+ incident: '{{.params.incident_id}}'
+ # This step waits for all parallel steps to complete
+ - id: create_summary
+ tool: docs_create_document
+ arguments:
+ title: 'Incident {{.params.incident_id}} Summary'
+ content: 'Logs: {{.steps.get_logs.output.results}}'
+ dependsOn: [get_logs, get_metrics, get_alerts]
+```
+
+### Deployment with approval
+
+Human-in-the-loop workflow for production deployments:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ compositeTools:
+ - name: deploy_with_approval
+ description: Deploy to production with human approval gate
+ parameters:
+ type: object
+ properties:
+ pr_number:
+ type: string
+ environment:
+ type: string
+ default: production
+ required:
+ - pr_number
+ steps:
+ - id: get_pr_details
+ tool: github_get_pull_request
+ arguments:
+ pr: '{{.params.pr_number}}'
+ - id: approval
+ type: elicitation
+ message: 'Deploy PR #{{.params.pr_number}} to {{.params.environment}}?'
+ schema:
+ type: object
+ properties:
+ approved:
+ type: boolean
+ timeout: '10m'
+ dependsOn: [get_pr_details]
+ - id: deploy
+ tool: deploy_trigger_deployment
+ arguments:
+ ref: '{{.steps.get_pr_details.output.head_sha}}'
+ environment: '{{.params.environment}}'
+ condition: '{{.steps.approval.content.approved}}'
+ dependsOn: [approval]
+```
+
+### Cross-system data aggregation
+
+Collect and correlate data from multiple backend MCP servers:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ compositeTools:
+ - name: security_scan_report
+ description: Run security scans and create consolidated report
+ parameters:
+ type: object
+ properties:
+ package_name:
+ type: string
+ ecosystem:
+ type: string
+ repo:
+ type: string
+ required:
+ - package_name
+ - ecosystem
+ - repo
+ steps:
+ - id: vulnerability_scan
+ tool: osv_query_vulnerability
+ arguments:
+ package_name: '{{.params.package_name}}'
+ ecosystem: '{{.params.ecosystem}}'
+ - id: secret_scan
+ tool: gitleaks_scan_repo
+ arguments:
+ repository: '{{.params.repo}}'
+ - id: create_issue
+ tool: github_create_issue
+ arguments:
+ repo: '{{.params.repo}}'
+ title: 'Security Scan Results'
+ body: 'Vulnerability scan completed for {{.params.package_name}}'
+ dependsOn: [vulnerability_scan, secret_scan]
+ onError:
+ action: continue
+```
+
+## Workflow definition
+
+### Parameters
+
+Define input parameters using JSON Schema format:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ compositeTools:
+ - name:
+ parameters:
+ type: object
+ properties:
+ required_param:
+ type: string
+ optional_param:
+ type: integer
+ default: 10
+ required:
+ - required_param
+```
+
+### Steps
+
+Each step can be a tool call or an elicitation:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ compositeTools:
+ - name:
+ steps:
+ - id: step_name # Unique identifier
+ tool: backend_tool # Tool to call
+ arguments: # Arguments with template expansion
+ arg1: '{{.params.input}}'
+ dependsOn: [other_step] # Dependencies (this step waits for other_step)
+ condition: '{{.steps.check.output.approved}}' # Optional condition
+ timeout: '30s' # Step timeout
+ onError:
+ action: abort # abort | continue | retry
+```
+
+The `tool` field specifies which MCP server tool to call. This depends on your
+[conflict resolution strategy](./tool-aggregation.mdx#conflict-resolution-strategies)
+and prefix format. For example, if you have a tool named `search` in an MCP
+server named `arxiv`, and you're using the default prefix format, you would
+reference it as `arxiv_search`.
+
+:::tip
+
+When using the `condition` field, downstream steps that reference the
+conditional step's output may require
+[default step outputs](#default-step-outputs) to handle cases where the
+condition evaluates to false.
+
+:::
+
+### Elicitation (user prompts)
+
+Request input from users during workflow execution:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ compositeTools:
+ - name:
+ steps:
+ - id: approval
+ type: elicitation
+ message: 'Proceed with deployment?'
+ schema:
+ type: object
+ properties:
+ confirm: { type: boolean }
+ timeout: '5m'
+```
+
+### Error handling
+
+Configure behavior when steps fail:
+
+| Action | Description |
+| ---------- | ------------------------------- |
+| `abort` | Stop workflow immediately |
+| `continue` | Log error, proceed to next step |
+| `retry` | Retry with exponential backoff |
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ compositeTools:
+ - name:
+ steps:
+ - id:
+ # ... other step config (tool, arguments, etc.)
+ onError:
+ action: retry
+ retryCount: 3
+```
+
+:::tip
+
+When using `onError.action: continue`, downstream steps that reference this
+step's output may require [default step outputs](#default-step-outputs) to
+handle cases where the step fails.
+
+:::
+
+### Default step outputs
+
+When steps can be skipped (due to `condition` being false or
+`onError.action: continue`), downstream steps that reference their outputs need
+fallback values. Use `defaultResults` to provide these values.
+
+#### When defaultResults are required
+
+You must provide `defaultResults` when **both** of these conditions are true:
+
+1. A step can be skipped (has a `condition` field or `onError.action: continue`)
+2. A downstream step references the skipped step's output in its arguments
+
+#### Configuration
+
+Define default values that match the expected output structure:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ compositeTools:
+ - name: optional_security_check
+ description: Run security scan with optional vulnerability check
+ parameters:
+ type: object
+ properties:
+ package_name:
+ type: string
+ ecosystem:
+ type: string
+ run_vuln_scan:
+ type: boolean
+ default: false
+ required:
+ - package_name
+ - ecosystem
+ steps:
+ # Step 1: Optional vulnerability scan
+ - id: vuln_scan
+ tool: osv_query_vulnerability
+ arguments:
+ package_name: '{{.params.package_name}}'
+ ecosystem: '{{.params.ecosystem}}'
+ condition: '{{.params.run_vuln_scan}}'
+ # highlight-start
+ defaultResults:
+ vulns: []
+ # highlight-end
+ # Step 2: Create report using scan results
+ - id: create_report
+ tool: docs_create_document
+ arguments:
+ title: 'Security Report'
+ # This references vuln_scan output, so defaultResults are needed
+ body: 'Found {{len .steps.vuln_scan.output.vulns}} vulnerabilities'
+ dependsOn: [vuln_scan]
+```
+
+#### Continue on error example
+
+When using `onError.action: continue`, provide defaults for potential failures:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ compositeTools:
+ - name: multi_source_data
+ description: Gather data from multiple sources, continue on failures
+ steps:
+ # Step 1: Fetch from primary source (may fail)
+ - id: fetch_primary
+ tool: api_get_data
+ arguments:
+ source: 'primary'
+ onError:
+ action: continue
+ # highlight-start
+ defaultResults:
+ status: 'unavailable'
+ data: null
+ # highlight-end
+ # Step 2: Aggregate results
+ - id: aggregate
+ tool: processing_combine_data
+ arguments:
+ # Uses fetch_primary output even if it failed
+ primary: '{{.steps.fetch_primary.output.data}}'
+ dependsOn: [fetch_primary]
+```
+
+#### Validation
+
+vMCP validates `defaultResults` at configuration time:
+
+- **Missing defaults**: If a step can be skipped and downstream steps reference
+ its output, but `defaultResults` is not provided, vMCP returns a validation
+ error
+- **Structure**: The `defaultResults` value can be any valid JSON type (object,
+ array, string, number, boolean, null)
+- **No type checking**: vMCP does not verify that `defaultResults` match the
+ actual output structure—you must ensure they match the format your downstream
+ steps expect
+
+#### Example validation error
+
+```yaml
+# This will fail validation
+steps:
+ - id: conditional_step
+ tool: backend_fetch
+ condition: '{{.params.enabled}}'
+ # Missing defaultResults!
+ - id: use_result
+ tool: backend_process
+ arguments:
+ # References conditional_step output
+ data: '{{.steps.conditional_step.output.value}}'
+ dependsOn: [conditional_step]
+```
+
+**Error message:**
+
+```text
+step 'conditional_step' can be skipped but is referenced by downstream steps
+without defaultResults defined
+```
+
+## Template syntax
+
+Access workflow context in arguments:
+
+| Template | Description |
+| --------------------------- | ------------------------------------------ |
+| `{{.params.name}}` | Input parameter |
+| `{{.steps.id.output}}` | Step output (map) |
+| `{{.steps.id.output.text}}` | Text content from step output |
+| `{{.steps.id.content}}` | Elicitation response content |
+| `{{.steps.id.action}}` | Elicitation action (accept/decline/cancel) |
+
+### Template functions
+
+The following functions are available for use in templates:
+
+| Function | Description | Example |
+| ---------- | -------------------------------- | -------------------------------------------- |
+| `fromJson` | Parse a JSON string into a value | `{{(fromJson .steps.s1.output.text).field}}` |
+| `json` | Encode a value as a JSON string | `{{json .steps.s1.output}}` |
+| `quote` | Quote a string value | `{{quote .params.name}}` |
+| `index` | Access array elements by index | `{{index .steps.s1.output.items 0}}` |
+
+All
+[Go template built-in functions](https://pkg.go.dev/text/template#hdr-Functions)
+are also supported (e.g., `len`, `eq`, `and`, `or`, `printf`).
+
+### Accessing step outputs
+
+When an MCP server returns structured content, you can access output fields
+directly:
+
+```yaml
+# Direct access when server supports structuredContent
+result: '{{.steps.fetch.output.data}}'
+items: '{{index .steps.search.output.results 0}}'
+```
+
+This is the simplest approach and works when the backend MCP server populates
+the `structuredContent` field in its response.
+
+### Working with JSON text responses
+
+Some MCP servers return structured data as JSON text rather than using MCP's
+`structuredContent` field. When this happens, use `fromJson` to parse it:
+
+```yaml
+# Parse JSON text and access a nested field
+paper_id: '{{(index (fromJson .steps.search.output.text).papers 0).id}}'
+```
+
+This pattern:
+
+1. Gets the text output: `.steps.search.output.text`
+2. Parses it as JSON: `fromJson ...`
+3. Accesses the `papers` array and gets the first element: `index ... 0`
+4. Gets the `id` field: `.id`
+
+**How to tell which approach to use:** Call the backend tool directly and
+inspect the response. If `structuredContent` contains your data fields, use
+direct access. If `structuredContent` only has a `text` field containing JSON,
+use `fromJson`.
+
+## Complete example
+
+A VirtualMCPServer with an inline composite tool using the
+[arxiv-mcp-server](https://github.com/blazickjp/arxiv-mcp-server):
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: research-vmcp
+ namespace: toolhive-system
+spec:
+ incomingAuth:
+ type: anonymous
+ config:
+ groupRef: research-tools
+ aggregation:
+ conflictResolution: prefix
+ conflictResolutionConfig:
+ prefixFormat: '{workload}_'
+ compositeTools:
+ - name: research_topic
+ description: Search arXiv for papers and read the top result
+ parameters:
+ type: object
+ properties:
+ query:
+ type: string
+ description: Research topic to search for
+ required:
+ - query
+ steps:
+ - id: search
+ tool: arxiv_search_papers
+ arguments:
+ query: '{{.params.query}}'
+ max_results: 1
+ - id: download
+ tool: arxiv_download_paper
+ arguments:
+ paper_id: '{{(index (fromJson .steps.search.output.text).papers 0).id}}'
+ dependsOn: [search]
+ - id: read
+ tool: arxiv_read_paper
+ arguments:
+ paper_id: '{{(index (fromJson .steps.search.output.text).papers 0).id}}'
+ dependsOn: [download]
+ timeout: '5m'
+```
+
+> Note: The example above assumes you have:
+>
+> - An `MCPGroup` named `research-tools`.
+> - An `arxiv-mcp-server` deployed as an `MCPServer` or `MCPRemoteProxy`
+> resource that references the `research-tools` group.
+>
+> For a complete example of configuring MCP groups and backend servers, see the
+> quickstart and tool aggregation guides. For complex, reusable workflows,
+> create `VirtualMCPCompositeToolDefinition` resources and reference them with
+> `spec.config.compositeToolRefs`:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ groupRef: my-tools
+ compositeToolRefs:
+ - name: my-reusable-workflow
+ - name: another-workflow
+```
+
+## Related information
+
+- [Configure vMCP servers](./configuration.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-vmcp/configuration.mdx b/versioned_docs/version-1.0/toolhive/guides-vmcp/configuration.mdx
new file mode 100644
index 00000000..3c2f6505
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-vmcp/configuration.mdx
@@ -0,0 +1,300 @@
+---
+title: Configure vMCP servers
+description: How to configure a Virtual MCP Server for common scenarios.
+---
+
+This guide covers common configuration patterns for vMCP using the
+VirtualMCPServer resource. For a complete field reference, see the
+[VirtualMCPServer CRD specification](../reference/crd-spec.md#apiv1alpha1virtualmcpserver).
+
+## Create an MCPGroup
+
+Before creating a VirtualMCPServer, you need an
+[MCPGroup](../reference/crd-spec.md#apiv1alpha1mcpgroup) to organize the backend
+MCP servers. An MCPGroup is a logical container that groups related MCPServer
+and MCPRemoteProxy resources together.
+
+Create a basic MCPGroup:
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPGroup
+metadata:
+ name: my-group
+ namespace: toolhive-system
+spec:
+ description: Group of backend MCP servers for vMCP aggregation
+```
+
+The MCPGroup must exist in the same namespace as your VirtualMCPServer and be in
+a Ready state before the VirtualMCPServer can stafrt. Backend resources
+reference this group using the `groupRef` field in their spec.
+
+## Add backends to a group
+
+vMCP supports two types of backends that can be added to an MCPGroup:
+
+### MCPServer (local containers)
+
+MCPServer resources run container-based MCP servers in your cluster:
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: fetch
+ namespace: toolhive-system
+spec:
+ groupRef: my-group # Reference to the MCPGroup
+ image: ghcr.io/stackloklabs/gofetch/server
+ transport: streamable-http
+```
+
+### MCPRemoteProxy (remote servers)
+
+MCPRemoteProxy resources proxy external remote MCP servers. They can be added to
+an MCPGroup for discovery by vMCP:
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPRemoteProxy
+metadata:
+ name: context7-proxy
+ namespace: toolhive-system
+spec:
+ groupRef: my-group # Reference to the MCPGroup
+ remoteURL: https://mcp.context7.com/mcp
+ transport: streamable-http
+ port: 8080
+
+ # Validate incoming requests
+ oidcConfig:
+ type: inline
+ inline:
+ issuer: https://auth.company.com
+ audience: context7-proxy
+```
+
+:::caution[Current limitation]
+
+vMCP can discover MCPRemoteProxy backends in a group, but authentication between
+vMCP and MCPRemoteProxy is not yet fully implemented. This limitation will be
+addressed in a future release. See
+[Proxy remote MCP servers](../guides-k8s/remote-mcp-proxy.mdx#use-with-virtual-mcp-server)
+for details.
+
+:::
+
+For complete MCPRemoteProxy configuration options, see
+[Proxy remote MCP servers](../guides-k8s/remote-mcp-proxy.mdx).
+
+## Create a VirtualMCPServer
+
+At minimum, a VirtualMCPServer requires a reference to an MCPGroup (via
+`config.groupRef`) and an authentication type:
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: my-vmcp
+ namespace: toolhive-system
+spec:
+ config:
+ groupRef: my-group
+ incomingAuth:
+ type: anonymous # Disables authentication; do not use in production
+```
+
+The MCPGroup must exist in the same namespace and be in a Ready state before the
+VirtualMCPServer can start. By default, vMCP automatically discovers and
+aggregates all MCPServer and MCPRemoteProxy resources in the referenced group.
+You can also define backends explicitly in the configuration (inline mode). See
+[Backend discovery modes](./backend-discovery.mdx) for details on both
+approaches.
+
+## Configure authentication
+
+vMCP uses a two-boundary authentication model: client-to-vMCP (incoming) and
+vMCP-to-backends (outgoing). See the
+[Authentication guide](./authentication.mdx) for complete configuration options
+including anonymous, OIDC, and Kubernetes service account authentication.
+
+## Expose the service
+
+Choose how to expose the vMCP endpoint. The Service resource is created
+automatically on port 4483.
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ serviceType: ClusterIP # Default: cluster-internal (can be exposed via Ingress/Gateway)
+ # serviceType: LoadBalancer # Direct external access via cloud load balancer
+ # serviceType: NodePort # Direct external access via node ports
+```
+
+**Service types:**
+
+- **ClusterIP** (default): For production, use with Ingress or Gateway API for
+ controlled external access with TLS termination
+- **LoadBalancer**: Direct external access via cloud provider's load balancer
+ (simpler but less control)
+- **NodePort**: Direct access via node ports (typically for development/testing)
+
+The Service is named `vmcp-`, where `` is from `metadata.name` in
+the VirtualMCPServer resource.
+
+## Monitor status
+
+Check the VirtualMCPServer status to verify it's ready:
+
+```bash
+kubectl get virtualmcpserver my-vmcp
+```
+
+Key status fields:
+
+| Field | Description |
+| -------------------- | ------------------------------------------------ |
+| `phase` | Current state (Pending, Ready, Degraded, Failed) |
+| `url` | Service URL for client connections |
+| `backendCount` | Number of discovered backend MCP servers |
+| `discoveredBackends` | Details about each backend and its auth type |
+
+## Operational configuration
+
+### Health checks
+
+vMCP continuously monitors backend health to detect failures and route requests
+appropriately. Health check behavior is configurable via the VirtualMCPServer
+resource.
+
+#### Health check configuration
+
+Configure health monitoring in `spec.config.operational.failureHandling`:
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: my-vmcp
+ namespace: toolhive-system
+spec:
+ config:
+ groupRef: my-group
+ operational:
+ failureHandling:
+ # Health check interval (how often to check each backend)
+ # Default: 30s
+ healthCheckInterval: 30s
+
+ # Health check timeout (max duration for a single check)
+ # Should be less than healthCheckInterval
+ # Default: 10s
+ healthCheckTimeout: 10s
+
+ # Number of consecutive failures before marking unhealthy
+ # Default: 3
+ unhealthyThreshold: 3
+
+ # How often to report status updates to Kubernetes
+ # Default: 30s
+ statusReportingInterval: 30s
+ incomingAuth:
+ type: anonymous
+```
+
+#### Circuit breaker configuration
+
+Circuit breakers prevent cascading failures by temporarily stopping requests to
+consistently failing backends. For detailed configuration, behavior, and
+troubleshooting, see [Failure handling](./failure-handling.mdx).
+
+To enable circuit breaker:
+
+```yaml
+spec:
+ config:
+ operational:
+ failureHandling:
+ circuitBreaker:
+ enabled: true
+ failureThreshold: 5 # Number of failures before opening circuit
+ timeout: 60s # How long to wait before attempting recovery
+```
+
+### Timeouts
+
+Configure timeouts for backend requests:
+
+```yaml
+spec:
+ config:
+ operational:
+ timeouts:
+ # Default timeout for all backend requests (default: 30s)
+ default: 30s
+
+ # Per-workload timeout overrides
+ perWorkload:
+ slow-backend: 60s
+ fast-backend: 10s
+```
+
+:::note
+
+Health check timeouts are configured separately via
+`failureHandling.healthCheckTimeout` (default: 10s), not via the `timeouts`
+section.
+
+:::
+
+#### Remote workload health checks
+
+By default, health checks are:
+
+- **Always enabled** for local backends (MCPServer)
+- **Disabled by default** for remote backends (MCPRemoteProxy)
+
+To enable health checks for remote workloads, set the
+`TOOLHIVE_REMOTE_HEALTHCHECKS` environment variable in the vMCP pod:
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: my-vmcp
+spec:
+ podTemplateSpec:
+ spec:
+ containers:
+ - name: vmcp
+ env:
+ - name: TOOLHIVE_REMOTE_HEALTHCHECKS
+ value: 'true'
+```
+
+For detailed backend health monitoring, see
+[Verify backend status](./backend-discovery.mdx#verify-backend-status) in the
+Backend discovery guide.
+
+## Next steps
+
+- [Optimize tool discovery](./optimizer.mdx) by adding an `embeddingServerRef`
+ to reduce token usage across many backends
+- Review [scaling and performance guidance](./scaling-and-performance.mdx) for
+ resource planning
+- Discover your deployed MCP servers automatically using the
+ [Kubernetes registry](../guides-registry/configuration.mdx#kubernetes-registry)
+ feature in the ToolHive Registry Server
+
+## Related information
+
+- [VirtualMCPServer CRD specification](../reference/crd-spec.md#apiv1alpha1virtualmcpserver)
+- [Introduction to vMCP](./intro.mdx)
+- [Scaling and Performance](./scaling-and-performance.mdx)
+- [Backend discovery modes](./backend-discovery.mdx)
+- [Tool aggregation](./tool-aggregation.mdx)
+- [Optimize tool discovery](./optimizer.mdx)
+- [Composite tools](./composite-tools.mdx)
+- [Authentication](./authentication.mdx)
+- [Proxy remote MCP servers](../guides-k8s/remote-mcp-proxy.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-vmcp/failure-handling.mdx b/versioned_docs/version-1.0/toolhive/guides-vmcp/failure-handling.mdx
new file mode 100644
index 00000000..df6b533e
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-vmcp/failure-handling.mdx
@@ -0,0 +1,417 @@
+---
+title: Failure handling
+description:
+ Configure circuit breaker and partial failure modes to handle backend failures
+ gracefully.
+---
+
+Virtual MCP Server (vMCP) implements failure handling patterns to prevent
+cascading failures and provide graceful degradation when backends become
+unavailable. This guide covers circuit breaker configuration and partial failure
+modes.
+
+:::tip
+
+For backend health status monitoring and the `/status` endpoint, see
+[Backend discovery modes](./backend-discovery.mdx#verify-backend-status).
+
+:::
+
+## Overview
+
+When backends fail due to crashes, network issues, or rate limiting, vMCP
+provides circuit breaker and partial failure modes to handle failures
+gracefully:
+
+- **Circuit breaker**: Prevents cascading failures by immediately rejecting
+ requests to failing backends instead of waiting for timeouts
+- **Partial failure modes**: Choose whether to fail entire requests or continue
+ with available backends
+- **Automatic recovery**: Backends are automatically restored when they recover
+
+:::tip
+
+Enable circuit breaker for production environments where backends may experience
+temporary failures (deployments, restarts, rate limits). For highly stable
+backends, health checks alone may be sufficient.
+
+:::
+
+## Circuit breaker
+
+The circuit breaker tracks backend failures and transitions through three
+states:
+
+1. **Closed** (normal operation): Requests pass through to the backend. Failures
+ are counted.
+2. **Open** (failing state): After exceeding the failure threshold, the circuit
+ opens. Requests fail immediately without contacting the backend.
+3. **Half-open** (recovery testing): After a timeout period, the circuit allows
+ exactly one test request through. While this request is in progress, all
+ other requests are rejected (circuit remains half-open). If the test
+ succeeds, the circuit closes immediately and normal operation resumes. If it
+ fails, the circuit reopens for another timeout period.
+
+```mermaid
+stateDiagram-v2
+ [*] --> Closed
+ Closed --> Open: Failure threshold exceeded
+ Open --> HalfOpen: Timeout elapsed
+ HalfOpen --> Closed: Request succeeds
+ HalfOpen --> Open: Request fails
+ Closed --> Closed: Success (reset count)
+```
+
+### Enable circuit breaker
+
+Configure circuit breaker in the VirtualMCPServer resource:
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: my-vmcp
+ namespace: toolhive-system
+spec:
+ config:
+ groupRef: my-group
+ # highlight-start
+ operational:
+ failureHandling:
+ healthCheckInterval: 30s
+ unhealthyThreshold: 3
+ circuitBreaker:
+ enabled: true
+ failureThreshold: 5
+ timeout: 60s
+ # highlight-end
+ incomingAuth:
+ type: anonymous
+```
+
+### Configuration options
+
+| Field | Description | Default |
+| ------------------------- | ----------------------------------------------------- | ------- |
+| `healthCheckInterval` | Time between health checks for each backend | `30s` |
+| `unhealthyThreshold` | Consecutive failures before marking backend unhealthy | `3` |
+| `healthCheckTimeout` | Maximum duration for a single health check | `10s` |
+| `statusReportingInterval` | Interval for reporting status to Kubernetes | `30s` |
+| **Circuit breaker** | | |
+| `enabled` | Enable circuit breaker | `false` |
+| `failureThreshold` | Number of failures before opening the circuit | `5` |
+| `timeout` | Duration to wait before testing recovery | `60s` |
+
+:::note
+
+Circuit breaker is disabled by default. Health checks run independently of the
+circuit breaker and mark backends as healthy/unhealthy based on
+`unhealthyThreshold`.
+
+:::
+
+:::note[Two failure thresholds]
+
+vMCP uses two thresholds:
+
+- **`unhealthyThreshold`** (default: 3): Consecutive health check failures
+ before marking backend unhealthy
+- **`failureThreshold`** (default: 5): Consecutive request failures before
+ opening circuit breaker
+
+Health checks detect failures during idle periods (max detection time:
+`healthCheckInterval × unhealthyThreshold`). Circuit breaker provides fast
+failure protection during active traffic.
+
+:::
+
+## Partial failure modes
+
+Configure how vMCP behaves when some backends are unavailable:
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: my-vmcp
+ namespace: toolhive-system
+spec:
+ config:
+ groupRef: my-group
+ operational:
+ failureHandling:
+ # highlight-next-line
+ partialFailureMode: best_effort
+ incomingAuth:
+ type: anonymous
+```
+
+### Modes
+
+- **`fail`** (default): Entire request fails if any required backend is
+ unavailable. Use when all backends must be operational.
+- **`best_effort`**: Return results from healthy backends even if some fail.
+ Tools from failed backends are omitted from responses. Use for graceful
+ degradation.
+
+### Example: Best effort mode
+
+With `partialFailureMode: best_effort`, if the GitHub backend is down but Fetch
+is healthy, the `tools/list` response only includes tools from healthy backends:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "result": {
+ "tools": [{ "name": "fetch_url", "description": "Fetch URL content" }]
+ },
+ "id": 1
+}
+```
+
+GitHub tools are omitted from the response because the circuit breaker is open.
+The client doesn't see unavailable backend tools, preventing timeout errors when
+attempting to call them.
+
+## Monitor circuit breaker status
+
+Check backend health and circuit state:
+
+```bash
+kubectl get virtualmcpserver my-vmcp -n toolhive-system -o yaml
+```
+
+Status includes health information and circuit breaker state:
+
+```yaml
+status:
+ phase: Degraded # Ready|Degraded if some backends unhealthy
+ backendCount: 2 # Only counts ready backends (fetch-mcp, jira-mcp)
+ discoveredBackends:
+ - name: github-mcp
+ status: unavailable
+ lastHealthCheck: '2025-02-09T10:29:45Z'
+ message: 'connection timeout'
+ circuitBreakerState: open # Circuit breaker state: closed|open|half-open
+ circuitLastChanged: '2025-02-09T10:28:30Z' # When circuit opened
+ consecutiveFailures: 8 # Current failure count
+ - name: fetch-mcp
+ status: ready
+ lastHealthCheck: '2025-02-09T10:30:05Z'
+ circuitBreakerState: closed
+ consecutiveFailures: 0
+ - name: jira-mcp
+ status: ready
+ lastHealthCheck: '2025-02-09T10:30:03Z'
+ circuitBreakerState: half-open # Testing recovery
+ circuitLastChanged: '2025-02-09T10:30:00Z'
+ consecutiveFailures: 2 # Reduced after partial recovery
+```
+
+**Status fields:**
+
+- `status`: Backend health (ready, degraded, unavailable, unknown)
+- `circuitBreakerState`: Circuit state (closed, open, half-open) - empty if
+ circuit breaker disabled
+- `circuitLastChanged`: When the circuit breaker state last changed
+- `consecutiveFailures`: Count of consecutive health check failures
+- `message`: Additional information about backend status or errors
+
+The `/status` HTTP endpoint provides a simplified view:
+
+```bash
+curl http://localhost:4483/status
+```
+
+```json
+{
+ "backends": [
+ {
+ "name": "github-mcp",
+ "health": "unhealthy",
+ "transport": "sse",
+ "auth_type": "token_exchange"
+ },
+ {
+ "name": "fetch-mcp",
+ "health": "healthy",
+ "transport": "streamable-http",
+ "auth_type": "unauthenticated"
+ }
+ ],
+ "healthy": false,
+ "version": "v1.2.3",
+ "group_ref": "my-group"
+}
+```
+
+:::info
+
+The `/status` endpoint provides basic health information but does not include
+circuit breaker state. For detailed circuit breaker information
+(`circuitBreakerState`, `consecutiveFailures`, `circuitLastChanged`), use the
+Kubernetes status shown above. See
+[Backend discovery modes](./backend-discovery.mdx#verify-backend-status) for
+more details on the `/status` endpoint.
+
+:::
+
+## Example configurations
+
+### Production with aggressive failure detection
+
+Detect failures quickly and fail fast:
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: production-vmcp
+ namespace: toolhive-system
+spec:
+ config:
+ groupRef: production-backends
+ operational:
+ failureHandling:
+ # Check every 10 seconds
+ healthCheckInterval: 10s
+ # Mark unhealthy after 2 failures (20 seconds)
+ unhealthyThreshold: 2
+ healthCheckTimeout: 5s
+ # Open circuit after 3 failures
+ circuitBreaker:
+ enabled: true
+ failureThreshold: 3
+ timeout: 30s
+ # Fail requests if any backend down
+ partialFailureMode: fail
+ incomingAuth:
+ type: oidc
+ oidc:
+ issuerRef:
+ name: my-issuer
+```
+
+### Development with best effort
+
+Continue with available backends:
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: dev-vmcp
+ namespace: toolhive-system
+spec:
+ config:
+ groupRef: dev-backends
+ operational:
+ failureHandling:
+ healthCheckInterval: 30s
+ unhealthyThreshold: 3
+ circuitBreaker:
+ enabled: true
+ failureThreshold: 5
+ timeout: 60s
+ # Continue with healthy backends
+ partialFailureMode: best_effort
+ incomingAuth:
+ type: anonymous
+```
+
+## Troubleshooting
+
+
+Circuit breaker opens too frequently
+
+If the circuit breaker is too sensitive:
+
+**Increase failure threshold:**
+
+```yaml
+operational:
+ failureHandling:
+ circuitBreaker:
+ failureThreshold: 10 # Require more failures before opening
+```
+
+**Increase timeout:**
+
+```yaml
+operational:
+ failureHandling:
+ circuitBreaker:
+ timeout: 120s # Give backends more time to recover
+```
+
+
+
+
+Backends not recovering automatically
+
+If backends stay unhealthy after recovering:
+
+1. **Test backend connectivity**
+
+ Verify the backend MCP server is accessible from vMCP:
+
+ ```bash
+ kubectl exec -n toolhive-system deployment/vmcp-my-vmcp -- \
+ curl -v http://my-backend:8080/mcp
+ ```
+
+ The backend should respond with MCP protocol headers.
+
+2. **Increase circuit breaker timeout**
+
+ ```yaml
+ operational:
+ failureHandling:
+ circuitBreaker:
+ timeout: 90s # Allow more time for full recovery
+ ```
+
+3. **Review vMCP logs**
+
+ ```bash
+ kubectl logs -n toolhive-system deployment/vmcp-my-vmcp
+ ```
+
+ Look for circuit breaker state transitions:
+
+ ```
+ WARN Circuit breaker for backend github-mcp OPENED (threshold exceeded)
+ INFO Circuit breaker for backend github-mcp CLOSED (recovery successful)
+ ```
+
+
+
+
+Healthy backends marked unhealthy
+
+If backends are incorrectly marked unhealthy:
+
+**Increase health check timeout:**
+
+```yaml
+operational:
+ failureHandling:
+ healthCheckTimeout: 20s # Allow slower responses
+```
+
+**Increase unhealthy threshold:**
+
+```yaml
+operational:
+ failureHandling:
+ unhealthyThreshold: 5 # Allow more failures before marking unhealthy
+```
+
+
+
+## Related information
+
+- [Backend discovery modes](./backend-discovery.mdx) - Backend health status and
+ `/status` endpoint
+- [Configuration guide](./configuration.mdx)
+- [VirtualMCPServer CRD specification](../reference/crd-spec.md#apiv1alpha1virtualmcpserver)
diff --git a/versioned_docs/version-1.0/toolhive/guides-vmcp/index.mdx b/versioned_docs/version-1.0/toolhive/guides-vmcp/index.mdx
new file mode 100644
index 00000000..b6f898e7
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-vmcp/index.mdx
@@ -0,0 +1,26 @@
+---
+title: Virtual MCP Server
+description: Aggregate multiple MCP servers into a single unified endpoint.
+---
+
+Virtual MCP Server (vMCP) is a feature of the ToolHive Kubernetes Operator that
+aggregates multiple backend MCP servers into a single endpoint, enabling unified
+tool access, centralized authentication, and multi-step workflows.
+
+## When to use vMCP
+
+- You manage multiple MCP servers that should appear as one
+- You need to centralize authentication across backends
+- You want to create reusable workflows spanning multiple systems
+
+## Get started
+
+- [Understanding Virtual MCP Server](../concepts/vmcp.mdx) - Learn what vMCP
+ does and when to use it
+- [Quickstart: Virtual MCP Server](./quickstart.mdx) - Deploy your first vMCP
+
+## Contents
+
+import DocCardList from '@theme/DocCardList';
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-vmcp/intro.mdx b/versioned_docs/version-1.0/toolhive/guides-vmcp/intro.mdx
new file mode 100644
index 00000000..87c4f630
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-vmcp/intro.mdx
@@ -0,0 +1,113 @@
+---
+title: Introduction to vMCP
+description: Understand what Virtual MCP Server (vMCP) does and when to use it.
+---
+
+## Overview
+
+Virtual MCP Server (vMCP) is a feature of the ToolHive Kubernetes Operator that
+acts as an aggregation proxy, consolidating multiple backend MCP servers into a
+single unified interface. Instead of configuring clients to connect to each MCP
+server individually, you connect once to vMCP and access all backend tools
+through a single endpoint.
+
+vMCP supports two types of backends:
+
+- **MCPServer**: Container-based MCP servers running in your cluster
+- **MCPRemoteProxy**: Proxies to external remote MCP servers (such as Notion,
+ analytics platforms, or other SaaS MCP endpoints)
+
+:::note[MCPRemoteProxy support]
+
+vMCP can discover MCPRemoteProxy backends in a group, but authentication between
+vMCP and MCPRemoteProxy is not yet fully implemented. MCPServer backends work
+fully with vMCP. See
+[Proxy remote MCP servers](../guides-k8s/remote-mcp-proxy.mdx#use-with-virtual-mcp-server)
+for details on the current limitations.
+
+:::
+
+## Core capabilities
+
+- **Multi-server aggregation**: Connect to one endpoint instead of many,
+ including both local container-based servers and remote MCP proxies
+- **Tool conflict resolution**: Automatic namespacing when backend MCP servers
+ have overlapping tool names
+- **Centralized authentication**: Single sign-on with per-backend token exchange
+- **Composite workflows**: Multi-step operations across backend MCP servers with
+ parallel execution, approval gates, and error handling
+- **Tool optimization**: Replace all individual tool definitions with two
+ lightweight primitives (`find_tool` and `call_tool`) to reduce token usage and
+ improve tool selection. See [Optimize tool discovery](./optimizer.mdx) and the
+ underlying [concepts](../concepts/tool-optimization.mdx)
+
+## When to use vMCP
+
+### Good fit
+
+- You manage 5+ MCP servers (local or remote)
+- You need cross-system workflows requiring coordination
+- You have centralized authentication and authorization requirements
+- You need reusable workflow definitions
+- You want to aggregate external SaaS MCP servers with internal tools
+- You want to reduce token usage and improve tool selection accuracy across many
+ backends with the [optimizer](./optimizer.mdx)
+
+### Not needed
+
+- You use a single MCP server
+- You have simple, one-step operations
+- You have no orchestration requirements
+
+## Architecture overview
+
+```mermaid
+flowchart TB
+ Client[MCP Client] --> vMCP[Virtual MCP Server]
+
+ subgraph LocalBackends[Local MCP Servers]
+ GitHub[GitHub MCP]
+ Fetch[Fetch MCP]
+ end
+
+ subgraph RemoteBackends[Remote MCP Proxies]
+ Notion[Notion MCP]
+ Neon[Neon MCP]
+ end
+
+ vMCP --> LocalBackends
+ vMCP --> RemoteBackends
+
+ RemoteBackends -->|proxied| ExternalServices[External Services]
+```
+
+## How it works
+
+1. You define an MCPGroup (a resource that organizes related MCP servers)
+2. Backend resources reference the group using `groupRef`:
+ - **MCPServer** resources for container-based MCP servers
+ - **MCPRemoteProxy** resources for external remote MCP servers
+3. You create a VirtualMCPServer that references the group
+4. The operator discovers all MCPServer and MCPRemoteProxy backends in the group
+ and aggregates their capabilities
+5. Clients connect to the VirtualMCPServer endpoint and see a unified view of
+ all tools from both local and remote backends
+
+## Optimize tool discovery
+
+As the number of aggregated backends grows, clients receive a large number of
+tool definitions that consume tokens and can degrade tool selection accuracy.
+The vMCP optimizer addresses this by replacing all individual tool definitions
+with two lightweight primitives (`find_tool` and `call_tool`) and using hybrid
+semantic and keyword search to surface only the most relevant tools per request.
+To enable the optimizer, add an `embeddingServerRef` to your VirtualMCPServer
+resource. See [Optimize tool discovery](./optimizer.mdx) for the full setup
+guide.
+
+## Related information
+
+- [Quickstart: Virtual MCP Server](./quickstart.mdx)
+- [Understanding Virtual MCP Server](../concepts/vmcp.mdx)
+- [Optimize tool discovery](./optimizer.mdx)
+- [Scaling and Performance](./scaling-and-performance.mdx)
+- [Proxy remote MCP servers](../guides-k8s/remote-mcp-proxy.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/guides-vmcp/optimizer.mdx b/versioned_docs/version-1.0/toolhive/guides-vmcp/optimizer.mdx
new file mode 100644
index 00000000..fe159aaf
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-vmcp/optimizer.mdx
@@ -0,0 +1,317 @@
+---
+title: Optimize tool discovery
+description:
+ Enable the optimizer in vMCP to reduce token usage and improve tool selection
+ across aggregated backends.
+---
+
+When Virtual MCP Server (vMCP) aggregates many backend MCP servers, the total
+number of tools exposed to clients can grow quickly. The optimizer addresses
+this by filtering tools per request, reducing token usage and improving tool
+selection accuracy.
+
+For the desktop/CLI approach using the MCP Optimizer container, see the
+[MCP Optimizer tutorial](../tutorials/mcp-optimizer.mdx). This guide covers the
+Kubernetes operator approach using VirtualMCPServer and EmbeddingServer CRDs.
+
+## Benefits
+
+- **Reduced token usage**: Only relevant tools are included in context, not the
+ entire toolset
+- **Improved tool selection**: The right tools surface for each query. With
+ fewer tools to reason over, agents are more likely to choose correctly
+
+## How it works
+
+1. You send a prompt that requires tool assistance
+2. The AI calls `find_tool` with keywords extracted from the prompt
+3. vMCP performs hybrid semantic and keyword search across all backend tools
+4. Only the most relevant tools (up to 8 by default) are returned
+5. The AI calls `call_tool` to execute the selected tool, and vMCP routes the
+ request to the appropriate backend
+
+```mermaid
+flowchart TB
+ subgraph vmcpGroup["VirtualMCPServer"]
+ direction TB
+ vmcp["vMCP (optimizer enabled)"]
+ end
+ subgraph embedding["EmbeddingServer"]
+ direction TB
+ tei["Text Embeddings Inference"]
+ end
+ subgraph backends["MCPGroup backends"]
+ direction TB
+ mcp1["MCP server"]
+ mcp2["MCP server"]
+ mcp3["MCP server"]
+ end
+
+ client(["Client"]) <-- "find_tool / call_tool" --> vmcpGroup
+ vmcp <-. "semantic search" .-> embedding
+ vmcp <-. "discovers / routes" .-> backends
+```
+
+:::info[How search works internally]
+
+The optimizer uses an internal SQLite database for both keyword search (using
+full-text search) and storing semantic vectors. Keyword search runs locally
+against this database; semantic search uses vectors generated by an embedding
+server. You can control how results from these two sources are blended — see the
+[parameter reference](#parameter-reference) for details.
+
+:::
+
+## Quick start
+
+### Step 1: Create an EmbeddingServer
+
+Create an EmbeddingServer with default settings. This deploys a text embeddings
+inference (TEI) server using the `BAAI/bge-small-en-v1.5` model:
+
+```yaml title="embedding-server.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: EmbeddingServer
+metadata:
+ name: my-embedding
+ namespace: toolhive-system
+spec: {}
+```
+
+:::tip
+
+Wait for the EmbeddingServer to reach the `Running` phase before proceeding. The
+first startup may take a few minutes while the model downloads.
+
+```bash
+kubectl get embeddingserver my-embedding -n toolhive-system -w
+```
+
+:::
+
+### Step 2: Add the embedding reference to VirtualMCPServer
+
+Update your existing VirtualMCPServer to include `embeddingServerRef`. **This is
+the only change needed to enable the optimizer.** When you set
+`embeddingServerRef`, the operator automatically enables the optimizer with
+sensible defaults. You only need to add an explicit `optimizer` block if you
+want to [tune the parameters](#tune-the-optimizer).
+
+```yaml title="VirtualMCPServer resource"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: my-vmcp
+ namespace: toolhive-system
+spec:
+ # highlight-start
+ embeddingServerRef:
+ name: my-embedding
+ # highlight-end
+ config:
+ groupRef: my-group
+ incomingAuth:
+ type: anonymous
+```
+
+### Step 3: Verify
+
+Check that the VirtualMCPServer is ready:
+
+```bash
+kubectl get virtualmcpserver my-vmcp -n toolhive-system
+```
+
+Look for `READY: True` in the output. Once ready, clients connecting to the vMCP
+endpoint see only `find_tool` and `call_tool` instead of the full backend
+toolset.
+
+## EmbeddingServer resource
+
+The EmbeddingServer CRD manages the lifecycle of a TEI server. An empty
+`spec: {}` uses all defaults. The two most important fields you can customize
+are:
+
+- **`model`**: The Hugging Face embedding model to use. The default
+ (`BAAI/bge-small-en-v1.5`) is the tested and recommended model. You can
+ substitute any embedding model available on Hugging Face — see the
+ [MTEB leaderboard](https://huggingface.co/spaces/mteb/leaderboard) to compare
+ options.
+- **`image`**: The container image for
+ [text-embeddings-inference](https://github.com/huggingface/text-embeddings-inference)
+ (TEI). The default is the CPU-only image
+ (`ghcr.io/huggingface/text-embeddings-inference:cpu-latest`). Swap this for a
+ CUDA-enabled image if you have GPU nodes available.
+
+For the complete field reference, see the
+[EmbeddingServer CRD specification](../reference/crd-spec.md#apiv1alpha1embeddingserver).
+
+:::warning[ARM64 compatibility]
+
+The default TEI CPU images depend on Intel MKL, which is x86_64-only. No
+official ARM64 images exist yet. On ARM64 nodes (including Apple Silicon with
+kind), you can run the amd64 image under emulation as a workaround.
+
+First, pull the amd64 image and load it into your cluster:
+
+```bash
+docker pull --platform linux/amd64 \
+ ghcr.io/huggingface/text-embeddings-inference:cpu-1.7
+kind load docker-image \
+ ghcr.io/huggingface/text-embeddings-inference:cpu-1.7
+```
+
+The `kind load` command is specific to kind. For other cluster distributions,
+use the equivalent image-loading mechanism (for example, `ctr images import` for
+containerd, or push the image to a registry your cluster can pull from).
+
+Then, pin the image in your EmbeddingServer so the operator uses the pre-pulled
+tag instead of the default `cpu-latest`:
+
+```yaml title="embedding-server.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: EmbeddingServer
+metadata:
+ name: my-embedding
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/huggingface/text-embeddings-inference:cpu-1.7
+```
+
+Native ARM64 support is in progress upstream. Track the
+[TEI GitHub repository](https://github.com/huggingface/text-embeddings-inference)
+for updates.
+
+:::
+
+## Tune the optimizer
+
+To customize optimizer behavior, add the `optimizer` block under `spec.config`
+in your VirtualMCPServer resource:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ groupRef: my-group
+ # highlight-start
+ optimizer:
+ embeddingServiceTimeout: 30s
+ maxToolsToReturn: 8
+ hybridSearchSemanticRatio: '0.5'
+ semanticDistanceThreshold: '1.0'
+ # highlight-end
+```
+
+### Parameter reference
+
+| Parameter | Description | Default |
+| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
+| `embeddingServiceTimeout` | HTTP request timeout for calls to the embedding service | `30s` |
+| `maxToolsToReturn` | Maximum number of tools returned per search (1-50) | `8` |
+| `hybridSearchSemanticRatio` | Balance between semantic and keyword search. `0.0` = all keyword, `1.0` = all semantic. Default gives equal weight to both. | `"0.5"` |
+| `semanticDistanceThreshold` | Maximum distance from the search term for semantic results. `0` = identical, `2` = completely unrelated. Results beyond this threshold are filtered out. | `"1.0"` |
+
+:::note
+
+`hybridSearchSemanticRatio` and `semanticDistanceThreshold` are string-encoded
+floats (for example, `"0.5"` not `0.5`). This is a Kubernetes CRD limitation, as
+CRDs do not support float types portably.
+
+:::
+
+:::info[EmbeddingServer is always required]
+
+Even if you set `hybridSearchSemanticRatio` to `"0.0"` (all keyword search), the
+optimizer still requires a configured EmbeddingServer. The EmbeddingServer won't
+be used at runtime when the semantic ratio is `0.0`, but the configuration must
+be present due to how the optimizer is wired internally.
+
+:::
+
+:::tip[Tuning guidance]
+
+The defaults are well-tested and work for most use cases. If you do need to
+adjust them:
+
+- **Lower `semanticDistanceThreshold`** (for example, `"0.6"`) for higher
+ precision: only very close matches are returned
+- **Raise `semanticDistanceThreshold`** (for example, `"1.4"`) for higher
+ recall: broader matches are included
+- **Increase `maxToolsToReturn`** if the AI frequently cannot find the right
+ tool; decrease it to save tokens
+- **Adjust `hybridSearchSemanticRatio`** toward `"1.0"` if tool names are not
+ descriptive, or toward `"0.0"` if exact keyword matching is more useful
+- `semanticDistanceThreshold` filtering is applied before the `maxToolsToReturn`
+ cap. A low threshold can filter out candidates before the cap takes effect, so
+ you may need to raise the threshold if too few results are returned
+
+:::
+
+## Complete example
+
+This example shows a full configuration with all available options, including
+high availability for the embedding server, persistent model caching, and tuned
+optimizer parameters.
+
+The EmbeddingServer runs two replicas with resource limits and a persistent
+volume for model caching, so restarts don't re-download the model:
+
+```yaml title="embedding-server-full.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: EmbeddingServer
+metadata:
+ name: full-embedding
+ namespace: toolhive-system
+spec:
+ replicas: 2
+ resources:
+ requests:
+ cpu: '500m'
+ memory: '512Mi'
+ limits:
+ cpu: '2'
+ memory: '1Gi'
+ modelCache:
+ enabled: true
+ storageSize: 5Gi
+```
+
+The VirtualMCPServer uses a shorter embedding timeout (15s) because the
+EmbeddingServer is co-located with low-latency access. Increase this value if
+the embedding service is remote or under high load:
+
+```yaml title="vmcp-with-optimizer.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: full-vmcp
+ namespace: toolhive-system
+spec:
+ embeddingServerRef:
+ name: full-embedding
+ config:
+ groupRef: my-tools
+ optimizer:
+ embeddingServiceTimeout: 15s
+ maxToolsToReturn: 10
+ hybridSearchSemanticRatio: '0.6'
+ semanticDistanceThreshold: '0.8'
+ incomingAuth:
+ type: oidc
+ oidcConfig:
+ type: inline
+ inline:
+ issuer: https://auth.example.com
+ audience: vmcp-example
+```
+
+## Related information
+
+- [MCP Optimizer tutorial](../tutorials/mcp-optimizer.mdx) — desktop/CLI setup
+- [Optimizing LLM context](../concepts/tool-optimization.mdx) — background on
+ tool filtering and context pollution
+- [Configure vMCP servers](./configuration.mdx)
+- [EmbeddingServer CRD specification](../reference/crd-spec.md#apiv1alpha1embeddingserver)
+- [Virtual MCP Server overview](../concepts/vmcp.mdx) — conceptual overview of
+ vMCP
+- [VirtualMCPServer CRD specification](../reference/crd-spec.md#apiv1alpha1virtualmcpserver)
diff --git a/versioned_docs/version-1.0/toolhive/guides-vmcp/quickstart.mdx b/versioned_docs/version-1.0/toolhive/guides-vmcp/quickstart.mdx
new file mode 100644
index 00000000..1f60db06
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-vmcp/quickstart.mdx
@@ -0,0 +1,282 @@
+---
+title: 'Quickstart: Virtual MCP Server'
+sidebar_label: Quickstart
+description:
+ Learn how to aggregate multiple MCP servers into a single endpoint using
+ Virtual MCP Server.
+---
+
+In this tutorial, you'll learn how to deploy Virtual MCP Server (vMCP) to
+aggregate multiple MCP servers into a single endpoint. By the end, you'll have a
+working deployment that combines tools from multiple backends.
+
+## What you'll learn
+
+- How to create an MCPGroup to organize backend servers
+- How to deploy multiple MCPServers in a group
+- How to create a VirtualMCPServer that aggregates them
+- How tool conflict resolution works
+- How to connect your AI client to the aggregated endpoint
+
+## Prerequisites
+
+Before starting this tutorial, make sure you have:
+
+- A Kubernetes cluster with the ToolHive operator installed (see
+ [Quickstart: Kubernetes Operator](../guides-k8s/quickstart.mdx))
+- `kubectl` configured to communicate with your cluster
+- An MCP client (Visual Studio Code with Copilot is used in this tutorial)
+
+## Step 1: Create an MCPGroup
+
+First, create an MCPGroup to organize your backend MCP servers:
+
+```yaml title="mcpgroup.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPGroup
+metadata:
+ name: demo-tools
+ namespace: toolhive-system
+spec:
+ description: Demo group for vMCP aggregation
+```
+
+Apply the resource:
+
+```bash
+kubectl apply -f mcpgroup.yaml
+```
+
+Verify the group was created:
+
+```bash
+kubectl get mcpgroups -n toolhive-system
+```
+
+## Step 2: Deploy backend MCPServers
+
+Deploy two MCP servers that will be aggregated. Both reference the `demo-tools`
+group in the `groupRef` field:
+
+```yaml {11,30} title="mcpservers.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: fetch
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stackloklabs/gofetch/server
+ transport: streamable-http
+ proxyPort: 8080
+ mcpPort: 8080
+ groupRef: demo-tools
+ resources:
+ limits:
+ cpu: '100m'
+ memory: '128Mi'
+ requests:
+ cpu: '50m'
+ memory: '64Mi'
+---
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: osv
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stackloklabs/osv-mcp/server
+ transport: streamable-http
+ proxyPort: 8080
+ mcpPort: 8080
+ groupRef: demo-tools
+ resources:
+ limits:
+ cpu: '100m'
+ memory: '128Mi'
+ requests:
+ cpu: '50m'
+ memory: '64Mi'
+```
+
+Apply the resources:
+
+```bash
+kubectl apply -f mcpservers.yaml
+```
+
+Wait for both servers to be running:
+
+```bash
+kubectl get mcpservers -n toolhive-system -w
+```
+
+You should see both servers with `Running` status before continuing.
+
+## Step 3: Create a VirtualMCPServer
+
+Create a VirtualMCPServer that aggregates both backends:
+
+```yaml title="virtualmcpserver.yaml"
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: demo-vmcp
+ namespace: toolhive-system
+spec:
+ # No incoming auth for development (anonymous access)
+ incomingAuth:
+ type: anonymous
+
+ # Auto-discover auth config from backend MCPServers
+ outgoingAuth:
+ source: inline
+ # No default specified will use anonymous
+
+ # Expose as ClusterIP (cluster-internal or exposed via Ingress/Gateway)
+ serviceType: ClusterIP
+
+ config:
+ # Reference the MCPGroup containing fetch and osv servers
+ groupRef: demo-tools
+ # Tool aggregation with prefix strategy to avoid naming conflicts
+ aggregation:
+ conflictResolution: prefix
+ conflictResolutionConfig:
+ prefixFormat: '{workload}_'
+```
+
+Apply the resource:
+
+```bash
+kubectl apply -f virtualmcpserver.yaml
+```
+
+Check the status:
+
+```bash
+kubectl get virtualmcpservers -n toolhive-system
+```
+
+After about 30 seconds, you should see output similar to:
+
+```text
+NAME PHASE URL BACKENDS AGE READY
+demo-vmcp Ready http://vmcp-demo-vmcp.toolhive-system.svc.cluster.local:4483 2 30s True
+```
+
+Note the port number for step 5.
+
+:::info[What's happening?]
+
+The operator discovered both MCPServers in the group and configured vMCP to
+aggregate their tools. With the `prefix` conflict resolution strategy, all tools
+are prefixed with the backend name.
+
+:::
+
+## Step 4: Verify the aggregation
+
+Check the discovered backends:
+
+```bash
+kubectl describe virtualmcpserver demo-vmcp -n toolhive-system
+```
+
+Look for the `Discovered Backends` section in the status, which should show both
+backends.
+
+## Step 5: Connect your client
+
+In a separate terminal, port-forward the vMCP service to your local machine:
+
+```bash
+kubectl port-forward service/vmcp-demo-vmcp -n toolhive-system 4483:4483
+```
+
+Test the health endpoint:
+
+```bash
+curl http://localhost:4483/health
+```
+
+You should see `{"status":"ok"}`.
+
+Add the port-forwarded vMCP endpoint as a remote server in ToolHive:
+
+```bash
+thv run http://localhost:4483/mcp --name demo-vmcp
+```
+
+This registers the vMCP endpoint as a ToolHive-managed workload, which
+automatically configures your registered MCP clients to connect to it.
+
+:::tip
+
+If you haven't set up client configuration yet, run `thv client setup` to
+register your MCP clients. See
+[Client configuration](../guides-cli/client-configuration.mdx) for more details.
+
+:::
+
+## Step 6: Test the aggregated tools
+
+Try asking your AI assistant questions that use the aggregated tools. Both tools
+work through the same vMCP endpoint!
+
+## Step 7: Clean up
+
+Delete the resources when you're done:
+
+```bash
+kubectl delete virtualmcpserver demo-vmcp -n toolhive-system
+kubectl delete mcpserver fetch osv -n toolhive-system
+kubectl delete mcpgroup demo-tools -n toolhive-system
+```
+
+## What's next?
+
+Congratulations! You've successfully deployed vMCP and aggregated multiple
+backends into a single endpoint.
+
+Next steps:
+
+- [Configure authentication](../guides-vmcp/authentication.mdx) for production
+- [Customize tool aggregation](../guides-vmcp/tool-aggregation.mdx) with
+ filtering and overrides
+- [Understanding Virtual MCP Server](../concepts/vmcp.mdx)
+
+## Troubleshooting
+
+
+VirtualMCPServer stuck in Pending
+
+Check that the MCPGroup exists and backend MCPServers are running:
+
+```bash
+kubectl get mcpgroups,mcpservers -n toolhive-system
+```
+
+Check the operator logs:
+
+```bash
+kubectl logs -n toolhive-system -l app.kubernetes.io/name=toolhive-operator
+```
+
+
+
+
+Only some tools appearing
+
+Verify both backends are discovered:
+
+```bash
+kubectl get virtualmcpserver demo-vmcp -n toolhive-system -o jsonpath='{.status.discoveredBackends[*].name}'
+```
+
+Check backend health in the status:
+
+```bash
+kubectl describe virtualmcpserver demo-vmcp -n toolhive-system
+```
+
+
diff --git a/versioned_docs/version-1.0/toolhive/guides-vmcp/scaling-and-performance.mdx b/versioned_docs/version-1.0/toolhive/guides-vmcp/scaling-and-performance.mdx
new file mode 100644
index 00000000..559dec02
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-vmcp/scaling-and-performance.mdx
@@ -0,0 +1,88 @@
+---
+title: Scaling and Performance
+description: How to scale Virtual MCP Server deployments vertically and horizontally.
+---
+
+This guide explains how to scale Virtual MCP Server (vMCP) deployments.
+
+## Vertical scaling
+
+Vertical scaling (increasing CPU/memory per instance) is the simplest approach
+and works for all use cases, including stateful backends.
+
+To increase resources, configure `podTemplateSpec` in your VirtualMCPServer:
+
+```yaml
+spec:
+ podTemplateSpec:
+ spec:
+ containers:
+ - name: vmcp
+ resources:
+ requests:
+ cpu: '500m'
+ memory: 512Mi
+ limits:
+ cpu: '1'
+ memory: 1Gi
+```
+
+Vertical scaling is recommended as the starting point for most deployments.
+
+## Horizontal scaling
+
+Horizontal scaling (adding more replicas) can improve availability and handle
+higher request volumes.
+
+### How to scale horizontally
+
+The VirtualMCPServer CRD does not have a `replicas` field. The operator creates
+a Deployment named `vmcp-` (where `` is your VirtualMCPServer name)
+with 1 replica and preserves the replicas count, allowing you to manage scaling
+separately.
+
+**Option 1: Manual scaling**
+
+```bash
+kubectl scale deployment vmcp- -n --replicas=3
+```
+
+**Option 2: Autoscaling with HPA**
+
+```bash
+kubectl autoscale deployment vmcp- -n \
+ --min=2 --max=5 --cpu-percent=70
+```
+
+### When horizontal scaling is challenging
+
+Horizontal scaling works well for **stateless backends** (fetch, search,
+read-only operations) where sessions can be resumed on any instance.
+
+However, **stateful backends** make horizontal scaling difficult:
+
+- **Stateful backends** (Playwright browser sessions, database connections, file
+ system operations) require requests to be routed to the same vMCP instance
+ that established the session
+- Session resumption may not work reliably for stateful backends
+
+The `VirtualMCPServer` CRD includes a `sessionAffinity` field that controls how
+the Kubernetes Service routes repeated client connections. By default, it uses
+`ClientIP` affinity, which routes connections from the same client IP to the
+same pod. You can configure this using the `sessionAffinity` field:
+
+```yaml
+spec:
+ sessionAffinity: ClientIP # default
+```
+
+For stateful backends, vertical scaling or dedicated vMCP instances per team/use
+case are recommended instead of horizontal scaling.
+
+## Related information
+
+- [Introduction to vMCP](./intro.mdx)
+- [Configure health checks](./configuration.mdx#health-checks)
+- [Backend discovery modes](./backend-discovery.mdx)
+- [Telemetry and metrics](./telemetry-and-metrics.mdx)
+- [VirtualMCPServer CRD specification](../reference/crd-spec.md#apiv1alpha1virtualmcpserver)
diff --git a/versioned_docs/version-1.0/toolhive/guides-vmcp/telemetry-and-metrics.mdx b/versioned_docs/version-1.0/toolhive/guides-vmcp/telemetry-and-metrics.mdx
new file mode 100644
index 00000000..6e54d6bf
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-vmcp/telemetry-and-metrics.mdx
@@ -0,0 +1,176 @@
+---
+title: Telemetry and metrics
+description: How to enable OpenTelemetry traces and metrics for Virtual MCP Server.
+---
+
+Virtual MCP Server (vMCP) provides comprehensive observability through
+OpenTelemetry instrumentation. You can export traces and metrics to monitor
+backend operations and workflow executions.
+
+## Telemetry types
+
+vMCP supports two types of telemetry:
+
+- **Traces**: Track requests across vMCP and its backends, showing the full path
+ of tool calls, resource reads, and workflow executions
+- **Metrics**: Counters and histograms for backend request rates, error rates,
+ and latency distributions
+
+For general ToolHive observability concepts including trace structure and
+metrics, see the [observability overview](../concepts/observability.mdx).
+
+## Enable telemetry
+
+Configure telemetry in the VirtualMCPServer resource using the
+[`spec.config.telemetry` field](../reference/crd-spec.md#toolhivestacklokdevtelemetry):
+
+```yaml
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: my-vmcp
+ namespace: toolhive-system
+spec:
+ config:
+ groupRef: my-group
+ # highlight-start
+ telemetry:
+ endpoint: 'otel-collector:4318'
+ serviceName: 'my-vmcp'
+ insecure: true
+ tracingEnabled: true
+ samplingRate: '0.05'
+ metricsEnabled: true
+ enablePrometheusMetricsPath: true
+ # highlight-end
+ incomingAuth:
+ type: anonymous
+```
+
+### Configuration options
+
+| Field | Description | Default |
+| ----------------------------- | --------------------------------------- | --------------------- |
+| `endpoint` | OTLP collector endpoint (hostname:port) | - |
+| `serviceName` | Service name in traces and metrics | VirtualMCPServer name |
+| `tracingEnabled` | Enable tracing | `false` |
+| `metricsEnabled` | Enable OTLP metrics export | `false` |
+| `samplingRate` | Trace sampling rate (0.0-1.0) | `"0.05"` |
+| `insecure` | Use HTTP instead of HTTPS | `false` |
+| `enablePrometheusMetricsPath` | Expose `/metrics` endpoint | `false` |
+
+## Export to observability backends
+
+### Export to Jaeger via OpenTelemetry Collector
+
+Deploy an OpenTelemetry Collector configured to export to Jaeger:
+
+```yaml title="otel-collector-config.yaml"
+receivers:
+ otlp:
+ protocols:
+ http:
+ endpoint: 0.0.0.0:4318
+
+processors:
+ batch:
+ timeout: 10s
+ send_batch_size: 1024
+
+exporters:
+ otlp/jaeger:
+ endpoint: jaeger:4317
+ tls:
+ insecure: true
+
+service:
+ pipelines:
+ traces:
+ receivers: [otlp]
+ processors: [batch]
+ exporters: [otlp/jaeger]
+```
+
+Then configure vMCP to send telemetry to the collector:
+
+```yaml
+spec:
+ config:
+ telemetry:
+ endpoint: 'otel-collector:4318'
+ serviceName: 'production-vmcp'
+ tracingEnabled: true
+ metricsEnabled: true
+ insecure: true
+```
+
+## Metrics collection
+
+vMCP supports two methods for collecting metrics:
+
+- **Push via OpenTelemetry**: Set `metricsEnabled: true` to push metrics to your
+ OTel Collector via OTLP
+- **Pull via Prometheus**: Set `enablePrometheusMetricsPath: true` to expose a
+ `/metrics` endpoint on the vMCP service port (4483) for Prometheus to scrape
+
+### Backend metrics
+
+These metrics track requests to individual MCP server backends:
+
+| Metric | Type | Description |
+| ----------------------------------------- | --------- | ----------------------------------------------------------------------------- |
+| `toolhive_vmcp_backends_discovered` | Gauge | Number of backends discovered |
+| `toolhive_vmcp_backend_requests` | Counter | Total requests per backend |
+| `toolhive_vmcp_backend_errors` | Counter | Total errors per backend |
+| `toolhive_vmcp_backend_requests_duration` | Histogram | Duration of backend requests |
+| `mcp.client.operation.duration` | Histogram | MCP client operation duration (`mcp_client_operation_duration` on `/metrics`) |
+
+### Workflow metrics
+
+These metrics track workflow execution across backends:
+
+| Metric | Type | Description |
+| ----------------------------------- | --------- | ------------------------------- |
+| `toolhive_vmcp_workflow_executions` | Counter | Total workflow executions |
+| `toolhive_vmcp_workflow_errors` | Counter | Total workflow execution errors |
+| `toolhive_vmcp_workflow_duration` | Histogram | Duration of workflow executions |
+
+### Optimizer metrics
+
+When the vMCP optimizer is enabled, these metrics track tool-finding and
+tool-calling performance:
+
+| Metric | Type | Description |
+| ----------------------------------------------- | --------- | --------------------------------------- |
+| `toolhive_vmcp_optimizer_find_tool_requests` | Counter | Total FindTool calls |
+| `toolhive_vmcp_optimizer_find_tool_errors` | Counter | Total FindTool errors |
+| `toolhive_vmcp_optimizer_find_tool_duration` | Histogram | Duration of FindTool calls |
+| `toolhive_vmcp_optimizer_find_tool_results` | Histogram | Number of tools returned per call |
+| `toolhive_vmcp_optimizer_token_savings_percent` | Histogram | Token savings percentage per call |
+| `toolhive_vmcp_optimizer_call_tool_requests` | Counter | Total CallTool calls |
+| `toolhive_vmcp_optimizer_call_tool_errors` | Counter | Total CallTool errors |
+| `toolhive_vmcp_optimizer_call_tool_not_found` | Counter | CallTool calls where tool was not found |
+| `toolhive_vmcp_optimizer_call_tool_duration` | Histogram | Duration of CallTool calls |
+
+## Distributed tracing
+
+vMCP creates client-side spans for backend operations with the following span
+names:
+
+- `tools/call ` - Tool calls to backends
+- `resources/read` - Resource reads from backends
+- `prompts/get ` - Prompt retrieval from backends
+- `list_capabilities` - Backend capability discovery
+
+Each span includes attributes for the target backend (`target.workload_id`,
+`target.workload_name`, `target.base_url`) and the relevant MCP attributes
+(`mcp.method.name`, `gen_ai.tool.name`, `mcp.resource.uri`).
+
+## Related information
+
+- [Observability concepts](../concepts/observability.mdx) - Overview of
+ ToolHive's observability architecture
+- [Kubernetes telemetry guide](../guides-k8s/telemetry-and-metrics.mdx) -
+ Telemetry for MCPServer resources
+- [OpenTelemetry tutorial](../integrations/opentelemetry.mdx) - Set up a local
+ observability stack
diff --git a/versioned_docs/version-1.0/toolhive/guides-vmcp/tool-aggregation.mdx b/versioned_docs/version-1.0/toolhive/guides-vmcp/tool-aggregation.mdx
new file mode 100644
index 00000000..728d6915
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/guides-vmcp/tool-aggregation.mdx
@@ -0,0 +1,244 @@
+---
+title: Tool aggregation and conflict resolution
+description:
+ How vMCP aggregates tools from multiple backend MCP servers and resolves
+ naming conflicts.
+---
+
+When aggregating multiple MCP servers, tool name conflicts can occur when
+different backend servers expose tools with the same name. Virtual MCP Server
+(vMCP) provides strategies to resolve these conflicts automatically.
+
+## Overview
+
+vMCP discovers tools from all backend MCPServers in the referenced group and
+presents them as a unified set to clients. When two backend MCP servers have
+tools with the same name (for example, both GitHub and Jira have a
+`create_issue` tool), a conflict resolution strategy determines how to handle
+the collision.
+
+:::tip
+
+When aggregating many backends, the total number of exposed tools can grow
+quickly. Consider enabling the [optimizer](./optimizer.mdx) to reduce token
+usage and improve tool selection accuracy.
+
+:::
+
+## Conflict resolution strategies
+
+### Prefix strategy (default)
+
+By default, vMCP prefixes all tool names with the workload identifier (the
+`metadata.name` of each MCPServer resource). This guarantees unique names and is
+the safest option for most deployments.
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ aggregation:
+ conflictResolution: prefix
+ conflictResolutionConfig:
+ prefixFormat: '{workload}_'
+```
+
+**Prefix format options:**
+
+| Format | Example result |
+| ------------- | --------------------- |
+| `{workload}` | `githubcreate_issue` |
+| `{workload}_` | `github_create_issue` |
+| `{workload}.` | `github.create_issue` |
+
+**Example:**
+
+With backend servers `github` and `jira`, both exposing `create_issue`:
+
+- GitHub's tool becomes `github_create_issue`
+- Jira's tool becomes `jira_create_issue`
+
+### Priority strategy
+
+When multiple backend MCP servers offer tools with the same name, the `priority`
+strategy keeps the tool from the first backend in the priority order and drops
+the duplicate tools from lower-priority backend servers.
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ aggregation:
+ conflictResolution: priority
+ conflictResolutionConfig:
+ priorityOrder: ['github', 'jira', 'slack']
+```
+
+In this example, if both GitHub and Jira provide a `create_issue` tool, only
+GitHub's version is exposed. Jira's duplicate is dropped.
+
+**When to use:** When you have a preferred backend MCP server for specific tools
+and want to hide duplicates.
+
+:::warning
+
+The priority strategy drops tools from lower-priority backend servers. Ensure
+this is the intended behavior before using in production.
+
+:::
+
+### Manual strategy
+
+The `manual` strategy gives you explicit control over tool naming when conflicts
+occur. You must provide overrides for all conflicting tools, or the vMCP will
+fail to start.
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ aggregation:
+ conflictResolution: manual
+ tools:
+ - workload: github
+ overrides:
+ create_issue:
+ name: gh_create_issue
+ - workload: jira
+ overrides:
+ create_issue:
+ name: jira_ticket
+```
+
+**When to use:** Production deployments where you want explicit control over
+tool names.
+
+## Tool filtering
+
+Use filters to expose only specific tools from a backend MCP server, excluding
+all others. This is useful for reducing the tool surface area presented to LLM
+clients or removing unnecessary tools:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ aggregation:
+ tools:
+ - workload: github
+ filter: ['create_issue', 'list_issues', 'get_issue']
+```
+
+Only the listed tools are included; all others from that backend MCP server are
+excluded.
+
+## Tool overrides
+
+Use overrides to customize tool names and descriptions without modifying backend
+MCP server configurations. This is useful for disambiguating similarly-named
+tools or providing more context to LLM clients:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ aggregation:
+ tools:
+ - workload: github
+ overrides:
+ create_issue:
+ name: gh_new_issue
+ description: 'Create a new GitHub issue in the repository'
+```
+
+:::info
+
+You can also reference an `MCPToolConfig` resource using `toolConfigRef` instead
+of inline filter and overrides. This feature is currently in development.
+
+:::
+
+## Combine filters and overrides
+
+You can combine filtering and overrides for fine-grained control:
+
+```yaml title="VirtualMCPServer resource"
+spec:
+ config:
+ aggregation:
+ conflictResolution: prefix
+ conflictResolutionConfig:
+ prefixFormat: '{workload}_'
+ tools:
+ - workload: github
+ filter: ['create_issue', 'list_issues']
+ overrides:
+ create_issue:
+ description: 'Create a GitHub issue (engineering team)'
+ - workload: jira
+ filter: ['create_issue', 'search_issues']
+```
+
+## Example: Aggregating multiple MCP servers
+
+This example shows two MCP servers (fetch and osv) aggregated with prefix-based
+conflict resolution:
+
+```yaml
+# MCPGroup to organize backend servers
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPGroup
+metadata:
+ name: demo-tools
+ namespace: toolhive-system
+spec:
+ description: Demo group for tool aggregation
+---
+# First backend: fetch server
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: fetch
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stackloklabs/gofetch/server
+ transport: streamable-http
+ proxyPort: 8080
+ mcpPort: 8080
+ groupRef: demo-tools
+---
+# Second backend: osv server
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: MCPServer
+metadata:
+ name: osv
+ namespace: toolhive-system
+spec:
+ image: ghcr.io/stackloklabs/osv-mcp/server
+ transport: streamable-http
+ proxyPort: 8080
+ mcpPort: 8080
+ groupRef: demo-tools
+---
+# VirtualMCPServer aggregating both backends
+apiVersion: toolhive.stacklok.dev/v1alpha1
+kind: VirtualMCPServer
+metadata:
+ name: demo-vmcp
+ namespace: toolhive-system
+spec:
+ incomingAuth:
+ type: anonymous
+ config:
+ groupRef: demo-tools
+ aggregation:
+ conflictResolution: prefix
+ conflictResolutionConfig:
+ prefixFormat: '{workload}_'
+```
+
+With this configuration, tools from each backend are prefixed:
+
+- `fetch_*` tools from the fetch server
+- `osv_*` tools from the osv server
+
+## Related information
+
+- [VirtualMCPServer configuration reference](./configuration.mdx)
+- [Optimize tool discovery](./optimizer.mdx)
+- [Customize MCP server tools](../guides-k8s/customize-tools.mdx)
diff --git a/versioned_docs/version-1.0/toolhive/index.mdx b/versioned_docs/version-1.0/toolhive/index.mdx
new file mode 100644
index 00000000..c32cae3d
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/index.mdx
@@ -0,0 +1,237 @@
+---
+title: Introduction
+hide_title: true
+description: ToolHive helps you run and manage MCP servers easily and securely.
+---
+
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+
+
+
+
+---
+
+
+
+# What is ToolHive?
+
+ToolHive is an enterprise-grade open source (Apache 2.0) platform for running
+and managing Model Context Protocol (MCP) servers.
+
+New to the Model Context Protocol? Start with the
+[MCP primer](./concepts/mcp-primer.mdx).
+
+## Choose your path
+
+
+
+
+### Desktop app
+
+Run MCP servers with one click. The easiest way to get started for individual
+developers.
+
+**[Get started with the UI →](./guides-ui/quickstart.mdx)**
+
+
+
+
+### CLI
+
+For power users and automation. Full control over MCP servers from the command
+line.
+
+**[Get started with the CLI →](./guides-cli/quickstart.mdx)**
+
+
+
+
+### Kubernetes
+
+For teams and enterprises. Deploy and manage MCP servers at scale with the
+ToolHive Operator.
+
+**[Get started with K8s →](./guides-k8s/quickstart.mdx)**
+
+
+
+
+## ToolHive components
+
+ToolHive includes everything you need to use MCP servers in production. It's
+made up of four key components: the Runtime, Registry Server, Gateway, and
+Portal.
+
+
+
+### Runtime
+
+The ToolHive Runtime is the core component that runs MCP servers in isolated
+containers. It provides a secure and scalable environment for deploying MCP
+servers, with features like fine-grained permissions, network access controls,
+and secret management.
+
+The ToolHive Runtime is available in three different editions to suit different
+use cases:
+
+- [**ToolHive UI**](./guides-ui/index.mdx): A desktop application for individual
+ developers to run and manage MCP servers locally. It provides a user-friendly
+ interface to discover, deploy, and manage MCP servers.
+
+- [**ToolHive CLI**](./guides-cli/index.mdx): A command line interface to deploy
+ and manage MCP servers on your local machine or in development environments.
+ It allows quick deployment of MCP servers and supports advanced features like
+ telemetry and fine-grained authorization policies.
+
+- [**ToolHive Kubernetes Operator**](./guides-k8s/index.mdx): A Kubernetes
+ operator for teams and enterprises to run and manage MCP servers in multi-user
+ environments. It provides centralized management, security controls, and
+ integration with existing infrastructure.
+
+### Registry Server
+
+The [**ToolHive Registry Server**](./guides-registry/index.mdx) is an
+implementation of the official
+[MCP Registry API](https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/api/generic-registry-api.md).
+Curate a catalog of trusted servers from multiple backend sources for users to
+quickly discover and deploy. It can be deployed standalone or as part of the
+ToolHive Operator.
+
+### Gateway
+
+Implemented in the ToolHive Kubernetes Operator as the
+[**Virtual MCP Server (vMCP)**](./guides-vmcp/index.mdx), the ToolHive Gateway
+is a secure proxy for MCP server connectivity, aggregation, and orchestration.
+It's available as part of the ToolHive Operator for Kubernetes deployments.
+
+### Portal
+
+The ToolHive Portal is how users discover and install MCP servers. It's
+available as a cross-platform desktop app (the ToolHive UI) and an experimental
+[web-based frontend](https://github.com/stacklok/toolhive-cloud-ui) to the
+ToolHive Registry Server.
+
+### Better together
+
+ToolHive components work together to provide a seamless experience for both
+admins and users:
+
+1. **Admins** curate and organize MCP servers in the **Registry**, configuring
+ access and policies.
+2. **Users** discover and request MCP servers from the **Portal**, and ToolHive
+ orchestrates installation and access.
+3. **Runtime** securely deploys and manages MCP servers across local and cloud
+ environments, integrating seamlessly with existing SDLC workflows, exporting
+ analytics, and enforcing fine-grained access control.
+4. **Gateway** handles all inbound traffic, secures context and credentials,
+ optimizes tool selection, and applies organizational policies.
+
+## Why ToolHive?
+
+We want to help you get real value from MCP servers. While there are plenty of
+tools to help you quickly build MCP servers, the obstacles to using those
+servers effectively are largely operational. ToolHive addresses the runtime,
+security, and other considerations necessary to use MCP servers with confidence
+and in production.
+
+We address those considerations with proven, familiar technologies like
+containers and Kubernetes.
+
+ToolHive lets you run any MCP server, regardless of its underlying technology
+stack, even when the original authors didn't provide container images. We
+containerize the MCP server and let you use a simple CLI or Kubernetes to manage
+MCP deployments at any scale. Coordinate all your MCP servers from one place
+with sensible security controls and container-native simplicity.
+
+## Key features
+
+ToolHive includes a range of capabilities to help you run and manage MCP servers
+effectively:
+
+**Runtime:**
+
+- Run local MCP servers instantly from the built-in registry of vetted MCP
+ servers, any Docker container, or directly from package managers
+- Deploy MCP servers in the cloud via Kubernetes for enterprise scalability
+- Proxy remote MCP servers securely for unified management
+- Apply fine-grained permissions and network access filtering
+- Protect sensitive data with built-in secrets management and enterprise OAuth
+ integrations
+- Leverage OpenTelemetry and Prometheus for observability and audit logging
+
+**Registry Server:**
+
+- Curate a catalog of trusted MCP servers from multiple sources: the official
+ MCP Registry, other public registries, and custom files
+- Group servers based on role or use case
+- Manage your registry with an API-driven interface
+- Preset configurations and permissions for a frictionless user experience
+
+**Gateway:**
+
+- Orchestrate multiple tools with a deterministic workflow engine
+- Centralize control of security policy, authentication, authorization,
+ auditing, etc.
+- Customize and filter tools and descriptions to improve performance and reduce
+ token usage
+
+**Portal:**
+
+- Cross-platform [desktop app](https://github.com/stacklok/toolhive-studio) and
+ browser-based [cloud UI](https://github.com/stacklok/toolhive-cloud-ui)
+- Make it easy for end users to discover and deploy MCP servers
+- Install MCP servers with a single click
+- Connect with local clients like Claude Desktop, Cursor, VS Code, and many more
+
+## Additional resources
+
+Join the Stacklok [Discord community](https://discord.gg/stacklok) to connect
+with other ToolHive users, ask questions, and share your experiences.
+
+Source code and issue tracking:
+
+- ToolHive CLI & K8s Operator
+ - [GitHub repository](https://github.com/stacklok/toolhive)
+ - [Issue tracker](https://github.com/stacklok/toolhive/issues)
+- ToolHive UI
+ - [GitHub repository](https://github.com/stacklok/toolhive-studio)
+ - [Issue tracker](https://github.com/stacklok/toolhive-studio/issues)
+- ToolHive Registry Server
+ - [GitHub repository](https://github.com/stacklok/toolhive-registry-server)
+ - [Issue tracker](https://github.com/stacklok/toolhive-registry-server/issues)
+- ToolHive Cloud UI (experimental)
+ - [GitHub repository](https://github.com/stacklok/toolhive-cloud-ui)
+ - [Issue tracker](https://github.com/stacklok/toolhive-cloud-ui/issues)
diff --git a/versioned_docs/version-1.0/toolhive/integrations/aws-sts.mdx b/versioned_docs/version-1.0/toolhive/integrations/aws-sts.mdx
new file mode 100644
index 00000000..a09ed8db
--- /dev/null
+++ b/versioned_docs/version-1.0/toolhive/integrations/aws-sts.mdx
@@ -0,0 +1,663 @@
+---
+title: AWS STS authentication for the AWS MCP Server
+description:
+ Learn how to centralize AWS credential management by using ToolHive to
+ exchange OIDC tokens for temporary AWS credentials via STS.
+---
+
+This tutorial shows you how to use ToolHive as an authentication proxy for the
+[AWS MCP Server](https://docs.aws.amazon.com/aws-mcp/). Developers sign in
+through their company identity provider, and ToolHive exchanges their OIDC token
+for temporary AWS credentials via
+[`AssumeRoleWithWebIdentity`](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html).
+
+:::info[Prerequisites]
+
+Before starting this tutorial, ensure you have:
+
+- A Kubernetes cluster with the ToolHive Operator installed (see the
+ [Kubernetes quickstart guide](../guides-k8s/quickstart.mdx))
+- `kubectl` configured to access your cluster
+- The
+ [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
+ installed and configured with an AWS account that has permissions to create
+ IAM roles, policies, and OIDC identity providers
+- An OIDC identity provider (such as Okta, Auth0, Microsoft Entra ID, or
+ Keycloak) that your users authenticate with
+- Basic familiarity with AWS IAM concepts and OIDC
+
+:::
+
+## Overview
+
+The following diagram shows how ToolHive processes each request:
+
+```mermaid
+flowchart LR
+ subgraph client["Client"]
+ user["User"]
+ end
+
+ subgraph gateway["ToolHive"]
+ authn["OIDC Auth"]
+ mapper["Role Mapper"]
+ sts["STS Exchange"]
+ signer["SigV4 Signer"]
+ end
+
+ subgraph aws["AWS"]
+ stsapi["AWS STS"]
+ mcp["AWS MCP Server"]
+ end
+
+ user -->|"JWT"| authn
+ authn --> mapper
+ mapper -->|"Selected Role ARN"| sts
+ sts <-->|"AssumeRoleWithWebIdentity"| stsapi
+ sts --> signer
+ signer -->|"SigV4 Signed Request"| mcp
+```
+
+1. The client sends a request with an OIDC token from your identity provider.
+2. ToolHive validates the token against your OIDC provider's JWKS endpoint.
+3. The **role mapper** inspects JWT claims (such as `groups`) and selects an IAM
+ role based on your configured mappings.
+4. ToolHive calls AWS STS `AssumeRoleWithWebIdentity` to exchange the OIDC token
+ for temporary AWS credentials.
+5. The **SigV4 signer** signs the outgoing request with the temporary
+ credentials.
+6. The signed request is forwarded to the AWS MCP Server.
+
+The AWS MCP Server acts as the access point that allows AI assistants to connect
+to different AWS services. Compared to configuring AWS credentials directly,
+ToolHive adds:
+
+- **Company IdP integration:** Developers authenticate using company SSO.
+ ToolHive acquires short-lived, properly scoped credentials on their behalf. No
+ AWS CLI configuration is required, and AWS credentials are never stored on
+ developers' machines.
+- **Fine-grained authorization:** Control which MCP tools a user can invoke.
+ Cedar policies are evaluated on every request, using claims from the incoming
+ token to make access decisions.
+- **Observability, metrics, and auditing:** ToolHive provides OpenTelemetry
+ tracing, Prometheus metrics, and audit logs that correlate user identity
+ across the request flow.
+
+## Step 1: Register your identity provider with AWS IAM
+
+Create an OIDC identity provider in AWS IAM so that AWS STS trusts tokens from
+your identity provider.
+
+```bash
+aws iam create-open-id-connect-provider \
+ --url https:// \
+ --client-id-list
+```
+
+Replace the placeholders:
+
+- `