-
Notifications
You must be signed in to change notification settings - Fork 0
Add permission request notification tool and skill #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| --- | ||
| name: notify-on-permission-request | ||
| description: Ask the user for approval before running a risky command by sending a desktop notification via the Agent Notifications MCP tool `notify_permission_request`; include command, reason, agent, risk level, and optional timeout or context URL. | ||
| --- | ||
|
|
||
| # Notify on Permission Request | ||
|
|
||
| Use when an agent needs explicit user consent (e.g., modifying files, running scripts, network calls, package installs, deployments). | ||
|
|
||
| ## MCP tool | ||
|
|
||
| - Tool name: `notify_permission_request` | ||
| - Arguments (all trimmed): | ||
| - `command` (string, required): the command or action being requested. | ||
| - `reason` (string, required): why this is needed. | ||
| - `agent` (string, required): short id of the asking agent/workflow. | ||
| - `risk` ("low" | "medium" | "high", required): risk level. | ||
| - `timeoutSeconds` (int, optional): seconds to wait before timing out/auto-deny. | ||
| - `contextUrl` (string, optional): link to diff/log/ticket for review. | ||
|
|
||
| ## Message rules | ||
|
|
||
| 1. Be explicit that approval is needed; the app shows title `Permission needed` and body lines for command, reason, risk, optional timeout/context. | ||
| 2. Keep it short; stay under the existing ~950 character soft limit. | ||
| 3. Include the most specific command form (with flags/paths) so the user understands the exact action. | ||
| 4. Risk should reflect impact scope (file writes, network, privilege escalation, data exfil, deploys). | ||
| 5. If timeout matters, set `timeoutSeconds`; otherwise omit. | ||
|
|
||
| ## Example | ||
|
|
||
| ```json | ||
| { | ||
| "name": "notify_permission_request", | ||
| "arguments": { | ||
| "command": "npm install && npm test", | ||
| "reason": "Need dependencies to run the test suite", | ||
| "agent": "codex", | ||
| "risk": "medium", | ||
| "timeoutSeconds": 600, | ||
| "contextUrl": "http://localhost:4173/logs/test-run" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## When to skip | ||
|
|
||
| - If the platform already enforced the permission and user confirmed. | ||
| - For read-only or obviously safe actions (e.g., `ls`, `cat`). | ||
|
|
||
| ## Tips | ||
|
|
||
| - Pair with your normal approval loop (chat prompt, CLI confirm); this notification is a heads-up, not an implicit yes. | ||
| - Use `agent` consistently so users can filter/recognize the requester. | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -223,6 +223,53 @@ fn dispatch_notification( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn validate_permission_request_fields( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| command: &str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reason: &str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| agent: &str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| risk: &str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timeout_seconds: Option<i64>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context_url: Option<&str>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> Result<(String, String, String, String, Option<i64>, Option<String>), String> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let command = command.trim(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let reason = reason.trim(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let agent = agent.trim(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let risk = risk.trim().to_ascii_lowercase(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if command.is_empty() || reason.is_empty() || agent.is_empty() || risk.is_empty() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Err("'command', 'reason', 'agent', and 'risk' are required".into()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let risk = match risk.as_str() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "low" | "medium" | "high" => risk, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _ => return Err("'risk' must be one of: low, medium, high".into()), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Some(timeout) = timeout_seconds { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if timeout < 1 || timeout > 86_400 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Err("'timeoutSeconds' must be between 1 and 86400".into()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let context_url = context_url.and_then(|u| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let trimmed = u.trim(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if trimmed.is_empty() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Some(trimmed.to_owned()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| command.to_owned(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reason.to_owned(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| agent.to_owned(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| risk, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timeout_seconds, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context_url, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn notify_tool_descriptor() -> Value { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| json!({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "name": "notify", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -240,6 +287,26 @@ fn notify_tool_descriptor() -> Value { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn notify_permission_tool_descriptor() -> Value { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| json!({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "name": "notify_permission_request", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "description": "Send a desktop notification asking the user to approve a pending command, including command, reason, agent name, risk level, and optional timeout or context URL.", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "inputSchema": { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "type": "object", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "properties": { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "command": { "type": "string", "minLength": 1 }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "reason": { "type": "string", "minLength": 1 }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "agent": { "type": "string", "minLength": 1 }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "risk": { "type": "string", "enum": ["low", "medium", "high"] }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "timeoutSeconds": { "type": "integer", "minimum": 1, "maximum": 86400 }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "contextUrl": { "type": "string", "format": "uri" } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+300
to
+303
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "required": ["command", "reason", "agent", "risk"], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "additionalProperties": false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn jsonrpc_success(id: Value, result: Value) -> Value { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| json!({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "jsonrpc": "2.0", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -346,7 +413,7 @@ async fn mcp_post_handler( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "tools/list" => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let result = json!({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "tools": [notify_tool_descriptor()], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "tools": [notify_tool_descriptor(), notify_permission_tool_descriptor()], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "nextCursor": Value::Null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (StatusCode::OK, Json(jsonrpc_success(id, result))).into_response() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -376,14 +443,6 @@ async fn mcp_post_handler( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .into_response(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if tool_name != "notify" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| StatusCode::OK, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Json(jsonrpc_error(Some(id), -32601, "Tool not found")), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .into_response(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let Some(arguments) = param_obj.get("arguments").and_then(Value::as_object) else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| StatusCode::OK, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -396,56 +455,156 @@ async fn mcp_post_handler( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .into_response(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let title = arguments | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .get("title") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .and_then(Value::as_str) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap_or_default(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let content = arguments | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .get("content") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .and_then(Value::as_str) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap_or_default(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let agent = arguments | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .get("agent") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .and_then(Value::as_str) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap_or_default(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let Ok((title, content, agent)) = validate_notification_fields(title, content, agent) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| StatusCode::OK, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Json(jsonrpc_error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Some(id), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -32602, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Invalid params: 'title', 'content', and 'agent' are required and must be within limits", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .into_response(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| match tool_name { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "notify" => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let title = arguments | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .get("title") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .and_then(Value::as_str) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap_or_default(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let content = arguments | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .get("content") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .and_then(Value::as_str) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap_or_default(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let agent = arguments | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .get("agent") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .and_then(Value::as_str) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap_or_default(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let Ok((title, content, agent)) = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| validate_notification_fields(title, content, agent) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| StatusCode::OK, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Json(jsonrpc_error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Some(id), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -32602, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Invalid params: 'title', 'content', and 'agent' are required and must be within limits", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .into_response(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Err(err) = dispatch_notification(&state, &title, &content, &agent) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eprintln!("{err}"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| StatusCode::OK, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Json(jsonrpc_error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Some(id), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -32000, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Failed to dispatch notification", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .into_response(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Err(err) = dispatch_notification(&state, &title, &content, &agent) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eprintln!("{err}"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| StatusCode::OK, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Json(jsonrpc_error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Some(id), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -32000, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Failed to dispatch notification", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .into_response(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let result = json!({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content": [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "type": "text", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "text": format!("Notification sent: {title}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "isError": false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (StatusCode::OK, Json(jsonrpc_success(id, result))).into_response() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "notify_permission_request" => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let command = arguments | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .get("command") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .and_then(Value::as_str) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap_or_default(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let reason = arguments | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .get("reason") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .and_then(Value::as_str) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap_or_default(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let agent = arguments | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .get("agent") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .and_then(Value::as_str) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap_or_default(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let risk = arguments | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .get("risk") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .and_then(Value::as_str) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap_or_default(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let timeout_seconds = arguments.get("timeoutSeconds").and_then(Value::as_i64); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let context_url = arguments.get("contextUrl").and_then(Value::as_str); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+529
to
+530
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let timeout_seconds = arguments.get("timeoutSeconds").and_then(Value::as_i64); | |
| let context_url = arguments.get("contextUrl").and_then(Value::as_str); | |
| let timeout_seconds = match arguments.get("timeoutSeconds") { | |
| Some(v) => { | |
| if let Some(t) = v.as_i64() { | |
| Some(t) | |
| } else { | |
| return ( | |
| StatusCode::OK, | |
| Json(jsonrpc_error( | |
| Some(id), | |
| -32602, | |
| "Invalid params: permission request fields are required and must be within limits", | |
| )), | |
| ) | |
| .into_response(); | |
| } | |
| } | |
| None => None, | |
| }; | |
| let context_url = match arguments.get("contextUrl") { | |
| Some(v) => { | |
| if let Some(u) = v.as_str() { | |
| Some(u) | |
| } else { | |
| return ( | |
| StatusCode::OK, | |
| Json(jsonrpc_error( | |
| Some(id), | |
| -32602, | |
| "Invalid params: permission request fields are required and must be within limits", | |
| )), | |
| ) | |
| .into_response(); | |
| } | |
| } | |
| None => None, | |
| }; |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The permission-request notification body includes a first line with the agent name ("{agent} needs approval"), but dispatch_notification already prefixes the body with <agent>:. This results in duplicated agent text in the displayed notification (e.g. codex: codex needs approval ...). Consider removing the agent from the body lines (or adjusting dispatch_notification for this tool) so the agent only appears once.
| format!("{agent} needs approval"), | |
| "Permission request".to_string(), |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unlike the notify tool, the permission-request path doesn’t enforce the existing soft content limit (SOFT_CONTENT_LIMIT_CHARS) before calling dispatch_notification. As a result, long command/reason values can be silently truncated by MAX_NOTIFICATION_BODY_CHARS, potentially dropping the most important context. Consider validating the constructed content length (accounting for the <agent>: prefix) and returning -32602 when it exceeds the limit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
timeoutSecondsis documented as "seconds to wait before timing out/auto-deny", but this MCP tool only sends a notification and doesn’t implement any timeout/auto-deny behavior. Consider rewording this to clarify it’s a response deadline for the user/agent workflow (enforcement happens elsewhere), to avoid implying the server will auto-deny.