Skip to content

Commit 7e649be

Browse files
committed
update approval logic
1 parent 7a18a74 commit 7e649be

File tree

11 files changed

+95
-93
lines changed

11 files changed

+95
-93
lines changed

springdoc-openapi-starter-common-mcp/src/main/frontend/src/App.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ function App() {
1616
const [selectedTool, setSelectedTool] = useState<McpToolInfo | null>(null)
1717
const [response, setResponse] = useState<McpToolExecutionResponse | null>(null)
1818
const [activeTab, setActiveTab] = useState<ActiveTab>('tools')
19+
const [lastExecutedArgs, setLastExecutedArgs] = useState<{ toolName: string; args: string } | null>(null)
1920

2021
const { data: tools, isLoading, error } = useTools()
2122
const execution = useToolExecution()
2223
const security = useSecuritySettings()
2324
const { metrics, recordExecution, clearMetrics } = useToolMetrics()
2425

2526
const handleExecute = (toolName: string, args: string) => {
27+
setLastExecutedArgs({ toolName, args })
2628
execution.mutate(
2729
{ request: { toolName, arguments: args }, authHeaders: security.getAuthHeaders() },
2830
{
@@ -34,6 +36,20 @@ function App() {
3436
)
3537
}
3638

39+
const handleApprove = () => {
40+
if (!lastExecutedArgs) return
41+
const { toolName, args } = lastExecutedArgs
42+
execution.mutate(
43+
{ request: { toolName, arguments: args, approved: true }, authHeaders: security.getAuthHeaders() },
44+
{
45+
onSuccess: (data) => {
46+
setResponse(data)
47+
recordExecution(toolName, data)
48+
},
49+
},
50+
)
51+
}
52+
3753
return (
3854
<Layout toolCount={tools?.length ?? 0} activeTab={activeTab} onTabChange={setActiveTab}>
3955
{activeTab === 'tools' ? (
@@ -64,7 +80,13 @@ function App() {
6480
onExecute={handleExecute}
6581
isExecuting={execution.isPending}
6682
/>
67-
{response && <ResponseViewer response={response} />}
83+
{response && (
84+
<ResponseViewer
85+
response={response}
86+
onApprove={response.requiresApproval ? handleApprove : undefined}
87+
isApproving={execution.isPending}
88+
/>
89+
)}
6890
</div>
6991
) : (
7092
<div className="flex items-center justify-center h-full text-gray-400 dark:text-gray-500">

springdoc-openapi-starter-common-mcp/src/main/frontend/src/components/ResponseViewer.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import JsonTreeView from './JsonTreeView'
44

55
interface ResponseViewerProps {
66
response: McpToolExecutionResponse
7+
onApprove?: () => void
8+
isApproving?: boolean
79
}
810

9-
export default function ResponseViewer({ response }: ResponseViewerProps) {
11+
export default function ResponseViewer({ response, onApprove, isApproving }: ResponseViewerProps) {
1012
const [tab, setTab] = useState<'pretty' | 'raw'>('pretty')
1113

1214
const parsedResult = useMemo(() => {
@@ -95,10 +97,19 @@ export default function ResponseViewer({ response }: ResponseViewerProps) {
9597
d="M12 9v2m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"
9698
/>
9799
</svg>
98-
<div>
100+
<div className="flex-1">
99101
<p className="text-sm font-medium text-amber-800 dark:text-amber-300">Human Approval Required</p>
100102
<p className="text-xs text-amber-700 dark:text-amber-400 mt-1">{response.error}</p>
101103
</div>
104+
{onApprove && (
105+
<button
106+
onClick={onApprove}
107+
disabled={isApproving}
108+
className="flex-shrink-0 self-center px-4 py-2 text-sm font-medium text-white bg-amber-600 hover:bg-amber-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg transition-colors"
109+
>
110+
{isApproving ? 'Executing...' : 'Approve & Execute'}
111+
</button>
112+
)}
102113
</div>
103114
) : !response.success ? (
104115
<pre className="text-sm text-red-500 font-mono whitespace-pre-wrap break-all">{displayText}</pre>

springdoc-openapi-starter-common-mcp/src/main/frontend/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface McpToolInfo {
1212
export interface McpToolExecutionRequest {
1313
toolName: string
1414
arguments: string
15+
approved?: boolean
1516
}
1617

1718
export interface McpToolExecutionResponse {

springdoc-openapi-starter-common-mcp/src/main/java/org/springdoc/ai/dashboard/McpDashboardController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public ResponseEntity<McpToolExecutionResponse> executeTool(@RequestBody McpTool
111111
McpToolExecutionResponse firstError = null;
112112
for (McpDashboardToolSource source : toolSources) {
113113
McpToolExecutionResponse response = source.executeTool(request.getToolName(), request.getArguments(),
114-
extraHeaders);
114+
extraHeaders, request.isApproved());
115115
if (response != null) {
116116
if (response.isSuccess()) {
117117
return ResponseEntity.ok(response);

springdoc-openapi-starter-common-mcp/src/main/java/org/springdoc/ai/dashboard/McpDashboardToolSource.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,18 @@ public interface McpDashboardToolSource {
5353
*/
5454
McpToolExecutionResponse executeTool(String toolName, String arguments, Map<String, String> extraHeaders);
5555

56+
/**
57+
* Executes a tool by name with an explicit human approval flag. When
58+
* {@code approved} is {@code true}, the HITL guardrail is bypassed.
59+
* @param toolName the tool name
60+
* @param arguments the JSON arguments string
61+
* @param extraHeaders extra HTTP headers to forward (e.g. Authorization)
62+
* @param approved whether human approval has been granted
63+
* @return the execution response, or null if tool not found in this source
64+
*/
65+
default McpToolExecutionResponse executeTool(String toolName, String arguments, Map<String, String> extraHeaders,
66+
boolean approved) {
67+
return executeTool(toolName, arguments, extraHeaders);
68+
}
69+
5670
}

springdoc-openapi-starter-common-mcp/src/main/java/org/springdoc/ai/dashboard/McpToolExecutionRequest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ public class McpToolExecutionRequest {
4343
*/
4444
private String arguments;
4545

46+
/**
47+
* Whether human approval has been granted for this execution.
48+
*/
49+
private boolean approved;
50+
4651
/**
4752
* Gets tool name.
4853
* @return the tool name
@@ -75,4 +80,20 @@ public void setArguments(String arguments) {
7580
this.arguments = arguments;
7681
}
7782

83+
/**
84+
* Returns whether human approval has been granted.
85+
* @return true if approved
86+
*/
87+
public boolean isApproved() {
88+
return approved;
89+
}
90+
91+
/**
92+
* Sets whether human approval has been granted.
93+
* @param approved true if approved
94+
*/
95+
public void setApproved(boolean approved) {
96+
this.approved = approved;
97+
}
98+
7899
}

springdoc-openapi-starter-common-mcp/src/main/java/org/springdoc/ai/dashboard/ToolCallbackDashboardToolSource.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,21 @@ public List<McpToolInfo> getToolInfos() {
9595

9696
@Override
9797
public McpToolExecutionResponse executeTool(String toolName, String arguments, Map<String, String> extraHeaders) {
98+
return executeTool(toolName, arguments, extraHeaders, false);
99+
}
100+
101+
@Override
102+
public McpToolExecutionResponse executeTool(String toolName, String arguments, Map<String, String> extraHeaders,
103+
boolean approved) {
98104
for (ToolCallbackProvider provider : toolCallbackProviders) {
99105
for (ToolCallback callback : provider.getToolCallbacks()) {
100106
if (callback.getToolDefinition().name().equals(toolName)) {
101107
long start = System.currentTimeMillis();
102108
try {
103109
if (callback instanceof OpenApiToolCallback openApiToolCallback) {
104-
HttpResponse<String> httpResponse = openApiToolCallback.callWithStatusCode(arguments,
105-
extraHeaders);
110+
HttpResponse<String> httpResponse = approved
111+
? openApiToolCallback.callWithStatusCodeApproved(arguments, extraHeaders)
112+
: openApiToolCallback.callWithStatusCode(arguments, extraHeaders);
106113
long duration = System.currentTimeMillis() - start;
107114
int statusCode = httpResponse.statusCode();
108115
boolean success = statusCode >= 200 && statusCode < 300;

springdoc-openapi-starter-common-mcp/src/main/java/org/springdoc/ai/mcp/OpenApiToolCallback.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,17 @@ public HttpResponse<String> callWithStatusCode(String toolInput, Map<String, Str
372372
return executeHttp(toolInput, extraHeaders, !safe ? "BYPASSED" : null);
373373
}
374374

375+
/**
376+
* Executes the tool with explicit human approval, bypassing the HITL guardrail.
377+
* Used by the dashboard when a human operator clicks "Approve &amp; Execute".
378+
* @param toolInput the tool input JSON string
379+
* @param extraHeaders additional headers to include (e.g. Authorization)
380+
* @return the HTTP response with body and status code
381+
*/
382+
public HttpResponse<String> callWithStatusCodeApproved(String toolInput, Map<String, String> extraHeaders) {
383+
return executeHttp(toolInput, extraHeaders, "APPROVED");
384+
}
385+
375386
/**
376387
* Performs the actual HTTP call without any approval guard. Used internally by
377388
* {@link #call} (after approval) and {@link #callWithStatusCode} (when guardrails are

springdoc-openapi-starter-common-mcp/src/main/resources/mcp-ui/assets/index-B5657jO_.css

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)