Fix MCP tool array params to include required 'items' property

What does this MR do and why?

Fixes MCP tool array params to include required 'items' property

Updated ApiTool to generate valid JSON Schema for array parameters. Added test to ensure all MCP tools comply with JSON Schema spec.

Array needs to have items https://json-schema.org/understanding-json-schema/reference/array#items

The bug is reproducible in Copilot chat and leads to the following error

Failed to validate tool mcp_gitlab-mcp_create_issue: Error: tool parameters array type must have items. Please open a Github issue for the MCP server or extension which provides this tool

References

Related to #578261 (closed)

How to set up and validate locally

From gdk rails c run:

manager = Mcp::Tools::Manager.new
tool = manager.get_tool(name: 'create_issue')
puts JSON.pretty_generate(tool.input_schema)

That returns the tool's schema.

Before

    "assignee_ids": {
      "type": "array",
      "description": "The array of user IDs to assign issue"
    }
(full output)
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "description": "The ID or URL-encoded path of the project"
    },
    "title": {
      "type": "string",
      "description": "The title of an issue"
    },
    "description": {
      "type": "string",
      "description": "The description of an issue"
    },
    "assignee_ids": {
      "type": "array",
      "description": "The array of user IDs to assign issue"
    },
    "milestone_id": {
      "type": "integer",
      "description": "The ID of a milestone to assign issue"
    },
    "labels": {
      "type": "string",
      "description": "Comma-separated list of label names"
    },
    "confidential": {
      "type": "boolean",
      "description": "Boolean parameter if the issue should be confidential"
    },
    "epic_id": {
      "type": "integer",
      "description": "The ID of an epic to associate the issue with"
    }
  },
  "required": [
    "id",
    "title"
  ],
  "additionalProperties": false
}

After

    "assignee_ids": {
      "type": "array",
      "items": {
        "type": "integer"
      },
      "description": "The array of user IDs to assign issue"
    }
(full output)
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "description": "The ID or URL-encoded path of the project"
    },
    "title": {
      "type": "string",
      "description": "The title of an issue"
    },
    "description": {
      "type": "string",
      "description": "The description of an issue"
    },
    "assignee_ids": {
      "type": "array",
      "items": {
        "type": "integer"
      },
      "description": "The array of user IDs to assign issue"
    },
    "milestone_id": {
      "type": "integer",
      "description": "The ID of a milestone to assign issue"
    },
    "labels": {
      "type": "string",
      "description": "Comma-separated list of label names"
    },
    "confidential": {
      "type": "boolean",
      "description": "Boolean parameter if the issue should be confidential"
    },
    "epic_id": {
      "type": "integer",
      "description": "The ID of an epic to associate the issue with"
    }
  },
  "required": [
    "id",
    "title"
  ],
  "additionalProperties": false
}

The state of all the other tools

I've also checked other tools we have, and there are no other tools that use an array type.

manager = Mcp::Tools::Manager.new
manager.list_tools.each do |name, tool|
  schema = tool.input_schema
  array_params = schema[:properties].select do |param_name, param_def|
    param_def[:type] == 'array' || param_def[:type].to_s.include?('array')
  end

  puts "Tool: #{name}"
  if array_params.any?
    puts "  Has array parameters:"
    array_params.each do |param_name, param_def|
      has_items = param_def.key?(:items)
      status = has_items ? "VALID" : "INVALID"
      puts "    #{status} #{param_name}: #{param_def.inspect}"
    end
  else
    puts "  No array params"
  end
end

puts

Before

Tool: create_issue
  Has array parameters:
    INVALID assignee_ids: {:type=>"array", :description=>"The array of user IDs to assign issue"}
(full output)
Tool: get_mcp_server_version
  No array params
Tool: get_pipeline_jobs
  No array params
Tool: get_issue
  No array params
Tool: create_issue
  Has array parameters:
    INVALID assignee_ids: {:type=>"array", :description=>"The array of user IDs to assign issue"}
Tool: create_merge_request
  No array params
Tool: get_merge_request
  No array params
Tool: get_merge_request_commits
  No array params
Tool: get_merge_request_diffs
  No array params
Tool: get_merge_request_pipelines
  No array params
Tool: gitlab_search
  No array params
Tool: semantic_code_search
  No array params

After

Tool: create_issue
  Has array parameters:
    VALID assignee_ids: {:type=>"array", :items=>{:type=>"integer"}, :description=>"The array of user IDs to assign issue"}
(full output)
Tool: get_mcp_server_version
  No array params
Tool: get_pipeline_jobs
  No array params
Tool: get_issue
  No array params
Tool: create_issue
  Has array parameters:
    VALID assignee_ids: {:type=>"array", :items=>{:type=>"integer"}, :description=>"The array of user IDs to assign issue"}
Tool: create_merge_request
  No array params
Tool: get_merge_request
  No array params
Tool: get_merge_request_commits
  No array params
Tool: get_merge_request_diffs
  No array params
Tool: get_merge_request_pipelines
  No array params
Tool: gitlab_search
  No array params
Tool: semantic_code_search
  No array params

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Merge request reports

Loading