Skip to content

Conversation

@therealnb
Copy link
Collaborator

@therealnb therealnb commented Dec 3, 2025

Summary

This PR addresses issue #89 by skipping registry ingestion when running in k8s mode, and adds comprehensive Kubernetes deployment examples.

Changes

Issue #89: Skip registry ingestion in k8s mode

  • Added check in ingest_registry() to skip registry ingestion when runtime_mode == "k8s"
  • In k8s mode, workloads come from Kubernetes CRDs, not ToolHive's registry API
  • Registry is not available via ToolHive HTTP API in k8s mode

Additional Changes

  • URL normalization improvements for streamable-http transport
    • Strips URL fragments (#github → removed)
    • Converts /sse paths to /mcp
    • Ensures path ends with /mcp if missing
    • For SSE: preserves original URL (fragments needed for container identification)
  • Enhanced MCP client with better error handling
  • Added comprehensive Kubernetes deployment examples:
    • MCPServer manifests for fetch, github, toolhive-doc-mcp, and mcp-optimizer
    • Helper scripts for managing MCP servers (apply, delete, status)
    • Shared ServiceAccount configuration
    • Ingress configuration
    • Comprehensive README with installation instructions

Related Issues

  • Addresses StacklokLabs/research#89: Call k8s registry API instead of thv registry API when running in k8s

therealnb and others added 9 commits December 2, 2025 15:01
Signed-off-by: nigel brown <nigel@stacklok.com>
Signed-off-by: nigel brown <nigel@stacklok.com>
Signed-off-by: nigel brown <nigel@stacklok.com>
For streamable-http:
  Strips URL fragments (#github → removed)
  Converts /sse paths to /mcp
  Ensures path ends with /mcp if missing
For SSE:
  preserves original URL (fragments needed for container identification)

Signed-off-by: nigel brown <nigel@stacklok.com>
Automated update of ToolHive API models from OpenAPI specification.

Co-authored-by: aponcedeleonch <7890853+aponcedeleonch@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…81)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
@claude
Copy link

claude bot commented Dec 3, 2025

PR Review - Skip registry ingestion in k8s mode and enable OpenTelemetry

Summary

Overall solid PR addressing two distinct issues. The code quality is good, with clear separation of concerns. A few minor improvements recommended.

Key Changes Reviewed

  1. K8s mode registry skip (ingestion.py:1716-1724) ✅
  2. URL normalization (mcp_client.py:42-95) ✅
  3. Error handling improvements (mcp_client.py:97-130) ✅
  4. K8s deployment examples

Issues Found

1. URL Normalization Logic - Redundancy (mcp_client.py:68-73)

elif not path.endswith("/mcp"):
    if path.endswith("/"):
        path = path + "mcp"
    else:
        path = path + "/mcp"

Can be simplified to:

elif not path.endswith("/mcp"):
    path = path.rstrip("/") + "/mcp"

2. Shell Script - Missing Quotes (create-github-secrets.sh:49)
The read -r GITHUB_USERNAME should validate input more robustly. Also line 67 should quote "${GITHUB_USERNAME}" consistently.

3. Inconsistent f-string Usage (mcp_client.py:145, 154, 178, 185)
Mixed use of f-strings and structured logging. Since you're using structlog with kwargs, avoid f-strings in log messages:

# Current
logger.debug(f"Determining proxy mode from proxy_mode field: {proxy_mode_lower}", ...)
# Better
logger.debug("Determining proxy mode from proxy_mode field", proxy_mode=proxy_mode_lower, ...)

4. Potential Breaking Change - URL Modification
The URL normalization changing /sse to /mcp (line 66-67) could break existing deployments if any workloads use /sse endpoints. Consider:

  • Adding migration notes
  • Or checking if endpoint responds before assuming it needs normalization

Positive Observations

  • Clear comments explaining k8s mode behavior
  • Good error message extraction from ExceptionGroups
  • Comprehensive K8s examples with RBAC
  • Shell scripts have proper error handling with set -e

Performance & Security

  • ✅ No performance regressions expected
  • ✅ No security issues found
  • ⚠️ Shell scripts handle secrets properly but could use readonly variables

Recommendations

  1. Simplify path normalization logic
  2. Standardize logging (avoid f-strings with structlog)
  3. Add migration notes for URL normalization behavior change
  4. Consider making shell script variables readonly where applicable

Estimated Fix Time: 10-15 minutes

@therealnb therealnb force-pushed the thv-group-investigation branch from ec3eacd to a6c87eb Compare December 3, 2025 18:43
@therealnb therealnb changed the title Skip registry ingestion in k8s mode and enable OpenTelemetry for toolhive-doc-mcp Skip registry ingestion in k8s mode and add Kubernetes deployment examples Dec 3, 2025
- Updated normalization logic to check if /mcp already exists in path before adding it
- Updated test expectations to account for URL normalization behavior
- Fixes failing tests where URLs like /mcp/test-server were getting /mcp appended
Copy link
Member

@aponcedeleonch aponcedeleonch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the only major comment I have is the _normalize_url logic. I believe we should not mangle with the URL coming from ToolHive

steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these updates coming from a rebase? not sure why they're showing in the diff..

Comment on lines +59 to +96
if proxy_mode == ToolHiveProxyMode.STREAMABLE:
# Strip fragments for streamable-http
# (fragments not supported by streamable-http client)
parsed = urlparse(url)

# Fix path: streamable-http uses /mcp endpoint, not /sse
path = parsed.path
if path.endswith("/sse"):
path = path.replace("/sse", "/mcp")
elif not path.endswith("/mcp"):
# Only add /mcp if the path doesn't already contain /mcp
# This prevents double-adding /mcp to URLs like /mcp/test-server
if "/mcp" not in path:
# If path doesn't end with /mcp or /sse, and doesn't contain /mcp,
# ensure it ends with /mcp
if path.endswith("/"):
path = path + "mcp"
else:
path = path + "/mcp"

# Reconstruct URL without fragment and with corrected path
normalized_tuple = (
parsed.scheme,
parsed.netloc,
path,
parsed.params,
parsed.query,
"", # Empty fragment
)
normalized = str(urlunparse(normalized_tuple))
if normalized != url:
logger.debug(
"Normalized URL for streamable-http",
original_url=url,
normalized_url=normalized,
workload=self.workload.name,
)
return normalized
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you sure we need this logic? I remember we had problems in the past when trying to enforce the URL to follow a pattern. I did a quick search on the specification and there's no mention that the URL of streamable-http transport need to finish in /mcp. I think we need to trust ToolHive and that it gives us the correct URL. If it doesn't then probably it's a bug in ToolHive. Maybe instead of trying to assume and enforce the URL we should log a warning that it doesn't follow the convention of ending in /mcp

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If toolhive gives us the right URLs, then this is a NOP. I'm inclined to leave it in there for now. It was needed to get it working with github. I'll raise a toolhive issue to consider it there.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -1,6 +1,6 @@
# generated by datamodel-codegen:
# filename: http://127.0.0.1:8080/api/openapi.json
# timestamp: 2025-11-02T00:37:46+00:00
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these changes in toolhive/api_models also rebase leftovers?

Copy link
Collaborator Author

@therealnb therealnb Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to ask you about that. There's no actual change, just timestamps.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They come from #92

@therealnb therealnb merged commit dcf4923 into main Dec 4, 2025
14 checks passed
@therealnb therealnb deleted the thv-group-investigation branch December 4, 2025 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants