Skip to content

Commit 7cbe313

Browse files
authored
Merge branch 'main' into update-docs-v0.16.0
2 parents 9fbf3a8 + 5eb5d05 commit 7cbe313

4 files changed

Lines changed: 151 additions & 78 deletions

File tree

.claude/skills/docs-review/SKILL.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Perform critical editorial reviews as a tech writer / copyeditor, focusing on cl
5050
- **TOC test**: If the table of contents fills a screen, the doc is likely too long
5151
- **Heading count**: 20+ headings signals over-segmentation; consolidate
5252
- **Information flow**: Conceptual content (trade-offs, when to use) should come before implementation details, not after
53+
- **Admonition weight**: Tips, notes, and warnings should be supplementary. If an admonition contains primary feature documentation (full YAML examples, the only explanation of a field or concept), it should be promoted to a proper section or its own page. A good test: if this admonition is the only place a feature is documented, it's not a tip - it's a section that needs a heading and ToC visibility.
5354
- **Diataxis alignment**: Don't mix tutorials, how-tos, reference, and concepts in one doc without clear separation
5455

5556
### LLM-Generated Content Patterns
@@ -79,6 +80,7 @@ The docs follow a product-area-based information architecture under `docs/toolhi
7980
- **Inbound link coverage for new pages**: When a PR adds a new page, check that it is reachable beyond just the sidebar. New pages need inbound links from related pages (overviews, intros, feature lists, related how-to guides). A page with no inbound links is isolated in the user journey. For major new features, also check whether top-level intro/overview pages should mention the capability.
8081
- **Introduction pages**: Each product section should have an Introduction as the first sidebar child. New sections must follow this pattern.
8182
- **Progressive disclosure**: Core workflows should appear before advanced topics. Check that advanced content isn't mixed in with beginner-facing pages.
83+
- **Pattern consistency for peer concepts**: When a PR introduces a new resource type, feature, or component that has established peers in the doc set, check whether it follows the same documentation pattern (dedicated page, equivalent depth of coverage, parallel sidebar placement). A new CRD documented only as a subsection of another page, when its peer CRDs each have their own page, is a structural gap.
8284
- **Forward navigation path**: A reader should be able to follow "Next steps" links from the quickstart through core workflows without relying on the sidebar.
8385

8486
### Clarity and Readability

.claude/skills/upstream-release-docs/SKILL.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,28 @@ For each PR identified in Phase 1 (skip internal/infra unless user requests):
8585
- Do not attempt to fabricate the "why" or consumer story from code structure alone — this produces documentation that covers the "what" and "how" but misses the perspective and voice that only comes from understanding the product intent
8686
- Incremental changes (new config options, default changes, annotation additions) can proceed without this step
8787

88-
5. **Read the actual source code at the release tag** to verify every claim made in the PR description:
88+
5. **Check related repositories** — components often span multiple repos. For example, a server's CRD/operator may live in a different repo than the server itself. When a release changes config structures, API surfaces, or deployment models, check whether related repos (operators, CLIs, client libraries) have also released changes that affect the documentation. Ask the user which repos are related if unclear.
89+
90+
6. **Read the actual source code at the release tag** to verify every claim made in the PR description:
8991

9092
```bash
9193
gh api repos/<OWNER>/<REPO>/contents/<PATH>?ref=<TAG>
9294
```
9395

9496
The response is base64-encoded; decode it to read the content.
9597

96-
6. Note discrepancies between PR descriptions and actual code. Trust the code.
98+
7. Note discrepancies between PR descriptions and actual code. Trust the code.
99+
100+
8. **Deep-verify behavioral claims** — these are the most common source of documentation inaccuracy. For each feature, verify not just struct definitions but actual runtime behavior:
101+
- **API routes**: Check the actual route registration code (e.g., `r.Get`, `r.Post`, `r.Delete`), not just handler names. Docs often claim endpoints exist at paths where no handler is registered.
102+
- **Required fields**: Check validation code (e.g., `if field == ""` checks), not just struct definitions. A field present in a struct is not necessarily required — only fields checked in validation logic are enforced.
103+
- **Default values**: Check the actual defaulting code or fallback logic, not comments or struct tags. For example, "defaults to main" may actually mean "defaults to the remote's HEAD" in practice.
104+
- **Precedence rules**: Read the actual `if/else` chain. For example, `if commit != "" { ... } else if branch != "" { ... } else if tag != "" { ... }` means commit > branch > tag, not commit > tag > branch.
105+
- **Delete/cleanup behavior**: Check whether the code reassigns pointers, cascades deletes, or leaves orphans. Delete behavior is frequently mis-documented.
106+
- **Query parameters**: Check whether parsed parameters are actually wired to the service layer and database queries. Parameters can be parsed from the URL but silently ignored if no service option or SQL filter exists for them.
107+
- **Containment/authorization direction**: When documenting subset/superset checks, verify which argument is the caller and which is the resource. Getting the direction wrong produces examples that show the opposite of actual behavior.
97108

98-
7. Identify:
109+
9. Identify:
99110
- **Auto-generated content** — files generated from upstream (OpenAPI specs, CLI reference docs, JSON schemas). Do not manually edit these; flag them for automated update instead. However, auto-generated reference docs (e.g., API endpoints from a swagger spec) do **not** replace the need for conceptual explanations, guide content, or cross-references in existing pages. A new feature with auto-generated API docs still needs: (1) a conceptual explanation of what it is and why it exists, (2) mentions and cross-references in related existing pages (intro pages, feature lists, related guides), and (3) guide content if the feature has non-trivial workflows. Only skip creating a **duplicate API reference page** — never skip the surrounding documentation.
100111
- **Hidden/experimental features** — look for indicators like `Hidden: true` in CLI command definitions, feature flags, or internal-only annotations. Do not document these unless the user explicitly asks.
101112

@@ -167,7 +178,7 @@ Apply the approved changes:
167178

168179
## Phase 5: Validation
169180

170-
1. **Re-verify every factual claim** against source code at the tag. This is the third verification pass (after Phase 2 and Phase 4).
181+
1. **Re-verify every factual claim** against source code at the tag. This is the third verification pass (after Phase 2 and Phase 4). For large doc sets, spawn parallel verification agents — one per file or topic area — to check all claims concurrently. Each agent should read the doc file and verify every factual claim (struct fields, API routes, defaults, behavioral logic) against the actual source code at the release tag. Collect and resolve any discrepancies before proceeding.
171182

172183
2. **Build the site** — run the project's build command to check for broken links, missing references, or build errors.
173184

docs/toolhive/integrations/aws-sts.mdx

Lines changed: 134 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -119,20 +119,18 @@ mapping.
119119

120120
### Understanding the AWS MCP Server permission model
121121

122-
AWS MCP Server authorization works in two layers:
122+
AWS MCP Server authorization works at the AWS service level using standard IAM
123+
policies. There are no separate `aws-mcp:*` actions — you grant the AWS service
124+
permissions you want users to have, and the AWS MCP Server enforces them.
123125

124-
1. **MCP layer** (`aws-mcp:*` actions) - controls which categories of MCP tools
125-
the user can invoke
126-
2. **AWS service layer** (e.g., `s3:*`, `ec2:*`) - controls what the
127-
`aws___call_aws` tool can actually do when it makes AWS API calls
126+
Two IAM condition keys let you scope policies specifically to MCP traffic:
128127

129-
The `aws-mcp` namespace defines three actions:
130-
131-
- `InvokeMcp` - required to connect and discover available tools
132-
- `CallReadOnlyTool` - search documentation, list regions, get CLI suggestions
133-
(most tools)
134-
- `CallReadWriteTool` - execute real AWS API calls via the `aws___call_aws` tool
135-
(requires additional service permissions)
128+
- `aws:ViaAWSMCPService` (Bool) — `true` for any request routed through an
129+
AWS-managed MCP server. Use this when you want to allow access via any AWS MCP
130+
server, or to deny direct API access outside of MCP.
131+
- `aws:CalledViaAWSMCP` (String) — identifies the specific MCP server that
132+
originated the request (for example, `aws-mcp.amazonaws.com`). Use this when
133+
you want to restrict access to a particular MCP server endpoint.
136134

137135
### Default role
138136

@@ -163,17 +161,35 @@ condition rejects tokens meant for other services:
163161
}
164162
```
165163

166-
The permission policy grants read-only MCP access. Users can search AWS
167-
documentation and get suggestions, but cannot execute AWS API calls:
164+
The permission policy grants minimal access. It allows `sts:GetCallerIdentity`
165+
as a smoke test (so you can verify the role assumption works), and includes a
166+
deny guardrail that prevents the credentials from being used outside of MCP:
168167

169168
```json title="default-mcp-permissions.json"
170169
{
171170
"Version": "2012-10-17",
172171
"Statement": [
173172
{
173+
"Sid": "AllowMinimalAccessViaMCP",
174174
"Effect": "Allow",
175-
"Action": ["aws-mcp:InvokeMcp", "aws-mcp:CallReadOnlyTool"],
176-
"Resource": "*"
175+
"Action": "sts:GetCallerIdentity",
176+
"Resource": "*",
177+
"Condition": {
178+
"Bool": {
179+
"aws:ViaAWSMCPService": "true"
180+
}
181+
}
182+
},
183+
{
184+
"Sid": "DenyDirectAPIAccess",
185+
"Effect": "Deny",
186+
"Action": "*",
187+
"Resource": "*",
188+
"Condition": {
189+
"BoolIfExists": {
190+
"aws:ViaAWSMCPService": "false"
191+
}
192+
}
177193
}
178194
]
179195
}
@@ -207,34 +223,42 @@ blast radius even if a role's identity policy is overly permissive.
207223

208224
You can create additional roles with different permissions and map them to
209225
specific groups using ToolHive's role mappings. This example creates a role that
210-
grants `CallReadWriteTool` (so the `aws___call_aws` tool can execute API calls)
211-
and scopes the underlying AWS permissions to S3 read-only access:
226+
grants S3 read-only access when using the AWS MCP Server:
212227

213228
```json title="s3-readonly-permissions.json"
214229
{
215230
"Version": "2012-10-17",
216231
"Statement": [
217232
{
233+
"Sid": "AllowS3ReadAccessViaMCP",
218234
"Effect": "Allow",
219-
"Action": [
220-
"aws-mcp:InvokeMcp",
221-
"aws-mcp:CallReadOnlyTool",
222-
"aws-mcp:CallReadWriteTool"
223-
],
224-
"Resource": "*"
235+
"Action": ["s3:GetObject", "s3:ListBucket"],
236+
"Resource": "*",
237+
"Condition": {
238+
"StringEquals": {
239+
"aws:CalledViaAWSMCP": "aws-mcp.amazonaws.com"
240+
}
241+
}
225242
},
226243
{
227-
"Effect": "Allow",
228-
"Action": ["s3:GetObject", "s3:ListBucket"],
229-
"Resource": "*"
244+
"Sid": "DenyDirectAPIAccess",
245+
"Effect": "Deny",
246+
"Action": "*",
247+
"Resource": "*",
248+
"Condition": {
249+
"BoolIfExists": {
250+
"aws:ViaAWSMCPService": "false"
251+
}
252+
}
230253
}
231254
]
232255
}
233256
```
234257

235-
The first statement unlocks the `aws___call_aws` tool. The second statement
236-
limits what that tool can actually do - in this case, only S3 read operations.
237-
Without the S3 permissions, API calls to other services would be denied by IAM.
258+
No `aws-mcp:*` actions are required — you grant only the AWS service permissions
259+
you need. The `aws:CalledViaAWSMCP` condition scopes access to requests
260+
originating from the AWS MCP Server, and the deny guardrail prevents the
261+
temporary credentials from being used outside of MCP.
238262

239263
```bash
240264
aws iam create-role \
@@ -263,6 +287,9 @@ spec:
263287
awsSts:
264288
region: <YOUR_AWS_REGION>
265289

290+
# SigV4 service name for the AWS MCP Server
291+
service: aws-mcp
292+
266293
# Default role when no role mapping matches
267294
fallbackRoleArn: >-
268295
arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:role/DefaultMCPRole
@@ -347,10 +374,13 @@ spec:
347374
# OIDC configuration for validating incoming client tokens
348375
oidcConfig:
349376
type: inline
377+
# Public URL of this proxy's MCP endpoint, advertised to clients
378+
# via WWW-Authenticate so they can discover your OIDC provider.
379+
# Must match the hostname you configure in Step 5.
380+
resourceUrl: https://<YOUR_DOMAIN>/mcp
350381
inline:
351382
issuer: https://<YOUR_OIDC_ISSUER>
352383
audience: <YOUR_OIDC_AUDIENCE>
353-
clientId: <YOUR_OIDC_CLIENT_ID>
354384
355385
proxyPort: 8080
356386
transport: streamable-http
@@ -492,11 +522,19 @@ TOKEN=$(oauth2c https://<YOUR_OIDC_ISSUER> \
492522
--response-types code \
493523
--pkce | jq -r '.access_token')
494524
495-
# This should return a list of tools
525+
# Step 1: initialize the MCP session and capture the session ID
526+
SESSION=$(curl -si -X POST http://localhost:8080/mcp \
527+
-H "Authorization: Bearer $TOKEN" \
528+
-H "Content-Type: application/json" \
529+
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl-test","version":"1.0"}}}' \
530+
| grep -i "^mcp-session-id:" | awk '{print $2}' | tr -d '\r')
531+
532+
# Step 2: list available tools using the session ID
496533
curl -X POST http://localhost:8080/mcp \
497534
-H "Authorization: Bearer $TOKEN" \
498535
-H "Content-Type: application/json" \
499-
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
536+
-H "Mcp-Session-Id: $SESSION" \
537+
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
500538
```
501539

502540
Check the proxy logs to confirm role selection is working:
@@ -508,6 +546,58 @@ kubectl logs -n toolhive-system \
508546

509547
Look for log entries showing role selection and STS exchange results.
510548

549+
## Security best practices
550+
551+
### The deny guardrail pattern
552+
553+
Both policy examples above include a `DenyDirectAPIAccess` statement that uses
554+
`BoolIfExists`:
555+
556+
```json
557+
{
558+
"Sid": "DenyDirectAPIAccess",
559+
"Effect": "Deny",
560+
"Action": "*",
561+
"Resource": "*",
562+
"Condition": {
563+
"BoolIfExists": {
564+
"aws:ViaAWSMCPService": "false"
565+
}
566+
}
567+
}
568+
```
569+
570+
`BoolIfExists` is important here. Without the `IfExists` suffix, the deny would
571+
apply even when the condition key is absent — which would block legitimate STS
572+
operations like `AssumeRoleWithWebIdentity` itself. With `IfExists`, the
573+
condition only evaluates when the key is present:
574+
575+
| `aws:ViaAWSMCPService` present? | Value | Deny applies? |
576+
| ------------------------------- | ------- | ------------- |
577+
| No (key absent) | — | No |
578+
| Yes | `true` | No |
579+
| Yes | `false` | Yes |
580+
581+
This means credentials can only be used when the request flows through an AWS
582+
MCP server. If the temporary credentials are ever extracted and used directly
583+
against the AWS API, the deny fires and access is blocked.
584+
585+
### Choosing between condition keys
586+
587+
Use `aws:ViaAWSMCPService` when you want to:
588+
589+
- Allow or deny access based on whether the request came through _any_
590+
AWS-managed MCP server
591+
- Write deny guardrails that apply regardless of which MCP server is used
592+
593+
Use `aws:CalledViaAWSMCP` when you want to:
594+
595+
- Scope access to a _specific_ MCP server endpoint (for example,
596+
`aws-mcp.amazonaws.com`)
597+
- Prevent credentials from being used with other MCP servers
598+
599+
You can combine both in a single policy for the tightest scoping.
600+
511601
## Observability and audit
512602

513603
ToolHive sets the STS session name to the user's `sub` claim from their JWT.
@@ -550,7 +640,7 @@ aws iam delete-open-id-connect-provider \
550640
arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:oidc-provider/<YOUR_OIDC_ISSUER>
551641
```
552642

553-
## What's next?
643+
## Next steps
554644

555645
- Learn about the concepts behind
556646
[backend authentication](../concepts/backend-auth.mdx) and
@@ -589,19 +679,22 @@ aws iam get-role --role-name DefaultMCPRole \
589679

590680
<details>
591681
<summary>Service access denied: "User is not authorized to perform
592-
aws-mcp:InvokeMcp"</summary>
682+
s3:ListBucket" (or similar)</summary>
593683

594-
This error means the assumed role doesn't have the required permissions. Verify
595-
the permission policy attached to the role includes the necessary actions:
684+
This error means the assumed role doesn't have the required AWS service
685+
permissions. Authorization is now at the service level — there are no
686+
`aws-mcp:*` actions. Verify the permission policy attached to the role includes
687+
the actions the MCP tool needs:
596688

597689
```bash
598690
aws iam get-role-policy \
599-
--role-name DefaultMCPRole \
600-
--policy-name DefaultMCPPolicy
691+
--role-name S3ReadOnlyMCPRole \
692+
--policy-name S3ReadOnlyMCPPolicy
601693
```
602694

603-
Ensure the policy includes `aws-mcp:InvokeMcp` and any other actions your MCP
604-
tools require.
695+
Ensure the policy includes the relevant service actions (for example,
696+
`s3:GetObject`, `s3:ListBucket`) and that the `aws:CalledViaAWSMCP` or
697+
`aws:ViaAWSMCPService` conditions are correctly set.
605698

606699
</details>
607700

docusaurus.config.ts

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -103,24 +103,6 @@ const config: Config = {
103103
},
104104
};
105105
},
106-
function thredSearchSignals() {
107-
return {
108-
name: 'thred-search-signals',
109-
injectHtmlTags() {
110-
if (!isProductionDeploy) return {};
111-
return {
112-
postBodyTags: [
113-
{
114-
tagName: 'script',
115-
attributes: {
116-
src: 'https://cdn.thred.dev/thred-track.js?browserKey=8516ce3e-18ef-4f7f-be44-2bf408657d14',
117-
},
118-
},
119-
],
120-
};
121-
},
122-
};
123-
},
124106
],
125107

126108
// Set the production url of your site here
@@ -216,21 +198,6 @@ const config: Config = {
216198
],
217199
],
218200

219-
scripts: [
220-
// HubSpot tracking script (production only)
221-
...(isProductionDeploy
222-
? [
223-
{
224-
id: 'hs-script-loader',
225-
type: 'text/javascript',
226-
src: '//js-na2.hs-scripts.com/42544743.js',
227-
async: true,
228-
defer: true,
229-
},
230-
]
231-
: []),
232-
],
233-
234201
themeConfig: {
235202
colorMode: {
236203
defaultMode: 'dark',

0 commit comments

Comments
 (0)