McpToolAdapter.groovy
7.39 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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/*
* This software is in the public domain under CC0 1.0 Universal plus a
* Grant of Patent License.
*
* To the extent possible under law, author(s) have dedicated all
* copyright and related and neighboring rights to this software to the
* public domain worldwide. This software is distributed without any
* warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software (see the LICENSE.md file). If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
package org.moqui.mcp.adapter
import org.moqui.context.ExecutionContext
import org.slf4j.Logger
import org.slf4j.LoggerFactory
/**
* Adapter that maps MCP tool calls to Moqui services.
* Provides a clean translation layer between MCP protocol and Moqui service framework.
*/
class McpToolAdapter {
protected final static Logger logger = LoggerFactory.getLogger(McpToolAdapter.class)
// MCP tool name → Moqui service name mapping
private static final Map<String, String> TOOL_SERVICE_MAP = [
'moqui_browse_screens': 'McpServices.mcp#BrowseScreens',
'moqui_search_screens': 'McpServices.mcp#SearchScreens',
'moqui_get_screen_details': 'McpServices.mcp#GetScreenDetails',
'moqui_get_help': 'McpServices.mcp#GetHelp'
]
// MCP method → Moqui service name mapping for JSON-RPC methods
private static final Map<String, String> METHOD_SERVICE_MAP = [
'initialize': 'McpServices.mcp#Initialize',
'ping': 'McpServices.mcp#Ping',
'tools/list': 'McpServices.list#Tools',
'tools/call': 'McpServices.mcp#ToolsCall',
'resources/list': 'McpServices.mcp#ResourcesList',
'resources/read': 'McpServices.mcp#ResourcesRead',
'resources/templates/list': 'McpServices.mcp#ResourcesTemplatesList',
'resources/subscribe': 'McpServices.mcp#ResourcesSubscribe',
'resources/unsubscribe': 'McpServices.mcp#ResourcesUnsubscribe',
'prompts/list': 'McpServices.mcp#PromptsList',
'prompts/get': 'McpServices.mcp#PromptsGet',
'roots/list': 'McpServices.mcp#RootsList',
'sampling/createMessage': 'McpServices.mcp#SamplingCreateMessage',
'elicitation/create': 'McpServices.mcp#ElicitationCreate'
]
// Tool descriptions for MCP tool definitions
private static final Map<String, String> TOOL_DESCRIPTIONS = [
'moqui_browse_screens': 'Browse Moqui screen hierarchy and render screen content',
'moqui_search_screens': 'Search for screens by name to find their paths',
'moqui_get_screen_details': 'Get screen field details including dropdown options',
'moqui_get_help': 'Fetch extended documentation for a screen or service'
]
/**
* Call an MCP tool, translating to the appropriate Moqui service
* @param ec The execution context
* @param toolName The MCP tool name
* @param arguments The tool arguments
* @return The result map or error map
*/
Map callTool(ExecutionContext ec, String toolName, Map arguments) {
String serviceName = TOOL_SERVICE_MAP.get(toolName)
if (!serviceName) {
logger.warn("Unknown tool: ${toolName}")
return [error: [code: -32601, message: "Unknown tool: ${toolName}"]]
}
logger.debug("Calling tool ${toolName} -> service ${serviceName} with args: ${arguments}")
try {
ec.artifactExecution.disableAuthz()
def result = ec.service.sync()
.name(serviceName)
.parameters(arguments ?: [:])
.call()
logger.debug("Tool ${toolName} completed successfully")
// Extract result from service response if wrapped
if (result?.containsKey('result')) {
return result.result
}
return result ?: [:]
} catch (Exception e) {
logger.error("Error calling tool ${toolName}: ${e.message}", e)
return [error: [code: -32000, message: e.message]]
} finally {
ec.artifactExecution.enableAuthz()
}
}
/**
* Call an MCP method, translating to the appropriate Moqui service
* @param ec The execution context
* @param method The MCP method name
* @param params The method parameters
* @return The result map or error map
*/
Map callMethod(ExecutionContext ec, String method, Map params) {
String serviceName = METHOD_SERVICE_MAP.get(method)
if (!serviceName) {
logger.warn("Unknown method: ${method}")
return [error: [code: -32601, message: "Method not found: ${method}"]]
}
logger.debug("Calling method ${method} -> service ${serviceName}")
try {
ec.artifactExecution.disableAuthz()
def result = ec.service.sync()
.name(serviceName)
.parameters(params ?: [:])
.call()
logger.debug("Method ${method} completed successfully")
// Extract result from service response if wrapped
if (result?.containsKey('result')) {
return result.result
}
return result ?: [:]
} catch (Exception e) {
logger.error("Error calling method ${method}: ${e.message}", e)
return [error: [code: -32603, message: "Internal error: ${e.message}"]]
} finally {
ec.artifactExecution.enableAuthz()
}
}
/**
* Check if a tool name is valid
* @param toolName The tool name to check
* @return true if the tool is known
*/
boolean isValidTool(String toolName) {
return TOOL_SERVICE_MAP.containsKey(toolName)
}
/**
* Check if a method name is valid (has a service mapping)
* @param method The method name to check
* @return true if the method has a service mapping
*/
boolean isValidMethod(String method) {
return METHOD_SERVICE_MAP.containsKey(method)
}
/**
* Get the service name for a given tool
* @param toolName The tool name
* @return The service name or null if not found
*/
String getServiceForTool(String toolName) {
return TOOL_SERVICE_MAP.get(toolName)
}
/**
* Get the service name for a given method
* @param method The method name
* @return The service name or null if not found
*/
String getServiceForMethod(String method) {
return METHOD_SERVICE_MAP.get(method)
}
/**
* Get the list of available tools with their definitions
* @return List of tool definition maps
*/
List<Map> listTools() {
return TOOL_SERVICE_MAP.keySet().collect { toolName ->
[
name: toolName,
description: TOOL_DESCRIPTIONS.get(toolName) ?: "MCP tool: ${toolName}",
serviceName: TOOL_SERVICE_MAP.get(toolName)
]
}
}
/**
* Get tool description
* @param toolName The tool name
* @return The tool description or null if not found
*/
String getToolDescription(String toolName) {
return TOOL_DESCRIPTIONS.get(toolName)
}
/**
* Get all supported tool names
* @return Set of tool names
*/
Set<String> getToolNames() {
return TOOL_SERVICE_MAP.keySet()
}
/**
* Get all supported method names
* @return Set of method names
*/
Set<String> getMethodNames() {
return METHOD_SERVICE_MAP.keySet()
}
}