643b4b58 by Ean Schuessler

Committing WIP

1 parent 8ad686a8
1 # Moqui MCP v2 Server Agent Guide
2
3 This guide explains how to interact with the Moqui MCP v2 server component for AI-Moqui integration using the Model Context Protocol (MCP).
4
5 ## Architecture Overview
6
7 ```
8 moqui-mcp-2/
9 ├── AGENTS.md # This file - MCP server agent guide
10 ├── component.xml # Component configuration
11 ├── entity/ # MCP entity definitions
12 │ └── McpCoreEntities.xml # Core MCP entities
13 ├── service/ # MCP service implementations
14 │ ├── McpDiscoveryServices.xml # Tool discovery services
15 │ ├── McpExecutionServices.xml # Tool execution services
16 │ ├── McpSecurityServices.xml # Session/security services
17 │ └── mcp.rest.xml # REST API endpoints
18 ├── screen/ # Web interface screens
19 │ └── webapp.xml # MCP web interface
20 └── template/ # UI templates
21 └── McpInfo.html.ftl # MCP info page
22 ```
23
24 ## MCP Server Capabilities
25
26 ### Core Features
27 - **Session Management**: Secure session creation, validation, and termination
28 - **Tool Discovery**: Dynamic discovery of available Moqui services and entities
29 - **Tool Execution**: Secure execution of services with proper authorization
30 - **Entity Operations**: Direct entity query capabilities with permission checks
31 - **Health Monitoring**: Server health and status monitoring
32
33 ### Security Model
34 - **Session-based Authentication**: Uses Moqui's built-in user authentication
35 - **Context Tokens**: Secure token-based session validation
36 - **Permission Validation**: All operations checked against Moqui security framework
37 - **Audit Logging**: Complete audit trail of all MCP operations
38
39 ## REST API Endpoints
40
41 ### Session Management
42 ```
43 POST /mcp-2/session
44 - Create new MCP session
45 - Parameters: username, password, clientInfo, ipAddress, userAgent
46 - Returns: sessionId, contextToken, expiration
47
48 POST /mcp-2/session/{visitId}/validate
49 - Validate existing session
50 - Parameters: visitId (from path), contextToken
51 - Returns: session validity, user info
52
53 POST /mcp-2/session/{visitId}/terminate
54 - Terminate active session
55 - Parameters: visitId (from path), contextToken
56 - Returns: termination confirmation
57 ```
58
59 ### Tool Discovery
60 ```
61 POST /mcp-2/tools/discover
62 - Discover available MCP tools
63 - Parameters: userAccountId, visitId, servicePattern, packageName, verb, noun
64 - Returns: List of available tools with metadata
65
66 POST /mcp-2/tools/validate
67 - Validate access to specific tools
68 - Parameters: userAccountId, serviceNames, visitId
69 - Returns: Access validation results
70 ```
71
72 ### Tool Execution
73 ```
74 POST /mcp-2/tools/execute
75 - Execute service as MCP tool
76 - Parameters: sessionId, contextToken, serviceName, parameters, toolCallId
77 - Returns: Service execution results
78
79 POST /mcp-2/tools/entity/query
80 - Execute entity query as MCP tool
81 - Parameters: sessionId, contextToken, serviceName, parameters, toolCallId
82 - Returns: Entity query results
83 ```
84
85 ### Health Check
86 ```
87 GET /mcp-2/health
88 - Server health status
89 - Returns: Server status, version, available endpoints
90 ```
91
92 ## Agent Integration Patterns
93
94 ### 1. Session Initialization
95 ```bash
96 # Create session
97 curl -X POST http://localhost:8080/mcp-2/session \
98 -H "Content-Type: application/json" \
99 -d '{
100 "username": "admin",
101 "password": "admin",
102 "clientInfo": "AI-Agent-v1.0"
103 }'
104
105 # Response: { "sessionId": "12345", "contextToken": "abc123", "expires": "2025-01-01T00:00:00Z" }
106 ```
107
108 ### 2. Tool Discovery Workflow
109 ```bash
110 # Discover available tools
111 curl -X POST http://localhost:8080/mcp-2/tools/discover \
112 -H "Content-Type: application/json" \
113 -H "Authorization: Bearer abc123" \
114 -d '{
115 "userAccountId": "ADMIN",
116 "visitId": "12345",
117 "servicePattern": "org.moqui.*"
118 }'
119
120 # Response: { "tools": [ { "name": "create#Order", "description": "...", "parameters": [...] } ] }
121 ```
122
123 ### 3. Tool Execution Pattern
124 ```bash
125 # Execute a service
126 curl -X POST http://localhost:8080/mcp-2/tools/execute \
127 -H "Content-Type: application/json" \
128 -H "Authorization: Bearer abc123" \
129 -d '{
130 "sessionId": "12345",
131 "contextToken": "abc123",
132 "serviceName": "org.moqui.example.Services.create#Example",
133 "parameters": { "field1": "value1", "field2": "value2" },
134 "toolCallId": "call_123"
135 }'
136
137 # Response: { "result": { "exampleId": "EX123" }, "success": true }
138 ```
139
140 ## Security Considerations
141
142 ### Authentication Requirements
143 - All MCP operations require valid session authentication
144 - Context tokens must be included in all requests after session creation
145 - Sessions expire based on configured timeout settings
146
147 ### Permission Validation
148 - Tool access validated against Moqui user permissions
149 - Entity operations respect Moqui entity access controls
150 - Service execution requires appropriate service permissions
151
152 ### Audit and Logging
153 - All MCP operations logged to Moqui audit system
154 - Session creation, tool discovery, and execution tracked
155 - Failed authentication attempts monitored
156
157 ## Error Handling
158
159 ### Common Error Responses
160 ```json
161 {
162 "error": "INVALID_SESSION",
163 "message": "Session expired or invalid",
164 "code": 401
165 }
166
167 {
168 "error": "PERMISSION_DENIED",
169 "message": "User lacks permission for tool",
170 "code": 403
171 }
172
173 {
174 "error": "TOOL_NOT_FOUND",
175 "message": "Requested tool not available",
176 "code": 404
177 }
178 ```
179
180 ### Error Recovery Strategies
181 - **Session Expiration**: Re-authenticate and create new session
182 - **Permission Issues**: Request additional permissions or use alternative tools
183 - **Tool Not Found**: Re-discover available tools and update tool registry
184
185 ## Performance Optimization
186
187 ### Caching Strategies
188 - Tool discovery results cached for session duration
189 - Permission validation results cached per user
190 - Entity query results cached based on Moqui cache settings
191
192 ### Connection Management
193 - Use persistent HTTP connections where possible
194 - Implement session pooling for high-frequency operations
195 - Monitor session lifecycle and cleanup expired sessions
196
197 ## Integration Examples
198
199 ### Python Client Example
200 ```python
201 import requests
202 import json
203
204 class MoquiMCPClient:
205 def __init__(self, base_url, username, password):
206 self.base_url = base_url
207 self.session_id = None
208 self.context_token = None
209 self.authenticate(username, password)
210
211 def authenticate(self, username, password):
212 response = requests.post(f"{self.base_url}/mcp-2/session", json={
213 "username": username,
214 "password": password
215 })
216 data = response.json()
217 self.session_id = data["sessionId"]
218 self.context_token = data["contextToken"]
219
220 def discover_tools(self, pattern="*"):
221 response = requests.post(f"{self.base_url}/mcp-2/tools/discover",
222 headers={"Authorization": f"Bearer {self.context_token}"},
223 json={"servicePattern": pattern}
224 )
225 return response.json()
226
227 def execute_tool(self, service_name, parameters):
228 response = requests.post(f"{self.base_url}/mcp-2/tools/execute",
229 headers={"Authorization": f"Bearer {self.context_token}"},
230 json={
231 "sessionId": self.session_id,
232 "contextToken": self.context_token,
233 "serviceName": service_name,
234 "parameters": parameters
235 }
236 )
237 return response.json()
238 ```
239
240 ### JavaScript Client Example
241 ```javascript
242 class MoquiMCPClient {
243 constructor(baseUrl, username, password) {
244 this.baseUrl = baseUrl;
245 this.authenticate(username, password);
246 }
247
248 async authenticate(username, password) {
249 const response = await fetch(`${this.baseUrl}/mcp-2/session`, {
250 method: 'POST',
251 headers: { 'Content-Type': 'application/json' },
252 body: JSON.stringify({ username, password })
253 });
254 const data = await response.json();
255 this.sessionId = data.sessionId;
256 this.contextToken = data.contextToken;
257 }
258
259 async discoverTools(pattern = '*') {
260 const response = await fetch(`${this.baseUrl}/mcp-2/tools/discover`, {
261 method: 'POST',
262 headers: {
263 'Content-Type': 'application/json',
264 'Authorization': `Bearer ${this.contextToken}`
265 },
266 body: JSON.stringify({ servicePattern: pattern })
267 });
268 return response.json();
269 }
270
271 async executeTool(serviceName, parameters) {
272 const response = await fetch(`${this.baseUrl}/mcp-2/tools/execute`, {
273 method: 'POST',
274 headers: {
275 'Content-Type': 'application/json',
276 'Authorization': `Bearer ${this.contextToken}`
277 },
278 body: JSON.stringify({
279 sessionId: this.sessionId,
280 contextToken: this.contextToken,
281 serviceName,
282 parameters
283 })
284 });
285 return response.json();
286 }
287 }
288 ```
289
290 ## Monitoring and Debugging
291
292 ### Health Monitoring
293 - Monitor `/mcp-2/health` endpoint for server status
294 - Track session creation and termination rates
295 - Monitor tool execution success/failure rates
296
297 ### Debug Information
298 - Check Moqui logs for MCP-related errors
299 - Use Moqui's built-in monitoring tools
300 - Monitor REST API access patterns
301
302 ### Performance Metrics
303 - Session creation time
304 - Tool discovery response time
305 - Tool execution duration
306 - Concurrent session limits
307
308 ## Configuration
309
310 ### Component Configuration
311 - Session timeout settings in `component.xml`
312 - Security permission mappings
313 - Cache configuration for performance
314
315 ### Runtime Configuration
316 - Database connection settings
317 - REST API endpoint configuration
318 - Security context configuration
319
320 ---
321
322 **This guide focuses on MCP server interaction patterns and agent integration.**
323 For Moqui framework integration details, see the main project AGENTS.md files.
324 For component-specific implementation details, refer to individual service files.
...\ No newline at end of file ...\ No newline at end of file
1 # Moqui MCP Server v0.2.0
2
3 ## Overview
4 Simplified MCP server that uses Moqui's native service engine directly, eliminating need for manual tool registry management.
5
6 ## Evolution from v0.1.0 (moqui-mcp) to v0.2.0 (moqui-mcp-2)
7
8 ### Architectural Problems Identified in v0.1.0
9
10 The original moqui-mcp implementation had significant architectural duplications with Moqui's native systems:
11
12 1. **McpToolRegistry vs Service Engine**
13 - Manual tool registry duplicated Moqui's service discovery
14 - Required manual maintenance of tool definitions
15 - Added unnecessary database queries and complexity
16
17 2. **McpAuditLog vs ArtifactHit**
18 - Custom audit logging duplicated Moqui's proven ArtifactHit system
19 - Missed built-in performance monitoring and aggregation
20 - Separate audit trail instead of unified system-wide analytics
21
22 3. **McpSession vs Visit Entity**
23 - Custom session management duplicated Moqui's Visit entity
24 - Lost geo-location, referrer tracking, and other built-in features
25 - Separate session lifecycle instead of leveraging existing patterns
26
27 4. **McpToolCall vs ArtifactHit**
28 - Custom tool call tracking duplicated ArtifactHit functionality
29 - Manual performance tracking instead of automatic aggregation
30 - Separate error handling and logging
31
32 ### v0.2.0 Architectural Solutions
33
34 **Complete Elimination of Duplications**:
35 - ❌ McpToolRegistry → ✅ Service Engine (`ec.service.getServiceNames()`)
36 - ❌ McpAuditLog → ✅ ArtifactHit (native audit & performance)
37 - ❌ McpSession → ✅ Visit Entity (extended with MCP fields)
38 - ❌ McpToolCall → ✅ ArtifactHit (with `artifactType="MCP"`)
39
40 **Key Architectural Changes**:
41
42 1. **Native Service Discovery**
43 ```groovy
44 // Before: Query McpToolRegistry
45 def tools = ec.entity.find("McpToolRegistry").list()
46
47 // After: Direct service engine access
48 def allServiceNames = ec.service.getServiceNames()
49 def hasPermission = ec.service.hasPermission(serviceName)
50 ```
51
52 2. **Unified Session Management**
53 ```xml
54 <!-- Before: Custom McpSession entity -->
55 <entity entity-name="McpSession" package="org.moqui.mcp">
56
57 <!-- After: Extend native Visit entity -->
58 <extend-entity entity-name="Visit" package="moqui.server">
59 <field name="mcpContextToken" type="text-medium"/>
60 <field name="mcpStatusId" type="id"/>
61 <field name="mcpExpiresDate" type="date-time"/>
62 </extend-entity>
63 ```
64
65 3. **System-Wide Analytics**
66 ```sql
67 -- Before: Separate MCP tracking
68 SELECT * FROM McpToolCall ORDER BY startTime DESC;
69 SELECT * FROM McpAuditLog WHERE severity = 'McpSeverityWarning';
70
71 -- After: Unified system tracking
72 SELECT * FROM moqui.server.ArtifactHit
73 WHERE artifactType = 'MCP' ORDER BY startDateTime DESC;
74 SELECT * FROM moqui.server.ArtifactHitReport
75 WHERE artifactType = 'MCP';
76 ```
77
78 ### Benefits Achieved
79
80 **Zero Maintenance**:
81 - No manual tool registry updates
82 - Automatic reflection of actual Moqui services
83 - No custom audit infrastructure to maintain
84
85 **Maximum Performance**:
86 - Direct service engine access (no extra lookups)
87 - Native caching and optimization
88 - Built-in performance monitoring
89
90 **Unified Analytics**:
91 - MCP operations visible in system-wide reports
92 - Automatic aggregation via ArtifactHitBin
93 - Standard reporting tools and dashboards
94
95 **Architectural Purity**:
96 - Follows Moqui's established patterns
97 - Leverages proven native systems
98 - REST service pattern (MCP as service invocation layer)
99
100 ### Technical Implementation
101
102 **MCP Operation Flow**:
103 1. Session creation `Visit` record with MCP extensions
104 2. Tool discovery `ec.service.getServiceNames()` + permission check
105 3. Tool execution Direct service call + `ArtifactHit` creation
106 4. Analytics Native `ArtifactHitReport` aggregation
107
108 **Data Model**:
109 - Sessions: `moqui.server.Visit` (extended)
110 - Operations: `moqui.server.ArtifactHit` (filtered)
111 - Performance: `moqui.server.ArtifactHitBin` (automatic)
112 - Security: `moqui.security.UserGroupPermission` (native)
113
114 This evolution represents a complete architectural alignment with Moqui's core design principles while maintaining full MCP functionality.
115
116 ## Quick Start
117
118 ### 1. Add Component to Moqui
119 Add to your `MoquiConf.xml`:
120 ```xml
121 <component name="moqui-mcp-2" location="moqui-mcp-2/"/>
122 ```
123
124 ### 2. Start Moqui
125 ```bash
126 ./gradlew run
127 ```
128
129 ### 3. Test MCP Server
130 ```bash
131 # Health check
132 curl http://localhost:8080/mcp-2/health
133
134 # Create session
135 curl -X POST http://localhost:8080/mcp-2/session \
136 -H "Content-Type: application/json" \
137 -d '{"username": "admin", "password": "admin"}'
138 ```
139
140 ## Integration with Opencode
141
142 ### MCP Skill Configuration
143 Add to your Opencode configuration:
144
145 ```json
146 {
147 "skills": [
148 {
149 "name": "moqui-mcp",
150 "type": "mcp",
151 "endpoint": "http://localhost:8080/mcp-2",
152 "auth": {
153 "type": "session",
154 "username": "admin",
155 "password": "admin"
156 },
157 "description": "Query business data from Moqui ERP system"
158 }
159 ]
160 }
161 ```
162
163 ### Example Business Questions
164 Once configured, you can ask:
165
166 - "Show me all active customers"
167 - "What are recent orders for ACME Corp?"
168 - "List all products in inventory"
169 - "Find unpaid invoices"
170 - "Show user accounts with admin permissions"
171
172 ## API Endpoints
173
174 ### Session Management
175 - `POST /session` - Create session
176 - `POST /session/{visitId}/validate` - Validate session
177 - `POST /session/{visitId}/terminate` - Terminate session
178
179 ### Tool Operations
180 - `POST /tools/discover` - Discover available tools
181 - `POST /tools/validate` - Validate tool access
182 - `POST /tools/execute` - Execute service tool
183 - `POST /tools/entity/query` - Execute entity query
184
185 ### System
186 - `GET /health` - Server health check
187
188 ## Security Model
189
190 ### Permission-Based Access
191 Tools are discovered based on user's `UserGroupPermission` settings:
192 - Services: `ec.service.hasPermission(serviceName)`
193 - Entities: `ec.user.hasPermission("entity:EntityName", "VIEW")`
194
195 ### Audit Trail
196 All operations are tracked via Moqui's native `ArtifactHit` system:
197 - Automatic performance monitoring
198 - Built-in security event tracking
199 - Configurable persistence (database/ElasticSearch)
200 - Standard reporting via `ArtifactHitReport`
201
202 ## Monitoring
203
204 ### Check Logs
205 ```bash
206 # MCP operations
207 tail -f moqui.log | grep "MCP"
208
209 # Session activity
210 tail -f moqui.log | grep "Visit"
211 ```
212
213 ### Database Queries
214 ```sql
215 -- MCP operations via ArtifactHit
216 SELECT * FROM moqui.server.ArtifactHit
217 WHERE artifactType = 'MCP'
218 ORDER BY startDateTime DESC LIMIT 10;
219
220 -- MCP sessions (using Visit entity)
221 SELECT * FROM moqui.server.Visit
222 WHERE mcpStatusId = 'McsActive' AND webappName = 'mcp-2';
223
224 -- MCP service operations
225 SELECT * FROM moqui.server.ArtifactHit
226 WHERE artifactType = 'MCP' AND artifactSubType = 'Service'
227 ORDER BY startDateTime DESC LIMIT 10;
228
229 -- MCP entity operations
230 SELECT * FROM moqui.server.ArtifactHit
231 WHERE artifactType = 'MCP' AND artifactSubType = 'Entity'
232 ORDER BY startDateTime DESC LIMIT 10;
233
234 -- Performance analytics
235 SELECT * FROM moqui.server.ArtifactHitReport
236 WHERE artifactType = 'MCP';
237 ```
238
239 ## Next Steps
240
241 1. **Test Basic Functionality**: Verify session creation and tool discovery
242 2. **Configure Opencode Skill**: Add MCP skill to your Opencode instance
243 3. **Test Business Queries**: Try natural language business questions
244 4. **Monitor Performance**: Check logs and audit tables
245 5. **Extend as Needed**: Add custom services/entities for specific business logic
246
247 ## Architecture Benefits
248
249 - **Zero Maintenance**: No manual tool registry updates
250 - **Always Current**: Reflects actual Moqui services in real-time
251 - **Secure**: Uses Moqui's proven permission system
252 - **Performant**: Direct service engine access, no extra lookups
253 - **Unified Analytics**: All MCP operations tracked via native ArtifactHit
254 - **Built-in Reporting**: Uses ArtifactHitReport for standard analytics
255 - **Native Session Management**: Uses Moqui's Visit entity for session tracking
256 - **REST Service Pattern**: MCP is fundamentally a REST service invocation layer
257 - **Zero Custom Tracking**: Eliminates McpToolCall duplication with ArtifactHit
258
259 This MVP provides core functionality needed to integrate Moqui with Opencode as an MCP skill for business intelligence queries.
...\ No newline at end of file ...\ No newline at end of file
1 // Moqui MCP v2 Component Build File
2 // Minimal dependencies - depends only on moqui framework
3
4 // No additional repositories or dependencies needed
5 // All functionality uses native Moqui framework capabilities
...\ No newline at end of file ...\ No newline at end of file
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!-- This software is in the public domain under CC0 1.0 Universal plus a
3 Grant of Patent License.
4
5 To the extent possible under law, the author(s) have dedicated all
6 copyright and related and neighboring rights to this software to the
7 public domain worldwide. This software is distributed without any warranty.
8
9 You should have received a copy of the CC0 Public Domain Dedication
10 along with this software (see the LICENSE.md file). If not, see
11 <https://creativecommons.org/publicdomain/zero/1.0/>. -->
12
13 <component xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
14 xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/component-definition-3.xsd">
15
16 <!-- No dependencies - uses only core framework -->
17
18 <entity-factory load-path="entity/" />
19 <service-factory load-path="service/" />
20 <screen-factory load-path="screen/" />
21
22 <!-- Load seed data -->
23 <entity-factory load-data="data/McpSecuritySeedData.xml" />
24
25
26 <webapp name="mcp-2"
27 title="MCP Server v2"
28 server="default"
29 location="webapp"
30 https-enabled="false"
31 require-session-token="false">
32 <root-screen host=".*" screen-path="mcp-2"/>
33 </webapp>
34
35
36
37 </component>
...\ No newline at end of file ...\ No newline at end of file
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!-- This software is in the public domain under CC0 1.0 Universal plus a
3 Grant of Patent License.
4
5 To the extent possible under law, the author(s) have dedicated all
6 copyright and related and neighboring rights to this software to the
7 public domain worldwide. This software is distributed without any warranty.
8
9 You should have received a copy of the CC0 Public Domain Dedication
10 along with this software (see the LICENSE.md file). If not, see
11 <https://creativecommons.org/publicdomain/zero/1.0/>. -->
12
13 <entity-facade-xml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
14 xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/entity-facade-3.xsd">
15
16 <!-- MCP User Group -->
17 <moqui.security.UserGroup userGroupId="McpUser" description="MCP Server Users"/>
18
19 <!-- MCP Artifact Groups -->
20 <moqui.security.ArtifactGroup artifactGroupId="McpRestPaths" description="MCP REST Paths"/>
21 <moqui.security.ArtifactGroup artifactGroupId="McpServices" description="MCP Services"/>
22
23 <!-- MCP Artifact Group Members -->
24 <moqui.security.ArtifactGroupMember artifactGroupId="McpRestPaths" artifactName="/rest/s1/mcp-2" artifactTypeEnumId="AT_REST_PATH"/>
25 <moqui.security.ArtifactGroupMember artifactGroupId="McpRestPaths" artifactName="/rest/s1/mcp-2/*" artifactTypeEnumId="AT_REST_PATH"/>
26 <moqui.security.ArtifactGroupMember artifactGroupId="McpServices" artifactName="org.moqui.mcp.*" artifactTypeEnumId="AT_SERVICE"/>
27
28 <!-- MCP Artifact Authz -->
29 <moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpRestPaths" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/>
30 <moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpServices" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/>
31
32 <!-- Add john.doe to MCP user group -->
33 <moqui.security.UserGroupMember userGroupId="McpUser" userId="JOHN_DOE" fromDate="2025-01-01 00:00:00.000"/>
34
35 <!-- Add MCP user to MCP user group -->
36 <moqui.security.UserGroupMember userGroupId="McpUser" userId="MCP_USER" fromDate="2025-01-01 00:00:00.000"/>
37
38 <!-- Create minimal MCP user for testing -->
39 <moqui.security.UserAccount userId="MCP_USER" username="mcp-user" disabled="N" requirePasswordChange="N"
40 currentPassword="16ac58bbfa332c1c55bd98b53e60720bfa90d394" passwordHashType="SHA" />
41
42 </entity-facade-xml>
...\ No newline at end of file ...\ No newline at end of file
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!-- This software is in the public domain under CC0 1.0 Universal plus a
3 Grant of Patent License.
4
5 To the extent possible under law, the author(s) have dedicated all
6 copyright and related and neighboring rights to this software to the
7 public domain worldwide. This software is distributed without any warranty.
8
9 You should have received a copy of the CC0 Public Domain Dedication
10 along with this software (see the LICENSE.md file). If not, see
11 <https://creativecommons.org/publicdomain/zero/1.0/>. -->
12
13 <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
14 xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/entity-definition-3.xsd">
15
16 <!-- Extend Visit entity for MCP-specific session tracking -->
17 <extend-entity entity-name="Visit" package="moqui.server">
18 <field name="mcpContextToken" type="text-medium"/>
19 <field name="mcpStatusId" type="id"/>
20 <field name="mcpExpiresDate" type="date-time"/>
21 <field name="mcpClientInfo" type="text-long"/>
22 <field name="mcpSessionData" type="text-very-long"/>
23
24 <relationship type="one" related-entity-name="moqui.basic.StatusItem" short-alias="mcpStatus">
25 <key-map field-name="mcpStatusId"/>
26 </relationship>
27
28 <index name="VISIT_MCP_TOKEN" unique="true">
29 <index-field name="mcpContextToken"/>
30 </index>
31 <index name="VISIT_MCP_USER_STATUS">
32 <index-field name="userId"/>
33 <index-field name="mcpStatusId"/>
34 </index>
35 <index name="VISIT_MCP_EXPIRE">
36 <index-field name="mcpExpiresDate"/>
37 </index>
38 </extend-entity>
39
40
41
42
43
44 </entities>
...\ No newline at end of file ...\ No newline at end of file
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!-- This software is in the public domain under CC0 1.0 Universal plus a
3 Grant of Patent License.
4
5 To the extent possible under law, the author(s) have dedicated all
6 copyright and related and neighboring rights to this software to the
7 public domain worldwide. This software is distributed without any warranty.
8
9 You should have received a copy of the CC0 Public Domain Dedication
10 along with this software (see the LICENSE.md file). If not, see
11 <https://creativecommons.org/publicdomain/zero/1.0/>. -->
12
13 <screen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
14 xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/screen-definition-3.xsd">
15
16 <parameter name="sessionId"/>
17 <parameter name="contextToken"/>
18
19 <widgets>
20 <render-mode>
21 <text type="html" location="component://moqui-mcp-2/template/McpInfo.html.ftl"/>
22 </render-mode>
23 </widgets>
24 </screen>
...\ No newline at end of file ...\ No newline at end of file
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!-- This software is in the public domain under CC0 1.0 Universal plus a
3 Grant of Patent License.
4
5 To the extent possible under law, the author(s) have dedicated all
6 copyright and related and neighboring rights to this software to the
7 public domain worldwide. This software is distributed without any warranty.
8
9 You should have received a copy of the CC0 Public Domain Dedication
10 along with this software (see the LICENSE.md file). If not, see
11 <https://creativecommons.org/publicdomain/zero/1.0/>. -->
12
13 <services xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
14 xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/service-definition-3.xsd">
15
16 <!-- MCP Native Service Discovery using Moqui Service Engine Directly -->
17
18 <service verb="discover" noun="McpTools" authenticate="false" transaction-timeout="60">
19 <description>Discover available MCP tools from native Moqui service definitions based on UserGroupPermission</description>
20 <in-parameters>
21 <parameter name="userAccountId" type="id" required="true"/>
22 <parameter name="sessionId" type="id"/>
23 <parameter name="servicePattern" type="text-medium"/>
24 <parameter name="packageName" type="text-medium"/>
25 <parameter name="verb" type="text-medium"/>
26 <parameter name="noun" type="text-medium"/>
27 <parameter name="includeParameters" type="text-indicator" default="Y"/>
28 </in-parameters>
29 <out-parameters>
30 <parameter name="toolList" type="text-very-long"/>
31 <parameter name="toolCount" type="number-integer"/>
32 <parameter name="summary" type="text-long"/>
33 </out-parameters>
34 <actions>
35 <script><![CDATA[
36 import org.moqui.context.ExecutionContext
37 import groovy.json.JsonBuilder
38 import groovy.json.JsonSlurper
39
40 ExecutionContext ec = context.ec
41
42 // Set user context for permission checks
43 ec.user.setUserIdByToken(userAccountId)
44
45 // Get all service names from Moqui service engine
46 def allServiceNames = ec.service.getServiceNames()
47
48 // Filter services based on criteria
49 def filteredServiceNames = []
50 for (serviceName in allServiceNames) {
51 // Apply package filter
52 if (packageName && !serviceName.startsWith(packageName)) {
53 continue
54 }
55
56 // Apply service pattern filter
57 if (servicePattern && !serviceName.matches(servicePattern)) {
58 continue
59 }
60
61 // Apply verb/noun filters
62 if (verb || noun) {
63 def serviceInfo = ec.service.getServiceInfo(serviceName)
64 if (!serviceInfo) continue
65
66 if (verb && serviceInfo.verb != verb) continue
67 if (noun && serviceInfo.noun != noun) continue
68 }
69
70 filteredServiceNames << serviceName
71 }
72
73 // Check permissions and build tool info
74 def availableTools = []
75
76 for (serviceName in filteredServiceNames) {
77 try {
78 def serviceInfo = ec.service.getServiceInfo(serviceName)
79 if (!serviceInfo) continue
80
81 // Check if user has permission to execute this service
82 def hasPermission = ec.service.hasPermission(serviceName)
83
84 if (hasPermission) {
85 def toolData = [
86 toolName: serviceName,
87 serviceName: serviceName,
88 verb: serviceInfo.verb,
89 noun: serviceInfo.noun,
90 description: serviceInfo.description ?: "",
91 authenticate: serviceInfo.authenticate,
92 hasPermission: true
93 ]
94
95 // Include parameter information if requested
96 if (includeParameters == "Y") {
97 toolData.parameters = serviceInfo.getInParameterNames().collect { paramName ->
98 def paramInfo = serviceInfo.getInParameter(paramName)
99 [
100 name: paramName,
101 type: paramInfo.type,
102 required: paramInfo.required,
103 defaultValue: paramInfo.defaultValue,
104 description: paramInfo.description ?: ""
105 ]
106 }
107
108 toolData.outputParameters = serviceInfo.getOutParameterNames().collect { paramName ->
109 def paramInfo = serviceInfo.getOutParameter(paramName)
110 [
111 name: paramName,
112 type: paramInfo.type,
113 description: paramInfo.description ?: ""
114 ]
115 }
116 }
117
118 availableTools << toolData
119 }
120
121 } catch (Exception e) {
122 // Skip services that can't be accessed
123 ec.logger.warn("Error accessing service info for ${serviceName}: ${e.message}")
124 }
125 }
126
127 toolCount = availableTools.size()
128
129 // Create summary
130 def summaryInfo = [
131 totalServices: allServiceNames.size(),
132 filteredServices: filteredServiceNames.size(),
133 availableTools: availableTools.size(),
134 userAccountId: userAccountId,
135 hasSession: sessionId ? true : false
136 ]
137 summary = new JsonBuilder(summaryInfo).toString()
138
139 // Build final result
140 def result = [
141 userAccountId: userAccountId,
142 availableTools: availableTools,
143 summary: summaryInfo
144 ]
145
146 toolList = new JsonBuilder(result).toString()
147
148 // Log discovery via ArtifactHit
149 if (sessionId) {
150 ec.message.addMessage("Discovered ${availableTools.size()} available tools via native discovery", "info")
151 }
152 ]]></script>
153 </actions>
154 </service>
155
156 <service verb="validate" noun="McpToolAccess" authenticate="false" transaction-timeout="30">
157 <description>Validate if user can access specific MCP tools using native permission checking</description>
158 <in-parameters>
159 <parameter name="userAccountId" type="id" required="true"/>
160 <parameter name="serviceNames" type="list" required="true"/>
161 <parameter name="sessionId" type="id"/>
162 </in-parameters>
163 <out-parameters>
164 <parameter name="validationResults" type="text-very-long"/>
165 <parameter name="accessibleTools" type="text-long"/>
166 <parameter name="deniedTools" type="text-long"/>
167 </out-parameters>
168 <actions>
169 <script><![CDATA[
170 import org.moqui.context.ExecutionContext
171 import groovy.json.JsonBuilder
172
173 ExecutionContext ec = context.ec
174
175 // Set user context for permission checks
176 ec.user.setUserIdByToken(userAccountId)
177
178 def results = []
179 def accessible = []
180 def denied = []
181
182 for (serviceName in serviceNames) {
183 def result = [
184 serviceName: serviceName,
185 toolName: serviceName,
186 serviceExists: false,
187 accessible: false,
188 reason: ""
189 ]
190
191 // Check if service exists
192 def serviceDefined = ec.service.isServiceDefined(serviceName)
193 result.serviceExists = serviceDefined
194
195 if (!serviceDefined) {
196 result.reason = "Service not defined"
197 denied << result
198 results << result
199 continue
200 }
201
202 // Get service info
203 def serviceInfo = ec.service.getServiceInfo(serviceName)
204 if (serviceInfo) {
205 result.verb = serviceInfo.verb
206 result.noun = serviceInfo.noun
207 result.description = serviceInfo.description ?: ""
208 result.authenticate = serviceInfo.authenticate
209 }
210
211 // Check service permission
212 def hasPermission = ec.service.hasPermission(serviceName)
213 result.accessible = hasPermission
214 result.reason = hasPermission ?
215 "Service permission granted" :
216 "Service permission denied"
217
218 if (hasPermission) {
219 accessible << result
220 } else {
221 denied << result
222 }
223
224 results << result
225 }
226
227 validationResults = new JsonBuilder(results).toString()
228 accessibleTools = new JsonBuilder(accessible).toString()
229 deniedTools = new JsonBuilder(denied).toString()
230
231 // Log validation via ArtifactHit
232 if (sessionId) {
233 def msg = "Validated access to ${serviceNames.size()} tools: ${accessible.size()} allowed, ${denied.size()} denied"
234 ec.message.addMessage(msg, denied.size() > 0 ? "warning" : "info")
235 }
236 ]]></script>
237 </actions>
238 </service>
239
240 </services>
...\ No newline at end of file ...\ No newline at end of file
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!-- This software is in the public domain under CC0 1.0 Universal plus a
3 Grant of Patent License.
4
5 To the extent possible under law, the author(s) have dedicated all
6 copyright and related and neighboring rights to this software to the
7 public domain worldwide. This software is distributed without any warranty.
8
9 You should have received a copy of the CC0 Public Domain Dedication
10 along with this software (see the LICENSE.md file). If not, see
11 <https://creativecommons.org/publicdomain/zero/1.0/>. -->
12
13 <services xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
14 xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/service-definition-3.xsd">
15
16 <!-- MCP Direct Service Execution Services -->
17
18 <service verb="execute" noun="McpTool" authenticate="false" transaction-timeout="300">
19 <description>Execute MCP tool directly using Moqui service engine with proper validation and security</description>
20 <in-parameters>
21 <parameter name="sessionId" type="id" required="true"/>
22 <parameter name="serviceName" type="text-medium" required="true"/>
23 <parameter name="parameters" type="Map" required="true"/>
24 <parameter name="toolCallId" type="id"/>
25 </in-parameters>
26 <out-parameters>
27 <parameter name="result" type="text-very-long"/>
28 <parameter name="success" type="text-indicator"/>
29 <parameter name="errorMessage" type="text-long"/>
30 <parameter name="serviceName" type="text-medium"/>
31 <parameter name="recordCount" type="number-integer"/>
32 </out-parameters>
33 <actions>
34 <script><![CDATA[
35 import org.moqui.context.ExecutionContext
36 import groovy.json.JsonBuilder
37 import groovy.json.JsonSlurper
38
39 ExecutionContext ec = context.ec
40
41 // Get visit information
42 def visit = ec.entity.find("moqui.server.Visit")
43 .condition("visitId", sessionId)
44 .condition("mcpContextToken", contextToken)
45 .condition("mcpStatusId", "McsActive")
46 .one()
47 if (!visit) {
48 success = "N"
49 errorMessage = "Invalid session"
50 return
51 }
52
53 // Set user context
54 ec.user.setUserIdByToken(visit.userId)
55
56 // Check if service exists
57 if (!ec.service.isServiceDefined(serviceName)) {
58 success = "N"
59 errorMessage = "Service not defined: ${serviceName}"
60 return
61 }
62
63 // Check service permission
64 if (!ec.service.hasPermission(serviceName)) {
65 success = "N"
66 errorMessage = "Permission denied for service: ${serviceName}"
67
68 // Log permission denial via ArtifactHit
69 ec.message.addError("Permission denied for service ${serviceName}")
70 return
71 }
72
73 // Create ArtifactHit record for MCP tool execution
74 def artifactHit = ec.entity.makeValue("moqui.server.ArtifactHit")
75 artifactHit.setSequencedIdPrimary()
76 artifactHit.visitId = sessionId // This is visitId
77 artifactHit.userId = visit.userId
78 artifactHit.artifactType = "MCP"
79 artifactHit.artifactSubType = "Service"
80 artifactHit.artifactName = serviceName
81 artifactHit.parameterString = new JsonBuilder(parameters).toString()
82 artifactHit.startDateTime = ec.user.now
83 artifactHit.serverIpAddress = ec.web.request.serverIpAddress
84 artifactHit.serverHostName = ec.web.request.serverHostName
85 artifactHit.create()
86
87 if (!toolCallId) toolCallId = artifactHit.hitId
88
89 // Execute the service directly
90 def startTime = System.currentTimeMillis()
91 try {
92 def serviceResult = ec.service.sync(serviceName, parameters)
93 def executionTime = (System.currentTimeMillis() - startTime) / 1000.0
94
95 // Convert result to JSON
96 def resultJson = new JsonBuilder(serviceResult).toString()
97
98 // Update ArtifactHit record
99 artifactHit.runningTimeMillis = executionTime
100 artifactHit.wasError = "N"
101 artifactHit.outputSize = resultJson.length()
102 artifactHit.update()
103
104 result = resultJson
105 success = "Y"
106 serviceName = serviceName
107
108 // Log successful execution via ArtifactHit
109 ec.message.addMessage("Successfully executed service ${serviceName}", "info")
110
111 } catch (Exception e) {
112 def executionTime = (System.currentTimeMillis() - startTime) / 1000.0
113
114 // Update ArtifactHit record with error
115 artifactHit.runningTimeMillis = executionTime
116 artifactHit.wasError = "Y"
117 artifactHit.errorMessage = e.message
118 artifactHit.update()
119
120 success = "N"
121 errorMessage = "Error executing service ${serviceName}: ${e.message}"
122 ec.logger.error("MCP Tool execution error", e)
123
124 // Log execution error via ArtifactHit
125 ec.message.addError("Service execution failed: ${serviceName} - ${e.message}")
126 }
127 ]]></script>
128 </actions>
129 </service>
130
131 <service verb="execute" noun="EntityQuery" authenticate="false" transaction-timeout="120">
132 <description>Execute entity query as MCP tool with security validation</description>
133 <in-parameters>
134 <parameter name="sessionId" type="id" required="true"/>
135 <parameter name="entityName" type="text-medium" required="true"/>
136 <parameter name="queryType" type="text-short" default="list"/> <!-- list, one, count -->
137 <parameter name="conditions" type="Map"/>
138 <parameter name="orderBy" type="text-medium"/>
139 <parameter name="limit" type="number-integer" default="100"/>
140 <parameter name="offset" type="number-integer" default="0"/>
141 <parameter name="toolCallId" type="id"/>
142 </in-parameters>
143 <out-parameters>
144 <parameter name="result" type="text-very-long"/>
145 <parameter name="success" type="text-indicator"/>
146 <parameter name="errorMessage" type="text-long"/>
147 <parameter name="recordCount" type="number-integer"/>
148 </out-parameters>
149 <actions>
150 <script><![CDATA[
151 import org.moqui.context.ExecutionContext
152 import groovy.json.JsonBuilder
153
154 ExecutionContext ec = context.ec
155
156 // Get visit information
157 def visit = ec.entity.find("moqui.server.Visit")
158 .condition("visitId", sessionId)
159 .condition("mcpContextToken", contextToken)
160 .condition("mcpStatusId", "McsActive")
161 .one()
162 if (!visit) {
163 success = "N"
164 errorMessage = "Invalid session"
165 return
166 }
167
168 // Set user context
169 ec.user.setUserIdByToken(visit.userId)
170
171 // Check if entity exists
172 if (!ec.entity.isEntityDefined(entityName)) {
173 success = "N"
174 errorMessage = "Entity not defined: ${entityName}"
175 return
176 }
177
178 // Check entity permission
179 if (!ec.user.hasPermission("entity:${entityName}", "VIEW")) {
180 success = "N"
181 errorMessage = "Permission denied for entity: ${entityName}"
182 return
183 }
184
185 // Create ArtifactHit record for MCP entity query
186 def artifactHit = ec.entity.makeValue("moqui.server.ArtifactHit")
187 artifactHit.setSequencedIdPrimary()
188 artifactHit.visitId = sessionId // This is visitId
189 artifactHit.userId = visit.userId
190 artifactHit.artifactType = "MCP"
191 artifactHit.artifactSubType = "Entity"
192 artifactHit.artifactName = "execute#EntityQuery"
193 artifactHit.parameterString = new JsonBuilder([
194 entityName: entityName,
195 queryType: queryType,
196 conditions: conditions,
197 orderBy: orderBy,
198 limit: limit,
199 offset: offset
200 ]).toString()
201 artifactHit.startDateTime = ec.user.now
202 artifactHit.serverIpAddress = ec.web.request.serverIpAddress
203 artifactHit.serverHostName = ec.web.request.serverHostName
204 artifactHit.create()
205
206 if (!toolCallId) toolCallId = artifactHit.hitId
207
208 def startTime = System.currentTimeMillis()
209 try {
210 def entityBuilder = ec.entity.find(entityName)
211
212 // Apply conditions if provided
213 if (conditions) {
214 conditions.each { key, value ->
215 if (value instanceof List) {
216 entityBuilder.condition(key, value, "in")
217 } else {
218 entityBuilder.condition(key, value)
219 }
220 }
221 }
222
223 // Apply ordering if provided
224 if (orderBy) {
225 entityBuilder.orderBy(orderBy)
226 }
227
228 // Execute query based on type
229 def queryResult
230 switch (queryType) {
231 case "one":
232 queryResult = entityBuilder.one()
233 recordCount = queryResult ? 1 : 0
234 break
235 case "count":
236 queryResult = entityBuilder.count()
237 recordCount = queryResult
238 break
239 case "list":
240 default:
241 entityBuilder.limit(limit).offset(offset)
242 queryResult = entityBuilder.list()
243 recordCount = queryResult.size()
244 break
245 }
246
247 def executionTime = (System.currentTimeMillis() - startTime) / 1000.0
248
249 // Convert result to JSON
250 def resultJson = new JsonBuilder([
251 entityName: entityName,
252 queryType: queryType,
253 conditions: conditions,
254 result: queryResult,
255 recordCount: recordCount,
256 executionTime: executionTime
257 ]).toString()
258
259 // Update ArtifactHit record
260 artifactHit.runningTimeMillis = executionTime
261 artifactHit.wasError = "N"
262 artifactHit.outputSize = resultJson.length()
263 artifactHit.update()
264
265 result = resultJson
266 success = "Y"
267
268 // Log successful query via ArtifactHit
269 ec.message.addMessage("Successfully queried entity ${entityName} (${queryType})", "info")
270
271 } catch (Exception e) {
272 def executionTime = (System.currentTimeMillis() - startTime) / 1000.0
273
274 // Update ArtifactHit record with error
275 artifactHit.runningTimeMillis = executionTime
276 artifactHit.wasError = "Y"
277 artifactHit.errorMessage = e.message
278 artifactHit.update()
279
280 success = "N"
281 errorMessage = "Error querying entity ${entityName}: ${e.message}"
282 ec.logger.error("MCP Entity query error", e)
283
284 // Log query error via ArtifactHit
285 ec.message.addError("Entity query failed: ${entityName} - ${e.message}")
286 }
287 ]]></script>
288 </actions>
289 </service>
290
291 </services>
...\ No newline at end of file ...\ No newline at end of file
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!-- This software is in the public domain under CC0 1.0 Universal plus a
3 Grant of Patent License.
4
5 To the extent possible under law, the author(s) have dedicated all
6 copyright and related and neighboring rights to this software to the
7 public domain worldwide. This software is distributed without any warranty.
8
9 You should have received a copy of the CC0 Public Domain Dedication
10 along with this software (see the LICENSE.md file). If not, see
11 <https://creativecommons.org/publicdomain/zero/1.0/>. -->
12
13 <services xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
14 xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/service-definition-3.xsd">
15
16 <!-- MCP Unified Security and Audit Services -->
17
18 <service verb="create" noun="McpSession" authenticate="false" transaction-timeout="30">
19 <description>Create a new MCP session using Moqui's Visit entity</description>
20 <in-parameters>
21 <parameter name="username" type="text-medium" required="true"/>
22 <parameter name="password" type="text-medium" required="true"/>
23 <parameter name="clientInfo" type="text-long"/>
24 <parameter name="ipAddress" type="text-short"/>
25 <parameter name="userAgent" type="text-medium"/>
26 </in-parameters>
27 <out-parameters>
28 <parameter name="visitId" type="id"/>
29 <parameter name="contextToken" type="text-medium"/>
30 <parameter name="userAccountId" type="id"/>
31 <parameter name="success" type="text-indicator"/>
32 <parameter name="errorMessage" type="text-long"/>
33 </out-parameters>
34 <actions>
35 <script><![CDATA[
36 import org.moqui.context.ExecutionContext
37 import java.util.UUID
38
39 ExecutionContext ec = context.ec
40
41 // Authenticate user
42 def userAccount = ec.entity.find("moqui.security.UserAccount")
43 .condition("username", username)
44 .one()
45
46 if (!userAccount) {
47 success = "N"
48 errorMessage = "Invalid username or password"
49 return
50 }
51
52 // Verify password using Moqui's password service
53 try {
54 def authResult = ec.service.sync("org.moqui.security.UserServices.authenticate#User", [
55 username: username,
56 password: password
57 ])
58
59 if (!authResult.authenticated) {
60 success = "N"
61 errorMessage = "Invalid username or password"
62 return
63 }
64 } catch (Exception e) {
65 success = "N"
66 errorMessage = "Authentication error: ${e.message}"
67 return
68 }
69
70 // Create Visit record (Moqui's native session tracking)
71 def visit = ec.entity.makeValue("moqui.server.Visit")
72 visit.setSequencedIdPrimary()
73 visit.userId = userAccount.userAccountId
74 visit.userCreated = "N"
75 visit.sessionId = UUID.randomUUID().toString()
76 visit.webappName = "mcp-2"
77 visit.initialLocale = ec.user.locale.toString()
78 visit.initialRequest = "MCP Session Creation"
79 visit.initialUserAgent = userAgent
80 visit.clientIpAddress = ipAddress
81 visit.fromDate = ec.user.now
82
83 // Add MCP-specific fields
84 visit.mcpContextToken = UUID.randomUUID().toString()
85 visit.mcpStatusId = "McsActive"
86 visit.mcpExpiresDate = new Date(ec.user.now.time + (24 * 60 * 60 * 1000)) // 24 hours
87 visit.mcpClientInfo = clientInfo
88
89 visit.create()
90
91 visitId = visit.visitId
92 contextToken = visit.mcpContextToken
93 userAccountId = userAccount.userAccountId
94 success = "Y"
95
96 // Log session creation via ArtifactHit
97 ec.message.addMessage("MCP session created for user ${username}", "info")
98 ]]></script>
99 </actions>
100 </service>
101
102 <service verb="validate" noun="McpSession" authenticate="false" transaction-timeout="30">
103 <description>Validate MCP session using Visit entity</description>
104 <in-parameters>
105 <parameter name="visitId" type="id" required="true"/>
106 <parameter name="contextToken" type="text-medium" required="true"/>
107 </in-parameters>
108 <out-parameters>
109 <parameter name="valid" type="text-indicator"/>
110 <parameter name="userAccountId" type="id"/>
111 <parameter name="errorMessage" type="text-long"/>
112 </out-parameters>
113 <actions>
114 <script><![CDATA[
115 import org.moqui.context.ExecutionContext
116
117 ExecutionContext ec = context.ec
118
119 def visit = ec.entity.find("moqui.server.Visit")
120 .condition("visitId", visitId)
121 .condition("mcpContextToken", contextToken)
122 .condition("mcpStatusId", "McsActive")
123 .one()
124
125 if (!visit) {
126 valid = "N"
127 errorMessage = "Invalid or expired session"
128 return
129 }
130
131 // Check if session has expired
132 if (visit.mcpExpiresDate && visit.mcpExpiresDate.before(ec.user.now)) {
133 // Mark as expired
134 visit.mcpStatusId = "McsExpired"
135 visit.thruDate = ec.user.now
136 visit.update()
137
138 valid = "N"
139 errorMessage = "Session expired"
140 return
141 }
142
143 // Update Visit thruDate (acts as last accessed)
144 visit.thruDate = ec.user.now
145 visit.update()
146
147 valid = "Y"
148 userAccountId = visit.userId
149 ]]></script>
150 </actions>
151 </service>
152
153 <service verb="terminate" noun="McpSession" authenticate="false" transaction-timeout="30">
154 <description>Terminate MCP session using Visit entity</description>
155 <in-parameters>
156 <parameter name="visitId" type="id" required="true"/>
157 <parameter name="contextToken" type="text-medium" required="true"/>
158 </in-parameters>
159 <out-parameters>
160 <parameter name="success" type="text-indicator"/>
161 <parameter name="errorMessage" type="text-long"/>
162 </out-parameters>
163 <actions>
164 <script><![CDATA[
165 import org.moqui.context.ExecutionContext
166
167 ExecutionContext ec = context.ec
168
169 def visit = ec.entity.find("moqui.server.Visit")
170 .condition("visitId", visitId)
171 .condition("mcpContextToken", contextToken)
172 .one()
173
174 if (!visit) {
175 success = "N"
176 errorMessage = "Invalid session"
177 return
178 }
179
180 // Mark as terminated
181 visit.mcpStatusId = "McsTerminated"
182 visit.thruDate = ec.user.now
183 visit.update()
184
185 success = "Y"
186
187 // Log session termination via ArtifactHit
188 ec.message.addMessage("MCP session terminated", "info")
189 ]]></script>
190 </actions>
191 </service>
192
193
194
195 <service verb="cleanup" noun="ExpiredSessions" authenticate="true" transaction-timeout="300">
196 <description>Cleanup expired MCP sessions</description>
197 <in-parameters>
198 <parameter name="dryRun" type="text-indicator" default="N"/>
199 </in-parameters>
200 <out-parameters>
201 <parameter name="expiredCount" type="number-integer"/>
202 <parameter name="cleanedCount" type="number-integer"/>
203 </out-parameters>
204 <actions>
205 <script><![CDATA[
206 import org.moqui.context.ExecutionContext
207
208 ExecutionContext ec = context.ec
209
210 // Find expired MCP sessions
211 def expiredVisits = ec.entity.find("moqui.server.Visit")
212 .condition("mcpStatusId", "McsActive")
213 .condition("mcpExpiresDate", ec.user.now, "less-than")
214 .list()
215
216 expiredCount = expiredVisits.size()
217 cleanedCount = 0
218
219 if (dryRun != "Y") {
220 for (visit in expiredVisits) {
221 visit.mcpStatusId = "McsExpired"
222 visit.thruDate = ec.user.now
223 visit.update()
224 cleanedCount++
225
226 // Log session expiration via ArtifactHit
227 ec.message.addMessage("MCP session expired during cleanup", "info")
228 }
229 }
230 ]]></script>
231 </actions>
232 </service>
233
234 </services>
...\ No newline at end of file ...\ No newline at end of file
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!-- This software is in the public domain under CC0 1.0 Universal plus a
3 Grant of Patent License.
4
5 To the extent possible under law, the author(s) have dedicated all
6 copyright and related and neighboring rights to this software to the
7 public domain worldwide. This software is distributed without any warranty.
8
9 You should have received a copy of the CC0 Public Domain Dedication
10 along with this software (see the LICENSE.md file). If not, see
11 <https://creativecommons.org/publicdomain/zero/1.0/>. -->
12
13 <resource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
14 xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/rest-api-3.xsd"
15 name="mcp-2" displayName="MCP v2 REST API" version="2.0.0"
16 description="Model Context Protocol (MCP) v2 server endpoints for AI-Moqui integration">
17
18 <!-- MCP Session Management -->
19
20 <resource name="session" description="MCP Session Management">
21 <method type="post" json-request="true">
22 <description>Create new MCP session</description>
23 <service name="org.moqui.mcp.McpSecurityServices.create#McpSession"/>
24 <parameter-map>
25 <parameter field-name="username" required="true"/>
26 <parameter field-name="password" required="true"/>
27 <parameter field-name="clientInfo"/>
28 <parameter field-name="ipAddress"/>
29 <parameter field-name="userAgent"/>
30 </parameter-map>
31 </method>
32
33 <id name="visitId">
34 <method type="post" json-request="true">
35 <description>Validate MCP session</description>
36 <service name="org.moqui.mcp.McpSecurityServices.validate#McpSession"/>
37 <parameter-map>
38 <parameter field-name="visitId" from="uri-path"/>
39 <parameter field-name="contextToken" required="true"/>
40 </parameter-map>
41 </method>
42
43 <resource name="terminate">
44 <method type="post" json-request="true">
45 <description>Terminate MCP session</description>
46 <service name="org.moqui.mcp.McpSecurityServices.terminate#McpSession"/>
47 <parameter-map>
48 <parameter field-name="visitId" from="uri-path"/>
49 <parameter field-name="contextToken" required="true"/>
50 </parameter-map>
51 </method>
52 </resource>
53 </id>
54 </resource>
55
56 <!-- MCP Tool Discovery -->
57
58 <resource name="tools" description="MCP Tool Discovery and Execution">
59 <resource name="discover">
60 <method type="post" json-request="true">
61 <description>Discover available MCP tools</description>
62 <service name="org.moqui.mcp.McpDiscoveryServices.discover#McpTools"/>
63 <parameter-map>
64 <parameter field-name="userAccountId" required="true"/>
65 <parameter field-name="visitId"/>
66 <parameter field-name="servicePattern"/>
67 <parameter field-name="packageName"/>
68 <parameter field-name="verb"/>
69 <parameter field-name="noun"/>
70 <parameter field-name="includeParameters"/>
71 </parameter-map>
72 </method>
73 </resource>
74
75 <resource name="validate">
76 <method type="post" json-request="true">
77 <description>Validate access to specific MCP tools</description>
78 <service name="org.moqui.mcp.McpDiscoveryServices.validate#McpToolAccess"/>
79 <parameter-map>
80 <parameter field-name="userAccountId" required="true"/>
81 <parameter field-name="serviceNames" required="true"/>
82 <parameter field-name="visitId"/>
83 </parameter-map>
84 </method>
85 </resource>
86
87 <method type="post" json-request="true">
88 <description>Execute MCP tool (service)</description>
89 <service name="org.moqui.mcp.McpExecutionServices.execute#McpTool"/>
90 <parameter-map>
91 <parameter field-name="sessionId" required="true"/>
92 <parameter field-name="contextToken" required="true"/>
93 <parameter field-name="serviceName" required="true"/>
94 <parameter field-name="parameters" required="true"/>
95 <parameter field-name="toolCallId"/>
96 </parameter-map>
97 </method>
98
99 <resource name="entity">
100 <resource name="query">
101 <method type="post" json-request="true">
102 <description>Execute entity query as MCP tool</description>
103 <service name="org.moqui.mcp.McpExecutionServices.execute#EntityQuery"/>
104 <parameter-map>
105 <parameter field-name="sessionId" required="true"/>
106 <parameter field-name="contextToken" required="true"/>
107 <parameter field-name="serviceName" required="true"/>
108 <parameter field-name="parameters" required="true"/>
109 <parameter field-name="toolCallId"/>
110 </parameter-map>
111 </method>
112 </resource>
113 </resource>
114 </resource>
115
116 <!-- MCP Health Check -->
117
118 <resource name="health" description="MCP Server Health Check">
119 <method type="get">
120 <actions>
121 <script><![CDATA[
122 import groovy.json.JsonBuilder
123
124 def health = [
125 status: "healthy",
126 timestamp: ec.user.now,
127 version: "2.0.0-MVP",
128 server: "Moqui MCP Server v2",
129 endpoints: [
130 "POST /session - Create session",
131 "POST /session/{visitId}/validate - Validate session",
132 "POST /session/{visitId}/terminate - Terminate session",
133 "POST /tools/discover - Discover tools",
134 "POST /tools/validate - Validate tool access",
135 "POST /tools/execute - Execute service tool",
136 "POST /tools/entity/query - Execute entity query",
137 "GET /health - Health check"
138 ]
139 ]
140
141 response = new JsonBuilder(health).toString()
142 ]]></script>
143 </actions>
144 </method>
145 </resource>
146
147 </resource>
...\ No newline at end of file ...\ No newline at end of file
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <title>Moqui MCP Server v2</title>
5 <style>
6 body { font-family: Arial, sans-serif; margin: 40px; }
7 .header { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; }
8 .endpoint { background: #f8f9fa; padding: 10px; margin: 5px 0; border-radius: 5px; }
9 .method { color: #28a745; font-weight: bold; }
10 .path { font-family: monospace; background: #e9ecef; padding: 2px 5px; }
11 </style>
12 </head>
13 <body>
14 <h1 class="header">Moqui MCP Server v2 (MVP)</h1>
15
16 <h2>Server Information</h2>
17 <p><strong>Version:</strong> 2.0.0-MVP</p>
18 <p><strong>Status:</strong> Active</p>
19 <p><strong>Description:</strong> Simplified MCP server using Moqui's native service engine</p>
20
21 <h2>Available Endpoints</h2>
22
23 <div class="endpoint">
24 <span class="method">POST</span> <span class="path">/session</span><br>
25 <strong>Description:</strong> Create new MCP session<br>
26 <strong>Required:</strong> username, password<br>
27 <strong>Optional:</strong> clientInfo, ipAddress, userAgent
28 </div>
29
30 <div class="endpoint">
31 <span class="method">POST</span> <span class="path">/session/{sessionId}/validate</span><br>
32 <strong>Description:</strong> Validate MCP session<br>
33 <strong>Required:</strong> contextToken
34 </div>
35
36 <div class="endpoint">
37 <span class="method">POST</span> <span class="path">/session/{sessionId}/terminate</span><br>
38 <strong>Description:</strong> Terminate MCP session<br>
39 <strong>Required:</strong> contextToken
40 </div>
41
42 <div class="endpoint">
43 <span class="method">POST</span> <span class="path">/tools/discover</span><br>
44 <strong>Description:</strong> Discover available MCP tools<br>
45 <strong>Required:</strong> userAccountId<br>
46 <strong>Optional:</strong> sessionId, servicePattern, packageName, verb, noun, includeParameters
47 </div>
48
49 <div class="endpoint">
50 <span class="method">POST</span> <span class="path">/tools/validate</span><br>
51 <strong>Description:</strong> Validate access to specific MCP tools<br>
52 <strong>Required:</strong> userAccountId, serviceNames<br>
53 <strong>Optional:</strong> sessionId
54 </div>
55
56 <div class="endpoint">
57 <span class="method">POST</span> <span class="path">/tools/execute</span><br>
58 <strong>Description:</strong> Execute MCP tool (service)<br>
59 <strong>Required:</strong> sessionId, serviceName, parameters<br>
60 <strong>Optional:</strong> toolCallId
61 </div>
62
63 <div class="endpoint">
64 <span class="method">POST</span> <span class="path">/tools/entity/query</span><br>
65 <strong>Description:</strong> Execute entity query as MCP tool<br>
66 <strong>Required:</strong> sessionId, entityName<br>
67 <strong>Optional:</strong> queryType, conditions, orderBy, limit, offset, toolCallId
68 </div>
69
70 <div class="endpoint">
71 <span class="method">GET</span> <span class="path">/health</span><br>
72 <strong>Description:</strong> Server health check
73 </div>
74
75 <h2>Key Features</h2>
76 <ul>
77 <li><strong>Native Service Engine:</strong> Uses Moqui's service engine directly (no registry)</li>
78 <li><strong>Dynamic Tool Discovery:</strong> Tools = services user has permission to execute</li>
79 <li><strong>Unified Security:</strong> Single permission validation using UserGroupPermission</li>
80 <li><strong>Audit Logging:</strong> Complete audit trail of all operations</li>
81 <li><strong>Session Management:</strong> Secure session handling with expiration</li>
82 </ul>
83
84 <h2>Usage Example</h2>
85 <pre>
86 # 1. Create session
87 curl -X POST http://localhost:8080/mcp-2/session \
88 -H "Content-Type: application/json" \
89 -d '{"username": "admin", "password": "admin"}'
90
91 # 2. Discover tools
92 curl -X POST http://localhost:8080/mcp-2/tools/discover \
93 -H "Content-Type: application/json" \
94 -d '{"userAccountId": "ADMIN", "sessionId": "YOUR_SESSION_ID"}'
95
96 # 3. Execute tool
97 curl -X POST http://localhost:8080/mcp-2/tools/execute \
98 -H "Content-Type: application/json" \
99 -d '{
100 "sessionId": "YOUR_SESSION_ID",
101 "serviceName": "org.moqui.entity.EntityServices.find#Entity",
102 "parameters": {"entityName": "moqui.security.UserAccount"}
103 }'
104 </pre>
105 </body>
106 </html>
...\ No newline at end of file ...\ No newline at end of file