Module: OdataDuty::McpServerBuilder

Extended by:
McpServerBuilder
Included in:
McpServerBuilder
Defined in:
lib/odata_duty/mcp_server_builder.rb

Instance Method Summary collapse

Instance Method Details

#build(schema) ⇒ Object



8
9
10
11
12
13
14
15
16
# File 'lib/odata_duty/mcp_server_builder.rb', line 8

def build(schema)
  server = MCP::Server.new(
    name: schema.title,
    version: schema.version,
    capabilities: { tools: {} }
  )
  schema.endpoints.each { |endpoint| register_endpoint_tools(server, schema, endpoint) }
  server
end

#define_tool(server, schema, action, url_for:, **tool_args) ⇒ Object

On the .mutant.yml ignore list: server_context[:context] has only equivalent mutants ([] vs fetch/dig); the key is always present, so no public-API test distinguishes them.



89
90
91
92
93
94
95
# File 'lib/odata_duty/mcp_server_builder.rb', line 89

def define_tool(server, schema, action, url_for:, **tool_args)
  server.define_tool(**tool_args) do |server_context:, **args|
    McpServerBuilder.run_tool(action, url: url_for.call(args), schema: schema,
                                      context: server_context[:context],
                                      query_options: args.transform_keys(&:to_s))
  end
end

#keyed_url_for(endpoint) ⇒ Object

Builds the <url>('<key>') locator from the tool arguments. The dynamic args[key] lookup has no mutation-testable equivalent (the key is always present, enforced by the SDK's required-argument check), so this is the one MCP subject left on the .mutant.yml ignore list.



82
83
84
85
# File 'lib/odata_duty/mcp_server_builder.rb', line 82

def keyed_url_for(endpoint)
  key = endpoint.entity_type.property_refs.first.name
  ->(args) { "#{endpoint.url}('#{args[key]}')" }
end

#register_collection_tools(server, schema, endpoint) ⇒ Object



26
27
28
29
# File 'lib/odata_duty/mcp_server_builder.rb', line 26

def register_collection_tools(server, schema, endpoint)
  register_list_tool(server, schema, endpoint)
  register_count_tool(server, schema, endpoint) if endpoint.supports_count?
end

#register_count_tool(server, schema, endpoint) ⇒ Object



47
48
49
50
51
52
53
# File 'lib/odata_duty/mcp_server_builder.rb', line 47

def register_count_tool(server, schema, endpoint)
  input_schema = McpInputSchemas.count_input_schema(supports_search: endpoint.supports_search?)
  define_tool(server, schema, :execute,
              url_for: ->(_args) { "#{endpoint.url}/$count" },
              name: "count_#{endpoint.name}",
              description: "Count #{endpoint.name} records", input_schema: input_schema)
end

#register_create_tool(server, schema, endpoint) ⇒ Object



55
56
57
58
59
60
61
# File 'lib/odata_duty/mcp_server_builder.rb', line 55

def register_create_tool(server, schema, endpoint)
  define_tool(server, schema, :create,
              url_for: ->(_args) { endpoint.url },
              name: "create_#{endpoint.name}",
              description: "Create a new #{endpoint.name} record",
              input_schema: McpInputSchemas.create_input_schema(endpoint.entity_type))
end

#register_delete_tool(server, schema, endpoint) ⇒ Object



35
36
37
# File 'lib/odata_duty/mcp_server_builder.rb', line 35

def register_delete_tool(server, schema, endpoint)
  register_key_tool(server, schema, endpoint, :delete, 'Delete an existing')
end

#register_endpoint_tools(server, schema, endpoint) ⇒ Object



18
19
20
21
22
23
24
# File 'lib/odata_duty/mcp_server_builder.rb', line 18

def register_endpoint_tools(server, schema, endpoint)
  register_collection_tools(server, schema, endpoint) if endpoint.supports_collection?
  register_get_tool(server, schema, endpoint) if endpoint.supports_individual?
  register_create_tool(server, schema, endpoint) if endpoint.supports_create?
  register_update_tool(server, schema, endpoint) if endpoint.supports_update?
  register_delete_tool(server, schema, endpoint) if endpoint.supports_delete?
end

#register_get_tool(server, schema, endpoint) ⇒ Object



63
64
65
66
67
68
69
# File 'lib/odata_duty/mcp_server_builder.rb', line 63

def register_get_tool(server, schema, endpoint)
  define_tool(server, schema, :execute,
              url_for: keyed_url_for(endpoint),
              name: "get_#{endpoint.name}",
              description: "Get a single #{endpoint.name} record by ID",
              input_schema: McpInputSchemas.get_input_schema(endpoint.entity_type))
end

#register_key_tool(server, schema, endpoint, action, verb) ⇒ Object



71
72
73
74
75
76
77
# File 'lib/odata_duty/mcp_server_builder.rb', line 71

def register_key_tool(server, schema, endpoint, action, verb)
  input_schema = McpInputSchemas.public_send("#{action}_input_schema", endpoint.entity_type)
  define_tool(server, schema, action,
              url_for: keyed_url_for(endpoint),
              name: "#{action}_#{endpoint.name}",
              description: "#{verb} #{endpoint.name} record", input_schema: input_schema)
end

#register_list_tool(server, schema, endpoint) ⇒ Object



39
40
41
42
43
44
45
# File 'lib/odata_duty/mcp_server_builder.rb', line 39

def register_list_tool(server, schema, endpoint)
  input_schema = McpInputSchemas.list_input_schema(supports_search: endpoint.supports_search?)
  define_tool(server, schema, :execute,
              url_for: ->(_args) { endpoint.url },
              name: "list_#{endpoint.name}",
              description: "List #{endpoint.name} records", input_schema: input_schema)
end

#register_update_tool(server, schema, endpoint) ⇒ Object



31
32
33
# File 'lib/odata_duty/mcp_server_builder.rb', line 31

def register_update_tool(server, schema, endpoint)
  register_key_tool(server, schema, endpoint, :update, 'Update an existing')
end

#run_tool(action, url:, schema:, context:, query_options:) ⇒ Object

On the .mutant.yml ignore list: e.message has only an equivalent mutant (e), since an OdataDuty::Error renders identically to its message in the text content block.



99
100
101
102
103
104
105
# File 'lib/odata_duty/mcp_server_builder.rb', line 99

def run_tool(action, url:, schema:, context:, query_options:)
  result = Executor.public_send(action, url: url, context: context,
                                        query_options: query_options, schema: schema)
  MCP::Tool::Response.new([{ type: 'text', text: result.to_s }])
rescue OdataDuty::Error => e
  MCP::Tool::Response.new([{ type: 'text', text: e.message }], error: true)
end