Class: Otto::MCP::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/otto/mcp/server.rb

Overview

MCP server implementation providing Model Context Protocol endpoints

Constant Summary collapse

UNAUTHENTICATED_WARNING =

Warning emitted when the MCP HTTP endpoint is exposed with no token authentication. Unconditional (not gated on Otto.debug): an unauthenticated MCP endpoint lets any caller invoke every registered tool, so it must be visible in normal boot output.

<<~MSG.gsub(/\s+/, ' ').strip.freeze
  [MCP] HTTP endpoint %s is enabled without authentication:
  any caller can list and invoke MCP tools and resources.
  Pass auth_tokens: ['<token>'] to require a bearer token, or
  allow_unauthenticated: true to acknowledge this intentionally.
MSG

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(otto_instance) ⇒ Server

Returns a new instance of Server.



30
31
32
33
34
# File 'lib/otto/mcp/server.rb', line 30

def initialize(otto_instance)
  @otto_instance = otto_instance
  @protocol      = Protocol.new(otto_instance)
  @enabled       = false
end

Instance Attribute Details

#otto_instanceObject (readonly)

Returns the value of attribute otto_instance.



18
19
20
# File 'lib/otto/mcp/server.rb', line 18

def otto_instance
  @otto_instance
end

#protocolObject (readonly)

Returns the value of attribute protocol.



18
19
20
# File 'lib/otto/mcp/server.rb', line 18

def protocol
  @protocol
end

Class Method Details

.normalize_options(opts = {}, scope = :explicit) ⇒ Object

Normalize raw options into the canonical MCP option hash.

Parameters:

  • opts (Hash) (defaults to: {})

    raw options

  • scope (Symbol) (defaults to: :explicit)

    :explicit (strict, for #enable_mcp!) or :constructor (ignores non-MCP keys, for Otto.new)

See Also:



26
27
28
# File 'lib/otto/mcp/server.rb', line 26

def self.normalize_options(opts = {}, scope = :explicit)
  Otto::MCP::Options.normalize(opts, scope)
end

Instance Method Details

#enable!(options = {}) ⇒ Object

Enable the MCP server.

Enabling is one-shot. Each call appends a route, an endpoint-setting proc and the MCP middleware to the Otto instance without removing the previous set, so a second call with a different endpoint would leave the first endpoint routed but guarded by nothing (the auth middleware only matches the newest endpoint). Rather than try to unwind that, a second call raises.

Parameters:

  • options (Hash) (defaults to: {})

    canonical or aliased options; normalized via normalize_options, so both the canonical keys (:http_endpoint, :auth_tokens, ...) and their mcp_-prefixed spellings (:mcp_endpoint, :mcp_auth_tokens, ...) are accepted.

Raises:

  • (ArgumentError)

    if the server is already enabled



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/otto/mcp/server.rb', line 61

def enable!(options = {})
  if @enabled
    raise ArgumentError,
          "MCP server is already enabled on #{@http_endpoint}; pass all MCP options " \
          'in a single Otto.new or enable_mcp! call'
  end

  options = self.class.normalize_options(options)

  Validator.ensure_available! if options[:enable_validation]
  Otto::Security::RateLimiting.ensure_available! if options[:enable_rate_limiting]

  @enabled               = true
  @http_endpoint         = options[:http_endpoint]
  @auth_tokens           = options[:auth_tokens]
  @enable_validation     = options[:enable_validation]
  @enable_rate_limiting  = options[:enable_rate_limiting]
  @allow_unauthenticated = options[:allow_unauthenticated]

  apply_rate_limits(options)

  # Configure middleware
  configure_middleware(options)

  # Add MCP endpoint route to Otto
  add_mcp_endpoint_route

  warn_if_unauthenticated!

  Otto.logger.info "[MCP] Server enabled with HTTP endpoint: #{@http_endpoint}" if Otto.debug
end

#enabled?Boolean

Returns:

  • (Boolean)


93
94
95
# File 'lib/otto/mcp/server.rb', line 93

def enabled?
  @enabled
end

#register_mcp_route(route_info) ⇒ Object



97
98
99
100
101
102
103
104
# File 'lib/otto/mcp/server.rb', line 97

def register_mcp_route(route_info)
  case route_info[:type]
  when :mcp_resource
    register_resource(route_info)
  when :mcp_tool
    register_tool(route_info)
  end
end