connec
if (connectorFolder.getName().contains(Constant.INBOUND_CONNECTOR_PREFIX) ) {
String schema = Utils.readFile(connectorFolder.toPath().resolve(Constant.RESOURCES)
.resolve(Constant.UI_SCHEMA_JSON).toFile());
- String fileName = Utils.getJsonObject(schema).get(Constant.NAME).getAsString() + Constant.JSON_FILE_EXT;
+ String inboundName = Utils.getJsonObject(schema).get(Constant.NAME).getAsString();
String projectFolderName = connectorExtractFolder.getParentFile().getName();
- File schemaToRemove = Path.of(getUserHome(), Constant.WSO2_MI,
- Constant.INBOUND_CONNECTORS).resolve(projectFolderName).resolve(fileName).toFile();
- FileUtils.delete(schemaToRemove);
+ Path inboundCacheDir = Path.of(getUserHome(), Constant.WSO2_MI,
+ Constant.INBOUND_CONNECTORS).resolve(projectFolderName);
+ File schemaToRemove = inboundCacheDir.resolve(inboundName + Constant.JSON_FILE_EXT).toFile();
+ if (schemaToRemove.exists()) {
+ FileUtils.delete(schemaToRemove);
+ }
+ File inputSchemaToRemove = inboundCacheDir
+ .resolve(inboundName + InboundConnectorHolder.INPUT_SCHEMA_FILE_SUFFIX).toFile();
+ if (inputSchemaToRemove.exists()) {
+ FileUtils.delete(inputSchemaToRemove);
+ }
}
FileUtils.deleteDirectory(connectorFolder);
notifyRemoveConnector(connectorName, true, "Connector deleted successfully");
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/SchemaGenerate.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/SchemaGenerate.java
index 06d32ca88..4cf2b5ed4 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/SchemaGenerate.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/SchemaGenerate.java
@@ -108,6 +108,7 @@ private static String getConnectorSchema(ConnectorHolder holder) {
}
sb.append(" \n");
sb.append(" \n");
+ sb.append(" \n");
sb.append(" \n" +
" \n");
}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorAction.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorAction.java
index c9246be1d..858f988fa 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorAction.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorAction.java
@@ -14,21 +14,15 @@
package org.eclipse.lemminx.customservice.synapse.connectors.entity;
-import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
-import org.apache.commons.lang3.StringUtils;
import org.eclipse.lemminx.customservice.synapse.mediator.tryout.pojo.Property;
-import org.eclipse.lemminx.customservice.synapse.utils.Constant;
import org.eclipse.lemminx.customservice.synapse.utils.Utils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
public class ConnectorAction {
@@ -151,91 +145,10 @@ private void loadOutputSchema() throws IOException {
String outputSchemaString = Utils.readFile(new File(outputSchemaPath));
JsonObject outputSchemaJson = Utils.getJsonObject(outputSchemaString);
if (outputSchemaJson != null) {
- outputSchema = createSchemaObject(outputSchemaJson);
+ outputSchema = ConnectorVariableSchemaUtils.buildSchemaProperty(outputSchemaJson);
}
}
- private Property createSchemaObject(JsonObject outputSchemaJson) {
- JsonObject properties = outputSchemaJson.getAsJsonObject(Constant.PROPERTIES);
- if (properties == null) {
- return null;
- }
- Property outputSchemaObject = new Property("root", StringUtils.EMPTY);
- // Store definitions for reference resolution
- JsonObject definitions = outputSchemaJson.getAsJsonObject(Constant.DEFINITIONS);
- List propertiesList = extractProperties(properties, definitions, new HashSet<>());
- outputSchemaObject.setProperties(propertiesList);
- return outputSchemaObject;
- }
-
- private List extractProperties(JsonObject propertiesObject, JsonObject definitions, Set processedRefs) {
- List propertiesList = new ArrayList<>();
- for (Map.Entry entry : propertiesObject.entrySet()) {
- String key = entry.getKey();
- JsonElement value = entry.getValue();
- if (value.isJsonObject()) {
- JsonObject propertyObject = value.getAsJsonObject();
-
- // Check if this is a reference to a definition
- if (propertyObject.has(Constant.REF)) {
- String ref = propertyObject.get(Constant.REF).getAsString();
- // Handle only definitions references (#/definitions/...)
- if (ref.startsWith(Constant.SCHEMA_DEFINITION) && definitions != null) {
- String definitionKey = ref.substring(Constant.SCHEMA_DEFINITION.length());
-
- // Prevent circular references
- if (!processedRefs.contains(definitionKey)) {
- processedRefs.add(definitionKey);
-
- JsonObject definitionObj = definitions.getAsJsonObject(definitionKey);
- if (definitionObj != null) {
- // Create property with the key from the property name
- Property property = new Property(key, StringUtils.EMPTY);
-
- // Get description from the definition if available
- if (definitionObj.has(Constant.DESCRIPTION)) {
- property.setDescription(definitionObj.get(Constant.DESCRIPTION).getAsString());
- }
-
- // Extract nested properties from the definition
- if (definitionObj.has(Constant.PROPERTIES)) {
- List nestedProps = extractProperties(
- definitionObj.getAsJsonObject(Constant.PROPERTIES),
- definitions,
- new HashSet<>(processedRefs)
- );
- property.setProperties(nestedProps);
- }
-
- propertiesList.add(property);
- }
- }
- continue;
- }
- }
-
- // Process regular properties (non-reference)
- JsonElement propDescriptionObj = propertyObject.get(Constant.DESCRIPTION);
- String propDescription = propDescriptionObj != null ?
- propDescriptionObj.getAsString() : StringUtils.EMPTY;
-
- Property property = new Property(key, StringUtils.EMPTY, propDescription);
-
- if (propertyObject.has(Constant.PROPERTIES)) {
- List properties = extractProperties(
- propertyObject.getAsJsonObject(Constant.PROPERTIES),
- definitions,
- new HashSet<>(processedRefs)
- );
- property.setProperties(properties);
- }
-
- propertiesList.add(property);
- }
- }
- return propertiesList;
- }
-
public Property getOutputSchema() {
if (outputSchema == null) {
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorVariableSchemaUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorVariableSchemaUtils.java
new file mode 100644
index 000000000..d507c95a2
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorVariableSchemaUtils.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com).
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * WSO2 LLC - support for WSO2 Micro Integrator Configuration
+ */
+
+package org.eclipse.lemminx.customservice.synapse.connectors.entity;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.lemminx.customservice.synapse.mediator.tryout.pojo.Property;
+import org.eclipse.lemminx.customservice.synapse.utils.Constant;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utility for converting a JSON schema (the {@code outputschema.json} shipped by
+ * connectors and inbound connectors) into the {@link Property} tree consumed by the
+ * mediator tryout system.
+ */
+public class ConnectorVariableSchemaUtils {
+
+ private ConnectorVariableSchemaUtils() {
+
+ }
+
+ /**
+ * Builds a {@link Property} tree rooted at "root" from the given output schema
+ * JSON object. Returns {@code null} if the schema has no {@code properties}.
+ */
+ public static Property buildSchemaProperty(JsonObject outputSchemaJson) {
+
+ if (outputSchemaJson == null) {
+ return null;
+ }
+ JsonObject properties = outputSchemaJson.getAsJsonObject(Constant.PROPERTIES);
+ if (properties == null) {
+ return null;
+ }
+ Property outputSchemaObject = new Property("root", StringUtils.EMPTY);
+ // Store definitions for reference resolution
+ JsonObject definitions = outputSchemaJson.getAsJsonObject(Constant.DEFINITIONS);
+ List propertiesList = extractProperties(properties, definitions, new HashSet<>());
+ outputSchemaObject.setProperties(propertiesList);
+ return outputSchemaObject;
+ }
+
+ private static List extractProperties(JsonObject propertiesObject, JsonObject definitions,
+ Set processedRefs) {
+ List propertiesList = new ArrayList<>();
+ for (Map.Entry entry : propertiesObject.entrySet()) {
+ String key = entry.getKey();
+ JsonElement value = entry.getValue();
+ if (value.isJsonObject()) {
+ JsonObject propertyObject = value.getAsJsonObject();
+
+ // Check if this is a reference to a definition
+ if (propertyObject.has(Constant.REF)) {
+ String ref = propertyObject.get(Constant.REF).getAsString();
+ // Handle only definitions references (#/definitions/...)
+ if (ref.startsWith(Constant.SCHEMA_DEFINITION) && definitions != null) {
+ String definitionKey = ref.substring(Constant.SCHEMA_DEFINITION.length());
+
+ // Prevent circular references. Keep processedRefs path-scoped: do not mutate the
+ // shared set, so sibling properties can reuse the same definition.
+ if (!processedRefs.contains(definitionKey)) {
+ JsonObject definitionObj = definitions.getAsJsonObject(definitionKey);
+ if (definitionObj != null) {
+ // Create property with the key from the property name
+ Property property = new Property(key, StringUtils.EMPTY);
+
+ // Get description from the definition if available
+ if (definitionObj.has(Constant.DESCRIPTION)) {
+ property.setDescription(definitionObj.get(Constant.DESCRIPTION).getAsString());
+ }
+
+ // Extract nested properties from the definition
+ if (definitionObj.has(Constant.PROPERTIES)) {
+ Set nestedRefs = new HashSet<>(processedRefs);
+ nestedRefs.add(definitionKey);
+ List nestedProps = extractProperties(
+ definitionObj.getAsJsonObject(Constant.PROPERTIES),
+ definitions,
+ nestedRefs
+ );
+ property.setProperties(nestedProps);
+ }
+
+ propertiesList.add(property);
+ }
+ }
+ continue;
+ }
+ }
+
+ // Process regular properties (non-reference)
+ JsonElement propDescriptionObj = propertyObject.get(Constant.DESCRIPTION);
+ String propDescription = propDescriptionObj != null ?
+ propDescriptionObj.getAsString() : StringUtils.EMPTY;
+
+ Property property = new Property(key, StringUtils.EMPTY, propDescription);
+
+ if (propertyObject.has(Constant.PROPERTIES)) {
+ List properties = extractProperties(
+ propertyObject.getAsJsonObject(Constant.PROPERTIES),
+ definitions,
+ new HashSet<>(processedRefs)
+ );
+ property.setProperties(properties);
+ }
+
+ propertiesList.add(property);
+ }
+ }
+ return propertiesList;
+ }
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/generate/ConnectorGeneratorResponse.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/generate/ConnectorGeneratorResponse.java
index ac8e63754..67af55bd3 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/generate/ConnectorGeneratorResponse.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/generate/ConnectorGeneratorResponse.java
@@ -18,10 +18,18 @@ public class ConnectorGeneratorResponse {
public boolean buildStatus;
public String connectorPath;
+ public String errorMessage;
public ConnectorGeneratorResponse(boolean buildStatus, String connectorPath) {
this.buildStatus = buildStatus;
this.connectorPath = connectorPath;
}
+
+ public ConnectorGeneratorResponse(boolean buildStatus, String connectorPath, String errorMessage) {
+
+ this.buildStatus = buildStatus;
+ this.connectorPath = connectorPath;
+ this.errorMessage = errorMessage;
+ }
}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/dependency/tree/OverviewModelGenerator.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/dependency/tree/OverviewModelGenerator.java
index 406ab1eb1..19f575c37 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/dependency/tree/OverviewModelGenerator.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/dependency/tree/OverviewModelGenerator.java
@@ -55,8 +55,12 @@ public static OverviewModel getOverviewModel(String projectPath) {
NewProjectResourceFinder newProjectResourceFinder = new NewProjectResourceFinder();
ResourceResponse response = newProjectResourceFinder.getAvailableResources(projectPath, Either.forRight(requiredResources));
for (Resource resource : response.getResources()) {
+ if (((ArtifactResource) resource).isMcpInbound()) {
+ continue;
+ }
+ String absolutePath = ((ArtifactResource) resource).getAbsolutePath();
DependencyScanner dependencyScanner = new DependencyScanner(projectPath);
- DependencyTree dependencyTree = dependencyScanner.analyzeArtifact(((ArtifactResource) resource).getAbsolutePath());
+ DependencyTree dependencyTree = dependencyScanner.analyzeArtifact(absolutePath);
dependencyTreeList.add(dependencyTree);
}
return convertDataToOverviewModel(Paths.get(projectPath).getFileName().toString(), dependencyTreeList);
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryMapResponse.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryMapResponse.java
index 8e707d589..ccaae0846 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryMapResponse.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryMapResponse.java
@@ -70,7 +70,7 @@ private JsonElement convertFormat(JsonElement jsonTree) {
String[] artifactNames = {"apis", "endpoints", "sequences", "proxyServices", "inboundEndpoints",
"messageStores", "messageProcessors", "tasks", "localEntries", "connections", "templates",
- "dataServices", "dataSources"};
+ "dataServices", "dataSources", "mcpServers"};
processLocalEntries(jsonObject);
for (String element : artifactNames) {
artifacts.add(element, jsonObject.get(element));
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java
index ab3ea82c4..04b23b0a6 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java
@@ -19,6 +19,9 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.eclipse.lemminx.customservice.synapse.directoryTree.node.APINode;
import org.eclipse.lemminx.customservice.synapse.directoryTree.node.APIResource;
@@ -55,7 +58,9 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -69,6 +74,8 @@ public class DirectoryTreeBuilder {
private static final String WSO2MI = "wso2mi";
private static final String RESOURCES = "resources";
private static final String JAVA = "java";
+ private static final String MCP_SERVERS_SECTION = "MCP Servers";
+ private static final String MCP_SERVERS_KEY = "mcpServers";
private static String projectPath;
private static String mainSequence;
private static List artifactResourcePaths = new ArrayList<>();
@@ -104,9 +111,37 @@ public static DirectoryMapResponse buildDirectoryTree(WorkspaceFolder projectFol
}
DirectoryMapResponse directoryMapResponse = new DirectoryMapResponse(directoryTree);
+ if (directoryTree instanceof IntegrationDirectoryTree) {
+ applyMcpClassification(directoryMapResponse);
+ }
return directoryMapResponse;
}
+ /**
+ * Classifies MCP server artifacts in the raw directory tree response.
+ */
+ private static void applyMcpClassification(DirectoryMapResponse response) {
+
+ if (response.getDirectoryMap() == null) return;
+ JsonObject root = response.getDirectoryMap().getAsJsonObject();
+ JsonObject src = root.getAsJsonObject(Constant.SRC);
+ if (src == null) return;
+
+ JsonObject main = src.getAsJsonObject(MAIN);
+ if (main == null) return;
+
+ JsonObject wso2mi = main.getAsJsonObject(WSO2MI);
+ if (wso2mi == null) return;
+
+ JsonObject artifacts = wso2mi.getAsJsonObject(Constant.ARTIFACTS);
+ if (artifacts == null) return;
+
+ JsonArray[] result = extractMcpServers(artifacts);
+ artifacts.add(Constant.INBOUNDENDPOINTS, result[1]);
+ artifacts.add(Constant.LOCALENTRIES, result[2]);
+ artifacts.add(MCP_SERVERS_KEY, result[0]);
+ }
+
/**
* Generate model for the project explorer
*
@@ -128,10 +163,18 @@ public static DirectoryMapResponse getProjectExplorerModel(WorkspaceFolder proje
JsonNode resources = root.path(Constant.SRC).path(MAIN).path(WSO2MI).path(Constant.RESOURCES);
ObjectNode newArtifacts = mapper.createObjectNode();
+ ArrayNode mcpServersArray = artifacts.path(MCP_SERVERS_KEY).isArray()
+ ? (ArrayNode) artifacts.path(MCP_SERVERS_KEY) : mapper.createArrayNode();
+ ArrayNode filteredInboundEndpoints = artifacts.path(Constant.INBOUNDENDPOINTS).isArray()
+ ? (ArrayNode) artifacts.path(Constant.INBOUNDENDPOINTS) : mapper.createArrayNode();
+ ArrayNode filteredLocalEntries = artifacts.path(Constant.LOCALENTRIES).isArray()
+ ? (ArrayNode) artifacts.path(Constant.LOCALENTRIES) : mapper.createArrayNode();
+
newArtifacts.set("APIs", artifacts.path(Constant.APIS));
- newArtifacts.set("Event Integrations", artifacts.path(Constant.INBOUNDENDPOINTS));
+ newArtifacts.set("Event Integrations", filteredInboundEndpoints);
newArtifacts.set("Automations", artifacts.path(Constant.TASKS));
newArtifacts.set("Data Services", artifacts.path(Constant.DATA_SERVICES));
+ newArtifacts.set(MCP_SERVERS_SECTION, mcpServersArray);
ObjectNode otherArtifacts = newArtifacts.putObject("Other Artifacts");
otherArtifacts.set("Sequences", artifacts.path(Constant.SEQUENCES));
@@ -152,7 +195,7 @@ public static DirectoryMapResponse getProjectExplorerModel(WorkspaceFolder proje
otherArtifacts.set("Proxy Services", artifacts.path(Constant.PROXYSERVICES));
otherArtifacts.set("Message Stores", artifacts.path(Constant.MESSAGE_STORES));
otherArtifacts.set("Message Processors", artifacts.path(Constant.MESSAGE_PROCESSORS));
- otherArtifacts.set("Local Entries", artifacts.path(Constant.LOCALENTRIES));
+ otherArtifacts.set("Local Entries", filteredLocalEntries);
otherArtifacts.set("Templates", artifacts.path(Constant.TEMPLATES));
JsonNode registryFolders = root.path(Constant.SRC).path(MAIN).path(WSO2MI).path(Constant.RESOURCES)
@@ -387,6 +430,7 @@ private static void analyzeResources(IntegrationDirectoryTree directoryTree) {
analyzeRegistryResources(directoryTree);
analyzeConnectorResources(directoryTree);
+ analyzeInboundConnectorResources(directoryTree);
analyzeMetadataResources(directoryTree);
analyzeNewResources(directoryTree);
}
@@ -450,6 +494,26 @@ private static void analyzeConnectorResources(IntegrationDirectoryTree directory
}
}
+ private static void analyzeInboundConnectorResources(IntegrationDirectoryTree directoryTree) {
+
+ String resourcesPath = projectPath + File.separator + Constant.SRC + File.separator + MAIN
+ + File.separator + WSO2MI + File.separator + RESOURCES + File.separator;
+ for (String dirName : new String[]{Constant.INBOUND_ENDPOINTS, Constant.INBOUND_CONNECTORS_DIR}) {
+ File folder = new File(resourcesPath + dirName);
+ File[] listOfFiles = folder.listFiles();
+ if (listOfFiles != null) {
+ for (File file : listOfFiles) {
+ if (Utils.isZipFile(file) && !file.isHidden()) {
+ String name = file.getName();
+ String path = file.getAbsolutePath();
+ Node resource = new Node("inboundConnector", name, path);
+ directoryTree.getResources().addInboundConnector(resource);
+ }
+ }
+ }
+ }
+ }
+
private static void analyzeMetadataResources(IntegrationDirectoryTree directoryTree) {
String metadataPath = projectPath + File.separator + Constant.SRC + File.separator + MAIN +
@@ -690,6 +754,12 @@ private static AdvancedNode createAdvancedEsbComponent(Node component, String ty
if (Constant.API.equalsIgnoreCase(type)) {
addResources(rootElement, advancedNode);
}
+ if (Constant.INBOUND_ENDPOINT.equalsIgnoreCase(type) && Utils.isMcpInboundEndpoint(rootElement)) {
+ String mcpConfigRef = getMcpConfigReference(rootElement);
+ if (mcpConfigRef != null) {
+ advancedNode.setMcpConfigReference(mcpConfigRef);
+ }
+ }
}
return advancedNode;
}
@@ -702,6 +772,12 @@ private static Node createLocalEntry(Node component, String path) {
if (domDocument != null) {
DOMElement rootElement = domDocument.getDocumentElement();
String key = rootElement.getAttribute(Constant.KEY);
+
+ if (isMcpConfig(rootElement)) {
+ component.setIsMcpConfig(true);
+ return component;
+ }
+
DOMElement childElement = Utils.getFirstElement(rootElement);
if (childElement != null) {
String entryTag = childElement.getNodeName();
@@ -736,6 +812,39 @@ private static String getConnectionType(DOMElement element) {
return null;
}
+ private static boolean isMcpConfig(DOMElement rootElement) {
+
+ List children = rootElement.getChildren();
+ if (children != null) {
+ for (DOMNode child : children) {
+ if ("mcptools".equals(child.getNodeName())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static String getMcpConfigReference(DOMElement inboundElement) {
+
+ DOMNode parametersNode = Utils.getChildNodeByName(inboundElement, "parameters");
+ if (parametersNode != null) {
+ List children = parametersNode.getChildren();
+ if (children != null) {
+ for (DOMNode child : children) {
+ if ("parameter".equals(child.getNodeName())) {
+ String paramName = ((DOMElement) child).getAttribute("name");
+ if ("mcp.tools.localentry".equals(paramName)) {
+ String paramValue = Utils.getInlineString(child.getFirstChild());
+ return paramValue;
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
private static String getApiContext(String path) {
File file = new File(path);
@@ -822,6 +931,85 @@ private static void addResources(DOMElement rootElement, AdvancedNode advancedNo
}
}
+ /**
+ * Separates MCP server artifacts from the regular inbound endpoints and local entries.
+ */
+ private static JsonArray[] extractMcpServers(JsonObject artifacts) {
+
+ JsonElement inboundEndpointsElem = artifacts.get(Constant.INBOUNDENDPOINTS);
+ JsonElement localEntriesElem = artifacts.get(Constant.LOCALENTRIES);
+
+ JsonArray inboundEndpointsNode = (inboundEndpointsElem != null && inboundEndpointsElem.isJsonArray())
+ ? inboundEndpointsElem.getAsJsonArray() : new JsonArray();
+ JsonArray localEntriesNode = (localEntriesElem != null && localEntriesElem.isJsonArray())
+ ? localEntriesElem.getAsJsonArray() : new JsonArray();
+
+ Map mcpLocalEntries = new LinkedHashMap<>();
+ JsonArray filteredLocalEntries = new JsonArray();
+ for (JsonElement localEntry : localEntriesNode) {
+ if (!localEntry.isJsonObject() || !localEntry.getAsJsonObject().has(Constant.NAME)) {
+ filteredLocalEntries.add(localEntry);
+ continue;
+ }
+ JsonObject entryObj = localEntry.getAsJsonObject();
+ String entryName = entryObj.get(Constant.NAME).getAsString();
+
+ if (entryObj.has("isMcpConfig") && entryObj.get("isMcpConfig").getAsBoolean()) {
+ mcpLocalEntries.put(entryName, localEntry);
+ } else {
+ filteredLocalEntries.add(localEntry);
+ }
+ }
+
+ Map mcpInboundEndpoints = new LinkedHashMap<>();
+ JsonArray filteredInboundEndpoints = new JsonArray();
+ for (JsonElement inboundEndpoint : inboundEndpointsNode) {
+ if (!inboundEndpoint.isJsonObject()) {
+ filteredInboundEndpoints.add(inboundEndpoint);
+ continue;
+ }
+ JsonObject endpointObj = inboundEndpoint.getAsJsonObject();
+ String mcpConfigRef = null;
+
+ if (endpointObj.has("mcpConfigReference") && !endpointObj.get("mcpConfigReference").isJsonNull()) {
+ mcpConfigRef = endpointObj.get("mcpConfigReference").getAsString();
+ }
+
+ if (mcpConfigRef != null && mcpLocalEntries.containsKey(mcpConfigRef)) {
+ mcpInboundEndpoints.put(mcpConfigRef, inboundEndpoint);
+ } else {
+ filteredInboundEndpoints.add(inboundEndpoint);
+ }
+ }
+
+ JsonArray mcpServersArray = new JsonArray();
+ for (String mcpConfigKey : mcpInboundEndpoints.keySet()) {
+ JsonElement localEntry = mcpLocalEntries.get(mcpConfigKey);
+ if (localEntry == null) {
+ filteredInboundEndpoints.add(mcpInboundEndpoints.get(mcpConfigKey));
+ continue;
+ }
+ JsonObject mcpServer = new JsonObject();
+ JsonObject inboundEndpointObj = mcpInboundEndpoints.get(mcpConfigKey).getAsJsonObject();
+ String serverName = (inboundEndpointObj.has(Constant.NAME) && !inboundEndpointObj.get(Constant.NAME).isJsonNull())
+ ? inboundEndpointObj.get(Constant.NAME).getAsString()
+ : mcpConfigKey;
+ mcpServer.addProperty(Constant.NAME, serverName);
+ mcpServer.add(Constant.LOCAL_ENTRY, localEntry);
+ mcpServer.add(Constant.INBOUND_ENDPOINT, mcpInboundEndpoints.get(mcpConfigKey));
+ mcpServersArray.add(mcpServer);
+ }
+
+ // Restore MCP local entries (with ) that have no matching endpoint
+ for (Map.Entry entry : mcpLocalEntries.entrySet()) {
+ if (!mcpInboundEndpoints.containsKey(entry.getKey())) {
+ filteredLocalEntries.add(entry.getValue());
+ }
+ }
+
+ return new JsonArray[]{mcpServersArray, filteredInboundEndpoints, filteredLocalEntries};
+ }
+
private static void extractClassMediators(JsonNode mediatorFolders, ArrayNode classMediatorArray) {
for (JsonNode classMediatorFolder : mediatorFolders) {
if (classMediatorFolder.has(Constant.FILES)) {
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Node.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Node.java
index 4947867b3..4ebe9aa39 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Node.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Node.java
@@ -21,6 +21,8 @@ public class Node {
String name;
String path;
Boolean isFaulty = false;
+ String mcpConfigReference;
+ Boolean isMcpConfig = false;
public Node(String type, String name, String path) {
@@ -87,6 +89,26 @@ public void setFaulty(Boolean faulty) {
isFaulty = faulty;
}
+ public String getMcpConfigReference() {
+
+ return mcpConfigReference;
+ }
+
+ public void setMcpConfigReference(String mcpConfigReference) {
+
+ this.mcpConfigReference = mcpConfigReference;
+ }
+
+ public Boolean getIsMcpConfig() {
+
+ return isMcpConfig;
+ }
+
+ public void setIsMcpConfig(Boolean isMcpConfig) {
+
+ this.isMcpConfig = isMcpConfig;
+ }
+
protected Boolean equals(Node component) {
return this.type.equals(component.type) && this.name.equals(component.name) && this.path.equals(component.path);
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Resource.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Resource.java
index f1c364379..edd682360 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Resource.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Resource.java
@@ -21,6 +21,7 @@ public class Resource {
private RegistryResource registry;
private List connectors;
+ private List inboundConnectors;
private List metadata;
private FolderNode newResources;
@@ -28,6 +29,7 @@ public Resource() {
registry = new RegistryResource();
connectors = new ArrayList<>();
+ inboundConnectors = new ArrayList<>();
metadata = new ArrayList<>();
}
@@ -46,6 +48,11 @@ public void addConnector(Node connector) {
connectors.add(connector);
}
+ public void addInboundConnector(Node inboundConnector) {
+
+ inboundConnectors.add(inboundConnector);
+ }
+
public void addMetadata(Node meta) {
metadata.add(meta);
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/expression/SemanticExpressionValidator.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/expression/SemanticExpressionValidator.java
index fc8a729d4..11f46a5dc 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/expression/SemanticExpressionValidator.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/expression/SemanticExpressionValidator.java
@@ -198,6 +198,64 @@ private boolean isOverloadCompatible(FunctionSignature sig, String[] literalType
return true;
}
+ /**
+ * Detects the operator-precedence pitfall where a logical operator ('and'/'or') is used as a
+ * direct, unparenthesized operand of a comparison operator. In the Synapse expression grammar,
+ * 'and'/'or' bind TIGHTER than the comparison operators (<, >, <=, >=, ==, !=), so an
+ * expression such as {@code a <= 0 or b > 10} parses as {@code a <= (0 or b) > 10} instead of
+ * the usually-intended {@code (a <= 0) or (b > 10)}. A warning is emitted recommending explicit
+ * parentheses. The correctly-parenthesized form parses as a single top-level logical expression
+ * with no comparison operator at this node, so it is never flagged (no false positives).
+ */
+ @Override
+ public Void visitComparisonExpression(ExpressionParser.ComparisonExpressionContext ctx) {
+ if (hasComparisonOperator(ctx)) {
+ for (ExpressionParser.LogicalExpressionContext operand : ctx.logicalExpression()) {
+ TerminalNode logicalOp = operand.AND() != null ? operand.AND() : operand.OR();
+ if (logicalOp != null) {
+ Token opToken = logicalOp.getSymbol();
+ String op = opToken.getText();
+ String message = "Operator precedence: '" + op + "' binds tighter than the comparison "
+ + "operators (<, >, <=, >=, ==, !=) in Synapse expressions, so '" + op
+ + "' is evaluated before the comparison. This is likely not the intended "
+ + "behavior. Add parentheses around each comparison to make the intent "
+ + "explicit, e.g. (a <= 0) " + op + " (b > 10).";
+ ExpressionError error = new ExpressionError(opToken.getLine(),
+ opToken.getCharPositionInLine(), message, opToken, null);
+ error.setWarning(true);
+ errors.add(error);
+ break; // One warning per comparison expression is sufficient.
+ }
+ }
+ }
+ return visitChildren(ctx);
+ }
+
+ /**
+ * Returns true if this comparison expression actually applies a comparison operator, as opposed
+ * to being a pass-through to a single logical expression. Checks the direct children for a
+ * comparison operator token rather than relying on a specific generated accessor.
+ */
+ private boolean hasComparisonOperator(ExpressionParser.ComparisonExpressionContext ctx) {
+ for (int i = 0; i < ctx.getChildCount(); i++) {
+ if (ctx.getChild(i) instanceof TerminalNode) {
+ int type = ((TerminalNode) ctx.getChild(i)).getSymbol().getType();
+ switch (type) {
+ case ExpressionLexer.GT:
+ case ExpressionLexer.LT:
+ case ExpressionLexer.GTE:
+ case ExpressionLexer.LTE:
+ case ExpressionLexer.EQ:
+ case ExpressionLexer.NEQ:
+ return true;
+ default:
+ break;
+ }
+ }
+ }
+ return false;
+ }
+
@Override
public Void visitArithmeticExpression(ExpressionParser.ArithmeticExpressionContext ctx) {
List terms = ctx.term();
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java
index 38801b406..860d70580 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java
@@ -22,10 +22,13 @@
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.fge.jackson.JsonLoader;
import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.lemminx.customservice.synapse.connectors.UiSchemaFlattener;
+import org.eclipse.lemminx.customservice.synapse.connectors.entity.ConnectorVariableSchemaUtils;
+import org.eclipse.lemminx.customservice.synapse.mediator.tryout.pojo.Property;
import org.eclipse.lemminx.customservice.synapse.parser.Node;
import org.eclipse.lemminx.customservice.synapse.parser.OverviewPageDetailsResponse;
import org.eclipse.lemminx.customservice.synapse.syntaxTree.SyntaxTreeGenerator;
@@ -56,13 +59,17 @@
public class InboundConnectorHolder {
private static final Logger LOGGER = Logger.getLogger(InboundConnectorHolder.class.getName());
+ public static final String INPUT_SCHEMA_FILE_SUFFIX = "_inputschema.json";
+ private static InboundConnectorHolder instance;
private String projectId;
private String projectPath;
private String tempFolderPath;
// map
- private HashMap connectorIdMap;
+ private final HashMap connectorIdMap;
// map
- private HashMap inboundConnectors;
+ private final HashMap inboundConnectors;
+ // map
+ private final HashMap inboundConnectorInputSchemas;
private Map localInboundConnectors;
private JsonObject inboundConnectorListJson;
private String projectRuntimeVersion;
@@ -77,6 +84,15 @@ public InboundConnectorHolder() {
this.inboundConnectors = new HashMap<>();
this.connectorIdMap = new HashMap<>();
+ this.inboundConnectorInputSchemas = new HashMap<>();
+ }
+
+ public static InboundConnectorHolder getInstance() {
+
+ if (instance == null) {
+ throw new IllegalStateException("InboundConnectorHolder has not yet been initialized");
+ }
+ return instance;
}
public void init(String projectPath, String projectRuntimeVersion) {
@@ -106,6 +122,7 @@ public void init(String projectPath, String projectRuntimeVersion) {
getCustomInboundConnectors();
loadInboundConnectors();
this.localInboundEndpointsListForCopilot = generateInboundConnectorArray();
+ instance = this;
}
private void loadInboundConnectors() {
@@ -123,15 +140,28 @@ private void loadInboundConnectors() {
}
}
- public void getCustomInboundConnectors() {
+ public synchronized String getCustomInboundConnectors() {
- File extractFolder = new File(Path.of(this.projectPath, Constant.SRC, Constant.MAIN, Constant.WSO2MI,
- Constant.RESOURCES, Constant.INBOUND_CONNECTORS_DIR).toString());
+ boolean isInboundConnectorAdded = false;
InputStream inputStream = JsonLoader.class
.getResourceAsStream("/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_"
+ this.projectRuntimeVersion.replace(".", StringUtils.EMPTY) + Constant.JSON_FILE_EXT);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
this.inboundConnectorListJson = JsonParser.parseReader(reader).getAsJsonObject();
+ Path resourcesPath = Path.of(this.projectPath, Constant.SRC, Constant.MAIN, Constant.WSO2MI,
+ Constant.RESOURCES);
+ for (String dirName : new String[]{Constant.INBOUND_ENDPOINTS, Constant.INBOUND_CONNECTORS_DIR}) {
+ File extractFolder = new File(resourcesPath.resolve(dirName).toString());
+ if (importInboundConnectorsFromDirectory(extractFolder)) {
+ isInboundConnectorAdded = true;
+ }
+ }
+ return isInboundConnectorAdded ? "success" : "Failed to import the inbound-connector";
+ }
+
+ private boolean importInboundConnectorsFromDirectory(File extractFolder) {
+
+ boolean isInboundConnectorAdded = false;
List inboundConnectorZips = getInboundConnectorZips(extractFolder);
for (File zip : inboundConnectorZips) {
String zipName = zip.getName().replace(Constant.DOT + "zip", StringUtils.EMPTY);
@@ -140,18 +170,30 @@ public void getCustomInboundConnectors() {
Utils.extractZip(zip, extractToFolder);
String schema = Utils.readFile(extractToFolder.toPath().resolve(Constant.RESOURCES)
.resolve(Constant.UI_SCHEMA_JSON).toFile());
- saveInboundConnector(Utils.getJsonObject(schema).get(Constant.NAME).getAsString(), schema);
- JsonObject newConnector = new JsonObject();
JsonObject connectorSchema = Utils.getJsonObject(schema);
- newConnector.addProperty(Constant.NAME, connectorSchema.get(Constant.TITLE) != null ?
- connectorSchema.get(Constant.TITLE).getAsString() : StringUtils.EMPTY);
- newConnector.addProperty(Constant.ID, connectorSchema.get(Constant.ID) != null ?
- connectorSchema.get(Constant.ID).getAsString() : StringUtils.EMPTY);
- newConnector.addProperty(Constant.DESCRIPTION, connectorSchema.get(Constant.DESCRIPTION) != null ?
- connectorSchema.get(Constant.DESCRIPTION).getAsString() : StringUtils.EMPTY);
- newConnector.addProperty(Constant.TYPE, Constant.INBOUND_DASH_ENDPOINT);
- JsonArray connectorArray = this.inboundConnectorListJson.getAsJsonArray(Constant.INBOUND_CONNECTOR_DATA);
- connectorArray.add(newConnector);
+ if (saveInboundConnector(connectorSchema.get(Constant.NAME).getAsString(), schema)) {
+ File inputSchemaFile = extractToFolder.toPath().resolve(Constant.RESOURCES)
+ .resolve(Constant.INPUT_SCHEMA_JSON).toFile();
+ if (inputSchemaFile.exists()) {
+ saveInboundConnectorInputSchema(connectorSchema.get(Constant.NAME).getAsString(),
+ connectorSchema.has(Constant.ID) ? connectorSchema.get(Constant.ID).getAsString() : null,
+ Utils.readFile(inputSchemaFile));
+ }
+ JsonArray connectorArray = this.inboundConnectorListJson.getAsJsonArray(Constant.INBOUND_CONNECTOR_DATA);
+ String connectorId = connectorSchema.get(Constant.ID) != null ?
+ connectorSchema.get(Constant.ID).getAsString() : StringUtils.EMPTY;
+ if (!isConnectorAlreadyListed(connectorArray, connectorId)) {
+ JsonObject newConnector = new JsonObject();
+ newConnector.addProperty(Constant.NAME, connectorSchema.get(Constant.TITLE) != null ?
+ connectorSchema.get(Constant.TITLE).getAsString() : StringUtils.EMPTY);
+ newConnector.addProperty(Constant.ID, connectorId);
+ newConnector.addProperty(Constant.DESCRIPTION, connectorSchema.get(Constant.DESCRIPTION) != null ?
+ connectorSchema.get(Constant.DESCRIPTION).getAsString() : StringUtils.EMPTY);
+ newConnector.addProperty(Constant.TYPE, Constant.INBOUND_DASH_ENDPOINT);
+ connectorArray.add(newConnector);
+ }
+ isInboundConnectorAdded = true;
+ }
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Failed to import custom inbound-connector:" + zipName, e);
}
@@ -163,6 +205,21 @@ public void getCustomInboundConnectors() {
}
}
}
+ return isInboundConnectorAdded;
+ }
+
+ private boolean isConnectorAlreadyListed(JsonArray connectorArray, String connectorId) {
+
+ if (connectorId == null || connectorId.isEmpty()) {
+ return false;
+ }
+ for (JsonElement element : connectorArray) {
+ JsonObject connector = element.getAsJsonObject();
+ if (connector.has(Constant.ID) && connectorId.equals(connector.get(Constant.ID).getAsString())) {
+ return true;
+ }
+ }
+ return false;
}
private List getInboundConnectorZips(File extractFolder) {
@@ -183,8 +240,13 @@ private List getInboundConnectorZips(File extractFolder) {
private void loadInboundConnector(File file) {
+ String fileName = file.getName();
+ // Input schema files are loaded together with their uischema, skip them here.
+ if (fileName.endsWith(INPUT_SCHEMA_FILE_SUFFIX)) {
+ return;
+ }
try {
- String connectorName = file.getName().replace(".json", "");
+ String connectorName = fileName.replace(Constant.JSON_FILE_EXT, StringUtils.EMPTY);
String uiSchema = Utils.readFile(file);
JsonObject inboundConnector = Utils.getJsonObject(uiSchema);
if (inboundConnector == null || !inboundConnector.has(Constant.ID)) {
@@ -193,6 +255,10 @@ private void loadInboundConnector(File file) {
String id = inboundConnector.get(Constant.ID).getAsString();
connectorIdMap.put(connectorName, id);
inboundConnectors.put(id, file.getAbsolutePath());
+ File inputSchemaFile = Path.of(tempFolderPath, connectorName + INPUT_SCHEMA_FILE_SUFFIX).toFile();
+ if (inputSchemaFile.exists()) {
+ inboundConnectorInputSchemas.put(id, inputSchemaFile.getAbsolutePath());
+ }
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Error occurred while loading inbound connector schema from file", e);
}
@@ -215,6 +281,47 @@ public Boolean saveInboundConnector(String connectorName, String uiSchema) {
return false;
}
+ /**
+ * Persists the input schema shipped by an inbound connector (its
+ * {@code resources/inputschema.json}) next to the uischema in the per-project
+ * temp folder and registers it against the connector id.
+ *
+ * @param connectorName the connector name (uischema {@code name})
+ * @param id the connector id (uischema {@code id})
+ * @param inputSchema the raw input schema JSON
+ */
+ public void saveInboundConnectorInputSchema(String connectorName, String id, String inputSchema) {
+
+ if (StringUtils.isEmpty(id) || StringUtils.isEmpty(inputSchema)) {
+ return;
+ }
+ Path filePath = Path.of(tempFolderPath, connectorName + INPUT_SCHEMA_FILE_SUFFIX);
+ if (saveToFile(filePath.toFile(), inputSchema)) {
+ inboundConnectorInputSchemas.put(id, filePath.toString());
+ }
+ }
+
+ /**
+ * Returns the input schema of the inbound connector with the given id as a
+ * {@link Property} tree (built the same way as regular connector output schemas),
+ * or {@code null} if no input schema is registered.
+ */
+ public Property getInboundConnectorInputSchema(String id) {
+
+ String inputSchemaPath = inboundConnectorInputSchemas.get(id);
+ if (StringUtils.isEmpty(inputSchemaPath)) {
+ return null;
+ }
+ try {
+ String inputSchema = Utils.readFile(new File(inputSchemaPath));
+ JsonObject inputSchemaJson = Utils.getJsonObject(inputSchema);
+ return ConnectorVariableSchemaUtils.buildSchemaProperty(inputSchemaJson);
+ } catch (IOException e) {
+ LOGGER.log(Level.SEVERE, "Error occurred while reading inbound connector input schema from file", e);
+ }
+ return null;
+ }
+
public InboundConnectorResponse getInboundConnectorSchema(File inboundEPFile) {
try {
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/ServerLessTryoutHandler.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/ServerLessTryoutHandler.java
index be5a30302..470007ab7 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/ServerLessTryoutHandler.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/ServerLessTryoutHandler.java
@@ -15,6 +15,8 @@
package org.eclipse.lemminx.customservice.synapse.mediator.schema.generate;
import com.google.gson.JsonPrimitive;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.lemminx.customservice.synapse.InvalidConfigurationException;
import org.eclipse.lemminx.customservice.synapse.mediator.TryOutUtils;
import org.eclipse.lemminx.customservice.synapse.mediator.schema.generate.visitor.SchemaVisitor;
import org.eclipse.lemminx.customservice.synapse.mediator.schema.generate.visitor.SchemaVisitorFactory;
@@ -23,6 +25,9 @@
import org.eclipse.lemminx.customservice.synapse.mediator.tryout.pojo.MediatorTryoutRequest;
import org.eclipse.lemminx.customservice.synapse.syntaxTree.SyntaxTreeGenerator;
import org.eclipse.lemminx.customservice.synapse.syntaxTree.pojo.STNode;
+import org.eclipse.lemminx.customservice.synapse.syntaxTree.pojo.inbound.InboundEndpoint;
+import org.eclipse.lemminx.customservice.synapse.utils.ConfigFinder;
+import org.eclipse.lemminx.customservice.synapse.utils.Constant;
import org.eclipse.lemminx.customservice.synapse.utils.Utils;
import org.eclipse.lemminx.dom.DOMDocument;
@@ -44,27 +49,48 @@ public ServerLessTryoutHandler(String projectUri) {
public MediatorTryoutInfo handle(MediatorTryoutRequest request) {
try {
- String filePath = request.getFile();
+ String visitFilePath = request.getFile();
if (request.getEdits() != null) {
+ STNode node = getSTNode(request.getFile());
String documentUri = request.getFile();
+ String editFilePath = TEMP_FOLDER.resolve(TEMP_FILE_NAME).toString();
+ if (node instanceof InboundEndpoint) {
+ String sequence = ((InboundEndpoint) node).getSequence();
+ if (StringUtils.isNotEmpty(sequence)) {
+ String seqPath = ConfigFinder.findEsbComponentPath(sequence, Constant.SEQUENCES, projectUri);
+ if (StringUtils.isNotEmpty(seqPath)) {
+ documentUri = seqPath;
+ }
+ }
+ } else {
+ visitFilePath = editFilePath;
+ }
Utils.copyFile(documentUri, TEMP_FOLDER.toString(), TEMP_FILE_NAME);
- filePath = TEMP_FOLDER.resolve(TEMP_FILE_NAME).toString();
- TryOutUtils.doEdits(request.getEdits(), Path.of(filePath));
- request = new MediatorTryoutRequest(filePath, request.getLine(), request.getColumn() + 1,
+ TryOutUtils.doEdits(request.getEdits(), Path.of(editFilePath));
+ request = new MediatorTryoutRequest(editFilePath, request.getLine(), request.getColumn() + 1,
request.getInputPayload(), null);
}
- DOMDocument domDocument = Utils.getDOMDocument(new File(filePath));
+ DOMDocument domDocument = Utils.getDOMDocument(new File(visitFilePath));
STNode node = SyntaxTreeGenerator.buildTree(domDocument.getDocumentElement());
MediatorTryoutInfo mediatorTryoutInfo = createInitialMediatorTryoutInfo(request);
if (node != null) {
visitNode(node, request, mediatorTryoutInfo);
}
return mediatorTryoutInfo;
- } catch (IOException e) {
+ } catch (IOException | InvalidConfigurationException e) {
return new MediatorTryoutInfo(e.getMessage());
}
}
+ private STNode getSTNode(String filePath) throws IOException, InvalidConfigurationException {
+
+ if (StringUtils.isEmpty(filePath)) {
+ throw new IllegalArgumentException("FilePath is null");
+ }
+ DOMDocument domDocument = Utils.getDOMDocument(new File(filePath));
+ return SyntaxTreeGenerator.buildTree(domDocument.getDocumentElement());
+ }
+
private MediatorTryoutInfo createInitialMediatorTryoutInfo(MediatorTryoutRequest request) {
MediatorInfo mediatorInfo = new MediatorInfo();
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java
index f38835174..69dff96f6 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java
@@ -15,10 +15,15 @@
package org.eclipse.lemminx.customservice.synapse.mediator.schema.generate.visitor;
import org.apache.commons.lang3.StringUtils;
+import org.eclipse.lemminx.customservice.synapse.inbound.conector.InboundConnectorHolder;
import org.eclipse.lemminx.customservice.synapse.mediator.tryout.pojo.MediatorTryoutInfo;
import org.eclipse.lemminx.customservice.synapse.mediator.tryout.pojo.MediatorTryoutRequest;
+import org.eclipse.lemminx.customservice.synapse.mediator.tryout.pojo.Property;
import org.eclipse.lemminx.customservice.synapse.syntaxTree.pojo.STNode;
import org.eclipse.lemminx.customservice.synapse.syntaxTree.pojo.inbound.InboundEndpoint;
+import org.eclipse.lemminx.customservice.synapse.syntaxTree.pojo.inbound.InboundEndpointParameters;
+import org.eclipse.lemminx.customservice.synapse.syntaxTree.pojo.misc.common.Parameter;
+import org.eclipse.lemminx.customservice.synapse.utils.Constant;
import java.io.IOException;
import java.util.logging.Level;
@@ -37,14 +42,69 @@ public InboundEndpointVisitor(String projectPath) {
@Override
public void visit(STNode node, MediatorTryoutInfo info, MediatorTryoutRequest request) {
- String sequence = ((InboundEndpoint) node).getSequence();
+ InboundEndpoint inboundEndpoint = (InboundEndpoint) node;
+ String sequence = inboundEndpoint.getSequence();
if (StringUtils.isEmpty(sequence)) {
return;
}
+
+ loadInboundVariable(inboundEndpoint, info);
+
try {
Utils.visitSequenceByKey(sequence, projectPath, info, request);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, String.format("Error occurred while visiting the sequence: %s", sequence), e);
}
}
+
+ /**
+ * If the inbound endpoint declares an {@code inboundVariableName} parameter, load the
+ * input schema of the corresponding inbound connector and seed a variable with that
+ * name into the tryout info. This mirrors how connector mediators seed their response
+ * variable, making the incoming message structure available to the dispatched sequence.
+ */
+ private void loadInboundVariable(InboundEndpoint inboundEndpoint, MediatorTryoutInfo info) {
+
+ String inboundVariableName = getParameterValue(inboundEndpoint, Constant.INBOUND_VARIABLE_NAME);
+ if (StringUtils.isEmpty(inboundVariableName)) {
+ return;
+ }
+ InboundConnectorHolder holder;
+ try {
+ holder = InboundConnectorHolder.getInstance();
+ } catch (IllegalStateException e) {
+ LOGGER.severe("Inbound connector holder is not initialized");
+ return;
+ }
+ String id = inboundEndpoint.getProtocol() != null ? inboundEndpoint.getProtocol()
+ : inboundEndpoint.getClazz();
+ if (StringUtils.isEmpty(id)) {
+ return;
+ }
+ Property inputSchema = holder.getInboundConnectorInputSchema(id);
+ if (inputSchema == null) {
+ return;
+ }
+ inputSchema.setKey(inboundVariableName);
+ info.addOutputVariable(inputSchema);
+ }
+
+ private String getParameterValue(InboundEndpoint inboundEndpoint, String parameterName) {
+
+ InboundEndpointParameters[] parametersList = inboundEndpoint.getParameters();
+ if (parametersList == null) {
+ return null;
+ }
+ for (InboundEndpointParameters parameters : parametersList) {
+ if (parameters == null || parameters.getParameter() == null) {
+ continue;
+ }
+ for (Parameter parameter : parameters.getParameter()) {
+ if (parameter != null && parameterName.equals(parameter.getName())) {
+ return parameter.getContent();
+ }
+ }
+ }
+ return null;
+ }
}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/AIConnectorHandler.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/AIConnectorHandler.java
index 46aece6d1..ec30f49fe 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/AIConnectorHandler.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/AIConnectorHandler.java
@@ -555,6 +555,14 @@ private Map processToolData(Map data, String seq
return toolData;
}
+ private static String unwrapExpression(String value) {
+
+ if (StringUtils.isNotBlank(value) && value.startsWith("${") && value.endsWith("}")) {
+ return value.substring(2, value.length() - 1);
+ }
+ return value;
+ }
+
/**
* Generates a unique sequence template name for the newly added tool.
*/
@@ -931,7 +939,7 @@ private void addToolConfigurations(JsonObject schema, DOMNode node, Mediator med
JsonObject expression = new JsonObject();
expression.addProperty(Constant.IS_EXPRESSION, true);
- expression.addProperty(Constant.VALUE, node.getAttribute(RESULT_EXPRESSION));
+ expression.addProperty(Constant.VALUE, unwrapExpression(node.getAttribute(RESULT_EXPRESSION)));
toolData.put(TOOL_RESULT_EXPRESSION, expression);
toolData.put(TOOL_DESCRIPTION, node.getAttribute(Constant.DESCRIPTION));
@@ -1408,19 +1416,37 @@ public MCPToolResponse fetchMcpTools(String documentUri, Range range, List 0) {
+ break;
+ }
+ continue;
+ }
+ if (line.startsWith("data:")) {
+ dataBuffer.append(line.substring(5).trim());
+ }
}
+ responseJson = dataBuffer.toString();
+ } else {
+ responseJson = responseBody;
+ }
+
+ if (StringUtils.isBlank(responseJson)) {
+ response.error = "Empty MCP response";
+ return response;
}
- String responseJson = dataBuffer.toString();
ObjectMapper mapper = new ObjectMapper();
JsonNode dataJson = mapper.readTree(responseJson);
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/MediatorHandler.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/MediatorHandler.java
index 6094bfdd9..9a94631c1 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/MediatorHandler.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/MediatorHandler.java
@@ -176,6 +176,11 @@ private SynapseConfigResponse generateConnectorSynapseConfig(STNode node, String
Map connectorData = new HashMap<>();
connectorData.put(Constant.TAG, operation.getTag());
connectorData.put(Constant.CONFIG_KEY, data.get(Constant.CONFIG_KEY));
+ Object description = data.get(Constant.DESCRIPTION);
+ if (description == null && node instanceof Connector) {
+ description = ((Connector) node).getDescription();
+ }
+ connectorData.put(Constant.DESCRIPTION, description);
List
+
diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/misc/resource.xsd b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/misc/resource.xsd
index 2313b5360..5a10574af 100644
--- a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/misc/resource.xsd
+++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/misc/resource.xsd
@@ -75,6 +75,8 @@
+
+
diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/task.xsd b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/task.xsd
index 4c5482d16..830cc1f11 100644
--- a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/task.xsd
+++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/task.xsd
@@ -49,6 +49,7 @@
+
diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/api.xsd b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/api.xsd
index bf49c6330..93d56092b 100644
--- a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/api.xsd
+++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/api.xsd
@@ -74,6 +74,7 @@
+
diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/misc/resource.xsd b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/misc/resource.xsd
index 2313b5360..5a10574af 100644
--- a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/misc/resource.xsd
+++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/misc/resource.xsd
@@ -75,6 +75,8 @@
+
+
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/CodeDiagnosticFileNameTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/CodeDiagnosticFileNameTest.java
new file mode 100644
index 000000000..51315537e
--- /dev/null
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/CodeDiagnosticFileNameTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com).
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * WSO2 LLC - support for WSO2 Micro Integrator Configuration
+ */
+
+package org.eclipse.lemminx.extensions.synapse;
+
+import org.eclipse.lemminx.AbstractCacheBasedTest;
+import org.eclipse.lemminx.XMLAssert;
+import org.eclipse.lemminx.customservice.synapse.utils.Utils;
+import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.extensions.contentmodel.settings.ContentModelSettings;
+import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationRootSettings;
+import org.eclipse.lemminx.services.XMLLanguageService;
+import org.eclipse.lsp4j.Diagnostic;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for the {@code synapse/codeDiagnostic} document-URI plumbing.
+ *
+ * The MI Copilot agent validates in-memory (unsaved) code via {@code synapse/codeDiagnostic}.
+ * Before the fix this path parsed the code with the literal URI {@code "temp"}, which made the
+ * URI-gated {@code SynapseExpressionValidator} (it only runs for files under
+ * {@code src/main/wso2mi/artifacts}) silently skip every expression diagnostic. The fix lets the
+ * caller supply the real file name as the document URI (with {@code "temp"} as the backward
+ * compatible fallback).
+ *
+ *
These tests exercise the exact pipeline {@code SynapseLanguageService.codeDiagnostic()} runs
+ * internally — {@code Utils.getDOMDocument(code, uri, resolver)} followed by
+ * {@code XMLLanguageService.doDiagnostics(...)} — and assert the operator-precedence warning is
+ * surfaced only when the URI is under the artifacts path.
+ */
+public class CodeDiagnosticFileNameTest extends AbstractCacheBasedTest {
+
+ private static final String SYNAPSE_NS = "http://ws.apache.org/ns/synapse";
+ private static final String SYNAPSE_CATALOG_440 =
+ "src/main/resources/org/eclipse/lemminx/schemas/440/catalog.xml";
+ // An absolute path under the project artifacts directory, as the MI extension sends. The
+ // SynapseExpressionValidator gate is separator-agnostic, so this forward-slash URI works on all OSes.
+ private static final String ARTIFACT_URI =
+ "/home/proj/src/main/wso2mi/artifacts/sequences/Test.xml";
+
+ // with an unparenthesized comparison/logical mix — the precedence pitfall.
+ private static final String UNPARENTHESIZED = ""
+ + ""
+ + "";
+ // The corrected, explicitly parenthesized form.
+ private static final String PARENTHESIZED = ""
+ + ""
+ + "";
+
+ /**
+ * Reproduces the codeDiagnostic pipeline: build the DOM document with the given URI (via the
+ * fileName-aware overload the fix added), then run the full XML diagnostics pipeline.
+ */
+ private List codeDiagnostic(String code, String fileName) {
+ XMLLanguageService ls = new XMLLanguageService();
+ String uri = fileName != null ? fileName : "temp";
+ DOMDocument document = Utils.getDOMDocument(code, uri, ls.getResolverExtensionManager());
+ ls.setDocumentProvider(u -> document);
+
+ ContentModelSettings settings = new ContentModelSettings();
+ settings.setUseCache(false);
+ XMLValidationRootSettings validation = new XMLValidationRootSettings();
+ validation.setNoGrammar("ignore");
+ settings.setValidation(validation);
+ settings.setCatalogs(new String[]{SYNAPSE_CATALOG_440});
+ ls.doSave(new XMLAssert.SettingsSaveContext(settings));
+
+ return ls.doDiagnostics(document, settings.getValidation(), Collections.emptyMap(), () -> {});
+ }
+
+ private List precedenceWarnings(List diagnostics) {
+ return diagnostics.stream()
+ .filter(d -> d.getMessage() != null && d.getMessage().contains("Operator precedence"))
+ .collect(Collectors.toList());
+ }
+
+ @Test
+ public void testPrecedenceWarningReturnedForArtifactsPath() {
+ // With a real artifacts file name, the expression validator runs and reports the warning.
+ List diags = codeDiagnostic(UNPARENTHESIZED, ARTIFACT_URI);
+ assertFalse(precedenceWarnings(diags).isEmpty(),
+ "codeDiagnostic with an artifacts file name should surface the operator-precedence warning");
+ }
+
+ @Test
+ public void testNoPrecedenceWarningForParenthesizedExpression() {
+ // The corrected, parenthesized form must not be flagged even on the artifacts path.
+ List diags = codeDiagnostic(PARENTHESIZED, ARTIFACT_URI);
+ assertTrue(precedenceWarnings(diags).isEmpty(),
+ "Parenthesized comparisons should produce no operator-precedence warning");
+ }
+
+ @Test
+ public void testTempFallbackDocumentsUnchangedBehavior() {
+ // Backward compatibility: a null file name falls back to "temp", which is not under the
+ // artifacts path, so the expression validator stays disabled (unchanged pre-fix behavior).
+ List diags = codeDiagnostic(UNPARENTHESIZED, null);
+ assertTrue(precedenceWarnings(diags).isEmpty(),
+ "The \"temp\" fallback keeps the expression validator disabled, as before the fix");
+ }
+
+ // ===== URI plumbing unit checks (deterministic, no validation pipeline) =====
+
+ @Test
+ public void testGetDOMDocumentUsesProvidedUri() {
+ DOMDocument document = Utils.getDOMDocument(UNPARENTHESIZED, ARTIFACT_URI, null);
+ assertEquals(ARTIFACT_URI, document.getDocumentURI(),
+ "The 3-arg overload should assign the supplied URI to the document");
+ }
+
+ @Test
+ public void testGetDOMDocumentDefaultStillUsesTemp() {
+ // The legacy overloads (which delegate with no URI) must keep the "temp" URI unchanged.
+ DOMDocument document = Utils.getDOMDocument(UNPARENTHESIZED);
+ assertEquals("temp", document.getDocumentURI(),
+ "The legacy overloads should keep the \"temp\" URI for backward compatibility");
+ }
+
+ @Test
+ public void testCodeDiagnosticRequestCarriesFileName() {
+ org.eclipse.lemminx.customservice.synapse.CodeDiagnosticRequest request =
+ new org.eclipse.lemminx.customservice.synapse.CodeDiagnosticRequest();
+ request.setCode(UNPARENTHESIZED);
+ request.setFileName(ARTIFACT_URI);
+ assertEquals(UNPARENTHESIZED, request.getCode());
+ assertEquals(ARTIFACT_URI, request.getFileName(),
+ "CodeDiagnosticRequest must carry the fileName sent by the extension");
+ }
+
+ @Test
+ public void testCodeDiagnosticRequestSkipCrossFileDefaultsFalse() {
+ org.eclipse.lemminx.customservice.synapse.CodeDiagnosticRequest request =
+ new org.eclipse.lemminx.customservice.synapse.CodeDiagnosticRequest();
+ assertFalse(request.isSkipCrossFileValidation(),
+ "skipCrossFileValidation must default to false so the editor/validate-all paths are unchanged");
+ request.setSkipCrossFileValidation(true);
+ assertTrue(request.isSkipCrossFileValidation());
+ }
+}
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java
index 6ba9e5610..585b37d47 100644
--- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java
@@ -468,6 +468,171 @@ public void testResponseVariableDefinesVariable() {
assertTrue(diags.isEmpty(), "responseVariable child element should define the variable");
}
+ // ===== Variable references in element text content (Issue #4) =====
+
+ @Test
+ public void testUndefinedVariableInElementText() {
+ // Connector operation parameter references vars.X in element text, not in an attribute.
+ // 'soqlQuery1' is a typo for the defined 'soqlQuery' — this is the exact MI Copilot case.
+ String xml = ""
+ + ""
+ + ""
+ + "{${vars.soqlQuery1}}
"
+ + "sfResponse"
+ + "false"
+ + ""
+ + "";
+ List diags = diagnosticsWithCode(diagnose(xml), "UndefinedVariable");
+ assertEquals(1, diags.size(), "Undefined variable referenced in element text should warn");
+ assertEquals(DiagnosticSeverity.Warning, diags.get(0).getSeverity());
+ assertTrue(diags.get(0).getMessage().contains("soqlQuery1"));
+ }
+
+ @Test
+ public void testDefinedVariableInElementTextNoWarning() {
+ // The correctly-spelled variable referenced in element text should not warn.
+ String xml = ""
+ + ""
+ + ""
+ + "{${vars.soqlQuery}}
"
+ + "sfResponse"
+ + ""
+ + "";
+ List diags = diagnosticsWithCode(diagnose(xml), "UndefinedVariable");
+ assertTrue(diags.isEmpty(), "Defined variable referenced in element text should not warn");
+ }
+
+ @Test
+ public void testUndefinedVariableInElementTextPlainExpressionForm() {
+ // The plain ${...} form (not {${...}}) inside element text should also be detected.
+ String xml = ""
+ + ""
+ + "{\"id\": \"${vars.missingId}\"}"
+ + ""
+ + "";
+ List diags = diagnosticsWithCode(diagnose(xml), "UndefinedVariable");
+ assertEquals(1, diags.size(), "Undefined variable in ${...} text form should warn");
+ assertTrue(diags.get(0).getMessage().contains("missingId"));
+ }
+
+ @Test
+ public void testScriptBodyNotScannedForVariables() {
+ // Raw-code (script) bodies must not be treated as Synapse expressions (no false positives).
+ String xml = ""
+ + ""
+ + "";
+ List diags = diagnosticsWithCode(diagnose(xml), "UndefinedVariable");
+ assertTrue(diags.isEmpty(), "Script body should not be scanned for variable references");
+ }
+
+ @Test
+ public void testPlainTextWithoutExpressionNoWarning() {
+ // Element text that merely contains the literal 'vars.' but no ${...} expression must not warn.
+ String xml = ""
+ + ""
+ + "SELECT vars.field FROM Account
"
+ + "sfResponse"
+ + ""
+ + "";
+ List diags = diagnosticsWithCode(diagnose(xml), "UndefinedVariable");
+ assertTrue(diags.isEmpty(), "Plain text without a ${...} expression should not warn");
+ }
+
+ // ===== Unclosed expression delimiters =====
+
+ @Test
+ public void testUnclosedExpressionInAttributeWarns() {
+ // '${' opened but never closed — previously treated as a plain string with no feedback.
+ String xml = ""
+ + " 0\"/>"
+ + "";
+ List diags = diagnosticsWithCode(diagnose(xml), "UnclosedExpression");
+ assertEquals(1, diags.size(), "An unclosed ${ in an attribute should warn");
+ assertEquals(DiagnosticSeverity.Warning, diags.get(0).getSeverity());
+ assertTrue(diags.get(0).getMessage().contains("Unclosed expression"));
+ }
+
+ @Test
+ public void testClosedExpressionInAttributeNoWarning() {
+ String xml = ""
+ + " 0}\"/>"
+ + "";
+ List diags = diagnosticsWithCode(diagnose(xml), "UnclosedExpression");
+ assertTrue(diags.isEmpty(), "A properly closed ${...} must not be flagged");
+ }
+
+ @Test
+ public void testUnclosedExpressionInElementTextWarns() {
+ // Same gap in element text (e.g. a connector operation parameter).
+ String xml = ""
+ + ""
+ + "${payload.count
"
+ + ""
+ + "";
+ List diags = diagnosticsWithCode(diagnose(xml), "UnclosedExpression");
+ assertEquals(1, diags.size(), "An unclosed ${ in element text should warn");
+ assertTrue(diags.get(0).getMessage().contains("Unclosed expression"));
+ }
+
+ @Test
+ public void testWrappedClosedExpressionNoWarning() {
+ // The {${...}} form, properly closed, must not be flagged as unclosed.
+ String xml = ""
+ + ""
+ + "{${vars.q}}
"
+ + "";
+ List diags = diagnosticsWithCode(diagnose(xml), "UnclosedExpression");
+ assertTrue(diags.isEmpty(), "A closed {${...}} expression must not be flagged");
+ }
+
+ @Test
+ public void testExpressionWithBraceInStringLiteralNoWarning() {
+ // A '}' inside a string literal must not be mistaken for the closing delimiter, and the
+ // real closing '}' must still be recognized — so this valid expression is not flagged.
+ String xml = ""
+ + ""
+ + "";
+ List diags = diagnosticsWithCode(diagnose(xml), "UnclosedExpression");
+ assertTrue(diags.isEmpty(), "A brace inside a string literal must not cause a false positive");
+ }
+
+ @Test
+ public void testScriptBodyUnclosedExpressionNotFlagged() {
+ // Raw-code (script) bodies are excluded — a ${ in JS is not a Synapse expression.
+ String xml = ""
+ + ""
+ + "";
+ List diags = diagnosticsWithCode(diagnose(xml), "UnclosedExpression");
+ assertTrue(diags.isEmpty(), "Script bodies must not be scanned for unclosed expressions");
+ }
+
+ // ===== CDATA payloads are scanned too =====
+
+ @Test
+ public void testUndefinedVariableInCdataWarns() {
+ // ${vars.x} inside a CDATA payload (e.g. payloadFactory format) must still be validated.
+ String xml = ""
+ + ""
+ + ""
+ + ""
+ + "";
+ List diags = diagnosticsWithCode(diagnose(xml), "UndefinedVariable");
+ assertEquals(1, diags.size(), "Undefined variable referenced inside CDATA should warn");
+ assertTrue(diags.get(0).getMessage().contains("missingCdata"));
+ }
+
+ @Test
+ public void testUnclosedExpressionInCdataWarns() {
+ // An unclosed ${ inside a CDATA payload must still be flagged.
+ String xml = ""
+ + ""
+ + ""
+ + ""
+ + "";
+ List diags = diagnosticsWithCode(diagnose(xml), "UnclosedExpression");
+ assertEquals(1, diags.size(), "Unclosed ${ inside CDATA should warn");
+ }
+
// ===== Non-Synapse document skipping =====
@Test
@@ -1518,6 +1683,7 @@ public void restoreUserHome() {
originalUserHome = null;
}
SynapseLanguageService.setLoadedResourceFinder(null);
+ SynapseDiagnosticsParticipant.clearSkipCrossFileValidation();
}
/**
@@ -1606,4 +1772,139 @@ public void testUnknownSequenceStillFlaggedWithDependencies(@TempDir Path tempDi
assertEquals(1, unresolved.size());
assertTrue(unresolved.get(0).getMessage().contains("reallyDoesNotExist"));
}
+
+ // ===== skipCrossFileValidation opt-out (Change 1) =====
+
+ /** As {@link #diagnoseAtPath(String, Path)} but with the request-scoped cross-file opt-out set. */
+ private List diagnoseAtPath(String xml, Path xmlFilePath, boolean skipCrossFile) throws Exception {
+ try {
+ SynapseDiagnosticsParticipant.setSkipCrossFileValidation(skipCrossFile);
+ return diagnoseAtPath(xml, xmlFilePath);
+ } finally {
+ SynapseDiagnosticsParticipant.clearSkipCrossFileValidation();
+ }
+ }
+
+ @Test
+ public void testSkipCrossFileValidationSuppressesUnresolvedButKeepsWithinFileChecks(@TempDir Path tempDir)
+ throws Exception {
+ originalUserHome = System.getProperty("user.home");
+ System.setProperty("user.home", tempDir.toString());
+
+ Path consumer = tempDir.resolve("consumer");
+ Path apiXml = consumer.resolve("src/main/wso2mi/artifacts/apis/cvh.xml");
+ // References a sequence that does not exist (cross-file) AND an undefined variable (within-file).
+ String xml = ""
+ + ""
+ + ""
+ + ""
+ + "";
+
+ List diags = diagnoseAtPath(xml, apiXml, true);
+ assertTrue(diagnosticsWithCode(diags, "UnresolvedArtifactReference").isEmpty(),
+ "skipCrossFileValidation must suppress the cross-file UnresolvedArtifactReference");
+ assertEquals(1, diagnosticsWithCode(diags, "UndefinedVariable").size(),
+ "Within-file UndefinedVariable must still be reported when cross-file checks are skipped");
+ }
+
+ @Test
+ public void testCrossFileValidationDefaultStillFlagsUnresolved(@TempDir Path tempDir) throws Exception {
+ originalUserHome = System.getProperty("user.home");
+ System.setProperty("user.home", tempDir.toString());
+
+ Path consumer = tempDir.resolve("consumer");
+ Path apiXml = consumer.resolve("src/main/wso2mi/artifacts/apis/cvh.xml");
+ String xml = ""
+ + ""
+ + ""
+ + "";
+
+ // Default (flag false) — cross-file validation runs and flags the unresolved reference.
+ List diags = diagnoseAtPath(xml, apiXml, false);
+ assertEquals(1, diagnosticsWithCode(diags, "UnresolvedArtifactReference").size(),
+ "With cross-file validation on (default), an unresolved reference must still be flagged");
+ }
+
+ // ===== Cached artifact index invalidation (Change 2) =====
+
+ /** Runs diagnostics with a caller-supplied participant so its artifact-index cache persists across calls. */
+ private List diagnoseAtPathWith(SynapseDiagnosticsParticipant participant, String xml,
+ Path xmlFilePath) throws Exception {
+ Files.createDirectories(xmlFilePath.getParent());
+ Files.writeString(xmlFilePath, xml);
+ TextDocument textDocument = new TextDocument(xml, xmlFilePath.toUri().toString());
+ DOMDocument document = DOMParser.getInstance().parse(textDocument, null);
+ List diagnostics = new ArrayList<>();
+ participant.doDiagnostics(document, diagnostics, null, () -> {});
+ return diagnostics;
+ }
+
+ @Test
+ public void testInvalidateArtifactIndexCacheRebuildsAfterFileChange(@TempDir Path tempDir) throws Exception {
+ originalUserHome = System.getProperty("user.home");
+ System.setProperty("user.home", tempDir.toString());
+
+ Path consumer = tempDir.resolve("consumer");
+ Path apiXml = consumer.resolve("src/main/wso2mi/artifacts/apis/cvh.xml");
+ String api = ""
+ + ""
+ + "";
+
+ // Reuse one participant so its cross-file index cache survives across calls (as in production).
+ SynapseDiagnosticsParticipant participant = new SynapseDiagnosticsParticipant();
+
+ // 1. Sibling does not exist yet -> unresolved, and the index is now cached for this project.
+ List first = diagnoseAtPathWith(participant, api, apiXml);
+ assertEquals(1, diagnosticsWithCode(first, "UnresolvedArtifactReference").size(),
+ "Sibling 'sibling' does not exist yet -> should be flagged unresolved");
+
+ // 2. Write the sibling on disk. Within the TTL and without invalidation the cache is stale.
+ Path siblingXml = consumer.resolve("src/main/wso2mi/artifacts/sequences/sibling.xml");
+ Files.createDirectories(siblingXml.getParent());
+ Files.writeString(siblingXml, "");
+
+ List stale = diagnoseAtPathWith(participant, api, apiXml);
+ assertEquals(1, diagnosticsWithCode(stale, "UnresolvedArtifactReference").size(),
+ "Within the TTL and without invalidation, the stale cached index still flags it unresolved");
+
+ // 3. Invalidate -> the next run rebuilds the index and resolves the now-present sibling.
+ SynapseDiagnosticsParticipant.invalidateArtifactIndexCache();
+ List fresh = diagnoseAtPathWith(participant, api, apiXml);
+ assertTrue(diagnosticsWithCode(fresh, "UnresolvedArtifactReference").isEmpty(),
+ "After invalidation the rebuilt index includes the new sibling -> no longer unresolved");
+ }
+
+ @Test
+ public void testStaleCrossFileStateNotLeakedWhenIndexUnavailable(@TempDir Path tempDir) throws Exception {
+ originalUserHome = System.getProperty("user.home");
+ System.setProperty("user.home", tempDir.toString());
+
+ Path project = tempDir.resolve("proj");
+ // Two artifacts sharing a name -> "DupSeq" becomes a known duplicate for this project.
+ Path seqA = project.resolve("src/main/wso2mi/artifacts/sequences/a.xml");
+ Path seqB = project.resolve("src/main/wso2mi/artifacts/sequences/b.xml");
+ Files.createDirectories(seqA.getParent());
+ Files.writeString(seqA, "");
+ Files.writeString(seqB, "");
+
+ // Reuse one participant so its instance-level cross-file state persists across requests.
+ SynapseDiagnosticsParticipant participant = new SynapseDiagnosticsParticipant();
+
+ // Request A: validate a doc inside the project so the duplicate index is built into the
+ // participant's instance state (duplicateArtifactNames = { "DupSeq" }).
+ Path apiXml = project.resolve("src/main/wso2mi/artifacts/apis/cvh.xml");
+ diagnoseAtPathWith(participant, ""
+ + "",
+ apiXml);
+
+ // Request B (same participant): a doc named "DupSeq" whose project path is not derivable, so
+ // the cross-file index is unavailable. The stale duplicate state must be cleared, not reused.
+ TextDocument textB = new TextDocument(
+ "", "test.xml");
+ DOMDocument docB = DOMParser.getInstance().parse(textB, null);
+ List diagsB = new ArrayList<>();
+ participant.doDiagnostics(docB, diagsB, null, () -> {});
+ assertTrue(diagnosticsWithCode(diagsB, "DuplicateArtifactName").isEmpty(),
+ "Stale cross-file duplicate state must not leak to a request with no project index");
+ }
}
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/directorytree/builder/DirectoryTreeBuilderTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/directorytree/builder/DirectoryTreeBuilderTest.java
index 7927b3cff..2babf94f4 100644
--- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/directorytree/builder/DirectoryTreeBuilderTest.java
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/directorytree/builder/DirectoryTreeBuilderTest.java
@@ -129,7 +129,7 @@ void getProjectIdentifiersWithEmptyArtifactList() {
private static JsonObject sanitizeJson(JsonObject jsonObject) {
JsonObject sanitizedJson = new JsonObject();
for (String key : jsonObject.keySet()) {
- if (!(key.equals("path") || key.equals("registryPath"))) {
+ if (!(key.equals("path") || key.equals("registryPath") || key.equals("mcpConfigReference") || key.equals("isMcpConfig"))) {
JsonElement value = jsonObject.get(key);
if (value.isJsonObject()) {
sanitizedJson.add(key, sanitizeJson(value.getAsJsonObject()));
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/expression/ExpressionValidatorTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/expression/ExpressionValidatorTest.java
index fe49955d7..d97c1dc59 100644
--- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/expression/ExpressionValidatorTest.java
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/expression/ExpressionValidatorTest.java
@@ -441,4 +441,68 @@ public void testZeroArrayIndexNoWarning() {
List errors = ExpressionValidator.validate("payload[0]");
assertTrue(errors.isEmpty(), "Zero array index should produce no warnings");
}
+
+ // ===== Operator precedence: logical operator (and/or) as a comparison operand =====
+ // In the Synapse expression grammar 'and'/'or' bind TIGHTER than the comparison operators,
+ // so 'a <= 0 or b > 10' parses as 'a <= (0 or b) > 10' rather than '(a <= 0) or (b > 10)'.
+
+ @Test
+ public void testOrBindsTighterThanComparisonWarns() {
+ // Parses as payload.id <= (0 or payload.id) > 10 — almost certainly not intended.
+ List errors = ExpressionValidator.validate("payload.id <= 0 or payload.id > 10");
+ assertEquals(1, errors.size(), "Mixing 'or' with comparisons without parentheses should warn once");
+ assertTrue(errors.get(0).getMessage().contains("Operator precedence"),
+ "Should warn about operator precedence: " + errors.get(0).getMessage());
+ assertTrue(errors.get(0).isWarning(), "Precedence issue should be a warning, not an error");
+ }
+
+ @Test
+ public void testCopilotPrecedenceExampleWarns() {
+ // The exact expression MI Copilot generated (Issue #1).
+ List errors = ExpressionValidator.validate(
+ "integer(params.queryParams.id) <= 0 or integer(params.queryParams.id) > 10");
+ assertTrue(errors.stream().anyMatch(e -> e.getMessage().contains("Operator precedence")),
+ "Copilot precedence example should produce a precedence warning");
+ assertTrue(errors.stream().filter(e -> e.getMessage().contains("Operator precedence"))
+ .allMatch(ExpressionError::isWarning),
+ "Precedence diagnostic should be a warning");
+ }
+
+ @Test
+ public void testAndBindsTighterThanComparisonWarns() {
+ // Parses as (payload.a and payload.b) <= 5.
+ List errors = ExpressionValidator.validate("payload.a and payload.b <= 5");
+ assertEquals(1, errors.size(), "Mixing 'and' with a comparison without parentheses should warn once");
+ assertTrue(errors.get(0).getMessage().contains("Operator precedence"),
+ "Should warn about operator precedence: " + errors.get(0).getMessage());
+ assertTrue(errors.get(0).isWarning(), "Precedence issue should be a warning, not an error");
+ }
+
+ @Test
+ public void testParenthesizedComparisonsNoWarning() {
+ // The corrected form: each comparison wrapped in parentheses. No precedence ambiguity.
+ List errors = ExpressionValidator.validate(
+ "(payload.id <= 0) or (payload.id > 10)");
+ assertTrue(errors.isEmpty(), "Parenthesized comparisons should produce no precedence warning");
+ }
+
+ @Test
+ public void testPureLogicalExpressionNoWarning() {
+ // No comparison operator present — pure logical expression, nothing to flag.
+ List errors = ExpressionValidator.validate("payload.a and payload.b");
+ assertTrue(errors.isEmpty(), "Pure logical expression without a comparison should not warn");
+ }
+
+ @Test
+ public void testPureComparisonNoWarning() {
+ // No logical operator present — pure comparison, nothing to flag.
+ List errors = ExpressionValidator.validate("payload.x <= 10");
+ assertTrue(errors.isEmpty(), "Pure comparison without a logical operator should not warn");
+ }
+
+ @Test
+ public void testComparisonBetweenTwoAccessesNoWarning() {
+ List errors = ExpressionValidator.validate("payload.x < payload.y");
+ assertTrue(errors.isEmpty(), "Comparison between two accesses should not warn");
+ }
}
diff --git a/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-directory-tree.json b/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-directory-tree.json
index a792654f1..d343531c1 100644
--- a/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-directory-tree.json
+++ b/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-directory-tree.json
@@ -1 +1 @@
-{"src":{"main":{"wso2mi":{"artifacts":{"apis":[{"context":"/test","resources":[{"methods":"GET","uriTemplate":"/","urlMapping":null}],"sequences":[],"endpoints":[],"type":"API","subType":null,"name":"testApi","isFaulty":false}],"endpoints":[{"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint3","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint4","isFaulty":false}],"sequences":[{"isMainSequence":true,"sequences":[],"endpoints":[],"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence2","isFaulty":false}],"proxyServices":[{"sequences":[],"endpoints":[],"type":"PROXY_SERVICE","subType":null,"name":"testProxy1","isFaulty":false}],"inboundEndpoints":[{"sequences":[],"endpoints":[],"type":"INBOUND_ENDPOINT","subType":"HTTPS","name":"HttpsInboundEndpoint","isFaulty":false}],"messageStores":[{"type":"MESSAGE_STORE","subType":"IN_MEMORY","name":"testMessageStore","isFaulty":false}],"messageProcessors":[{"type":"MESSAGE_PROCESSOR","subType":"MESSAGE_SAMPLING","name":"testMessageProcessor","isFaulty":false}],"tasks":[{"type":"TASK","subType":null,"name":"testTask","isFaulty":false}],"localEntries":[{"type":"LOCAL_ENTRY","subType":null,"name":"testLocalEntry","isFaulty":false}],"connections":[{"connectorName":"http","connectionType":"HTTPS","type":"localEntry","subType":null,"name":"HttpsCon","isFaulty":false}],"templates":[{"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate2","isFaulty":false},{"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate2","isFaulty":false}],"dataServices":[{"type":"DATA_SERVICE","subType":null,"name":"RDBMSDataservice","isFaulty":false}],"dataSources":[{"type":"DATA_SOURCE","subType":null,"name":"RDBMSDatasource","isFaulty":false}]},"resources":{"registry":{"conf":null,"gov":{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}},"connectors":[],"metadata":[],"newResources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}}},"java":null,"ballerina":null},"tests":{"wso2mi":null,"java":null}}}
\ No newline at end of file
+{"src":{"main":{"wso2mi":{"artifacts":{"apis":[{"context":"/test","resources":[{"methods":"GET","uriTemplate":"/","urlMapping":null}],"sequences":[],"endpoints":[],"type":"API","subType":null,"name":"testApi","isFaulty":false}],"endpoints":[{"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint3","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint4","isFaulty":false}],"sequences":[{"isMainSequence":true,"sequences":[],"endpoints":[],"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence2","isFaulty":false}],"proxyServices":[{"sequences":[],"endpoints":[],"type":"PROXY_SERVICE","subType":null,"name":"testProxy1","isFaulty":false}],"inboundEndpoints":[{"sequences":[],"endpoints":[],"type":"INBOUND_ENDPOINT","subType":"HTTPS","name":"HttpsInboundEndpoint","isFaulty":false}],"messageStores":[{"type":"MESSAGE_STORE","subType":"IN_MEMORY","name":"testMessageStore","isFaulty":false}],"messageProcessors":[{"type":"MESSAGE_PROCESSOR","subType":"MESSAGE_SAMPLING","name":"testMessageProcessor","isFaulty":false}],"tasks":[{"type":"TASK","subType":null,"name":"testTask","isFaulty":false}],"localEntries":[{"type":"LOCAL_ENTRY","subType":null,"name":"testLocalEntry","isFaulty":false}],"connections":[{"connectorName":"http","connectionType":"HTTPS","type":"localEntry","subType":null,"name":"HttpsCon","isFaulty":false}],"templates":[{"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate2","isFaulty":false},{"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate2","isFaulty":false}],"dataServices":[{"type":"DATA_SERVICE","subType":null,"name":"RDBMSDataservice","isFaulty":false}],"dataSources":[{"type":"DATA_SOURCE","subType":null,"name":"RDBMSDatasource","isFaulty":false}],"mcpServers":[]},"resources":{"registry":{"conf":null,"gov":{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}},"connectors":[],"inboundConnectors":[],"metadata":[],"newResources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}}},"java":null,"ballerina":null},"tests":{"wso2mi":null,"java":null}}}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-project-explorer.json b/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-project-explorer.json
index 8e94951bb..c7b8a4f6f 100644
--- a/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-project-explorer.json
+++ b/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-project-explorer.json
@@ -1 +1 @@
-{"src":{"main":{"wso2mi":{"artifacts":{"APIs":[{"context":"/test","resources":[{"methods":"GET","uriTemplate":"/","urlMapping":null}],"sequences":[],"endpoints":[],"type":"API","subType":null,"name":"testApi","isFaulty":false}],"Event Integrations":[{"sequences":[],"endpoints":[],"type":"INBOUND_ENDPOINT","subType":"HTTPS","name":"HttpsInboundEndpoint","isFaulty":false}],"Automations":[{"type":"TASK","subType":null,"name":"testTask","isFaulty":false}],"Data Services":[{"type":"DATA_SERVICE","subType":null,"name":"RDBMSDataservice","isFaulty":false}],"Other Artifacts":{"Sequences":[{"isMainSequence":true,"sequences":[],"endpoints":[],"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence2","isFaulty":false}],"Connections":[{"connectorName":"http","connectionType":"HTTPS","type":"localEntry","subType":null,"name":"HttpsCon","isFaulty":false}],"Data Sources":[{"type":"DATA_SOURCE","subType":null,"name":"RDBMSDatasource","isFaulty":false}],"Class Mediators":[],"Ballerina Modules":[],"Endpoints":[{"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint3","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint4","isFaulty":false}],"Proxy Services":[{"sequences":[],"endpoints":[],"type":"PROXY_SERVICE","subType":null,"name":"testProxy1","isFaulty":false}],"Message Stores":[{"type":"MESSAGE_STORE","subType":"IN_MEMORY","name":"testMessageStore","isFaulty":false}],"Message Processors":[{"type":"MESSAGE_PROCESSOR","subType":"MESSAGE_SAMPLING","name":"testMessageProcessor","isFaulty":false}],"Local Entries":[{"type":"LOCAL_ENTRY","subType":null,"name":"testLocalEntry","isFaulty":false}],"Templates":[{"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate2","isFaulty":false},{"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate2","isFaulty":false}],"Data Mappers":[]},"Resources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}},"resources":{"registry":{"conf":null,"gov":{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}},"connectors":[],"metadata":[],"newResources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}}},"java":null,"ballerina":null},"tests":{"wso2mi":null,"java":null}}}
\ No newline at end of file
+{"src":{"main":{"wso2mi":{"artifacts":{"APIs":[{"context":"/test","resources":[{"methods":"GET","uriTemplate":"/","urlMapping":null}],"sequences":[],"endpoints":[],"type":"API","subType":null,"name":"testApi","isFaulty":false}],"Event Integrations":[{"sequences":[],"endpoints":[],"type":"INBOUND_ENDPOINT","subType":"HTTPS","name":"HttpsInboundEndpoint","isFaulty":false}],"Automations":[{"type":"TASK","subType":null,"name":"testTask","isFaulty":false}],"Data Services":[{"type":"DATA_SERVICE","subType":null,"name":"RDBMSDataservice","isFaulty":false}],"MCP Servers":[],"Other Artifacts":{"Sequences":[{"isMainSequence":true,"sequences":[],"endpoints":[],"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence2","isFaulty":false}],"Connections":[{"connectorName":"http","connectionType":"HTTPS","type":"localEntry","subType":null,"name":"HttpsCon","isFaulty":false}],"Data Sources":[{"type":"DATA_SOURCE","subType":null,"name":"RDBMSDatasource","isFaulty":false}],"Class Mediators":[],"Ballerina Modules":[],"Endpoints":[{"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint3","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint4","isFaulty":false}],"Proxy Services":[{"sequences":[],"endpoints":[],"type":"PROXY_SERVICE","subType":null,"name":"testProxy1","isFaulty":false}],"Message Stores":[{"type":"MESSAGE_STORE","subType":"IN_MEMORY","name":"testMessageStore","isFaulty":false}],"Message Processors":[{"type":"MESSAGE_PROCESSOR","subType":"MESSAGE_SAMPLING","name":"testMessageProcessor","isFaulty":false}],"Local Entries":[{"type":"LOCAL_ENTRY","subType":null,"name":"testLocalEntry","isFaulty":false}],"Templates":[{"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate2","isFaulty":false},{"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate2","isFaulty":false}],"Data Mappers":[]},"Resources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}},"resources":{"registry":{"conf":null,"gov":{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}},"connectors":[],"inboundConnectors":[],"metadata":[],"newResources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}}},"java":null,"ballerina":null},"tests":{"wso2mi":null,"java":null}}}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 44c33afda..5c22fc0db 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
4.0.0
org.wso2.language.server
mi-language-server-parent
- 0.24.0-wso2v90
+ 0.24.0-wso2v95
pom
MI Language Server - Parent
LemMinX is a XML Language Server Protocol (LSP), and can be used with any editor that supports LSP, to offer an outstanding XML editing experience