From 10c01414d1b04d76e9b3249b91a740ed3a361d5d Mon Sep 17 00:00:00 2001 From: i-norden Date: Wed, 25 Mar 2026 22:54:36 -0400 Subject: [PATCH 1/2] Prefer current DuckDB dataset endpoint --- mcp/client.go | 6 +++--- mcp/client_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 mcp/client_test.go diff --git a/mcp/client.go b/mcp/client.go index 950305f..a5b01d0 100644 --- a/mcp/client.go +++ b/mcp/client.go @@ -521,14 +521,14 @@ func (c *Client) ExecuteSQL(query string) (json.RawMessage, error) { return json.RawMessage(body), nil } -// ListSpatialTables calls GET /api/sql/tables (or fallback /api/query/sql/datasets). +// ListSpatialTables calls GET /api/query/sql/datasets (or fallback /api/sql/tables). func (c *Client) ListSpatialTables() (json.RawMessage, error) { - body, code, err := c.get("/api/sql/tables", nil) + body, code, err := c.get("/api/query/sql/datasets", nil) if err != nil { return nil, err } if code == http.StatusNotFound || code == http.StatusMethodNotAllowed { - body, code, err = c.get("/api/query/sql/datasets", nil) + body, code, err = c.get("/api/sql/tables", nil) if err != nil { return nil, err } diff --git a/mcp/client_test.go b/mcp/client_test.go new file mode 100644 index 0000000..73e334f --- /dev/null +++ b/mcp/client_test.go @@ -0,0 +1,44 @@ +package mcp + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestListSpatialTablesPrefersCurrentDuckDBEndpoint(t *testing.T) { + tablesCalled := false + datasetsCalled := false + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/query/sql/datasets": + datasetsCalled = true + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`[{"name":"roads"}]`)) + case "/api/sql/tables": + tablesCalled = true + http.Error(w, `{"error":"legacy route should not be called first"}`, http.StatusGone) + default: + http.NotFound(w, r) + } + })) + defer srv.Close() + + client := NewClient(srv.URL, "") + client.HTTPClient = srv.Client() + + body, err := client.ListSpatialTables() + if err != nil { + t.Fatalf("ListSpatialTables error: %v", err) + } + if !datasetsCalled { + t.Fatal("expected current /api/query/sql/datasets route to be used") + } + if tablesCalled { + t.Fatal("legacy /api/sql/tables route should not be called when the current endpoint succeeds") + } + if string(body) != `[{"name":"roads"}]` { + t.Fatalf("unexpected body: %s", body) + } +} From e28cee16faeaaf64a963ebd3173e5a0d718810ea Mon Sep 17 00:00:00 2001 From: i-norden Date: Wed, 25 Mar 2026 23:03:47 -0400 Subject: [PATCH 2/2] Use current Cairn SQL endpoints only --- mcp/client.go | 15 +----------- mcp/client_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/mcp/client.go b/mcp/client.go index a5b01d0..8612896 100644 --- a/mcp/client.go +++ b/mcp/client.go @@ -508,31 +508,18 @@ func (c *Client) ExecuteSQL(query string) (json.RawMessage, error) { if err != nil { return nil, err } - if code == http.StatusNotFound || code == http.StatusMethodNotAllowed { - // Backward compatibility for older Roteiro deployments. - body, code, err = c.postJSON("/api/sql/query", map[string]string{"sql": query}) - if err != nil { - return nil, err - } - } if code != http.StatusOK { return nil, fmt.Errorf("POST SQL query endpoint returned %d: %s", code, truncate(body, 500)) } return json.RawMessage(body), nil } -// ListSpatialTables calls GET /api/query/sql/datasets (or fallback /api/sql/tables). +// ListSpatialTables calls GET /api/query/sql/datasets. func (c *Client) ListSpatialTables() (json.RawMessage, error) { body, code, err := c.get("/api/query/sql/datasets", nil) if err != nil { return nil, err } - if code == http.StatusNotFound || code == http.StatusMethodNotAllowed { - body, code, err = c.get("/api/sql/tables", nil) - if err != nil { - return nil, err - } - } if code != http.StatusOK { return nil, fmt.Errorf("GET SQL tables endpoint returned %d: %s", code, truncate(body, 500)) } diff --git a/mcp/client_test.go b/mcp/client_test.go index 73e334f..86f1fd9 100644 --- a/mcp/client_test.go +++ b/mcp/client_test.go @@ -42,3 +42,61 @@ func TestListSpatialTablesPrefersCurrentDuckDBEndpoint(t *testing.T) { t.Fatalf("unexpected body: %s", body) } } + +func TestListSpatialTablesDoesNotFallbackToLegacyEndpoint(t *testing.T) { + tablesCalled := false + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/query/sql/datasets": + http.NotFound(w, r) + case "/api/sql/tables": + tablesCalled = true + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`[{"name":"roads"}]`)) + default: + http.NotFound(w, r) + } + })) + defer srv.Close() + + client := NewClient(srv.URL, "") + client.HTTPClient = srv.Client() + + _, err := client.ListSpatialTables() + if err == nil { + t.Fatal("expected ListSpatialTables to return an error when the current endpoint is unavailable") + } + if tablesCalled { + t.Fatal("legacy /api/sql/tables route should not be called") + } +} + +func TestExecuteSQLDoesNotFallbackToLegacyEndpoint(t *testing.T) { + legacyCalled := false + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/query/sql": + http.NotFound(w, r) + case "/api/sql/query": + legacyCalled = true + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"rows":[{"value":1}]}`)) + default: + http.NotFound(w, r) + } + })) + defer srv.Close() + + client := NewClient(srv.URL, "") + client.HTTPClient = srv.Client() + + _, err := client.ExecuteSQL("SELECT 1") + if err == nil { + t.Fatal("expected ExecuteSQL to return an error when the current endpoint is unavailable") + } + if legacyCalled { + t.Fatal("legacy /api/sql/query route should not be called") + } +}