Class: Tidewave::Middleware

Inherits:
Object
  • Object
show all
Defined in:
lib/tidewave/middleware.rb

Constant Summary collapse

TIDEWAVE_ROUTE =
"tidewave".freeze
MCP_ROUTE =
"mcp".freeze
SHELL_ROUTE =
"shell".freeze
CONFIG_ROUTE =
"config".freeze
INVALID_IP =
"For security reasons, Tidewave does not accept remote connections by default.\n\nIf you really want to allow remote connections, set `config.tidewave.allow_remote_access = true`.\n".freeze

Instance Method Summary collapse

Constructor Details

#initialize(app, config) ⇒ Middleware

Returns a new instance of Middleware.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/tidewave/middleware.rb', line 25

def initialize(app, config)
  @allow_remote_access = config.allow_remote_access
  @client_url = config.client_url
  @team = config.team
  @project_name = Rails.application.class.module_parent.name

  @app = FastMcp.rack_middleware(app,
    name: "tidewave",
    version: Tidewave::VERSION,
    path_prefix: "/" + TIDEWAVE_ROUTE + "/" + MCP_ROUTE,
    transport: Tidewave::StreamableHttpTransport,
    logger: config.logger || Logger.new(Rails.root.join("log", "tidewave.log")),
    # Rails runs the HostAuthorization in dev, so we skip this
    allowed_origins: [],
    # We validate this one in Tidewave::Middleware
    localhost_only: false
  ) do |server|
    server.filter_tools do |request, tools|
      if request.params["include_fs_tools"] != "true"
        tools.reject { |tool| tool.tags.include?(:file_system_tool) }
      else
        tools
      end
    end

    server.register_tools(*Tidewave::Tools::Base.descendants)
  end
end

Instance Method Details

#call(env) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/tidewave/middleware.rb', line 54

def call(env)
  request = Rack::Request.new(env)
  path = request.path.split("/").reject(&:empty?)

  if path[0] == TIDEWAVE_ROUTE
    return forbidden(INVALID_IP) unless valid_client_ip?(request)

    # The MCP routes are handled downstream by FastMCP
    case [ request.request_method, path ]
    when [ "GET", [ TIDEWAVE_ROUTE ] ]
      return home(request)
    when [ "GET", [ TIDEWAVE_ROUTE, CONFIG_ROUTE ] ]
      return config_endpoint(request)
    when [ "POST", [ TIDEWAVE_ROUTE, SHELL_ROUTE ] ]
      return shell(request)
    end
  end

  status, headers, body = @app.call(env)

  # Remove X-Frame-Options headers for non-Tidewave routes to allow embedding.
  # CSP headers are configured in the CSP application environment.
  headers.delete("X-Frame-Options")

  [ status, headers, body ]
end