|
|
@@ -185,6 +185,41 @@ func TestToolListWithoutEdit(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// TestListToolsOutputSchemasAreObjects guards against the MCP spec violation
|
|
|
+// that made strict clients (for example pydantic-based ones) reject the whole
|
|
|
+// tools/list response: a tool whose outputSchema.type is not the string
|
|
|
+// "object" is invalid whenever an output schema is declared. The check runs
|
|
|
+// against the wire format, which is what strict clients validate.
|
|
|
+func TestListToolsOutputSchemasAreObjects(t *testing.T) {
|
|
|
+ cs, _ := newTestSession(t, true)
|
|
|
+
|
|
|
+ res, err := cs.ListTools(context.Background(), nil)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("ListTools() error = %v", err)
|
|
|
+ }
|
|
|
+ if len(res.Tools) == 0 {
|
|
|
+ t.Fatal("no tools listed")
|
|
|
+ }
|
|
|
+ for _, tool := range res.Tools {
|
|
|
+ if tool.OutputSchema == nil {
|
|
|
+ continue // optional per the MCP spec
|
|
|
+ }
|
|
|
+ raw, err := json.Marshal(tool.OutputSchema)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("marshal outputSchema of %q: %v", tool.Name, err)
|
|
|
+ }
|
|
|
+ var schema struct {
|
|
|
+ Type json.RawMessage `json:"type"`
|
|
|
+ }
|
|
|
+ if err := json.Unmarshal(raw, &schema); err != nil {
|
|
|
+ t.Fatalf("unmarshal outputSchema of %q: %v", tool.Name, err)
|
|
|
+ }
|
|
|
+ if string(schema.Type) != `"object"` {
|
|
|
+ t.Errorf("tool %q outputSchema.type = %s, want \"object\"", tool.Name, schema.Type)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func TestToolListWithEdit(t *testing.T) {
|
|
|
cs, _ := newTestSession(t, true)
|
|
|
|
|
|
@@ -349,12 +384,14 @@ func TestListPagesTool(t *testing.T) {
|
|
|
if res.IsError {
|
|
|
t.Fatalf("IsError = true: %s", toolText(t, res))
|
|
|
}
|
|
|
- var pages []wikijs.PageListItem
|
|
|
- if err := json.Unmarshal([]byte(toolText(t, res)), &pages); err != nil {
|
|
|
+ var out struct {
|
|
|
+ Pages []wikijs.PageListItem `json:"pages"`
|
|
|
+ }
|
|
|
+ if err := json.Unmarshal([]byte(toolText(t, res)), &out); err != nil {
|
|
|
t.Fatalf("decode pages: %v", err)
|
|
|
}
|
|
|
- if len(pages) != 1 || pages[0].ID != 1 {
|
|
|
- t.Errorf("pages = %+v", pages)
|
|
|
+ if len(out.Pages) != 1 || out.Pages[0].ID != 1 {
|
|
|
+ t.Errorf("pages = %+v", out.Pages)
|
|
|
}
|
|
|
|
|
|
req := fake.lastContaining(t, "query GetAllPages")
|