Skip to content

Commit 7be53ce

Browse files
committed
fix(proxy): use async communication for the ui
fix: name fix: update naming fix: add fix: remove keyword
1 parent a4f6554 commit 7be53ce

4 files changed

Lines changed: 163 additions & 405 deletions

File tree

lua/mcphub/extensions/proxy/init.lua

Lines changed: 106 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ function M.get()
3636
url = "",
3737
headers = {},
3838
env = {},
39-
alwaysAllow = true,
4039
}
4140
end
4241

@@ -89,99 +88,122 @@ function M.get_all_servers()
8988
return formatted_servers
9089
end
9190

92-
--- Call a tool via hub.
91+
--- Send result back to proxy.js via notification
92+
---@param request_id string The request ID to respond to
93+
---@param result table The result to send
94+
local function send_result(request_id, result)
95+
vim.rpcnotify(0, "mcphub_proxy_result", request_id, result)
96+
end
97+
98+
--- Async version of call_tool for proxy (uses nice UI)
99+
---@param request_id string Request ID for async response
93100
---@param server_name string Name of the server
94101
---@param tool_name string Name of the tool
95102
---@param params table Parameters including arguments and caller context
96-
---@return table Result or error
97-
function M.call_tool(server_name, tool_name, params)
98-
local mcphub = require("mcphub")
99-
local hub = mcphub.get_hub_instance()
100-
101-
if not hub or not hub:is_ready() then
102-
return { error = "Hub is not ready" }
103-
end
104-
105-
local arguments = params.arguments or {}
106-
local caller = params.caller or { type = "proxy" }
107-
108-
-- Handle approval flow (synchronous version for RPC context)
109-
local parsed_params = shared.parse_params({
110-
server_name = server_name,
111-
tool_name = tool_name,
112-
tool_input = arguments,
113-
}, "use_mcp_tool")
114-
115-
if #parsed_params.errors > 0 then
116-
return { error = table.concat(parsed_params.errors, "\n") }
117-
end
118-
119-
local approval = shared.handle_auto_approval_decision_sync(parsed_params)
120-
if approval.error then
121-
return { error = approval.error }
122-
end
123-
124-
local result, err = hub:call_tool(server_name, tool_name, arguments, {
125-
parse_response = false,
126-
caller = vim.tbl_extend("force", caller, { auto_approve = approval.approve }),
127-
})
128-
129-
if err then
130-
return { error = err }
131-
elseif result and result.result then
132-
return result.result
133-
elseif result then
134-
return result
135-
end
103+
function M.call_tool(request_id, server_name, tool_name, params)
104+
local async = require("plenary.async")
105+
106+
async.run(function()
107+
local mcphub = require("mcphub")
108+
local hub = mcphub.get_hub_instance()
109+
110+
if not hub or not hub:is_ready() then
111+
send_result(request_id, { error = "Hub is not ready" })
112+
return
113+
end
114+
115+
local arguments = params.arguments or {}
116+
local caller = params.caller or { type = "proxy" }
117+
118+
-- Parse and validate params
119+
local parsed_params = shared.parse_params({
120+
server_name = server_name,
121+
tool_name = tool_name,
122+
tool_input = arguments,
123+
}, "use_mcp_tool")
124+
125+
if #parsed_params.errors > 0 then
126+
send_result(request_id, { error = table.concat(parsed_params.errors, "\n") })
127+
return
128+
end
129+
130+
-- Use the async approval with nice UI!
131+
local approval = shared.handle_auto_approval_decision(parsed_params)
132+
if approval.error then
133+
send_result(request_id, { error = approval.error })
134+
return
135+
end
136+
137+
local result, err = hub:call_tool(server_name, tool_name, arguments, {
138+
parse_response = false,
139+
caller = vim.tbl_extend("force", caller, { auto_approve = approval.approve }),
140+
})
136141

137-
return { error = "No result returned from hub" }
142+
if err then
143+
send_result(request_id, { error = err })
144+
elseif result and result.result then
145+
send_result(request_id, result.result)
146+
elseif result then
147+
send_result(request_id, result)
148+
else
149+
send_result(request_id, { error = "No result returned from hub" })
150+
end
151+
end)
138152
end
139153

140-
--- Access a resource via hub.
154+
--- Async version of access_resource for proxy (uses nice UI)
155+
---@param request_id string Request ID for async response
141156
---@param server_name string Name of the server
142157
---@param uri string Resource URI
143158
---@param params? table Optional parameters including caller context
144-
---@return table Result or error
145-
function M.access_resource(server_name, uri, params)
146-
params = params or {}
147-
local mcphub = require("mcphub")
148-
local hub = mcphub.get_hub_instance()
149-
150-
if not hub or not hub:is_ready() then
151-
return { error = "Hub is not ready" }
152-
end
153-
154-
local caller = params.caller or { type = "proxy" }
155-
156-
-- Handle approval flow (same as codecompanion/avante)
157-
local parsed_params = shared.parse_params({
158-
server_name = server_name,
159-
uri = uri,
160-
}, "access_mcp_resource")
161-
162-
if #parsed_params.errors > 0 then
163-
return { error = table.concat(parsed_params.errors, "\n") }
164-
end
165-
166-
local approval = shared.handle_auto_approval_decision_sync(parsed_params)
167-
if approval.error then
168-
return { error = approval.error }
169-
end
170-
171-
local result, err = hub:access_resource(server_name, uri, {
172-
parse_response = false,
173-
caller = vim.tbl_extend("force", caller, { auto_approve = approval.approve }),
174-
})
175-
176-
if err then
177-
return { error = err }
178-
elseif result and result.result then
179-
return result.result
180-
elseif result then
181-
return result
182-
end
159+
function M.access_resource(request_id, server_name, uri, params)
160+
local async = require("plenary.async")
161+
162+
async.run(function()
163+
params = params or {}
164+
local mcphub = require("mcphub")
165+
local hub = mcphub.get_hub_instance()
166+
167+
if not hub or not hub:is_ready() then
168+
send_result(request_id, { error = "Hub is not ready" })
169+
return
170+
end
171+
172+
local caller = params.caller or { type = "proxy" }
173+
174+
-- Parse and validate params
175+
local parsed_params = shared.parse_params({
176+
server_name = server_name,
177+
uri = uri,
178+
}, "access_mcp_resource")
179+
180+
if #parsed_params.errors > 0 then
181+
send_result(request_id, { error = table.concat(parsed_params.errors, "\n") })
182+
return
183+
end
184+
185+
-- Use the async approval with nice UI!
186+
local approval = shared.handle_auto_approval_decision(parsed_params)
187+
if approval.error then
188+
send_result(request_id, { error = approval.error })
189+
return
190+
end
191+
192+
local result, err = hub:access_resource(server_name, uri, {
193+
parse_response = false,
194+
caller = vim.tbl_extend("force", caller, { auto_approve = approval.approve }),
195+
})
183196

184-
return { error = "No result returned from hub" }
197+
if err then
198+
send_result(request_id, { error = err })
199+
elseif result and result.result then
200+
send_result(request_id, result.result)
201+
elseif result then
202+
send_result(request_id, result)
203+
else
204+
send_result(request_id, { error = "No result returned from hub" })
205+
end
206+
end)
185207
end
186208

187209
--- Get a prompt via hub.

lua/mcphub/extensions/shared.lua

Lines changed: 0 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -292,107 +292,6 @@ function M.show_mcp_tool_prompt(params)
292292
return confirmed, cancelled
293293
end
294294

295-
--- Synchronous version for RPC context (can't use async UI)
296-
---@param params MCPHub.ParsedParams
297-
---@return boolean confirmed
298-
---@return boolean cancelled
299-
function M.show_mcp_tool_prompt_sync(params)
300-
local action_name = params.action
301-
local server_name = params.server_name
302-
local tool_name = params.tool_name
303-
local uri = params.uri
304-
local arguments = params.arguments or {}
305-
306-
local lines = {}
307-
local is_tool = action_name == "use_mcp_tool"
308-
309-
-- Header as a question
310-
local header_line = NuiLine()
311-
header_line:append(Text.icons.event, Text.highlights.warn)
312-
header_line:append(" Do you want to ", Text.highlights.text)
313-
if is_tool then
314-
header_line:append("call ", Text.highlights.text)
315-
header_line:append(tool_name, Text.highlights.warn_italic)
316-
else
317-
header_line:append("access ", Text.highlights.text)
318-
header_line:append(uri, Text.highlights.link)
319-
end
320-
header_line:append(" on ", Text.highlights.text)
321-
header_line:append(server_name, Text.highlights.success_italic)
322-
header_line:append("?", Text.highlights.text)
323-
table.insert(lines, header_line)
324-
325-
-- Parameters section
326-
if is_tool and next(arguments) then
327-
table.insert(lines, NuiLine():append(""))
328-
329-
for key, value in pairs(arguments) do
330-
-- Parameter name
331-
local param_name_line = NuiLine()
332-
param_name_line:append(Text.icons.param, Text.highlights.info)
333-
param_name_line:append(" " .. key .. ":", Text.highlights.json_property)
334-
table.insert(lines, param_name_line)
335-
336-
-- Parameter value
337-
local function add_value_lines(val)
338-
if type(val) == "string" then
339-
local value_lines = val:find("\n") and vim.split(val, "\n", { plain = true })
340-
or { '"' .. val .. '"' }
341-
for _, line in ipairs(value_lines) do
342-
local value_line = NuiLine()
343-
value_line:append(" " .. line, Text.highlights.json_string)
344-
table.insert(lines, value_line)
345-
end
346-
elseif type(val) == "boolean" then
347-
local value_line = NuiLine()
348-
value_line:append(" " .. tostring(val), Text.highlights.json_boolean)
349-
table.insert(lines, value_line)
350-
elseif type(val) == "number" then
351-
local value_line = NuiLine()
352-
value_line:append(" " .. tostring(val), Text.highlights.json_number)
353-
table.insert(lines, value_line)
354-
else
355-
for _, line in ipairs(vim.split(vim.inspect(val), "\n", { plain = true })) do
356-
local value_line = NuiLine()
357-
value_line:append(" " .. line, Text.highlights.muted)
358-
table.insert(lines, value_line)
359-
end
360-
end
361-
end
362-
363-
add_value_lines(value)
364-
table.insert(lines, NuiLine():append(""))
365-
end
366-
end
367-
368-
-- Fire event before showing confirmation window
369-
utils.fire("MCPHubApprovalWindowOpened", {
370-
action = action_name,
371-
server_name = server_name,
372-
tool_name = tool_name,
373-
uri = uri,
374-
arguments = arguments,
375-
})
376-
377-
local confirmed, cancelled = ui_utils.confirm_sync(lines, {
378-
min_width = 70,
379-
max_width = 100,
380-
})
381-
382-
-- Fire event after user makes decision
383-
utils.fire("MCPHubApprovalWindowClosed", {
384-
action = action_name,
385-
server_name = server_name,
386-
tool_name = tool_name,
387-
uri = uri,
388-
arguments = arguments,
389-
confirmed = confirmed,
390-
cancelled = cancelled,
391-
})
392-
393-
return confirmed, cancelled
394-
end
395-
396295
--- Check auto-approval status without showing any dialog
397296
---@param parsed_params MCPHub.ParsedParams
398297
---@return {error?:string, approve:boolean}
@@ -437,22 +336,4 @@ function M.handle_auto_approval_decision(parsed_params)
437336
return status
438337
end
439338

440-
--- Synchronous version for RPC context (can't use async UI)
441-
---@param parsed_params MCPHub.ParsedParams
442-
---@return {error?:string, approve:boolean}
443-
function M.handle_auto_approval_decision_sync(parsed_params)
444-
local status = M.check_auto_approval(parsed_params)
445-
446-
if status.error then
447-
return { error = status.error or "Something went wrong with auto-approval", approve = false }
448-
end
449-
450-
if status.approve == false and parsed_params.needs_confirmation_window then
451-
local confirmed, _ = M.show_mcp_tool_prompt_sync(parsed_params)
452-
return { error = not confirmed and "User cancelled the operation", approve = confirmed }
453-
end
454-
455-
return status
456-
end
457-
458339
return M

0 commit comments

Comments
 (0)