Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Enhance dynamic endpoint creation with union schema handling #5

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from

Conversation

Nick-PDS
Copy link

Came across an issue where schema wouldn't translate property in a custom MCP I built using unions and discriminator patterns using the example schema below. Introduces some run-time validation based on the union schema.

{
  "anyOf": [
    {
      "type": "object",
      "properties": {
        "operation": {
          "type": "string",
          "const": "getById",
          "description": "Get a page by its ID"
        },
        "useCache": {
          "type": "boolean",
          "default": true,
          "description": "Whether to prioritize cached data"
        },
        "id": {
          "type": "string",
          "description": "Page ID to retrieve"
        },
        "select": {
          "anyOf": [
            {
              "type": "string",
              "description": "Comma-separated list of field names to include in the response"
            },
            {
              "type": "array",
              "items": {
                "type": "string",
                "description": "Field name to include in the response"
              },
              "description": "Array of field names to include in the response"
            }
          ],
          "description": "Fields to include in the response"
        },
        "depth": {
          "type": "number",
          "minimum": 0,
          "maximum": 10,
          "description": "Number (0-10) for relationship population depth"
        }
      },
      "required": [
        "operation",
        "id"
      ],
      "additionalProperties": false,
      "description": "Get a single page by its ID"
    },
    {
      "type": "object",
      "properties": {
        "operation": {
          "type": "string",
          "const": "list",
          "description": "List pages with optional filtering and pagination"
        },
        "useCache": {
          "type": "boolean",
          "default": true,
          "description": "Whether to prioritize cached data"
        },
        "filters": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "where": {
                  "description": "Object of filter conditions with comparison operators"
                },
                "sort": {
                  "anyOf": [
                    {
                      "type": "string",
                      "description": "Field to sort by (prefix with - for descending)"
                    },
                    {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "description": "Field to sort by (prefix with - for descending)"
                      },
                      "description": "Array of fields to sort by"
                    }
                  ],
                  "description": "String or Array for sorting (prefix with - for descending)"
                },
                "limit": {
                  "type": "number",
                  "description": "Number of items per page"
                },
                "page": {
                  "type": "number",
                  "description": "Page number for pagination"
                },
                "depth": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 10,
                  "description": "Number (0-10) for relationship population depth"
                },
                "select": {
                  "$ref": "#/anyOf/0/properties/select",
                  "description": "Fields to include in the response"
                },
                "published": {
                  "type": "boolean",
                  "description": "Filter to only published pages (shorthand for where._status=published)"
                },
                "withBlockType": {
                  "type": "string",
                  "description": "Filter to pages containing a specific block type"
                }
              },
              "additionalProperties": false,
              "description": "Advanced filters for list/count operations (object format)"
            },
            {
              "type": "string",
              "description": "JSON string of filters - WARNING: Always prefer to send an object directly rather than a string"
            }
          ],
          "description": "Optional filters for the list operation"
        }
      },
      "required": [
        "operation"
      ],
      "additionalProperties": false,
      "description": "List pages with optional filtering and pagination"
    },
    {
      "type": "object",
      "properties": {
        "operation": {
          "type": "string",
          "const": "count",
          "description": "Count pages matching criteria"
        },
        "useCache": {
          "type": "boolean",
          "default": true,
          "description": "Whether to prioritize cached data"
        },
        "filters": {
          "$ref": "#/anyOf/1/properties/filters",
          "description": "Optional filters for the count operation"
        }
      },
      "required": [
        "operation"
      ],
      "additionalProperties": false,
      "description": "Count pages matching criteria"
    },
    {
      "type": "object",
      "properties": {
        "operation": {
          "type": "string",
          "const": "getFields",
          "description": "\ud83d\udd11 REQUIRED FIRST STEP: Get field definitions AND verification key for pages collection. You MUST call this before any create/update operations."
        },
        "useCache": {
          "type": "boolean",
          "default": true,
          "description": "Whether to prioritize cached data"
        }
      },
      "required": [
        "operation"
      ],
      "additionalProperties": false,
      "description": "\ud83d\udd11 REQUIRED FIRST STEP: Get field definitions AND verification key needed for page operations"
    },
    {
      "type": "object",
      "properties": {
        "operation": {
          "type": "string",
          "const": "getBlockFields",
          "description": "\ud83d\udd11 REQUIRED STEP: Get field definitions AND verification key for a specific block type. You MUST call this for EACH block type you plan to use."
        },
        "useCache": {
          "type": "boolean",
          "default": true,
          "description": "Whether to prioritize cached data"
        },
        "blockType": {
          "type": "string",
          "description": "Block type to get fields and verification key for (e.g., 'content', 'hero', etc.)"
        }
      },
      "required": [
        "operation",
        "blockType"
      ],
      "additionalProperties": false,
      "description": "\ud83d\udd11 REQUIRED STEP: Get field definitions AND verification key needed for block operations"
    },
    {
      "type": "object",
      "properties": {
        "operation": {
          "type": "string",
          "const": "getBlocksByType",
          "description": "Get blocks of a specific type from a page"
        },
        "useCache": {
          "type": "boolean",
          "default": true,
          "description": "Whether to prioritize cached data"
        },
        "id": {
          "type": "string",
          "description": "Page ID to search blocks in"
        },
        "blockType": {
          "type": "string",
          "description": "Block type to find"
        }
      },
      "required": [
        "operation",
        "id",
        "blockType"
      ],
      "additionalProperties": false,
      "description": "Get blocks of a specific type from a page"
    }
  ],
  "description": "Parameters",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "schema#",
  "type": "object",
  "properties": {}
}

@tjbck
Copy link
Collaborator

tjbck commented Apr 1, 2025

I'll do some refacs here but is this PR tested?

@Nick-PDS
Copy link
Author

Nick-PDS commented Apr 1, 2025

I'll do some refacs here but is this PR tested?

It’s tested, confirmed that this supports union schemas and standard schemas. There aren’t a lot of MCPs utilizing unions but the custom MCP I am using does use union schemas and the output of that schema I provided in the PR message.

Zod’s union methods: https://zod.dev/?id=unions

@tjbck tjbck changed the base branch from main to dev April 2, 2025 23:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants