1717
1818# Toolsearch proxy meta-tools — always pass through; the inner real-tool
1919# call re-enters the middleware via ctx.fastmcp.call_tool() and gets gated there.
20- PROXY_META_TOOLS = frozenset ({
21- "ha_call_read_tool" ,
22- "ha_call_write_tool" ,
23- "ha_call_delete_tool" ,
24- "ha_search_tools" ,
25- })
20+ PROXY_META_TOOLS = frozenset (
21+ {
22+ "ha_call_read_tool" ,
23+ "ha_call_write_tool" ,
24+ "ha_call_delete_tool" ,
25+ "ha_search_tools" ,
26+ }
27+ )
2628
2729
2830class PolicyMiddleware (Middleware ):
@@ -65,24 +67,33 @@ async def on_call_tool(
6567 existing = self ._queue .find (name , args_hash )
6668 if existing and existing .decision == "approved" :
6769 self ._queue .consume_and_maybe_remember (
68- existing , remember_minutes = rule .remember_minutes if rule else 0 ,
70+ existing ,
71+ remember_minutes = rule .remember_minutes if rule else 0 ,
6972 )
7073 return await call_next (context )
7174 if existing and existing .decision == "denied" :
7275 self ._queue .remove (existing .token )
7376 raise self ._denied_error ()
7477
7578 pending = existing or self ._queue .create (
76- name , args_hash , args , ttl_minutes = policy .approval_ttl_minutes ,
79+ name ,
80+ args_hash ,
81+ args ,
82+ ttl_minutes = policy .approval_ttl_minutes ,
7783 )
7884 approval_url = self ._approval_url_builder (pending .token )
7985
80- wait = self ._wait_override if self ._wait_override is not None else policy .wait_seconds
86+ wait = (
87+ self ._wait_override
88+ if self ._wait_override is not None
89+ else policy .wait_seconds
90+ )
8191 await self ._wait_for_decision (context , pending , approval_url , wait )
8292
8393 if pending .decision == "approved" :
8494 self ._queue .consume_and_maybe_remember (
85- pending , remember_minutes = rule .remember_minutes if rule else 0 ,
95+ pending ,
96+ remember_minutes = rule .remember_minutes if rule else 0 ,
8697 )
8798 return await call_next (context )
8899 if pending .decision == "denied" :
@@ -101,7 +112,8 @@ async def _wait_for_decision(
101112 deadline = anyio .current_time () + wait_seconds
102113 while anyio .current_time () < deadline and pending .decision == "pending" :
103114 await self ._safe_report_progress (
104- context , f"Awaiting user approval — open { approval_url } " )
115+ context , f"Awaiting user approval — open { approval_url } "
116+ )
105117 remaining = deadline - anyio .current_time ()
106118 if remaining <= 0 :
107119 break
@@ -118,33 +130,43 @@ async def _safe_report_progress(context: MiddlewareContext, message: str) -> Non
118130
119131 @staticmethod
120132 def _denied_error () -> ToolError :
121- return ToolError (json .dumps ({
122- "success" : False ,
123- "error" : {
124- "code" : "USER_DENIED" ,
125- "message" : "User explicitly denied this tool call." ,
126- "suggestions" : ["Do not retry without confirming with the user first." ],
127- },
128- }))
133+ return ToolError (
134+ json .dumps (
135+ {
136+ "success" : False ,
137+ "error" : {
138+ "code" : "USER_DENIED" ,
139+ "message" : "User explicitly denied this tool call." ,
140+ "suggestions" : [
141+ "Do not retry without confirming with the user first."
142+ ],
143+ },
144+ }
145+ )
146+ )
129147
130148 def _pending_error (self , pending : PendingApproval , approval_url : str ) -> ToolError :
131149 remaining = int ((pending .expires_at - pending .created_at ).total_seconds ())
132- return ToolError (json .dumps ({
133- "success" : False ,
134- "error" : {
135- "code" : "USER_APPROVAL_REQUIRED" ,
136- "message" : (
137- f"User approval required. Open { approval_url } to review the "
138- "exact call and approve. Re-call this tool with the same "
139- "arguments after the user approves."
140- ),
141- "context" : {
142- "approve_url" : approval_url ,
143- "expires_in_seconds" : remaining ,
144- },
145- "suggestions" : [
146- "Tell the user to click the approval link." ,
147- "Re-call this tool with the same arguments after the user approves." ,
148- ],
149- },
150- }))
150+ return ToolError (
151+ json .dumps (
152+ {
153+ "success" : False ,
154+ "error" : {
155+ "code" : "USER_APPROVAL_REQUIRED" ,
156+ "message" : (
157+ f"User approval required. Open { approval_url } to review the "
158+ "exact call and approve. Re-call this tool with the same "
159+ "arguments after the user approves."
160+ ),
161+ "context" : {
162+ "approve_url" : approval_url ,
163+ "expires_in_seconds" : remaining ,
164+ },
165+ "suggestions" : [
166+ "Tell the user to click the approval link." ,
167+ "Re-call this tool with the same arguments after the user approves." ,
168+ ],
169+ },
170+ }
171+ )
172+ )
0 commit comments