fix: parse MCP result payloads and drop place idempotency_key

Typed result structs replace empty envelopes. Equity place sends
ref_id only so live additionalProperties:false schemas accept the call.
This commit is contained in:
2026-09-01 14:18:39 -05:00
parent f52a19181d
commit a6bf8632ce
22 changed files with 2225 additions and 74 deletions
+111
View File
@@ -0,0 +1,111 @@
package scanner
import (
"encoding/json"
"s1d3sw1ped/robinhood-agentic-mcp/internal/wire"
)
func (r *FilterSpecsResult) UnmarshalJSON(b []byte) error {
type row struct {
FilterType string `json:"filter_type"`
Name string `json:"name"`
}
rows, _, err := wire.UnmarshalRows[row](b, "specs", "filters", "results")
if err != nil {
return err
}
r.Specs = make([]FilterSpec, 0, len(rows))
for _, row := range rows {
r.Specs = append(r.Specs, FilterSpec{FilterType: row.FilterType, Name: row.Name})
}
return nil
}
func (r *DatapointsResult) UnmarshalJSON(b []byte) error {
type row struct {
Name string `json:"name"`
}
rows, _, err := wire.UnmarshalRows[row](b, "datapoints", "results")
if err != nil {
return err
}
r.Datapoints = make([]Datapoint, 0, len(rows))
for _, row := range rows {
r.Datapoints = append(r.Datapoints, Datapoint{Name: row.Name})
}
return nil
}
func unmarshalScan(b []byte) (ScanResult, error) {
var wrap struct {
ID string `json:"id"`
ScanID string `json:"scan_id"`
Title string `json:"title"`
Total int `json:"total"`
Rows []struct {
Symbol string `json:"symbol"`
Ticker string `json:"ticker"`
InstrumentID string `json:"instrument_id"`
} `json:"rows"`
Results []struct {
Symbol string `json:"symbol"`
InstrumentID string `json:"instrument_id"`
} `json:"results"`
}
if err := json.Unmarshal(wire.Unwrap(b), &wrap); err != nil {
return ScanResult{}, err
}
out := ScanResult{ID: wrap.ID, Title: wrap.Title, Total: wrap.Total}
if out.ID == "" {
out.ID = wrap.ScanID
}
src := wrap.Rows
if len(src) == 0 {
for _, row := range wrap.Results {
src = append(src, struct {
Symbol string `json:"symbol"`
Ticker string `json:"ticker"`
InstrumentID string `json:"instrument_id"`
}{Symbol: row.Symbol, InstrumentID: row.InstrumentID})
}
}
for _, row := range src {
sym := row.Symbol
if sym == "" {
sym = row.Ticker
}
out.Rows = append(out.Rows, ScanRow{Symbol: sym, InstrumentID: row.InstrumentID})
}
return out, nil
}
func (r *CreateResult) UnmarshalJSON(b []byte) error {
s, err := unmarshalScan(b)
r.ScanResult = s
return err
}
func (r *PreviewResult) UnmarshalJSON(b []byte) error {
s, err := unmarshalScan(b)
r.ScanResult = s
return err
}
func (r *RunResult) UnmarshalJSON(b []byte) error {
s, err := unmarshalScan(b)
r.ScanResult = s
return err
}
func (r *UpdateFiltersResult) UnmarshalJSON(b []byte) error {
s, err := unmarshalScan(b)
r.ScanResult = s
return err
}
func (r *UpdateConfigResult) UnmarshalJSON(b []byte) error {
s, err := unmarshalScan(b)
r.ScanResult = s
return err
}
+46 -7
View File
@@ -42,14 +42,29 @@ type Column struct {
// FilterSpecsRequest is the argument set for get_scanner_filter_specs (none).
type FilterSpecsRequest struct{}
// FilterSpec is one filter type from get_scanner_filter_specs.
type FilterSpec struct {
FilterType string `json:"filter_type"`
Name string `json:"name"`
}
// FilterSpecsResult is the parsed get_scanner_filter_specs payload.
type FilterSpecsResult struct{}
type FilterSpecsResult struct {
Specs []FilterSpec
}
// DatapointsRequest is the argument set for get_scanner_datapoints (none).
type DatapointsRequest struct{}
// Datapoint is one expression token from get_scanner_datapoints.
type Datapoint struct {
Name string `json:"name"`
}
// DatapointsResult is the parsed get_scanner_datapoints payload.
type DatapointsResult struct{}
type DatapointsResult struct {
Datapoints []Datapoint
}
// ScansRequest is the argument set for get_scans (none).
type ScansRequest struct{}
@@ -74,8 +89,24 @@ type CreateRequest struct {
Title string
}
// ScanResult is a saved scan plus optional live rows.
type ScanResult struct {
ID string
Title string
Rows []ScanRow
Total int
}
// ScanRow is one live scanner match.
type ScanRow struct {
Symbol string
InstrumentID string
}
// CreateResult is the parsed create_scan payload.
type CreateResult struct{}
type CreateResult struct {
ScanResult
}
// PreviewRequest is the argument set for preview_scan.
type PreviewRequest struct {
@@ -84,7 +115,9 @@ type PreviewRequest struct {
}
// PreviewResult is the parsed preview_scan payload.
type PreviewResult struct{}
type PreviewResult struct {
ScanResult
}
// RunRequest is the argument set for run_scan.
type RunRequest struct {
@@ -92,7 +125,9 @@ type RunRequest struct {
}
// RunResult is the parsed run_scan payload.
type RunResult struct{}
type RunResult struct {
ScanResult
}
// UpdateFiltersRequest is the argument set for update_scan_filters.
type UpdateFiltersRequest struct {
@@ -101,7 +136,9 @@ type UpdateFiltersRequest struct {
}
// UpdateFiltersResult is the parsed update_scan_filters payload.
type UpdateFiltersResult struct{}
type UpdateFiltersResult struct {
ScanResult
}
// UpdateConfigRequest is the argument set for update_scan_config.
type UpdateConfigRequest struct {
@@ -112,7 +149,9 @@ type UpdateConfigRequest struct {
}
// UpdateConfigResult is the parsed update_scan_config payload.
type UpdateConfigResult struct{}
type UpdateConfigResult struct {
ScanResult
}
// FilterSpecs calls get_scanner_filter_specs.
func (c *Client) FilterSpecs(ctx context.Context, req FilterSpecsRequest) (FilterSpecsResult, error) {