@autonomaai/mcp-client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Hummingbot API Client
3
+ *
4
+ * Direct HTTP client for the official Hummingbot API (hummingbot/hummingbot-api)
5
+ * Uses Basic Auth and maps to real API endpoints
6
+ */
7
+
8
+ // Use btoa for Base64 encoding (works in both Node.js and browser)
9
+ declare const process: { env: Record<string, string | undefined> } | undefined;
10
+
11
+ export interface HummingbotAPIConfig {
12
+ baseUrl: string;
13
+ username: string;
14
+ password: string;
15
+ timeout?: number;
16
+ }
17
+
18
+ export interface BotOrchestrationStatus {
19
+ status: string;
20
+ data: Record<string, any>;
21
+ }
22
+
23
+ export interface BotRunsStats {
24
+ total: number;
25
+ running: number;
26
+ stopped: number;
27
+ archived: number;
28
+ }
29
+
30
+ export interface Connector {
31
+ name: string;
32
+ type: 'spot' | 'perpetual';
33
+ }
34
+
35
+ export interface Account {
36
+ name: string;
37
+ credentials: string[];
38
+ }
39
+
40
+ export class HummingbotAPIClient {
41
+ private config: Required<HummingbotAPIConfig>;
42
+ private authHeader: string;
43
+
44
+ constructor(config: HummingbotAPIConfig) {
45
+ this.config = {
46
+ timeout: 30000,
47
+ ...config
48
+ };
49
+
50
+ // Create Basic Auth header using btoa (universal)
51
+ const credentials = btoa(`${config.username}:${config.password}`);
52
+ this.authHeader = `Basic ${credentials}`;
53
+ }
54
+
55
+ private async request<T>(
56
+ endpoint: string,
57
+ options: {
58
+ method?: string;
59
+ body?: any;
60
+ } = {}
61
+ ): Promise<T> {
62
+ const { method = 'GET', body } = options;
63
+ const url = `${this.config.baseUrl}${endpoint}`;
64
+
65
+ const response = await fetch(url, {
66
+ method,
67
+ headers: {
68
+ 'Content-Type': 'application/json',
69
+ 'Authorization': this.authHeader
70
+ },
71
+ body: body ? JSON.stringify(body) : undefined
72
+ });
73
+
74
+ if (!response.ok) {
75
+ const errorText = await response.text();
76
+ throw new Error(`Hummingbot API error ${response.status}: ${errorText}`);
77
+ }
78
+
79
+ const contentType = response.headers.get('content-type');
80
+ if (contentType?.includes('application/json')) {
81
+ return await response.json() as T;
82
+ }
83
+ return await response.text() as unknown as T;
84
+ }
85
+
86
+ // =============================================================================
87
+ // Root & Health
88
+ // =============================================================================
89
+
90
+ async getInfo(): Promise<{ name: string; version: string; status: string }> {
91
+ return this.request('/');
92
+ }
93
+
94
+ // =============================================================================
95
+ // Accounts
96
+ // =============================================================================
97
+
98
+ async getAccounts(): Promise<string[]> {
99
+ return this.request('/accounts/');
100
+ }
101
+
102
+ async addAccount(name: string): Promise<any> {
103
+ return this.request('/accounts/add-account', {
104
+ method: 'POST',
105
+ body: { account_name: name }
106
+ });
107
+ }
108
+
109
+ async getAccountCredentials(accountName: string): Promise<string[]> {
110
+ return this.request(`/accounts/${accountName}/credentials`);
111
+ }
112
+
113
+ async addCredential(accountName: string, connectorName: string, credentials: Record<string, string>): Promise<any> {
114
+ return this.request(`/accounts/add-credential/${accountName}/${connectorName}`, {
115
+ method: 'POST',
116
+ body: credentials
117
+ });
118
+ }
119
+
120
+ // =============================================================================
121
+ // Connectors
122
+ // =============================================================================
123
+
124
+ async getConnectors(): Promise<string[]> {
125
+ return this.request('/connectors/');
126
+ }
127
+
128
+ async getConnectorConfigMap(connectorName: string): Promise<Record<string, any>> {
129
+ return this.request(`/connectors/${connectorName}/config-map`);
130
+ }
131
+
132
+ async getConnectorOrderTypes(connectorName: string): Promise<string[]> {
133
+ return this.request(`/connectors/${connectorName}/order-types`);
134
+ }
135
+
136
+ async getConnectorTradingRules(connectorName: string): Promise<any> {
137
+ return this.request(`/connectors/${connectorName}/trading-rules`);
138
+ }
139
+
140
+ // =============================================================================
141
+ // Bot Orchestration
142
+ // =============================================================================
143
+
144
+ async getBotOrchestrationStatus(): Promise<BotOrchestrationStatus> {
145
+ return this.request('/bot-orchestration/status');
146
+ }
147
+
148
+ async getBotRuns(): Promise<any[]> {
149
+ return this.request('/bot-orchestration/bot-runs');
150
+ }
151
+
152
+ async getBotRunsStats(): Promise<BotRunsStats> {
153
+ return this.request('/bot-orchestration/bot-runs/stats');
154
+ }
155
+
156
+ async startBot(config: {
157
+ bot_name: string;
158
+ controllers_config?: Record<string, any>[];
159
+ script?: string;
160
+ script_config?: Record<string, any>;
161
+ }): Promise<any> {
162
+ return this.request('/bot-orchestration/start-bot', {
163
+ method: 'POST',
164
+ body: config
165
+ });
166
+ }
167
+
168
+ async stopBot(botName: string): Promise<any> {
169
+ return this.request('/bot-orchestration/stop-bot', {
170
+ method: 'POST',
171
+ body: { bot_name: botName }
172
+ });
173
+ }
174
+
175
+ async getBotHistory(botName: string): Promise<any> {
176
+ return this.request(`/bot-orchestration/${botName}/history`);
177
+ }
178
+
179
+ async getBotStatus(botName: string): Promise<any> {
180
+ return this.request(`/bot-orchestration/${botName}/status`);
181
+ }
182
+
183
+ // =============================================================================
184
+ // Controllers
185
+ // =============================================================================
186
+
187
+ async getControllers(): Promise<any[]> {
188
+ return this.request('/controllers/');
189
+ }
190
+
191
+ async getControllerConfigs(): Promise<any[]> {
192
+ return this.request('/controllers/configs/');
193
+ }
194
+
195
+ async getControllerConfig(configName: string): Promise<any> {
196
+ return this.request(`/controllers/configs/${configName}`);
197
+ }
198
+
199
+ async getController(controllerType: string, controllerName: string): Promise<any> {
200
+ return this.request(`/controllers/${controllerType}/${controllerName}`);
201
+ }
202
+
203
+ async getControllerConfigTemplate(controllerType: string, controllerName: string): Promise<any> {
204
+ return this.request(`/controllers/${controllerType}/${controllerName}/config/template`);
205
+ }
206
+
207
+ async validateControllerConfig(controllerType: string, controllerName: string, config: any): Promise<any> {
208
+ return this.request(`/controllers/${controllerType}/${controllerName}/config/validate`, {
209
+ method: 'POST',
210
+ body: config
211
+ });
212
+ }
213
+
214
+ // =============================================================================
215
+ // Backtesting
216
+ // =============================================================================
217
+
218
+ async runBacktest(config: {
219
+ controller_type: string;
220
+ controller_name: string;
221
+ controller_config: Record<string, any>;
222
+ start_time?: number;
223
+ end_time?: number;
224
+ backtesting_resolution?: string;
225
+ trade_cost?: number;
226
+ }): Promise<any> {
227
+ return this.request('/backtesting/run-backtesting', {
228
+ method: 'POST',
229
+ body: config
230
+ });
231
+ }
232
+
233
+ // =============================================================================
234
+ // Market Data
235
+ // =============================================================================
236
+
237
+ async getMarketData(config: {
238
+ connector_name: string;
239
+ trading_pair: string;
240
+ }): Promise<any> {
241
+ return this.request('/market-data/', {
242
+ method: 'POST',
243
+ body: config
244
+ });
245
+ }
246
+
247
+ async getCandlesData(config: {
248
+ connector_name: string;
249
+ trading_pair: string;
250
+ interval?: string;
251
+ max_records?: number;
252
+ }): Promise<any> {
253
+ return this.request('/market-data/candles', {
254
+ method: 'POST',
255
+ body: config
256
+ });
257
+ }
258
+
259
+ // =============================================================================
260
+ // Docker Management
261
+ // =============================================================================
262
+
263
+ async getActiveContainers(): Promise<any[]> {
264
+ return this.request('/docker/active-containers');
265
+ }
266
+
267
+ async getAvailableImages(): Promise<any[]> {
268
+ return this.request('/docker/available-images/');
269
+ }
270
+
271
+ async isDockerRunning(): Promise<boolean> {
272
+ return this.request('/docker/running');
273
+ }
274
+
275
+ // =============================================================================
276
+ // Archived Bots
277
+ // =============================================================================
278
+
279
+ async getArchivedBots(): Promise<string[]> {
280
+ return this.request('/archived-bots/');
281
+ }
282
+
283
+ async getArchivedBotPerformance(dbPath: string): Promise<any> {
284
+ return this.request(`/archived-bots/${encodeURIComponent(dbPath)}/performance`);
285
+ }
286
+
287
+ async getArchivedBotSummary(dbPath: string): Promise<any> {
288
+ return this.request(`/archived-bots/${encodeURIComponent(dbPath)}/summary`);
289
+ }
290
+
291
+ async getArchivedBotTrades(dbPath: string): Promise<any[]> {
292
+ return this.request(`/archived-bots/${encodeURIComponent(dbPath)}/trades`);
293
+ }
294
+
295
+ async getArchivedBotOrders(dbPath: string): Promise<any[]> {
296
+ return this.request(`/archived-bots/${encodeURIComponent(dbPath)}/orders`);
297
+ }
298
+ }
299
+
300
+ // Helper to safely get env var
301
+ function getEnv(key: string, defaultValue: string): string {
302
+ try {
303
+ return (typeof process !== 'undefined' && process?.env?.[key]) || defaultValue;
304
+ } catch {
305
+ return defaultValue;
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Factory function to create a Hummingbot API client
311
+ */
312
+ export function createHummingbotAPIClient(config?: Partial<HummingbotAPIConfig>): HummingbotAPIClient {
313
+ const baseUrl = config?.baseUrl || getEnv('HUMMINGBOT_API_URL', 'http://localhost:8000');
314
+ const username = config?.username || getEnv('HUMMINGBOT_USERNAME', 'admin');
315
+ const password = config?.password || getEnv('HUMMINGBOT_PASSWORD', 'admin');
316
+
317
+ return new HummingbotAPIClient({
318
+ baseUrl,
319
+ username,
320
+ password,
321
+ timeout: config?.timeout || 30000
322
+ });
323
+ }
package/src/index.ts ADDED
@@ -0,0 +1,208 @@
1
+ /**
2
+ * autonoma MCP Client Library
3
+ *
4
+ * Unified client library for interacting with MCP servers across the
5
+ * autonoma trading ecosystem.
6
+ */
7
+
8
+ // Export the main client class and error
9
+ export { MCPClient, MCPClientError } from './client';
10
+
11
+ // Export the Hummingbot API client (direct HTTP with Basic Auth)
12
+ export { HummingbotAPIClient, createHummingbotAPIClient } from './hummingbot-api';
13
+ export type { HummingbotAPIConfig, BotOrchestrationStatus, BotRunsStats } from './hummingbot-api';
14
+
15
+ // Export all types
16
+ export * from './types';
17
+ export * from './tool-utils';
18
+
19
+ // Convenience factory function
20
+ import { MCPClient } from './client';
21
+ import { MCPClientConfig } from './types';
22
+
23
+ /**
24
+ * Create a new MCP client instance
25
+ */
26
+ export function createMCPClient(config: MCPClientConfig): MCPClient {
27
+ return new MCPClient(config);
28
+ }
29
+
30
+ // Configuration helper to safely access environment variables
31
+ function getEnvOrDefault(key: string, defaultValue: string): string {
32
+ try {
33
+ // @ts-ignore - process may not be available in all environments
34
+ return typeof process !== 'undefined' && process.env?.[key] || defaultValue;
35
+ } catch {
36
+ return defaultValue;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Create a new MCP client with common defaults for Hummingbot
42
+ */
43
+ export function createHummingbotClient(baseUrl: string = getEnvOrDefault('HUMMINGBOT_MCP_URL', 'http://localhost:8000')): MCPClient {
44
+ return new MCPClient({
45
+ baseUrl,
46
+ timeout: 30000,
47
+ retries: 3,
48
+ retryDelay: 1000,
49
+ enableLogging: false
50
+ });
51
+ }
52
+
53
+
54
+
55
+ /**
56
+ * Create a new MCP client for the DexScreener Solana service
57
+ * Supports both HTTP and stdio-based MCP tools
58
+ */
59
+ export function createDexscreenerClient(baseUrl: string = getEnvOrDefault('DEXSCREENER_MCP_URL', 'http://localhost:3010')): MCPClient {
60
+ return new MCPClient({
61
+ baseUrl,
62
+ timeout: 10000,
63
+ retries: 3,
64
+ retryDelay: 1000,
65
+ enableLogging: false
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Create a new MCP client for the RAG service
71
+ */
72
+ export function createRAGClient(baseUrl: string = getEnvOrDefault('RAG_MCP_URL', 'http://localhost:3002')): MCPClient {
73
+ return new MCPClient({
74
+ baseUrl,
75
+ timeout: 20000,
76
+ retries: 2,
77
+ retryDelay: 1000,
78
+ enableLogging: false
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Create a new MCP client for the APY Strategy service
84
+ * Supports comprehensive DeFi yield optimization tools
85
+ */
86
+ export function createAPYStrategyClient(baseUrl: string = getEnvOrDefault('APY_STRATEGY_MCP_URL', 'http://localhost:3008')): MCPClient {
87
+ return new MCPClient({
88
+ baseUrl,
89
+ timeout: 25000,
90
+ retries: 3,
91
+ retryDelay: 1500,
92
+ enableLogging: false
93
+ });
94
+ }
95
+
96
+ /**
97
+ * Create a unified MCP client with access to all autonoma services
98
+ * Provides comprehensive access to RAG, Data Collector, DexScreener, and APY Strategy tools
99
+ */
100
+ export function createUnifiedMCPClient(config?: {
101
+ ragServerUrl?: string;
102
+ dexscreenerUrl?: string;
103
+ apyStrategyUrl?: string;
104
+ hummingbotUrl?: string;
105
+ }): {
106
+ ragClient: MCPClient;
107
+ dexscreenerClient: MCPClient;
108
+ apyStrategyClient: MCPClient;
109
+ hummingbotClient: MCPClient;
110
+ } {
111
+ const {
112
+ ragServerUrl = 'http://localhost:8001',
113
+ dexscreenerUrl = 'http://localhost:3000',
114
+ apyStrategyUrl = 'http://localhost:8003',
115
+ hummingbotUrl = 'http://localhost:8080'
116
+ } = config || {};
117
+
118
+ return {
119
+ ragClient: createRAGClient(ragServerUrl),
120
+ dexscreenerClient: createDexscreenerClient(dexscreenerUrl),
121
+ apyStrategyClient: createAPYStrategyClient(apyStrategyUrl),
122
+ hummingbotClient: createHummingbotClient(hummingbotUrl)
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Create a unified MCP tools interface with all services
128
+ * Provides a single interface to access all 38+ MCP tools across services
129
+ */
130
+ export class UnifiedMCPTools {
131
+ public ragTools: MCPClient;
132
+ public dexscreenerTools: MCPClient;
133
+ public apyStrategyTools: MCPClient;
134
+ public hummingbotTools: MCPClient;
135
+
136
+ constructor(config?: {
137
+ ragServerUrl?: string;
138
+ dexscreenerUrl?: string;
139
+ apyStrategyUrl?: string;
140
+ hummingbotUrl?: string;
141
+ }) {
142
+ const clients = createUnifiedMCPClient(config);
143
+ this.ragTools = clients.ragClient;
144
+ this.dexscreenerTools = clients.dexscreenerClient;
145
+ this.apyStrategyTools = clients.apyStrategyClient;
146
+ this.hummingbotTools = clients.hummingbotClient;
147
+ }
148
+
149
+ /**
150
+ * Get health status from all services
151
+ */
152
+ async getHealthStatus(): Promise<{
153
+ rag: any;
154
+ dexscreener: any;
155
+ apyStrategy: any;
156
+ hummingbot: any;
157
+ }> {
158
+ const [rag, dexscreener, apyStrategy, hummingbot] = await Promise.allSettled([
159
+ this.ragTools.getHealth(),
160
+ this.dexscreenerTools.getHealth(),
161
+ this.apyStrategyTools.getHealth(),
162
+ this.hummingbotTools.getHealth()
163
+ ]);
164
+
165
+ return {
166
+ rag: rag.status === 'fulfilled' ? rag.value : { error: rag.reason?.message },
167
+ dexscreener: dexscreener.status === 'fulfilled' ? dexscreener.value : { error: dexscreener.reason?.message },
168
+ apyStrategy: apyStrategy.status === 'fulfilled' ? apyStrategy.value : { error: apyStrategy.reason?.message },
169
+ hummingbot: hummingbot.status === 'fulfilled' ? hummingbot.value : { error: hummingbot.reason?.message }
170
+ };
171
+ }
172
+
173
+ /**
174
+ * List all available tools across all services
175
+ */
176
+ async listAllTools(): Promise<{
177
+ rag: any;
178
+ dexscreener: any;
179
+ apyStrategy: any;
180
+ hummingbot: any;
181
+ }> {
182
+ const [rag, dexscreener, apyStrategy, hummingbot] = await Promise.allSettled([
183
+ this.ragTools.listTools(),
184
+ this.dexscreenerTools.listTools(),
185
+ this.apyStrategyTools.listTools(),
186
+ this.hummingbotTools.listTools()
187
+ ]);
188
+
189
+ return {
190
+ rag: rag.status === 'fulfilled' ? rag.value : { error: rag.reason?.message },
191
+ dexscreener: dexscreener.status === 'fulfilled' ? dexscreener.value : { error: dexscreener.reason?.message },
192
+ apyStrategy: apyStrategy.status === 'fulfilled' ? apyStrategy.value : { error: apyStrategy.reason?.message },
193
+ hummingbot: hummingbot.status === 'fulfilled' ? hummingbot.value : { error: hummingbot.reason?.message }
194
+ };
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Create a unified MCP tools instance
200
+ */
201
+ export function createUnifiedMCPTools(config?: {
202
+ ragServerUrl?: string;
203
+ dexscreenerUrl?: string;
204
+ apyStrategyUrl?: string;
205
+ hummingbotUrl?: string;
206
+ }): UnifiedMCPTools {
207
+ return new UnifiedMCPTools(config);
208
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Utility helpers for MCP tool execution, exposed as part of the MCP client package.
3
+ * Provides consistent payload shaping and error/response normalization for any caller.
4
+ */
5
+
6
+ export interface MCPToolExecutionResult {
7
+ success: boolean;
8
+ response?: any;
9
+ error?: string;
10
+ }
11
+
12
+ interface MCPClientLike {
13
+ callTool(payload: { name: string; arguments?: Record<string, any> }): Promise<any>;
14
+ }
15
+
16
+ export async function executeMCPTool(
17
+ client: MCPClientLike | null | undefined,
18
+ toolName: string,
19
+ args?: Record<string, any>
20
+ ): Promise<MCPToolExecutionResult> {
21
+ if (!client) {
22
+ return {
23
+ success: false,
24
+ error: 'MCP client is not configured'
25
+ };
26
+ }
27
+
28
+ try {
29
+ const payload = await client.callTool({
30
+ name: toolName,
31
+ arguments: args || {}
32
+ });
33
+
34
+ const isError = payload?.isError || false;
35
+ return {
36
+ success: !isError,
37
+ response: payload,
38
+ error: isError
39
+ ? payload?.meta?.error || 'MCP tool reported an error'
40
+ : undefined
41
+ };
42
+ } catch (error) {
43
+ return {
44
+ success: false,
45
+ error: error instanceof Error ? error.message : 'Unknown MCP error'
46
+ };
47
+ }
48
+ }