@@ -909,7 +909,10 @@ public ConnectorResponse queryConnector(ConnectorQuery query) {
909909 clientResponse .getError (),
910910 query .getConnectorId (),
911911 query .getOperation (),
912- null // processingTime not available from ClientResponse
912+ null , // processingTime not available from ClientResponse
913+ false , // redacted - not available from this endpoint
914+ null , // redactedFields - not available from this endpoint
915+ null // policyInfo - not available from this endpoint
913916 );
914917
915918 if (!result .isSuccess ()) {
@@ -935,6 +938,117 @@ public CompletableFuture<ConnectorResponse> queryConnectorAsync(ConnectorQuery q
935938 return CompletableFuture .supplyAsync (() -> queryConnector (query ), asyncExecutor );
936939 }
937940
941+ /**
942+ * Executes a query directly against the MCP connector endpoint.
943+ *
944+ * <p>This method calls the agent's /mcp/resources/query endpoint which provides:
945+ * <ul>
946+ * <li>Request-phase policy evaluation (SQLi blocking, PII blocking)</li>
947+ * <li>Response-phase policy evaluation (PII redaction)</li>
948+ * <li>PolicyInfo metadata in responses</li>
949+ * </ul>
950+ *
951+ * <p>Example usage:
952+ * <pre>
953+ * ConnectorResponse response = axonflow.mcpQuery("postgres", "SELECT * FROM customers LIMIT 10");
954+ * if (response.isRedacted()) {
955+ * System.out.println("Fields redacted: " + response.getRedactedFields());
956+ * }
957+ * System.out.println("Policies evaluated: " + response.getPolicyInfo().getPoliciesEvaluated());
958+ * </pre>
959+ *
960+ * @param connector name of the MCP connector (e.g., "postgres")
961+ * @param statement SQL statement or query to execute
962+ * @return ConnectorResponse with data, redaction info, and policy_info
963+ * @throws ConnectorException if the request is blocked by policy or fails
964+ */
965+ public ConnectorResponse mcpQuery (String connector , String statement ) {
966+ return mcpQuery (connector , statement , null );
967+ }
968+
969+ /**
970+ * Executes a query directly against the MCP connector endpoint with options.
971+ *
972+ * @param connector name of the MCP connector (e.g., "postgres")
973+ * @param statement SQL statement or query to execute
974+ * @param options optional additional options for the query
975+ * @return ConnectorResponse with data, redaction info, and policy_info
976+ * @throws ConnectorException if the request is blocked by policy or fails
977+ */
978+ public ConnectorResponse mcpQuery (String connector , String statement , Map <String , Object > options ) {
979+ Objects .requireNonNull (connector , "connector cannot be null" );
980+ Objects .requireNonNull (statement , "statement cannot be null" );
981+
982+ return retryExecutor .execute (() -> {
983+ Map <String , Object > body = new HashMap <>();
984+ body .put ("connector" , connector );
985+ body .put ("statement" , statement );
986+ if (options != null && !options .isEmpty ()) {
987+ body .put ("options" , options );
988+ }
989+
990+ Request httpRequest = buildRequest ("POST" , "/mcp/resources/query" , body );
991+ try (Response response = httpClient .newCall (httpRequest ).execute ()) {
992+ // Parse the response body
993+ ResponseBody responseBody = response .body ();
994+ if (responseBody == null ) {
995+ throw new ConnectorException ("Empty response from MCP query" , connector , "mcpQuery" );
996+ }
997+ String responseJson = responseBody .string ();
998+
999+ // Handle policy blocks (403 responses)
1000+ if (!response .isSuccessful ()) {
1001+ try {
1002+ Map <String , Object > errorData = objectMapper .readValue (responseJson ,
1003+ new com .fasterxml .jackson .core .type .TypeReference <Map <String , Object >>() {});
1004+ String errorMsg = errorData .get ("error" ) != null ?
1005+ errorData .get ("error" ).toString () :
1006+ "MCP query failed: " + response .code ();
1007+ throw new ConnectorException (errorMsg , connector , "mcpQuery" );
1008+ } catch (JsonProcessingException e ) {
1009+ throw new ConnectorException ("MCP query failed: " + response .code (), connector , "mcpQuery" );
1010+ }
1011+ }
1012+
1013+ return objectMapper .readValue (responseJson , ConnectorResponse .class );
1014+ }
1015+ }, "mcpQuery" );
1016+ }
1017+
1018+ /**
1019+ * Asynchronously executes a query against the MCP connector endpoint.
1020+ *
1021+ * @param connector name of the MCP connector
1022+ * @param statement SQL statement to execute
1023+ * @return a future containing the response
1024+ */
1025+ public CompletableFuture <ConnectorResponse > mcpQueryAsync (String connector , String statement ) {
1026+ return CompletableFuture .supplyAsync (() -> mcpQuery (connector , statement ), asyncExecutor );
1027+ }
1028+
1029+ /**
1030+ * Asynchronously executes a query against the MCP connector endpoint with options.
1031+ *
1032+ * @param connector name of the MCP connector
1033+ * @param statement SQL statement to execute
1034+ * @param options optional additional options
1035+ * @return a future containing the response
1036+ */
1037+ public CompletableFuture <ConnectorResponse > mcpQueryAsync (String connector , String statement , Map <String , Object > options ) {
1038+ return CompletableFuture .supplyAsync (() -> mcpQuery (connector , statement , options ), asyncExecutor );
1039+ }
1040+
1041+ /**
1042+ * Executes a statement against an MCP connector (alias for mcpQuery).
1043+ *
1044+ * @param connector name of the MCP connector
1045+ * @param statement SQL statement to execute
1046+ * @return ConnectorResponse with data, redaction info, and policy_info
1047+ */
1048+ public ConnectorResponse mcpExecute (String connector , String statement ) {
1049+ return mcpQuery (connector , statement );
1050+ }
1051+
9381052 // ========================================================================
9391053 // Policy CRUD - Static Policies
9401054 // ========================================================================
0 commit comments