Skip to content

Fix error with json.RawMessage breaking registry MCP server tool calls - #599

Open
inFocus7 wants to merge 2 commits into
agentregistry-dev:mainfrom
inFocus7:fix-mcp-server-rawmessage-tool-fails
Open

Fix error with json.RawMessage breaking registry MCP server tool calls#599
inFocus7 wants to merge 2 commits into
agentregistry-dev:mainfrom
inFocus7:fix-mcp-server-rawmessage-tool-fails

Conversation

@inFocus7

@inFocus7 inFocus7 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Description

Motivation: On resources that emit json.RawMessage fields, the existing MCP SDK schema parser/validator fails since it expects it to be a byte array. This means no output is ever returned to a caller of MCP tools like deployments.
What Changed: Special handling added for the json.RawMessage type so that it gets parsed as appropriate types.

Change Type

/kind fix

Changelog

Fix issue where the registry mcp server's tool calls error-ed when returning a resource with raw message data.

Additional Notes

Validation Script (place in repo root):

set -euo pipefail

cd "$(dirname "$0")"

API_URL="http://localhost:12121"
MCP_URL="http://localhost:31313"
ARCTL="./bin/arctl"

# Pin arctl to the local daemon; a shell-level ARCTL_API_BASE_URL export
# would otherwise redirect apply/get to a remote registry.
export ARCTL_API_BASE_URL="${API_URL}"
AGENT_NAME="mcp-schema-check-agent"
DEPLOYMENT_NAME="mcp-schema-check"

echo "==> Starting local docker environment (builds server image from this tree)"
make run-docker

echo "==> Waiting for the MCP bridge and API to come up"
for i in $(seq 1 60); do
  if curl -sf "${MCP_URL}/healthz" >/dev/null 2>&1 && "${ARCTL}" get runtimes >/dev/null 2>&1; then
    break
  fi
  if [ "$i" -eq 60 ]; then
    echo "ERROR: environment did not become ready" >&2
    exit 1
  fi
  sleep 2
done

echo "==> Publishing agent + deployment"
MANIFEST="$(mktemp -t mcp-schema-check.XXXXXX).yaml"
trap 'rm -f "${MANIFEST}"' EXIT
cat > "${MANIFEST}" <<EOF
apiVersion: ar.dev/v1alpha1
kind: Agent
metadata:
  name: ${AGENT_NAME}
spec:
  description: "Throwaway agent for MCP bridge schema validation"
  source:
    image: nginx:alpine
---
apiVersion: ar.dev/v1alpha1
kind: Deployment
metadata:
  name: ${DEPLOYMENT_NAME}
spec:
  targetRef:
    kind: Agent
    name: ${AGENT_NAME}
  runtimeRef:
    kind: Runtime
    name: local
EOF
"${ARCTL}" apply -f "${MANIFEST}"

echo "==> Waiting for status.details to be populated (controller reconcile)"
for i in $(seq 1 45); do
  if "${ARCTL}" get deployment "${DEPLOYMENT_NAME}" -o yaml 2>/dev/null | grep -q '^  details:'; then
    break
  fi
  if [ "$i" -eq 45 ]; then
    echo "WARNING: status.details still empty after 90s; the repro needs it populated." >&2
    echo "Inspect with: ${ARCTL} get deployment ${DEPLOYMENT_NAME} -o yaml" >&2
    break
  fi
  sleep 2
done
"${ARCTL}" get deployment "${DEPLOYMENT_NAME}" -o yaml | sed -n '/^status:/,$p'

echo "==> Minting a bearer token for the MCP bridge (local dev JWT key)"
# The daemon compose sets JWT_PRIVATE_KEY, which makes the MCP bridge
# reject anonymous calls. Sign a token with the same well-known dev seed
# via the repo's own auth package.
MINT_DIR=".tmp-mint-mcp-token"
mkdir -p "${MINT_DIR}"
cat > "${MINT_DIR}/main.go" <<'GOEOF'
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/golang-jwt/jwt/v5"

	"github.com/agentregistry-dev/agentregistry/internal/registry/config"
	"github.com/agentregistry-dev/agentregistry/pkg/registry/auth"
)

func main() {
	cfg := &config.Config{JWTPrivateKey: "0000000000000000000000000000000000000000000000000000000000000000"}
	resp, err := auth.NewJWTManager(cfg).GenerateTokenResponse(context.Background(), auth.JWTClaims{
		RegisteredClaims: jwt.RegisteredClaims{
			Subject:   "validate-mcp-deployments",
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
		},
		AuthMethod:  auth.MethodNone,
		Permissions: []auth.Permission{{Action: auth.PermissionActionRead, ResourcePattern: "*"}},
	})
	if err != nil {
		panic(err)
	}
	fmt.Print(resp.RegistryToken)
}
GOEOF
TOKEN="$(go run "./${MINT_DIR}")"
rm -rf "${MINT_DIR}"

cat <<EOF

============================================================
Environment ready. Now run the Claude side manually:

  1. Register the bridge (token is valid 24h; re-run this script to mint
     a fresh one; 'claude mcp remove agentregistry' first if re-adding):
       claude mcp add --transport http agentregistry ${MCP_URL}/ \\
         --header "Authorization: Bearer ${TOKEN}"

  2. In a Claude Code session, ask:
       "Using the agentregistry MCP server, list deployments and
        get the deployment named ${DEPLOYMENT_NAME}."

  Tear down when done: 
    - make down
    - claude mcp remove agentregistry
============================================================
EOF

Note: The script has a hacky way of getting an access token to use because the dev server is backed by the JWT_PRIVATE_KEY, requiring auth, but we don't have an issuer / oauth endpoints configured for that (since we are not an authz server). This means we can only auth via an access token minted, and interactive oauth isn't possible with JWT_PRIVATE_KEY. You could theoretically avoid this by not setting JWT_PRIVATE_KEY on installation which would make it all public.

Without fix (main):

Both calls failed with the same MCP output-schema validation error. Let me find where that schema mismatch comes from in the codebase.

[...]

With fix:

Both calls worked against the agentregistry MCP server. There's exactly one deployment in the registry, and it's the one you asked for:

mcp-schema-check (uid 410d524a-2610-4b51-b4d7-3f8c6edd377a)
- Target: Agent mcp-schema-check-agent
- Runtime: local (docker-compose)
- Created: 2026-07-29 20:47 UTC, last updated 21:59 UTC
- Status conditions:
  - RuntimeConfigured: True — local runtime ready
  - Progressing: True — "docker-compose stack reconciled; waiting for workload convergence"
- Last applied fingerprint: sha256:b1b934bc…

Signed-off-by: Fabian Gonzalez <fabian.gonzalez@solo.io>
Signed-off-by: Fabian Gonzalez <fabian.gonzalez@solo.io>
@inFocus7
inFocus7 marked this pull request as ready for review July 29, 2026 22:08
@inFocus7
inFocus7 enabled auto-merge July 29, 2026 22:09
@inFocus7 inFocus7 changed the title Fix error with RawMessage breaking registry MCP server tool calls Fix error with json.RawMessage breaking registry MCP server tool calls Jul 29, 2026
},
})
if err != nil {
panic(fmt.Sprintf("registryserver: output schema for %v: %v", rt, err))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

this panic would only occur on startup, so it's similar to like regexp.MustCompile() type calls we do which would also panic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant