Skip to content

packageArguments and runtimeArguments values stripped on ingest — only flag names persisted #851

Description

@snorlaX-sleeps

Bug description

When the registry server ingests upstream server definitions, all fields on model.Argument except Name are discarded for both packageArguments and runtimeArguments. Only the flag name is stored; value, type, default, valueHint, and all other fields are permanently lost at ingest time.

Steps to reproduce

  1. Define a server YAML with packageArguments or runtimeArguments containing value fields, e.g.:
"packageArguments": [
  {"type": "named", "name": "--url", "value": "https://example.com/mcp"},
  {"type": "named", "name": "--timeout", "value": "180"}
]
  1. Ingest the server into the registry
  2. Call GET /registry/<name>/v0.1/servers and inspect the returned packageArguments

Expected behavior

The full model.Argument objects are preserved and returned by the API, including value, type, default, and valueHint fields:

"packageArguments": [
  {"type": "named", "name": "--url", "value": "https://example.com/mcp"},
  {"type": "named", "name": "--timeout", "value": "180"}
]

Actual behavior

Only flag names are returned. Positional arguments (no name) are reduced to {"type": ""} with no recoverable information:

"packageArguments": [
  {"type": "", "name": "--url"},
  {"type": "", "name": "--timeout"}
]

This makes it impossible for clients to render correct launch commands from the registry data.

Environment

  • ToolHive Registry API version: v1.5.0 (latest release, confirmed still present as of 2026-07-20)

Additional context

Root cause: The package_arguments and runtime_arguments DB columns are typed as TEXT[]. On write, extractArgumentValues discards everything except the flag name. On read, toArguments reconstructs with only Name populated:

// internal/sync/writer/db.go and internal/service/db/types.go
func extractArgumentValues(arguments []model.Argument) []string {
    result := make([]string, len(arguments))
    for i, arg := range arguments {
        result[i] = arg.Name  // value, type, default, valueHint all discarded
    }
    return result
}
func toArguments(strings []string) []model.Argument {
    result := make([]model.Argument, len(strings))
    for i, str := range strings {
        result[i] = model.Argument{Name: str}  // only Name restored
    }
    return result
}

Proposed fix: This is the same class of bug fixed for env_vars in PR #371, which changed that column from TEXT to JSONB and updated storage/retrieval to preserve the full object. The fix is identical — migrate package_arguments and runtime_arguments from TEXT[] to JSONB, update extractArgumentValues to serialise the full model.Argument as JSON, and update toArguments to deserialise back to []model.Argument:

func extractArgumentValues(arguments []model.Argument) []byte {
    bytes, _ := json.Marshal(arguments)
    return bytes
}
func toArguments(data []byte) []model.Argument {
    var result []model.Argument
    _ = json.Unmarshal(data, &result)
    return result
}

Impact: Any server relying on packageArguments or runtimeArguments value fields renders incorrectly for all clients. We have worked around this in our internal client by storing argument overrides in _meta.io., but this is not a sustainable solution as it requires duplicating data across both packageArguments and _meta for every affected server.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds-triageIssue needs initial triage by a maintainer

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions