-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsse_handler.lua
More file actions
136 lines (111 loc) · 3.75 KB
/
sse_handler.lua
File metadata and controls
136 lines (111 loc) · 3.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
53
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
-- MCP HTTP Handler: Streamable HTTP transport (POST/GET/DELETE)
-- Single endpoint that routes by HTTP method
-- Entry kind: function.lua
--
-- Wippy HTTP handlers receive no arguments — use http.request() / http.response()
local http = require("http")
local json = require("json")
local jsonrpc = require("jsonrpc")
local handler = require("handler")
-- Shared handler instance (created on first request)
local h = nil
local session_counter = 0
--- Generate a new session ID
local function new_session_id()
session_counter = session_counter + 1
local id = string.format("%x%x", os.time(), session_counter)
return id
end
--- Get or create the shared handler instance
local function get_handler()
if h then
return h
end
h = handler.new({
name = "wippy-mcp",
version = "0.1.0",
capabilities = { tools = true, prompts = true }
})
return h
end
---------------------------------------------------------------------------
-- HTTP method handlers
---------------------------------------------------------------------------
--- Handle POST: JSON-RPC request/response
local function handle_post(req, res)
local mcp = get_handler()
-- Parse JSON body
local data, parse_err = req:body_json()
if parse_err or not data or type(data) ~= "table" then
res:set_status(400)
return res:write_json({error = "Invalid JSON body"})
end
-- Classify the JSON-RPC message
local msg = jsonrpc.classify(data)
-- Session management
local session_id
if msg.kind == "request" and msg.method == "initialize" then
-- New session
session_id = new_session_id()
mcp:create_session(session_id)
else
-- Existing session — read from header
session_id = req:header("Mcp-Session-Id")
if not session_id or not mcp:get_session(session_id) then
res:set_status(400)
return res:write_json({error = "Missing or invalid Mcp-Session-Id header"})
end
end
-- Dispatch through handler chain
local response = mcp:dispatch(session_id, msg)
if response then
-- Set session ID header on initialize response
if msg.kind == "request" and msg.method == "initialize" then
res:set_header("Mcp-Session-Id", session_id)
end
res:set_header("Content-Type", "application/json")
res:write(response)
else
-- Notification — no response body
res:set_status(204)
end
end
--- Handle GET: SSE stream (not yet implemented)
local function handle_get(req, res)
res:set_status(405)
res:write_json({error = "SSE streaming not yet supported"})
end
--- Handle DELETE: close session
local function handle_delete(req, res)
local mcp = get_handler()
local session_id = req:header("Mcp-Session-Id")
if not session_id then
res:set_status(400)
return res:write_json({error = "Missing Mcp-Session-Id header"})
end
if not mcp:get_session(session_id) then
res:set_status(404)
return res:write_json({error = "Session not found"})
end
mcp:delete_session(session_id)
res:set_status(200)
end
---------------------------------------------------------------------------
-- Main handler: route by HTTP method
---------------------------------------------------------------------------
local function handler_fn()
local req = http.request()
local res = http.response()
local method = req:method()
if method == "POST" then
handle_post(req, res)
elseif method == "GET" then
handle_get(req, res)
elseif method == "DELETE" then
handle_delete(req, res)
else
res:set_status(405)
res:write_json({error = "Method not allowed"})
end
end
return { handler = handler_fn }