Module: OdataDuty::McpInputSchemas

Extended by:
McpInputSchemas
Included in:
McpInputSchemas
Defined in:
lib/odata_duty/mcp_input_schemas.rb

Constant Summary collapse

QUERY_OPTION_ALIASES =

$-prefixed OData system query options are not valid Anthropic tool-schema property keys (^[a-zA-Z0-9_.-]{1,64}$), so tool schemas expose these odata_* aliases instead. McpServerBuilder uses the same mapping (inverted) to translate tools/call arguments back.

{
  '$filter' => 'odata_filter',
  '$select' => 'odata_select',
  '$search' => 'odata_search',
  '$top' => 'odata_top',
  '$skip' => 'odata_skip'
}.freeze

Instance Method Summary collapse

Instance Method Details

#add_alias!(properties, entity_type, query_option_key, value, tool_name:) ⇒ Object

Raises when an entity property is literally named like a reserved odata_* alias and would silently overwrite (or be overwritten by) the reserved query-option key in the same properties hash — see McpServerBuilder's tool-name/property-key validation.



42
43
44
45
46
47
48
49
50
51
# File 'lib/odata_duty/mcp_input_schemas.rb', line 42

def add_alias!(properties, entity_type, query_option_key, value, tool_name:)
  key = alias_for(query_option_key)
  if properties.keys.map(&:to_s).include?(key)
    raise InvalidMcpIdentifierError,
          "#{entity_type.name} property \"#{key}\" collides with the reserved #{key} " \
          "query-option key in the #{tool_name} tool input schema"
  end

  properties[key] = value
end

#alias_for(query_option_key) ⇒ Object



81
82
83
# File 'lib/odata_duty/mcp_input_schemas.rb', line 81

def alias_for(query_option_key)
  QUERY_OPTION_ALIASES.fetch(query_option_key)
end

#count_input_schema(supports_search:) ⇒ Object



19
20
21
22
23
# File 'lib/odata_duty/mcp_input_schemas.rb', line 19

def count_input_schema(supports_search:)
  properties = { alias_for('$filter') => { 'type' => 'string' } }
  properties[alias_for('$search')] = { 'type' => 'string' } if supports_search
  { 'properties' => properties, 'required' => [] }
end

#create_input_schema(entity_type) ⇒ Object



53
54
55
56
57
58
# File 'lib/odata_duty/mcp_input_schemas.rb', line 53

def create_input_schema(entity_type)
  writable = entity_type.properties.select(&:settable_on_create?)
  properties = writable.to_h { |p| [p.name, p.to_oas2] }
  required = writable.reject(&:nullable).map(&:name)
  { 'properties' => properties, 'required' => required }
end

#delete_input_schema(entity_type) ⇒ Object



76
77
78
79
# File 'lib/odata_duty/mcp_input_schemas.rb', line 76

def delete_input_schema(entity_type)
  key = entity_type.property_refs.first
  { 'properties' => { key.name => key.to_oas2 }, 'required' => [key.name] }
end

#get_input_schema(entity_type, tool_name:) ⇒ Object



68
69
70
71
72
73
74
# File 'lib/odata_duty/mcp_input_schemas.rb', line 68

def get_input_schema(entity_type, tool_name:)
  key = entity_type.property_refs.first
  properties = { key.name => key.to_oas2 }
  select_value = query_option('string', 'Comma-separated properties to return')
  add_alias!(properties, entity_type, '$select', select_value, tool_name: tool_name)
  { 'properties' => properties, 'required' => [key.name] }
end

#list_input_schema(supports_search:) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/odata_duty/mcp_input_schemas.rb', line 25

def list_input_schema(supports_search:)
  properties = {
    alias_for('$filter') => query_option('string', 'OData $filter expression'),
    alias_for('$select') => query_option('string', 'Comma-separated properties to return')
  }
  if supports_search
    properties[alias_for('$search')] = query_option('string',
                                                    'Search expression (AND, OR, NOT)')
  end
  properties[alias_for('$top')] = query_option('integer', 'Max records to return')
  properties[alias_for('$skip')] = query_option('integer', 'Records to skip')
  { 'properties' => properties, 'required' => [] }
end

#query_option(type, description) ⇒ Object



85
86
87
# File 'lib/odata_duty/mcp_input_schemas.rb', line 85

def query_option(type, description)
  { 'type' => type, 'description' => description }
end

#update_input_schema(entity_type) ⇒ Object



60
61
62
63
64
65
66
# File 'lib/odata_duty/mcp_input_schemas.rb', line 60

def update_input_schema(entity_type)
  key = entity_type.property_refs.first
  writable = entity_type.properties.select(&:settable_on_update?)
  properties = { key.name => key.to_oas2 }
  writable.each { |p| properties[p.name] = p.to_oas2 }
  { 'properties' => properties, 'required' => [key.name] }
end