MCP integrations
tnsai-mcp ships small clients for a few external services. The Composio client calls Composio's REST API. The database and Stripe clients wrap an McpClient that is already connected to a server and call that server's tools by fixed names.
Example
package com.example.tnsai.docs;
import com.fasterxml.jackson.databind.JsonNode;
import com.tnsai.mcp.client.McpClient;
import com.tnsai.mcp.client.StdioTransport;
import com.tnsai.mcp.composio.ComposioAuthBridge;
import com.tnsai.mcp.composio.ComposioClient;
import com.tnsai.mcp.composio.ComposioDiscovery;
import com.tnsai.mcp.database.PostgresMcpClient;
import com.tnsai.mcp.model.McpToolDefinition;
import com.tnsai.mcp.stripe.StripeMcpClient;
import com.tnsai.mcp.stripe.StripePaymentTool;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/** Compilation contract for mcp/integrations.md. */
public final class McpIntegrationsExample {
private McpIntegrationsExample() {
}
static JsonNode composio(String apiKey, String action, Map<String, Object> input) throws IOException {
if (!new ComposioAuthBridge(apiKey).verifyApiKey()) { // false on HTTP 401 or 403
throw new IOException("Composio rejected the API key");
}
ComposioClient composio = new ComposioClient(apiKey);
if (!composio.getConnectionStatus("github").connected()) {
throw new IOException("Connect a GitHub account in Composio first");
}
List<McpToolDefinition> tools = new ComposioDiscovery(composio).discoverTools("github");
tools.forEach(t -> System.out.println(t.getName() + ": " + t.getDescription()));
return composio.executeTool(action, input); // an action name from the list
}
static JsonNode postgres(String... serverCommand) throws IOException {
McpClient client = McpClient.create(StdioTransport.builder()
.command(serverCommand) // a server with an "execute_sql" tool
.build());
client.connect();
client.initialize();
try {
PostgresMcpClient postgres = new PostgresMcpClient(client);
postgres.listTables();
return postgres.executeQuery("SELECT id, title FROM docs LIMIT 10"); // calls the "execute_sql" tool
} finally {
client.disconnect();
}
}
static JsonNode stripe(McpClient connectedClient) throws IOException {
StripePaymentTool payments = new StripePaymentTool(new StripeMcpClient(connectedClient));
return payments.execute("create_payment_intent", Map.of("amount", 2_000, "currency", "usd"));
}
}Composio
com.tnsai.mcp.composio talks to Composio's REST API at https://backend.composio.dev/api/v2, not over MCP. Every request sends the API key in the X-API-Key header. The constructors reject a null or blank key with IllegalArgumentException.
| Class and method | Does |
|---|---|
ComposioClient.listApps() | GET /apps, as ComposioApp(name, description, logoUrl, connected) records |
ComposioClient.listTools(app) | GET /actions?appNames=<app>, as ComposioTool(name, description, appName, inputSchema) records |
ComposioClient.executeTool(action, input) | POST /actions/<action>/execute with actionName and input; returns the response body as a JsonNode |
ComposioClient.getConnectionStatus(app) | GET /connectedAccounts?appNames=<app>; connected when the list is not empty, with the first account's authScheme |
ComposioAuthBridge.verifyApiKey() | GET /apps; false when the request fails with HTTP 401 or 403 |
ComposioAuthBridge.getAuthHeaders() | X-API-Key and Accept: application/json |
ComposioDiscovery.discoverTools(app) | The app's actions as McpToolDefinitions |
ComposioDiscovery.discoverTools() | The actions of every app; an app whose list fails is skipped with a warning |
ComposioDiscovery.toServerEntries() | One McpServerEntry per app, named composio-<app>, with source composio |
Any HTTP status of 400 or more becomes an IOException with the status and body. The list endpoints accept either a JSON array or an object with an items array. The app name goes into the query string as is, so pass a plain app name.
The discovered McpToolDefinitions only describe the actions. To serve them from an MCP server, write a ToolProvider whose executeTool calls ComposioClient.executeTool. The entries from toServerEntries() point at the REST base URL, not at an MCP endpoint, so do not connect an MCP transport to them.
The paths and field names above are what the client sends and reads. They have not been checked against Composio's current API, so confirm them before you depend on this client.
Database servers
com.tnsai.mcp.database has three wrappers. Each takes a connected, initialized McpClient and calls one tool per method. Start and connect the server yourself with a transport.
| Class | Method | Tool called | Arguments |
|---|---|---|---|
PostgresMcpClient | executeQuery(sql) | execute_sql | query |
listTables() | list_tables | none | |
describeTable(table) | describe_table | table | |
Neo4jMcpClient | executeCypher(query) | execute_cypher | query |
listNodeLabels() | list_node_labels | none | |
getSchema() | get_schema | none | |
GoogleMcpToolboxClient | executeQuery(query) | execute_query | query |
listDatabases() | list_databases | none | |
describeTable(table) | describe_table | table |
Each method returns what McpClient.executeTool returns: the result's content array. A null or blank query or table name throws IllegalArgumentException. isAvailable() is true when the client is connected and initialized, and getMcpClient() returns it.
The tool names are fixed. If your server names its tools differently, call client.executeTool directly; run client.listTools() to see the names.
Stripe
StripeMcpClient wraps a connected McpClient the same way.
| Method | Tool called | Arguments |
|---|---|---|
createPaymentIntent(amount, currency) | create_payment_intent | amount, currency |
listPayments(limit) | list_payment_intents | limit |
getBalance() | get_balance | none |
listCustomers(limit) | list_customers | limit |
createCustomer(name, email) | create_customer | name, email |
StripePaymentTool puts the five operations behind one call. execute(operation, params) matches the operation name ignoring case; listOperations() lists create_payment_intent, list_payments, get_balance, list_customers and create_customer. An unknown operation throws IllegalArgumentException. The limit defaults to 10. A missing amount throws NumberFormatException, and a missing name, email or currency is sent as the string null. getName() is stripe_payment, but the class is not a TnsAI tool; to give it to an agent, call it from your own @Tool method.
These calls create real payment intents and customers on the account behind the server's key. Use a Stripe test-mode key while you build.