package ai.dynamixs.mcp; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import jakarta.json.Json; import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; import jakarta.json.JsonString; /** * Represents an MCP Resource based on Imixs-Workflow BPMN models. * * MCP Resources represent entities that can be read by the MCP client. Each * resource corresponds to a workflow task/event combination. */ public class MCPResource implements Serializable { private static final long serialVersionUID = 1L; // MCP Standard Fields private String uri; // Resource URI private String name; // Human-readable name private String description; // Resource description private String mimeType; // MIME type of the resource private Map annotations; // Additional metadata // Imixs-specific fields private String modelVersion; private int taskId; private int eventId; // Constructors public MCPResource() { this.annotations = new HashMap<>(); } public MCPResource(String modelVersion, int taskId, int eventId) { this(); this.modelVersion = modelVersion; this.taskId = taskId; this.eventId = eventId; this.uri = createResourceUri(modelVersion, taskId, eventId); } /** * Creates a resource URI from Imixs workflow components */ public static String createResourceUri(String modelVersion, int taskId, int eventId) { if (modelVersion == null || modelVersion.trim().isEmpty()) { throw new IllegalArgumentException("Model version cannot be null or empty"); } return String.format("imixs://workflow/%s/%d/%d", modelVersion, taskId, eventId); } /** * Parses model version from URI */ public String getModelVersionFromUri() { if (uri == null || !uri.startsWith("imixs://workflow/")) { throw new IllegalStateException("Invalid resource URI format: " + uri); } String path = uri.substring("imixs://workflow/".length()); String[] parts = path.split("/"); if (parts.length < 3) { throw new IllegalStateException("Invalid resource URI format: " + uri); } return parts[0]; } /** * Parses task ID from URI */ public int getTaskIdFromUri() { if (uri == null || !uri.startsWith("imixs://workflow/")) { throw new IllegalStateException("Invalid resource URI format: " + uri); } String path = uri.substring("imixs://workflow/".length()); String[] parts = path.split("/"); if (parts.length < 3) { throw new IllegalStateException("Invalid resource URI format: " + uri); } try { return Integer.parseInt(parts[1]); } catch (NumberFormatException e) { throw new IllegalStateException("Invalid task ID in resource URI: " + uri, e); } } /** * Parses event ID from URI */ public int getEventIdFromUri() { if (uri == null || !uri.startsWith("imixs://workflow/")) { throw new IllegalStateException("Invalid resource URI format: " + uri); } String path = uri.substring("imixs://workflow/".length()); String[] parts = path.split("/"); if (parts.length < 3) { throw new IllegalStateException("Invalid resource URI format: " + uri); } try { return Integer.parseInt(parts[2]); } catch (NumberFormatException e) { throw new IllegalStateException("Invalid event ID in resource URI: " + uri, e); } } /** * Converts this resource to MCP JSON format */ public JsonObject toJsonObject() { JsonObjectBuilder builder = Json.createObjectBuilder() .add("uri", uri != null ? uri : "") .add("name", name != null ? name : "") .add("description", description != null ? description : ""); if (mimeType != null) { builder.add("mimeType", mimeType); } // Annotations object if (annotations != null && !annotations.isEmpty()) { JsonObjectBuilder annotationsBuilder = Json.createObjectBuilder(); annotations.forEach((key, value) -> { if (value instanceof String) { annotationsBuilder.add(key, (String) value); } else if (value instanceof Number) { annotationsBuilder.add(key, ((Number) value).doubleValue()); } else if (value instanceof Boolean) { annotationsBuilder.add(key, (Boolean) value); } else if (value != null) { annotationsBuilder.add(key, value.toString()); } }); builder.add("annotations", annotationsBuilder.build()); } return builder.build(); } /** * Creates an MCPResource from a JsonObject */ public static MCPResource fromJsonObject(JsonObject jsonObject) { MCPResource resource = new MCPResource(); resource.setUri(jsonObject.getString("uri", "")); resource.setName(jsonObject.getString("name", "")); resource.setDescription(jsonObject.getString("description", "")); if (jsonObject.containsKey("mimeType")) { resource.setMimeType(jsonObject.getString("mimeType")); } // Annotations if (jsonObject.containsKey("annotations")) { JsonObject annotationsObject = jsonObject.getJsonObject("annotations"); Map annotations = new HashMap<>(); annotationsObject.forEach((key, value) -> { switch (value.getValueType()) { case STRING: annotations.put(key, ((JsonString) value).getString()); break; case NUMBER: annotations.put(key, ((jakarta.json.JsonNumber) value).doubleValue()); break; case TRUE: annotations.put(key, Boolean.TRUE); break; case FALSE: annotations.put(key, Boolean.FALSE); break; default: annotations.put(key, value.toString()); } }); resource.setAnnotations(annotations); } return resource; } /** * Validates this resource for MCP compliance */ public boolean isValid() { if (uri == null || uri.trim().isEmpty()) { return false; } if (name == null || name.trim().isEmpty()) { return false; } // Validate URI format try { getModelVersionFromUri(); getTaskIdFromUri(); getEventIdFromUri(); } catch (Exception e) { return false; } return true; } // Getters and Setters public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getMimeType() { return mimeType; } public void setMimeType(String mimeType) { this.mimeType = mimeType; } public Map getAnnotations() { return annotations; } public void setAnnotations(Map annotations) { this.annotations = annotations != null ? annotations : new HashMap<>(); } public String getModelVersion() { return modelVersion; } public void setModelVersion(String modelVersion) { this.modelVersion = modelVersion; } public int getTaskId() { return taskId; } public void setTaskId(int taskId) { this.taskId = taskId; } public int getEventId() { return eventId; } public void setEventId(int eventId) { this.eventId = eventId; } // Convenience methods for annotations public void addAnnotation(String key, Object value) { if (annotations == null) { annotations = new HashMap<>(); } annotations.put(key, value); } public String getAnnotationString(String key) { Object value = annotations != null ? annotations.get(key) : null; return value != null ? value.toString() : null; } public Boolean getAnnotationBoolean(String key) { Object value = annotations != null ? annotations.get(key) : null; if (value instanceof Boolean) { return (Boolean) value; } return null; } @Override public String toString() { return String.format("MCPResource{uri='%s', name='%s', modelVersion='%s', taskId=%d, eventId=%d}", uri, name, modelVersion, taskId, eventId); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; MCPResource that = (MCPResource) obj; return uri != null ? uri.equals(that.uri) : that.uri == null; } @Override public int hashCode() { return uri != null ? uri.hashCode() : 0; } }