Skip to content
tnsaiDocs
GitHub

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 methodDoes
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.

ClassMethodTool calledArguments
PostgresMcpClientexecuteQuery(sql)execute_sqlquery
listTables()list_tablesnone
describeTable(table)describe_tabletable
Neo4jMcpClientexecuteCypher(query)execute_cypherquery
listNodeLabels()list_node_labelsnone
getSchema()get_schemanone
GoogleMcpToolboxClientexecuteQuery(query)execute_queryquery
listDatabases()list_databasesnone
describeTable(table)describe_tabletable

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.

MethodTool calledArguments
createPaymentIntent(amount, currency)create_payment_intentamount, currency
listPayments(limit)list_payment_intentslimit
getBalance()get_balancenone
listCustomers(limit)list_customerslimit
createCustomer(name, email)create_customername, 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.