@nordsym/apiclaw 1.7.2 → 1.7.4

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.
Files changed (230) hide show
  1. package/convex/_generated/api.d.ts +115 -0
  2. package/convex/_generated/api.js +23 -0
  3. package/convex/_generated/dataModel.d.ts +60 -0
  4. package/convex/_generated/server.d.ts +143 -0
  5. package/convex/_generated/server.js +93 -0
  6. package/convex/adminActivate.d.ts +3 -0
  7. package/convex/adminActivate.d.ts.map +1 -0
  8. package/convex/adminActivate.js +47 -0
  9. package/convex/adminActivate.js.map +1 -0
  10. package/convex/adminActivate.ts +54 -0
  11. package/convex/adminStats.d.ts +3 -0
  12. package/convex/adminStats.d.ts.map +1 -0
  13. package/convex/adminStats.js +42 -0
  14. package/convex/adminStats.js.map +1 -0
  15. package/convex/adminStats.ts +44 -0
  16. package/convex/agents.d.ts +76 -0
  17. package/convex/agents.d.ts.map +1 -0
  18. package/convex/agents.js +699 -0
  19. package/convex/agents.js.map +1 -0
  20. package/convex/agents.ts +814 -0
  21. package/convex/analytics.d.ts +5 -0
  22. package/convex/analytics.d.ts.map +1 -0
  23. package/convex/analytics.js +166 -0
  24. package/convex/analytics.js.map +1 -0
  25. package/convex/analytics.ts +186 -0
  26. package/convex/billing.d.ts +88 -0
  27. package/convex/billing.d.ts.map +1 -0
  28. package/convex/billing.js +655 -0
  29. package/convex/billing.js.map +1 -0
  30. package/convex/billing.ts +791 -0
  31. package/convex/capabilities.d.ts +9 -0
  32. package/convex/capabilities.d.ts.map +1 -0
  33. package/convex/capabilities.js +145 -0
  34. package/convex/capabilities.js.map +1 -0
  35. package/convex/capabilities.ts +157 -0
  36. package/convex/chains.d.ts +68 -0
  37. package/convex/chains.d.ts.map +1 -0
  38. package/convex/chains.js +1105 -0
  39. package/convex/chains.js.map +1 -0
  40. package/convex/chains.ts +1318 -0
  41. package/convex/credits.d.ts +25 -0
  42. package/convex/credits.d.ts.map +1 -0
  43. package/convex/credits.js +186 -0
  44. package/convex/credits.js.map +1 -0
  45. package/convex/credits.ts +211 -0
  46. package/convex/crons.d.ts +3 -0
  47. package/convex/crons.d.ts.map +1 -0
  48. package/convex/crons.js +17 -0
  49. package/convex/crons.js.map +1 -0
  50. package/convex/crons.ts +28 -0
  51. package/convex/directCall.d.ts +72 -0
  52. package/convex/directCall.d.ts.map +1 -0
  53. package/convex/directCall.js +627 -0
  54. package/convex/directCall.js.map +1 -0
  55. package/convex/directCall.ts +678 -0
  56. package/convex/earnProgress.d.ts +58 -0
  57. package/convex/earnProgress.d.ts.map +1 -0
  58. package/convex/earnProgress.js +649 -0
  59. package/convex/earnProgress.js.map +1 -0
  60. package/convex/earnProgress.ts +753 -0
  61. package/convex/email.d.ts +14 -0
  62. package/convex/email.d.ts.map +1 -0
  63. package/convex/email.js +300 -0
  64. package/convex/email.js.map +1 -0
  65. package/convex/email.ts +329 -0
  66. package/convex/feedback.d.ts +7 -0
  67. package/convex/feedback.d.ts.map +1 -0
  68. package/convex/feedback.js +227 -0
  69. package/convex/feedback.js.map +1 -0
  70. package/convex/feedback.ts +265 -0
  71. package/convex/http.d.ts +3 -0
  72. package/convex/http.d.ts.map +1 -0
  73. package/convex/http.js +1405 -0
  74. package/convex/http.js.map +1 -0
  75. package/convex/http.ts +1577 -0
  76. package/convex/inbound.d.ts +2 -0
  77. package/convex/inbound.d.ts.map +1 -0
  78. package/convex/inbound.js +32 -0
  79. package/convex/inbound.js.map +1 -0
  80. package/convex/inbound.ts +32 -0
  81. package/convex/logs.d.ts +38 -0
  82. package/convex/logs.d.ts.map +1 -0
  83. package/convex/logs.js +487 -0
  84. package/convex/logs.js.map +1 -0
  85. package/convex/logs.ts +550 -0
  86. package/convex/mou.d.ts +6 -0
  87. package/convex/mou.d.ts.map +1 -0
  88. package/convex/mou.js +82 -0
  89. package/convex/mou.js.map +1 -0
  90. package/convex/mou.ts +91 -0
  91. package/convex/providerKeys.d.ts +31 -0
  92. package/convex/providerKeys.d.ts.map +1 -0
  93. package/convex/providerKeys.js +257 -0
  94. package/convex/providerKeys.js.map +1 -0
  95. package/convex/providerKeys.ts +289 -0
  96. package/convex/providers.d.ts +32 -0
  97. package/convex/providers.d.ts.map +1 -0
  98. package/convex/providers.js +814 -0
  99. package/convex/providers.js.map +1 -0
  100. package/convex/providers.ts +909 -0
  101. package/convex/purchases.d.ts +7 -0
  102. package/convex/purchases.d.ts.map +1 -0
  103. package/convex/purchases.js +157 -0
  104. package/convex/purchases.js.map +1 -0
  105. package/convex/purchases.ts +183 -0
  106. package/convex/ratelimit.d.ts +4 -0
  107. package/convex/ratelimit.d.ts.map +1 -0
  108. package/convex/ratelimit.js +91 -0
  109. package/convex/ratelimit.js.map +1 -0
  110. package/convex/ratelimit.ts +104 -0
  111. package/convex/schema.ts +802 -0
  112. package/convex/searchLogs.d.ts +4 -0
  113. package/convex/searchLogs.d.ts.map +1 -0
  114. package/convex/searchLogs.js +129 -0
  115. package/convex/searchLogs.js.map +1 -0
  116. package/convex/searchLogs.ts +146 -0
  117. package/convex/seedAPILayerAPIs.d.ts +7 -0
  118. package/convex/seedAPILayerAPIs.d.ts.map +1 -0
  119. package/convex/seedAPILayerAPIs.js +177 -0
  120. package/convex/seedAPILayerAPIs.js.map +1 -0
  121. package/convex/seedAPILayerAPIs.ts +191 -0
  122. package/convex/seedDirectCallConfigs.d.ts +2 -0
  123. package/convex/seedDirectCallConfigs.d.ts.map +1 -0
  124. package/convex/seedDirectCallConfigs.js +324 -0
  125. package/convex/seedDirectCallConfigs.js.map +1 -0
  126. package/convex/seedDirectCallConfigs.ts +336 -0
  127. package/convex/seedPratham.d.ts +6 -0
  128. package/convex/seedPratham.d.ts.map +1 -0
  129. package/convex/seedPratham.js +150 -0
  130. package/convex/seedPratham.js.map +1 -0
  131. package/convex/seedPratham.ts +161 -0
  132. package/convex/spendAlerts.d.ts +36 -0
  133. package/convex/spendAlerts.d.ts.map +1 -0
  134. package/convex/spendAlerts.js +380 -0
  135. package/convex/spendAlerts.js.map +1 -0
  136. package/convex/spendAlerts.ts +442 -0
  137. package/convex/stripeActions.d.ts +19 -0
  138. package/convex/stripeActions.d.ts.map +1 -0
  139. package/convex/stripeActions.js +411 -0
  140. package/convex/stripeActions.js.map +1 -0
  141. package/convex/stripeActions.ts +512 -0
  142. package/convex/teams.d.ts +21 -0
  143. package/convex/teams.d.ts.map +1 -0
  144. package/convex/teams.js +215 -0
  145. package/convex/teams.js.map +1 -0
  146. package/convex/teams.ts +243 -0
  147. package/convex/telemetry.d.ts +4 -0
  148. package/convex/telemetry.d.ts.map +1 -0
  149. package/convex/telemetry.js +74 -0
  150. package/convex/telemetry.js.map +1 -0
  151. package/convex/telemetry.ts +81 -0
  152. package/convex/tsconfig.json +25 -0
  153. package/convex/updateAPIStatus.d.ts +6 -0
  154. package/convex/updateAPIStatus.d.ts.map +1 -0
  155. package/convex/updateAPIStatus.js +40 -0
  156. package/convex/updateAPIStatus.js.map +1 -0
  157. package/convex/updateAPIStatus.ts +45 -0
  158. package/convex/usage.d.ts +27 -0
  159. package/convex/usage.d.ts.map +1 -0
  160. package/convex/usage.js +229 -0
  161. package/convex/usage.js.map +1 -0
  162. package/convex/usage.ts +260 -0
  163. package/convex/waitlist.d.ts +4 -0
  164. package/convex/waitlist.d.ts.map +1 -0
  165. package/convex/waitlist.js +49 -0
  166. package/convex/waitlist.js.map +1 -0
  167. package/convex/waitlist.ts +55 -0
  168. package/convex/webhooks.d.ts +12 -0
  169. package/convex/webhooks.d.ts.map +1 -0
  170. package/convex/webhooks.js +410 -0
  171. package/convex/webhooks.js.map +1 -0
  172. package/convex/webhooks.ts +494 -0
  173. package/convex/workspaces.d.ts +31 -0
  174. package/convex/workspaces.d.ts.map +1 -0
  175. package/convex/workspaces.js +975 -0
  176. package/convex/workspaces.js.map +1 -0
  177. package/convex/workspaces.ts +1130 -0
  178. package/dist/bin.js +0 -0
  179. package/dist/capability-router.js +1 -1
  180. package/dist/capability-router.js.map +1 -1
  181. package/dist/execute.d.ts +2 -0
  182. package/dist/execute.d.ts.map +1 -1
  183. package/dist/execute.js +18 -4
  184. package/dist/execute.js.map +1 -1
  185. package/dist/http-api.js +1 -1
  186. package/dist/http-api.js.map +1 -1
  187. package/dist/index.js +1 -1
  188. package/dist/index.js.map +1 -1
  189. package/dist/mcp-analytics.d.ts +32 -0
  190. package/dist/mcp-analytics.d.ts.map +1 -0
  191. package/dist/mcp-analytics.js +130 -0
  192. package/dist/mcp-analytics.js.map +1 -0
  193. package/package.json +1 -1
  194. package/dist/chain-types.d.ts +0 -187
  195. package/dist/chain-types.d.ts.map +0 -1
  196. package/dist/chain-types.js +0 -33
  197. package/dist/chain-types.js.map +0 -1
  198. package/dist/registry/apis.json.bak +0 -248811
  199. package/dist/src/bin.js +0 -17
  200. package/dist/src/capability-router.js +0 -240
  201. package/dist/src/chainExecutor.js +0 -451
  202. package/dist/src/chainResolver.js +0 -518
  203. package/dist/src/cli/commands/doctor.js +0 -324
  204. package/dist/src/cli/commands/mcp-install.js +0 -255
  205. package/dist/src/cli/commands/restore.js +0 -259
  206. package/dist/src/cli/commands/setup.js +0 -205
  207. package/dist/src/cli/commands/uninstall.js +0 -188
  208. package/dist/src/cli/index.js +0 -111
  209. package/dist/src/cli.js +0 -302
  210. package/dist/src/confirmation.js +0 -240
  211. package/dist/src/credentials.js +0 -357
  212. package/dist/src/credits.js +0 -260
  213. package/dist/src/crypto.js +0 -66
  214. package/dist/src/discovery.js +0 -504
  215. package/dist/src/enterprise/env.js +0 -123
  216. package/dist/src/enterprise/script-generator.js +0 -460
  217. package/dist/src/execute-dynamic.js +0 -473
  218. package/dist/src/execute.js +0 -1727
  219. package/dist/src/index.js +0 -2062
  220. package/dist/src/metered.js +0 -80
  221. package/dist/src/open-apis.js +0 -276
  222. package/dist/src/proxy.js +0 -28
  223. package/dist/src/session.js +0 -86
  224. package/dist/src/stripe.js +0 -407
  225. package/dist/src/telemetry.js +0 -49
  226. package/dist/src/types.js +0 -2
  227. package/dist/src/utils/backup.js +0 -181
  228. package/dist/src/utils/config.js +0 -220
  229. package/dist/src/utils/os.js +0 -105
  230. package/dist/src/utils/paths.js +0 -159
package/dist/src/index.js DELETED
@@ -1,2062 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * APIvault - Agent-Native API Discovery MCP Server
4
- *
5
- * Tools:
6
- * - discover_apis: Search for APIs by capability
7
- * - get_api_details: Get full info about an API
8
- * - purchase_access: Buy API access with credits
9
- * - check_balance: Check credits and active purchases
10
- * - add_credits: Add credits to account (for testing)
11
- */
12
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
13
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
14
- import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
15
- import { discoverAPIs, getAPIDetails, getCategories, getAllAPIs } from './discovery.js';
16
- import { trackStartup, trackSearch } from './telemetry.js';
17
- import { addCredits, purchaseAPIAccess, getBalanceSummary } from './credits.js';
18
- import { hasRealCredentials } from './credentials.js';
19
- import { getConnectedProviders } from './execute.js';
20
- import { executeMetered } from './metered.js';
21
- import { logAPICall } from './analytics.js';
22
- import { isOpenAPI, executeOpenAPI, listOpenAPIs } from './open-apis.js';
23
- import { PROXY_PROVIDERS } from './proxy.js';
24
- import { requiresConfirmationAsync, createPendingAction, consumePendingAction, generatePreview, validateParams } from './confirmation.js';
25
- import { executeCapability, listCapabilities, hasCapability } from './capability-router.js';
26
- import { readSession, writeSession, clearSession, getMachineFingerprint } from './session.js';
27
- import { ConvexHttpClient } from 'convex/browser';
28
- import { getOrCreateCustomer, createMeteredCheckoutSession, getUsageSummary, METERED_BILLING } from './stripe.js';
29
- import { estimateCost } from './metered.js';
30
- import { executeChain, getChainStatus, resumeChain } from './chainExecutor.js';
31
- // Default agent ID for MVP (in production, this would come from auth)
32
- const DEFAULT_AGENT_ID = 'agent_default';
33
- // Convex client for workspace management
34
- const CONVEX_URL = process.env.CONVEX_URL || 'https://brilliant-puffin-712.eu-west-1.convex.cloud';
35
- const convex = new ConvexHttpClient(CONVEX_URL);
36
- let workspaceContext = null;
37
- const anonymousRateLimits = new Map();
38
- // Rate limit constants
39
- const ANONYMOUS_HOURLY_LIMIT = 5;
40
- const ANONYMOUS_WEEKLY_LIMIT = 10;
41
- const FREE_WEEKLY_LIMIT = 50;
42
- /**
43
- * Calculate minutes until next hour
44
- */
45
- function calculateMinutesUntilNextHour() {
46
- const now = new Date();
47
- const nextHour = new Date(now);
48
- nextHour.setHours(now.getHours() + 1, 0, 0, 0);
49
- return Math.ceil((nextHour.getTime() - now.getTime()) / 60000);
50
- }
51
- /**
52
- * Get next Monday 00:00 UTC as ISO string
53
- */
54
- function getNextMondayUTC() {
55
- const now = new Date();
56
- const dayOfWeek = now.getUTCDay(); // 0 = Sunday, 1 = Monday, etc.
57
- const daysUntilMonday = dayOfWeek === 0 ? 1 : 8 - dayOfWeek;
58
- const nextMonday = new Date(now);
59
- nextMonday.setUTCDate(now.getUTCDate() + daysUntilMonday);
60
- nextMonday.setUTCHours(0, 0, 0, 0);
61
- return nextMonday.toISOString().replace('T', ' ').slice(0, 16) + ' UTC';
62
- }
63
- /**
64
- * Check anonymous rate limits for proxy provider usage
65
- */
66
- function checkAnonymousRateLimit(fingerprint) {
67
- const now = Date.now();
68
- const hourInMs = 60 * 60 * 1000;
69
- const weekInMs = 7 * 24 * hourInMs;
70
- // Get or initialize rate limit state
71
- let state = anonymousRateLimits.get(fingerprint);
72
- if (!state) {
73
- state = {
74
- hourlyCount: 0,
75
- hourlyResetTime: now + hourInMs,
76
- weeklyCount: 0,
77
- weeklyResetTime: now + weekInMs,
78
- };
79
- anonymousRateLimits.set(fingerprint, state);
80
- }
81
- // Reset hourly counter if time elapsed
82
- if (now >= state.hourlyResetTime) {
83
- state.hourlyCount = 0;
84
- state.hourlyResetTime = now + hourInMs;
85
- }
86
- // Reset weekly counter if time elapsed
87
- if (now >= state.weeklyResetTime) {
88
- state.weeklyCount = 0;
89
- state.weeklyResetTime = now + weekInMs;
90
- }
91
- // Check hourly limit
92
- if (state.hourlyCount >= ANONYMOUS_HOURLY_LIMIT) {
93
- return {
94
- allowed: false,
95
- error: JSON.stringify({
96
- success: false,
97
- error: `Hourly rate limit (${ANONYMOUS_HOURLY_LIMIT} calls/hour)`,
98
- retry_after_minutes: calculateMinutesUntilNextHour(),
99
- hint: "Rate limit resets at top of hour",
100
- action: "Register to get higher limits: register_owner({ email: 'you@example.com' })"
101
- }, null, 2)
102
- };
103
- }
104
- // Check weekly limit
105
- if (state.weeklyCount >= ANONYMOUS_WEEKLY_LIMIT) {
106
- return {
107
- allowed: false,
108
- error: JSON.stringify({
109
- success: false,
110
- error: `Weekly limit reached (${ANONYMOUS_WEEKLY_LIMIT} calls)`,
111
- hint: "Register to get 50 calls/week",
112
- action: "Run: register_owner({ email: 'you@example.com' })",
113
- retry_after: getNextMondayUTC()
114
- }, null, 2)
115
- };
116
- }
117
- // Increment counters
118
- state.hourlyCount++;
119
- state.weeklyCount++;
120
- return { allowed: true };
121
- }
122
- /**
123
- * Validate session on startup
124
- */
125
- async function validateSession() {
126
- const session = readSession();
127
- if (!session) {
128
- console.error('[APIClaw] No session found. Use register_owner to authenticate.');
129
- return false;
130
- }
131
- try {
132
- const result = await convex.query("workspaces:getWorkspaceStatus", {
133
- sessionToken: session.sessionToken,
134
- });
135
- if (!result.authenticated) {
136
- console.error('[APIClaw] Session invalid or expired. Clearing...');
137
- clearSession();
138
- return false;
139
- }
140
- if (result.status !== 'active') {
141
- console.error(`[APIClaw] Workspace status: ${result.status}. Please verify your email.`);
142
- return false;
143
- }
144
- workspaceContext = {
145
- sessionToken: session.sessionToken,
146
- workspaceId: session.workspaceId,
147
- email: result.email ?? '',
148
- tier: result.tier ?? 'free',
149
- usageRemaining: result.usageRemaining ?? 0,
150
- status: result.status ?? 'unknown',
151
- };
152
- console.error(`[APIClaw] ✓ Authenticated as ${result.email} (${result.tier} tier)`);
153
- console.error(`[APIClaw] ✓ Usage: ${result.usageCount}/${result.usageLimit === -1 ? '∞' : result.usageLimit} calls`);
154
- // Touch session to update last used
155
- await convex.mutation("workspaces:touchSession", {
156
- sessionToken: session.sessionToken,
157
- });
158
- return true;
159
- }
160
- catch (error) {
161
- console.error('[APIClaw] Error validating session:', error);
162
- return false;
163
- }
164
- }
165
- /**
166
- * Track earn progress after successful API call
167
- * Handles firstDirectCall and apisUsed tracking
168
- */
169
- async function trackEarnProgress(workspaceId, provider, action) {
170
- try {
171
- // Track first direct call
172
- await convex.mutation("earnProgress:markFirstDirectCall", {
173
- workspaceId: workspaceId,
174
- });
175
- // Track unique API usage
176
- const apiId = `${provider}:${action}`;
177
- await convex.mutation("earnProgress:trackApiUsed", {
178
- workspaceId: workspaceId,
179
- apiId,
180
- });
181
- }
182
- catch (e) {
183
- // Non-critical - don't fail the API call if earn tracking fails
184
- console.error('[APIClaw] Failed to track earn progress:', e);
185
- }
186
- }
187
- const rateLimitStore = new Map();
188
- /**
189
- * For proxy providers, allow anonymous usage with rate limiting
190
- */
191
- function checkWorkspaceAccess(providerId) {
192
- // Allow anonymous access for proxy providers
193
- if (providerId && PROXY_PROVIDERS.includes(providerId)) {
194
- if (!workspaceContext) {
195
- // Anonymous user - check rate limits
196
- const fingerprint = getMachineFingerprint();
197
- const rateLimitCheck = checkAnonymousRateLimit(fingerprint);
198
- if (!rateLimitCheck.allowed) {
199
- return {
200
- allowed: false,
201
- error: rateLimitCheck.error,
202
- isAnonymous: true
203
- };
204
- }
205
- return { allowed: true, isAnonymous: true };
206
- }
207
- // Authenticated user using proxy provider - allow with higher limits
208
- return { allowed: true, isAnonymous: false };
209
- }
210
- // Non-proxy providers require authentication
211
- if (!workspaceContext) {
212
- return {
213
- allowed: false,
214
- error: 'Not authenticated. Use register_owner to authenticate your workspace.'
215
- };
216
- }
217
- if (workspaceContext.status !== 'active') {
218
- return {
219
- allowed: false,
220
- error: `Workspace status: ${workspaceContext.status}. Please verify your email.`
221
- };
222
- }
223
- if (workspaceContext.usageRemaining === 0) {
224
- // Free tier hit weekly limit
225
- if (workspaceContext.tier === 'free') {
226
- return {
227
- allowed: false,
228
- error: JSON.stringify({
229
- success: false,
230
- error: `Weekly limit reached (${FREE_WEEKLY_LIMIT} calls)`,
231
- hint: "Upgrade to Backer for unlimited calls",
232
- upgrade_url: "https://apiclaw.nordsym.com/upgrade",
233
- retry_after: getNextMondayUTC()
234
- }, null, 2)
235
- };
236
- }
237
- // Other tiers (shouldn't happen, but handle gracefully)
238
- return {
239
- allowed: false,
240
- error: `Usage limit reached. Contact support or check your plan at https://apiclaw.nordsym.com/account`
241
- };
242
- }
243
- return { allowed: true, isAnonymous: false };
244
- }
245
- /**
246
- * Get customer API key from environment variable
247
- * Convention: {PROVIDER}_API_KEY (e.g., COACCEPT_API_KEY, ELKS_API_KEY)
248
- */
249
- function getCustomerKey(providerId) {
250
- // Try exact match first (e.g., 46elks -> 46ELKS_API_KEY)
251
- const exactKey = `${providerId.toUpperCase().replace(/-/g, '_')}_API_KEY`;
252
- if (process.env[exactKey]) {
253
- return process.env[exactKey];
254
- }
255
- // Try common variations
256
- const variations = [
257
- `${providerId.toUpperCase()}_API_KEY`,
258
- `${providerId.toUpperCase()}_KEY`,
259
- `${providerId.toUpperCase().replace(/_/g, '')}_API_KEY`,
260
- ];
261
- for (const key of variations) {
262
- if (process.env[key]) {
263
- return process.env[key];
264
- }
265
- }
266
- return undefined;
267
- }
268
- // Tool definitions
269
- const tools = [
270
- {
271
- name: 'apiclaw_help',
272
- description: 'Get help and see available commands. Start here if you are new to APIClaw.',
273
- inputSchema: {
274
- type: 'object',
275
- properties: {},
276
- required: []
277
- }
278
- },
279
- {
280
- name: 'discover_apis',
281
- description: 'Search for APIs based on what you need to do. Describe your use case naturally.',
282
- inputSchema: {
283
- type: 'object',
284
- properties: {
285
- query: {
286
- type: 'string',
287
- description: 'Natural language query describing what you need (e.g., "send SMS to Sweden", "search the web", "generate speech from text")'
288
- },
289
- category: {
290
- type: 'string',
291
- description: 'Filter by category: communication, search, ai',
292
- enum: ['communication', 'search', 'ai']
293
- },
294
- max_results: {
295
- type: 'number',
296
- description: 'Maximum number of results to return (default: 5)',
297
- default: 5
298
- },
299
- region: {
300
- type: 'string',
301
- description: 'Filter by region (e.g., "SE", "EU", "global")'
302
- },
303
- subagent_id: {
304
- type: 'string',
305
- description: 'Optional subagent identifier for multi-agent tracking'
306
- },
307
- ai_backend: {
308
- type: 'string',
309
- description: 'AI backend making this request (e.g., "claude-3-sonnet", "gpt-4"). Used for analytics.'
310
- }
311
- },
312
- required: ['query']
313
- }
314
- },
315
- {
316
- name: 'get_api_details',
317
- description: 'Get detailed information about a specific API provider, including endpoints, pricing, and features. Use compact=true to save ~60% tokens.',
318
- inputSchema: {
319
- type: 'object',
320
- properties: {
321
- api_id: {
322
- type: 'string',
323
- description: 'The API provider ID (e.g., "46elks", "resend", "openrouter")'
324
- },
325
- compact: {
326
- type: 'boolean',
327
- description: 'If true, returns minified spec (strips examples, keeps essential params). Saves ~60% context window.',
328
- default: false
329
- }
330
- },
331
- required: ['api_id']
332
- }
333
- },
334
- {
335
- name: 'purchase_access',
336
- description: 'Purchase access to an API using your credit balance. Returns API credentials on success.',
337
- inputSchema: {
338
- type: 'object',
339
- properties: {
340
- api_id: {
341
- type: 'string',
342
- description: 'The API provider ID to purchase access to'
343
- },
344
- amount_usd: {
345
- type: 'number',
346
- description: 'Amount in USD to spend on this API'
347
- },
348
- agent_id: {
349
- type: 'string',
350
- description: 'Your agent identifier (optional, uses default if not provided)'
351
- }
352
- },
353
- required: ['api_id', 'amount_usd']
354
- }
355
- },
356
- {
357
- name: 'check_balance',
358
- description: 'Check your credit balance and list active API purchases.',
359
- inputSchema: {
360
- type: 'object',
361
- properties: {
362
- agent_id: {
363
- type: 'string',
364
- description: 'Your agent identifier (optional, uses default if not provided)'
365
- }
366
- }
367
- }
368
- },
369
- {
370
- name: 'add_credits',
371
- description: 'Add credits to your account. (For testing/demo purposes)',
372
- inputSchema: {
373
- type: 'object',
374
- properties: {
375
- amount_usd: {
376
- type: 'number',
377
- description: 'Amount in USD to add to your balance'
378
- },
379
- agent_id: {
380
- type: 'string',
381
- description: 'Your agent identifier (optional, uses default if not provided)'
382
- }
383
- },
384
- required: ['amount_usd']
385
- }
386
- },
387
- {
388
- name: 'list_categories',
389
- description: 'List all available API categories.',
390
- inputSchema: {
391
- type: 'object',
392
- properties: {}
393
- }
394
- },
395
- {
396
- name: 'call_api',
397
- description: `Execute an API call through APIClaw. Supports single calls AND multi-step chains.
398
-
399
- SINGLE CALL: Provide provider + action + params
400
- CHAIN: Provide chain array to execute multiple APIs in sequence/parallel with cross-step references.
401
-
402
- Chain features:
403
- - Sequential: Steps execute in order, each can reference previous results via $stepId.property
404
- - Parallel: Use { parallel: [...steps] } to run concurrently
405
- - Conditional: Use { if: "$step.success", then: {...}, else: {...} }
406
- - Loops: Use { forEach: "$step.results", as: "item", do: {...} }
407
- - Error handling: Per-step retry/fallback via onError
408
- - Async: Set async: true to get chainId immediately, poll or use webhook
409
-
410
- Example chain:
411
- chain: [
412
- { id: "search", provider: "brave_search", action: "search", params: { query: "AI agents" } },
413
- { id: "summarize", provider: "openrouter", action: "chat", params: { message: "Summarize: $search.results" } }
414
- ]`,
415
- inputSchema: {
416
- type: 'object',
417
- properties: {
418
- // Single call params
419
- provider: {
420
- type: 'string',
421
- description: 'Provider ID (e.g., "46elks", "brave_search", "resend", "openrouter", "elevenlabs", "twilio", "coaccept", "frankfurter")'
422
- },
423
- action: {
424
- type: 'string',
425
- description: 'Action to perform (e.g., "send_sms", "search", "send_email", "chat", "send_invoice", "convert")'
426
- },
427
- params: {
428
- type: 'object',
429
- description: 'Parameters for the action. Varies by provider/action.'
430
- },
431
- customer_key: {
432
- type: 'string',
433
- description: 'Optional: Your own API key for providers that require customer authentication (e.g., CoAccept).'
434
- },
435
- confirm_token: {
436
- type: 'string',
437
- description: 'Confirmation token from a previous call. Required to execute actions that cost money after reviewing the preview.'
438
- },
439
- dry_run: {
440
- type: 'boolean',
441
- description: 'If true, shows what WOULD be sent without making actual API calls. Returns mock response and request details. Great for testing and debugging.'
442
- },
443
- // Chain execution params
444
- chain: {
445
- type: 'array',
446
- description: 'Execute multiple API calls as a single chain. Each step can reference previous results via $stepId.property',
447
- items: {
448
- type: 'object',
449
- properties: {
450
- id: { type: 'string', description: 'Step identifier for cross-step references' },
451
- provider: { type: 'string', description: 'API provider' },
452
- action: { type: 'string', description: 'Action to execute' },
453
- params: { type: 'object', description: 'Action parameters. Use $stepId.path for references.' },
454
- parallel: { type: 'array', description: 'Steps to run in parallel' },
455
- if: { type: 'string', description: 'Condition for conditional execution (e.g., "$step1.success")' },
456
- then: { type: 'object', description: 'Step to execute if condition is true' },
457
- else: { type: 'object', description: 'Step to execute if condition is false' },
458
- forEach: { type: 'string', description: 'Array reference to iterate (e.g., "$search.results")' },
459
- as: { type: 'string', description: 'Variable name for current item in loop' },
460
- do: { type: 'object', description: 'Step to execute for each item' },
461
- onError: {
462
- type: 'object',
463
- description: 'Error handling configuration',
464
- properties: {
465
- retry: {
466
- type: 'object',
467
- properties: {
468
- attempts: { type: 'number', description: 'Max retry attempts' },
469
- backoff: { type: 'string', description: '"exponential" or "linear" or array of ms delays' }
470
- }
471
- },
472
- fallback: { type: 'object', description: 'Fallback step if this fails' },
473
- abort: { type: 'boolean', description: 'Abort entire chain on failure' }
474
- }
475
- }
476
- }
477
- }
478
- },
479
- // Chain options
480
- continueOnError: {
481
- type: 'boolean',
482
- description: 'Continue chain execution even if a step fails (default: false)'
483
- },
484
- timeout: {
485
- type: 'number',
486
- description: 'Maximum execution time for the entire chain in milliseconds'
487
- },
488
- async: {
489
- type: 'boolean',
490
- description: 'Return immediately with chainId. Use get_chain_status to poll or provide webhook.'
491
- },
492
- webhook: {
493
- type: 'string',
494
- description: 'URL to POST results when async chain completes'
495
- },
496
- subagent_id: {
497
- type: 'string',
498
- description: 'Optional subagent identifier for multi-agent tracking'
499
- },
500
- ai_backend: {
501
- type: 'string',
502
- description: 'AI backend making this request (e.g., "claude-3-sonnet", "gpt-4"). Used for analytics.'
503
- }
504
- },
505
- required: []
506
- }
507
- },
508
- {
509
- name: 'list_connected',
510
- description: 'List all APIs available for Direct Call (no API key needed).',
511
- inputSchema: {
512
- type: 'object',
513
- properties: {}
514
- }
515
- },
516
- {
517
- name: 'capability',
518
- description: 'Execute an action by capability, not provider. APIClaw automatically selects the best provider, handles fallback, and optimizes for cost/speed. Example: capability("sms", "send", {to: "+46...", message: "Hello"})',
519
- inputSchema: {
520
- type: 'object',
521
- properties: {
522
- capability: {
523
- type: 'string',
524
- description: 'Capability ID: "sms", "email", "search", "tts", "invoice", "llm"'
525
- },
526
- action: {
527
- type: 'string',
528
- description: 'Action to perform: "send", "search", "generate", etc.'
529
- },
530
- params: {
531
- type: 'object',
532
- description: 'Parameters for the action (capability-standard params, not provider-specific)'
533
- },
534
- preferences: {
535
- type: 'object',
536
- description: 'Optional routing preferences',
537
- properties: {
538
- region: { type: 'string', description: 'Preferred region: "SE", "EU", "US"' },
539
- maxPrice: { type: 'number', description: 'Max price per unit in cents/öre' },
540
- preferredProvider: { type: 'string', description: 'Hint to prefer a specific provider' },
541
- fallback: { type: 'boolean', description: 'Enable fallback to other providers (default: true)' }
542
- }
543
- },
544
- subagent_id: {
545
- type: 'string',
546
- description: 'Optional subagent identifier for multi-agent tracking'
547
- },
548
- ai_backend: {
549
- type: 'string',
550
- description: 'AI backend making this request (e.g., "claude-3-sonnet", "gpt-4"). Used for analytics.'
551
- }
552
- },
553
- required: ['capability', 'action', 'params']
554
- }
555
- },
556
- {
557
- name: 'list_capabilities',
558
- description: 'List all available capabilities and their providers.',
559
- inputSchema: {
560
- type: 'object',
561
- properties: {}
562
- }
563
- },
564
- // ============================================
565
- // WORKSPACE TOOLS
566
- // ============================================
567
- {
568
- name: 'register_owner',
569
- description: 'Register your email to create a workspace. This authenticates your agent with APIClaw. You will receive a magic link to verify ownership.',
570
- inputSchema: {
571
- type: 'object',
572
- properties: {
573
- email: {
574
- type: 'string',
575
- description: 'Your email address (used for verification and account recovery)'
576
- }
577
- },
578
- required: ['email']
579
- }
580
- },
581
- {
582
- name: 'check_workspace_status',
583
- description: 'Check your workspace status, tier, and usage remaining.',
584
- inputSchema: {
585
- type: 'object',
586
- properties: {}
587
- }
588
- },
589
- {
590
- name: 'remind_owner',
591
- description: 'Send a reminder email to verify workspace ownership (if verification is pending).',
592
- inputSchema: {
593
- type: 'object',
594
- properties: {}
595
- }
596
- },
597
- // Metered Billing Tools
598
- {
599
- name: 'setup_metered_billing',
600
- description: 'Set up pay-per-call billing. Creates a subscription that charges $0.002 per API call at end of month.',
601
- inputSchema: {
602
- type: 'object',
603
- properties: {
604
- email: {
605
- type: 'string',
606
- description: 'Email for the billing account'
607
- },
608
- success_url: {
609
- type: 'string',
610
- description: 'URL to redirect after successful setup',
611
- default: 'https://apiclaw.nordsym.com/billing/success'
612
- },
613
- cancel_url: {
614
- type: 'string',
615
- description: 'URL to redirect if setup is cancelled',
616
- default: 'https://apiclaw.nordsym.com/billing/cancel'
617
- }
618
- },
619
- required: ['email']
620
- }
621
- },
622
- {
623
- name: 'get_usage_summary',
624
- description: 'Get current billing period usage and estimated cost for metered billing.',
625
- inputSchema: {
626
- type: 'object',
627
- properties: {
628
- subscription_id: {
629
- type: 'string',
630
- description: 'Stripe subscription ID (stored after setup_metered_billing)'
631
- }
632
- },
633
- required: ['subscription_id']
634
- }
635
- },
636
- {
637
- name: 'estimate_cost',
638
- description: 'Estimate the cost for a given number of API calls.',
639
- inputSchema: {
640
- type: 'object',
641
- properties: {
642
- call_count: {
643
- type: 'number',
644
- description: 'Number of API calls to estimate cost for'
645
- }
646
- },
647
- required: ['call_count']
648
- }
649
- },
650
- // ============================================
651
- // CHAIN MANAGEMENT TOOLS
652
- // ============================================
653
- {
654
- name: 'get_chain_status',
655
- description: 'Check the status of an async chain execution. Use the chainId returned from call_api with async: true.',
656
- inputSchema: {
657
- type: 'object',
658
- properties: {
659
- chain_id: {
660
- type: 'string',
661
- description: 'Chain ID returned from async chain execution'
662
- }
663
- },
664
- required: ['chain_id']
665
- }
666
- },
667
- {
668
- name: 'resume_chain',
669
- description: 'Resume a failed chain from the point of failure. Use the resumeToken from the error response. Requires the original chain definition.',
670
- inputSchema: {
671
- type: 'object',
672
- properties: {
673
- resume_token: {
674
- type: 'string',
675
- description: 'Resume token from a failed chain (e.g., "chain_xyz_step_2")'
676
- },
677
- original_chain: {
678
- type: 'array',
679
- description: 'The original chain definition that failed. Required to resume execution.',
680
- items: { type: 'object' }
681
- },
682
- overrides: {
683
- type: 'object',
684
- description: 'Optional parameter overrides for specific steps. Format: { "stepId": { ...newParams } }'
685
- }
686
- },
687
- required: ['resume_token', 'original_chain']
688
- }
689
- }
690
- ];
691
- // Create server
692
- const server = new Server({
693
- name: 'apivault',
694
- version: '0.1.0',
695
- }, {
696
- capabilities: {
697
- tools: {},
698
- },
699
- });
700
- // Handle list tools
701
- server.setRequestHandler(ListToolsRequestSchema, async () => {
702
- return { tools };
703
- });
704
- // Handle tool calls
705
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
706
- const { name, arguments: args } = request.params;
707
- try {
708
- switch (name) {
709
- case 'apiclaw_help': {
710
- const helpText = `
711
- 🦞 APIClaw — The API Layer for AI Agents
712
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
713
-
714
- DISCOVER APIs:
715
- discover_apis({ query: "send SMS to Sweden" })
716
- discover_apis({ query: "search the web", max_results: 10 })
717
- discover_apis({ query: "text to speech", category: "ai" })
718
-
719
- GET DETAILS:
720
- get_api_details({ api_id: "46elks" })
721
-
722
- DIRECT CALL (8 APIs, no key needed):
723
- get_connected_providers()
724
- call_api({ provider: "brave_search", endpoint: "search", params: { query: "AI agents" } })
725
-
726
- Available direct-call providers:
727
- • brave_search — Web search
728
- • 46elks — SMS (Sweden)
729
- • twilio — SMS (Global)
730
- • resend — Email
731
- • openrouter — LLM routing (100+ models)
732
- • elevenlabs — Text-to-speech
733
- • replicate — AI models (images, video, audio)
734
- • firecrawl — Web scraping & crawling
735
- • github — Code repos & developer data
736
- • e2b — Code sandbox for AI agents
737
-
738
- BROWSE:
739
- list_categories()
740
- list_all_apis({ category: "communication", limit: 20 })
741
-
742
- Docs: https://apiclaw.nordsym.com
743
- `;
744
- return {
745
- content: [{ type: 'text', text: helpText }]
746
- };
747
- }
748
- case 'discover_apis': {
749
- const query = args?.query;
750
- const category = args?.category;
751
- const maxResults = args?.max_results || 5;
752
- const region = args?.region;
753
- const subagentId = args?.subagent_id;
754
- const aiBackend = args?.ai_backend;
755
- const startTime = Date.now();
756
- const results = discoverAPIs(query, { category, maxResults, region });
757
- const responseTimeMs = Date.now() - startTime;
758
- trackSearch(query, results.length, responseTimeMs);
759
- // Log search to Convex analytics (authenticated + anonymous)
760
- const analyticsUserId = workspaceContext?.workspaceId || `anon:${getMachineFingerprint()}`;
761
- const convexUrl = process.env.APICLAW_CONVEX_URL || process.env.NEXT_PUBLIC_CONVEX_URL;
762
- if (convexUrl) {
763
- fetch(`${convexUrl}/api/mutation`, {
764
- method: 'POST',
765
- headers: { 'Content-Type': 'application/json' },
766
- body: JSON.stringify({
767
- path: 'analytics:log',
768
- args: {
769
- event: 'search_query',
770
- provider: undefined,
771
- query,
772
- identifier: analyticsUserId,
773
- metadata: {
774
- resultCount: results.length,
775
- matchedProviders: results.slice(0, 10).map(r => r.provider.id),
776
- responseTimeMs,
777
- category,
778
- authenticated: !!workspaceContext,
779
- },
780
- },
781
- }),
782
- }).catch(() => { }); // Fire and forget
783
- }
784
- // Log search to searchLogs table (authenticated only - requires workspace)
785
- if (workspaceContext?.sessionToken) {
786
- const searchLogPayload = {
787
- path: 'searchLogs:log',
788
- args: {
789
- sessionToken: workspaceContext.sessionToken,
790
- subagentId: subagentId || undefined,
791
- query,
792
- resultCount: results.length,
793
- matchedProviders: results.slice(0, 10).map(r => r.provider.id),
794
- responseTimeMs,
795
- },
796
- };
797
- fetch(`${convexUrl}/api/mutation`, {
798
- method: 'POST',
799
- headers: { 'Content-Type': 'application/json' },
800
- body: JSON.stringify(searchLogPayload),
801
- }).catch(() => { }); // Fire and forget
802
- }
803
- // Update AI backend tracking if provided
804
- if (aiBackend && workspaceContext?.sessionToken) {
805
- fetch('https://brilliant-puffin-712.eu-west-1.convex.cloud/api/mutation', {
806
- method: 'POST',
807
- headers: { 'Content-Type': 'application/json' },
808
- body: JSON.stringify({
809
- path: 'agents:updateAIBackend',
810
- args: {
811
- token: workspaceContext.sessionToken,
812
- subagentId: subagentId || undefined,
813
- aiBackend,
814
- },
815
- }),
816
- }).catch(() => { }); // Fire and forget
817
- }
818
- if (results.length === 0) {
819
- return {
820
- content: [
821
- {
822
- type: 'text',
823
- text: JSON.stringify({
824
- status: 'no_results',
825
- message: `No APIs found matching "${query}". Try broader terms or check available categories with list_categories.`,
826
- available_categories: getCategories()
827
- }, null, 2)
828
- }
829
- ]
830
- };
831
- }
832
- return {
833
- content: [
834
- {
835
- type: 'text',
836
- text: JSON.stringify({
837
- status: 'success',
838
- query,
839
- results_count: results.length,
840
- results: results.map(r => ({
841
- id: r.provider.id,
842
- name: r.provider.name,
843
- description: r.provider.description,
844
- category: r.provider.category,
845
- capabilities: r.provider.capabilities,
846
- pricing_model: r.provider.pricing.model,
847
- has_free_tier: r.provider.pricing.free_tier,
848
- agent_success_rate: r.provider.agent_success_rate,
849
- relevance_score: r.relevance_score,
850
- match_reasons: r.match_reasons
851
- }))
852
- }, null, 2)
853
- }
854
- ]
855
- };
856
- }
857
- case 'get_api_details': {
858
- const apiId = args?.api_id;
859
- const compact = args?.compact || false;
860
- const api = getAPIDetails(apiId, { compact });
861
- if (!api) {
862
- return {
863
- content: [
864
- {
865
- type: 'text',
866
- text: JSON.stringify({
867
- status: 'error',
868
- message: `API not found: ${apiId}`,
869
- hint: 'Try discover_apis to search, or list_connected for direct-call APIs',
870
- }, null, 2)
871
- }
872
- ]
873
- };
874
- }
875
- // Compact mode: minimal JSON, no pretty-print
876
- if (compact) {
877
- return {
878
- content: [
879
- {
880
- type: 'text',
881
- text: JSON.stringify({ status: 'ok', ...api })
882
- }
883
- ]
884
- };
885
- }
886
- return {
887
- content: [
888
- {
889
- type: 'text',
890
- text: JSON.stringify({
891
- status: 'success',
892
- api
893
- }, null, 2)
894
- }
895
- ]
896
- };
897
- }
898
- case 'purchase_access': {
899
- const apiId = args?.api_id;
900
- const amountUsd = args?.amount_usd;
901
- const agentId = args?.agent_id || DEFAULT_AGENT_ID;
902
- const result = purchaseAPIAccess(agentId, apiId, amountUsd);
903
- if (!result.success) {
904
- return {
905
- content: [
906
- {
907
- type: 'text',
908
- text: JSON.stringify({
909
- status: 'error',
910
- message: result.error
911
- }, null, 2)
912
- }
913
- ]
914
- };
915
- }
916
- const api = getAPIDetails(apiId);
917
- return {
918
- content: [
919
- {
920
- type: 'text',
921
- text: JSON.stringify({
922
- status: 'success',
923
- message: `Successfully purchased access to ${apiId}`,
924
- purchase: {
925
- id: result.purchase.id,
926
- provider: apiId,
927
- amount_paid_usd: amountUsd,
928
- credits_received: result.purchase.credits_purchased,
929
- status: result.purchase.status,
930
- real_credentials: hasRealCredentials(apiId)
931
- },
932
- credentials: result.purchase.credentials,
933
- access: {
934
- base_url: api?.base_url,
935
- docs_url: api?.docs_url,
936
- auth_type: api?.auth_type
937
- }
938
- }, null, 2)
939
- }
940
- ]
941
- };
942
- }
943
- case 'check_balance': {
944
- const agentId = args?.agent_id || DEFAULT_AGENT_ID;
945
- const summary = getBalanceSummary(agentId);
946
- return {
947
- content: [
948
- {
949
- type: 'text',
950
- text: JSON.stringify({
951
- status: 'success',
952
- agent_id: agentId,
953
- balance_usd: summary.credits.balance_usd,
954
- currency: summary.credits.currency,
955
- total_spent_usd: summary.total_spent_usd,
956
- real_credential_providers: summary.real_credentials_available,
957
- active_purchases: summary.active_purchases.map(p => ({
958
- id: p.id,
959
- provider: p.provider_id,
960
- credits_remaining: p.credits_purchased,
961
- status: p.status,
962
- real_credentials: hasRealCredentials(p.provider_id)
963
- }))
964
- }, null, 2)
965
- }
966
- ]
967
- };
968
- }
969
- case 'add_credits': {
970
- const amountUsd = args?.amount_usd;
971
- const agentId = args?.agent_id || DEFAULT_AGENT_ID;
972
- const credits = addCredits(agentId, amountUsd);
973
- return {
974
- content: [
975
- {
976
- type: 'text',
977
- text: JSON.stringify({
978
- status: 'success',
979
- message: `Added $${amountUsd.toFixed(2)} to your account`,
980
- new_balance_usd: credits.balance_usd
981
- }, null, 2)
982
- }
983
- ]
984
- };
985
- }
986
- case 'list_categories': {
987
- const categories = getCategories();
988
- const apisByCategory = {};
989
- for (const cat of categories) {
990
- apisByCategory[cat] = getAllAPIs()
991
- .filter(a => a.category === cat)
992
- .map(a => a.id);
993
- }
994
- return {
995
- content: [
996
- {
997
- type: 'text',
998
- text: JSON.stringify({
999
- status: 'success',
1000
- categories: apisByCategory
1001
- }, null, 2)
1002
- }
1003
- ]
1004
- };
1005
- }
1006
- case 'call_api': {
1007
- const provider = args?.provider;
1008
- const action = args?.action;
1009
- const params = args?.params || {};
1010
- const confirmToken = args?.confirm_token;
1011
- const dryRun = args?.dry_run;
1012
- const chain = args?.chain;
1013
- const subagentId = args?.subagent_id;
1014
- const aiBackend = args?.ai_backend;
1015
- // Track AI backend if provided
1016
- if (aiBackend && workspaceContext?.sessionToken) {
1017
- fetch('https://brilliant-puffin-712.eu-west-1.convex.cloud/api/mutation', {
1018
- method: 'POST',
1019
- headers: { 'Content-Type': 'application/json' },
1020
- body: JSON.stringify({
1021
- path: 'agents:updateAIBackend',
1022
- args: {
1023
- token: workspaceContext.sessionToken,
1024
- subagentId: subagentId || undefined,
1025
- aiBackend,
1026
- },
1027
- }),
1028
- }).catch(() => { }); // Fire and forget
1029
- }
1030
- // ============================================
1031
- // CHAIN EXECUTION MODE
1032
- // ============================================
1033
- if (chain && Array.isArray(chain) && chain.length > 0) {
1034
- // Check workspace access for chains
1035
- const access = checkWorkspaceAccess();
1036
- if (!access.allowed) {
1037
- // If error is already formatted JSON (from rate limit checks), return as-is
1038
- if (access.error?.startsWith('{')) {
1039
- return {
1040
- content: [{
1041
- type: 'text',
1042
- text: access.error
1043
- }],
1044
- isError: true
1045
- };
1046
- }
1047
- // Otherwise, wrap in standard error format
1048
- return {
1049
- content: [{
1050
- type: 'text',
1051
- text: JSON.stringify({
1052
- status: 'error',
1053
- error: access.error,
1054
- hint: 'Use register_owner to authenticate your workspace.',
1055
- }, null, 2)
1056
- }],
1057
- isError: true
1058
- };
1059
- }
1060
- try {
1061
- // Construct ChainDefinition from the input
1062
- const chainDefinition = {
1063
- steps: chain,
1064
- timeout: args?.timeout,
1065
- errorPolicy: args?.continueOnError
1066
- ? { mode: 'best-effort' }
1067
- : { mode: 'fail-fast' },
1068
- };
1069
- const chainCredentials = {
1070
- userId: DEFAULT_AGENT_ID,
1071
- customerKeys: {},
1072
- };
1073
- // Add customer key if provided
1074
- const customerKey = args?.customer_key;
1075
- if (customerKey) {
1076
- // Apply to all providers (or could be provider-specific)
1077
- chainCredentials.customerKeys = { default: customerKey };
1078
- }
1079
- const chainOptions = {
1080
- verbose: false,
1081
- };
1082
- // Execute the chain
1083
- const chainResult = await executeChain(chainDefinition, chainCredentials, {}, // inputs
1084
- chainOptions);
1085
- // Track usage for chain (count completed steps)
1086
- if (chainResult.success && workspaceContext) {
1087
- const completedCount = chainResult.completedSteps.length;
1088
- for (let i = 0; i < completedCount; i++) {
1089
- try {
1090
- await convex.mutation("workspaces:incrementUsage", {
1091
- workspaceId: workspaceContext.workspaceId,
1092
- });
1093
- }
1094
- catch (e) {
1095
- console.error('[APIClaw] Failed to track chain usage:', e);
1096
- }
1097
- }
1098
- }
1099
- // Format response to match expected chain response format
1100
- return {
1101
- content: [{
1102
- type: 'text',
1103
- text: JSON.stringify({
1104
- status: chainResult.success ? 'success' : 'error',
1105
- mode: 'chain',
1106
- chainId: chainResult.chainId,
1107
- steps: chainResult.trace.map(t => ({
1108
- id: t.stepId,
1109
- status: t.success ? 'completed' : 'failed',
1110
- result: t.output,
1111
- error: t.error,
1112
- latencyMs: t.latencyMs,
1113
- cost: t.cost,
1114
- })),
1115
- finalResult: chainResult.finalResult,
1116
- totalLatencyMs: chainResult.totalLatencyMs,
1117
- totalCost: chainResult.totalCost,
1118
- tokensSaved: (chain.length - 1) * 500, // Estimate tokens saved
1119
- ...(chainResult.error ? {
1120
- completedSteps: chainResult.completedSteps,
1121
- failedStep: chainResult.failedStep ? {
1122
- id: chainResult.failedStep.stepId,
1123
- error: chainResult.failedStep.error,
1124
- code: chainResult.failedStep.errorCode,
1125
- } : undefined,
1126
- partialResults: chainResult.results,
1127
- canResume: chainResult.canResume,
1128
- resumeToken: chainResult.resumeToken,
1129
- } : {}),
1130
- }, null, 2)
1131
- }],
1132
- isError: !chainResult.success
1133
- };
1134
- }
1135
- catch (error) {
1136
- return {
1137
- content: [{
1138
- type: 'text',
1139
- text: JSON.stringify({
1140
- status: 'error',
1141
- mode: 'chain',
1142
- error: error instanceof Error ? error.message : String(error),
1143
- }, null, 2)
1144
- }],
1145
- isError: true
1146
- };
1147
- }
1148
- }
1149
- // ============================================
1150
- // SINGLE CALL MODE (existing logic)
1151
- // ============================================
1152
- // Handle dry-run mode - no actual API calls, just show what would happen
1153
- if (dryRun) {
1154
- const { generateDryRun } = await import('./execute.js');
1155
- const dryRunResult = generateDryRun(provider, action, params);
1156
- return {
1157
- content: [{
1158
- type: 'text',
1159
- text: JSON.stringify(dryRunResult, null, 2)
1160
- }]
1161
- };
1162
- }
1163
- // Check workspace access (skip for free/open APIs)
1164
- const isFreeAPI = isOpenAPI(provider);
1165
- if (!isFreeAPI) {
1166
- const access = checkWorkspaceAccess(provider);
1167
- if (!access.allowed) {
1168
- return {
1169
- content: [{
1170
- type: 'text',
1171
- text: JSON.stringify({
1172
- status: 'error',
1173
- error: access.error,
1174
- hint: access.isAnonymous
1175
- ? 'Rate limit reached. Use register_owner to authenticate for higher limits.'
1176
- : 'Use register_owner to authenticate your workspace.',
1177
- }, null, 2)
1178
- }],
1179
- isError: true
1180
- };
1181
- }
1182
- }
1183
- const startTime = Date.now();
1184
- let result;
1185
- let apiType;
1186
- // Check if this is a confirmation of a pending action
1187
- if (confirmToken) {
1188
- const pending = consumePendingAction(confirmToken);
1189
- if (!pending) {
1190
- return {
1191
- content: [{
1192
- type: 'text',
1193
- text: JSON.stringify({
1194
- status: 'error',
1195
- error: 'Invalid or expired confirmation token. Please start over.',
1196
- }, null, 2)
1197
- }],
1198
- isError: true
1199
- };
1200
- }
1201
- // Execute the confirmed action with metered billing
1202
- apiType = 'direct';
1203
- const customerKey = args?.customer_key || getCustomerKey(pending.provider);
1204
- const stripeCustomerId = args?.stripe_customer_id || process.env.APICLAW_STRIPE_CUSTOMER_ID;
1205
- result = await executeMetered(pending.provider, pending.action, pending.params, {
1206
- customerId: stripeCustomerId,
1207
- customerKey,
1208
- userId: DEFAULT_AGENT_ID,
1209
- });
1210
- // Log the confirmed API call (with fingerprint for anonymous users)
1211
- const analyticsUserId = workspaceContext
1212
- ? workspaceContext.workspaceId
1213
- : `anon:${getMachineFingerprint()}`;
1214
- logAPICall({
1215
- timestamp: new Date().toISOString(),
1216
- provider: pending.provider,
1217
- action: pending.action,
1218
- type: apiType,
1219
- userId: analyticsUserId,
1220
- success: result.success,
1221
- latencyMs: Date.now() - startTime,
1222
- error: result.error,
1223
- });
1224
- // Track earn progress for confirmed actions
1225
- if (result.success && workspaceContext) {
1226
- await trackEarnProgress(workspaceContext.workspaceId, pending.provider, pending.action);
1227
- }
1228
- return {
1229
- content: [{
1230
- type: 'text',
1231
- text: JSON.stringify({
1232
- status: result.success ? 'success' : 'error',
1233
- provider: result.provider,
1234
- action: result.action,
1235
- confirmed: true,
1236
- ...(result.success ? { data: result.data } : { error: result.error }),
1237
- }, null, 2)
1238
- }],
1239
- isError: !result.success
1240
- };
1241
- }
1242
- // Check if this action requires confirmation (both hardcoded and dynamic providers)
1243
- const confirmCheck = await requiresConfirmationAsync(provider, action);
1244
- if (confirmCheck.required) {
1245
- // Validate params first (for hardcoded providers)
1246
- if (!confirmCheck.isDynamic) {
1247
- const validation = validateParams(provider, action, params);
1248
- if (!validation.valid) {
1249
- return {
1250
- content: [{
1251
- type: 'text',
1252
- text: JSON.stringify({
1253
- status: 'error',
1254
- error: 'Validation failed',
1255
- missing_or_invalid: validation.errors,
1256
- hint: 'Please provide all required fields before sending.',
1257
- }, null, 2)
1258
- }],
1259
- isError: true
1260
- };
1261
- }
1262
- }
1263
- // Generate preview and create pending action
1264
- const preview = generatePreview(provider, action, params);
1265
- if (confirmCheck.estimatedCost) {
1266
- preview.estimated_cost = confirmCheck.estimatedCost;
1267
- }
1268
- const pending = createPendingAction(provider, action, params, preview, DEFAULT_AGENT_ID);
1269
- return {
1270
- content: [{
1271
- type: 'text',
1272
- text: JSON.stringify({
1273
- status: 'requires_confirmation',
1274
- message: '⚠️ This action costs money. Please review and confirm.',
1275
- preview,
1276
- confirm_token: pending.token,
1277
- expires_in_seconds: 300,
1278
- how_to_confirm: `Call again with confirm_token: "${pending.token}"`,
1279
- }, null, 2)
1280
- }]
1281
- };
1282
- }
1283
- // Regular execution (no confirmation needed)
1284
- if (isOpenAPI(provider)) {
1285
- apiType = 'open';
1286
- result = await executeOpenAPI(provider, action, params);
1287
- }
1288
- else {
1289
- apiType = 'direct';
1290
- const customerKey = args?.customer_key || getCustomerKey(provider);
1291
- const stripeCustomerId = args?.stripe_customer_id || process.env.APICLAW_STRIPE_CUSTOMER_ID;
1292
- result = await executeMetered(provider, action, params, {
1293
- customerId: stripeCustomerId,
1294
- customerKey,
1295
- userId: DEFAULT_AGENT_ID,
1296
- });
1297
- }
1298
- // Log the API call for analytics (with fingerprint for anonymous users)
1299
- const analyticsUserId = workspaceContext
1300
- ? workspaceContext.workspaceId
1301
- : `anon:${getMachineFingerprint()}`;
1302
- logAPICall({
1303
- timestamp: new Date().toISOString(),
1304
- provider,
1305
- action,
1306
- type: apiType,
1307
- userId: analyticsUserId,
1308
- success: result.success,
1309
- latencyMs: Date.now() - startTime,
1310
- error: result.error,
1311
- });
1312
- // Increment usage for workspace (non-free APIs only)
1313
- if (result.success && workspaceContext && !isFreeAPI) {
1314
- try {
1315
- const usageResult = await convex.mutation("workspaces:incrementUsage", {
1316
- workspaceId: workspaceContext.workspaceId,
1317
- });
1318
- if (usageResult.success) {
1319
- workspaceContext.usageRemaining = usageResult.remaining ?? -1;
1320
- }
1321
- // Track earn progress (first direct call + unique APIs)
1322
- await trackEarnProgress(workspaceContext.workspaceId, provider, action);
1323
- }
1324
- catch (e) {
1325
- console.error('[APIClaw] Failed to track usage:', e);
1326
- }
1327
- }
1328
- return {
1329
- content: [
1330
- {
1331
- type: 'text',
1332
- text: JSON.stringify({
1333
- status: result.success ? 'success' : 'error',
1334
- provider: result.provider,
1335
- action: result.action,
1336
- type: apiType,
1337
- ...(result.success ? { data: result.data } : { error: result.error }),
1338
- ...(result.cost !== undefined ? { cost_sek: result.cost } : {})
1339
- }, null, 2)
1340
- }
1341
- ],
1342
- isError: !result.success
1343
- };
1344
- }
1345
- case 'list_connected': {
1346
- const directProviders = getConnectedProviders();
1347
- const openProviders = listOpenAPIs();
1348
- return {
1349
- content: [
1350
- {
1351
- type: 'text',
1352
- text: JSON.stringify({
1353
- status: 'success',
1354
- message: 'These APIs are available via call_api - no API key needed!',
1355
- direct_call: {
1356
- description: 'APIs where we handle authentication',
1357
- providers: directProviders,
1358
- },
1359
- open_apis: {
1360
- description: 'Free, open APIs (no auth required)',
1361
- providers: openProviders,
1362
- },
1363
- usage: 'Use call_api with provider, action, and params to execute calls.'
1364
- }, null, 2)
1365
- }
1366
- ]
1367
- };
1368
- }
1369
- case 'capability': {
1370
- const capabilityId = args?.capability;
1371
- const action = args?.action;
1372
- const params = args?.params || {};
1373
- const preferences = args?.preferences || {};
1374
- const subagentId = args?.subagent_id;
1375
- const aiBackend = args?.ai_backend;
1376
- // Track AI backend if provided
1377
- if (aiBackend && workspaceContext?.sessionToken) {
1378
- fetch('https://brilliant-puffin-712.eu-west-1.convex.cloud/api/mutation', {
1379
- method: 'POST',
1380
- headers: { 'Content-Type': 'application/json' },
1381
- body: JSON.stringify({
1382
- path: 'agents:updateAIBackend',
1383
- args: {
1384
- token: workspaceContext.sessionToken,
1385
- subagentId: subagentId || undefined,
1386
- aiBackend,
1387
- },
1388
- }),
1389
- }).catch(() => { }); // Fire and forget
1390
- }
1391
- // Check if capability exists
1392
- const exists = await hasCapability(capabilityId);
1393
- if (!exists) {
1394
- // Try to help with available capabilities
1395
- const available = await listCapabilities();
1396
- return {
1397
- content: [{
1398
- type: 'text',
1399
- text: JSON.stringify({
1400
- status: 'error',
1401
- error: `Unknown capability: ${capabilityId}`,
1402
- available_capabilities: available.map(c => c.id),
1403
- hint: 'Use list_capabilities to see all available capabilities.'
1404
- }, null, 2)
1405
- }],
1406
- isError: true
1407
- };
1408
- }
1409
- // Execute capability
1410
- const result = await executeCapability(capabilityId, action, params, DEFAULT_AGENT_ID, preferences);
1411
- return {
1412
- content: [{
1413
- type: 'text',
1414
- text: JSON.stringify({
1415
- status: result.success ? 'success' : 'error',
1416
- capability: result.capability,
1417
- action: result.action,
1418
- provider_used: result.providerUsed,
1419
- fallback_attempted: result.fallbackAttempted,
1420
- ...(result.fallbackReason ? { fallback_reason: result.fallbackReason } : {}),
1421
- ...(result.success ? { data: result.data } : { error: result.error }),
1422
- ...(result.cost !== undefined ? { cost: result.cost, currency: result.currency } : {}),
1423
- latency_ms: result.latencyMs,
1424
- }, null, 2)
1425
- }],
1426
- isError: !result.success
1427
- };
1428
- }
1429
- case 'list_capabilities': {
1430
- const capabilities = await listCapabilities();
1431
- return {
1432
- content: [{
1433
- type: 'text',
1434
- text: JSON.stringify({
1435
- status: 'success',
1436
- message: 'Available capabilities - use capability() to execute',
1437
- capabilities,
1438
- usage: 'capability("sms", "send", {to: "+46...", message: "Hello"})'
1439
- }, null, 2)
1440
- }]
1441
- };
1442
- }
1443
- // ============================================
1444
- // WORKSPACE TOOLS
1445
- // ============================================
1446
- case 'register_owner': {
1447
- const email = args?.email;
1448
- if (!email || !email.includes('@')) {
1449
- return {
1450
- content: [{
1451
- type: 'text',
1452
- text: JSON.stringify({
1453
- status: 'error',
1454
- error: 'Invalid email address',
1455
- }, null, 2)
1456
- }],
1457
- isError: true
1458
- };
1459
- }
1460
- try {
1461
- // Check if workspace already exists
1462
- const existing = await convex.query("workspaces:getByEmail", { email });
1463
- if (existing && existing.status === 'active') {
1464
- // Workspace exists and is active - create session directly
1465
- const fingerprint = getMachineFingerprint();
1466
- const sessionResult = await convex.mutation("workspaces:createAgentSession", {
1467
- workspaceId: existing.id,
1468
- fingerprint,
1469
- });
1470
- if (sessionResult.success) {
1471
- writeSession(sessionResult.sessionToken, existing.id, email);
1472
- // Claim anonymous usage history
1473
- try {
1474
- const claimResult = await convex.mutation("workspaces:claimAnonymousUsage", {
1475
- workspaceId: existing.id,
1476
- machineFingerprint: fingerprint,
1477
- });
1478
- if (claimResult.success && claimResult.claimedCount) {
1479
- console.error(`[APIClaw] ✓ Claimed ${claimResult.claimedCount} anonymous usage records`);
1480
- }
1481
- }
1482
- catch (err) {
1483
- // Non-critical error, just log it
1484
- console.error('[APIClaw] Warning: Failed to claim anonymous usage:', err);
1485
- }
1486
- // Update global context
1487
- workspaceContext = {
1488
- sessionToken: sessionResult.sessionToken,
1489
- workspaceId: existing.id,
1490
- email,
1491
- tier: existing.tier,
1492
- usageRemaining: existing.usageLimit - existing.usageCount,
1493
- status: existing.status,
1494
- };
1495
- return {
1496
- content: [{
1497
- type: 'text',
1498
- text: JSON.stringify({
1499
- status: 'success',
1500
- message: `Welcome back! Authenticated as ${email}`,
1501
- workspace: {
1502
- email,
1503
- tier: existing.tier,
1504
- usageCount: existing.usageCount,
1505
- usageLimit: existing.usageLimit,
1506
- },
1507
- }, null, 2)
1508
- }]
1509
- };
1510
- }
1511
- }
1512
- // Create workspace and magic link
1513
- const createResult = await convex.mutation("workspaces:createWorkspace", { email });
1514
- let workspaceId;
1515
- if (createResult.success) {
1516
- workspaceId = createResult.workspaceId;
1517
- }
1518
- else if (createResult.error === 'workspace_exists') {
1519
- workspaceId = createResult.workspaceId;
1520
- }
1521
- else {
1522
- throw new Error(createResult.error);
1523
- }
1524
- // Create magic link
1525
- const fingerprint = getMachineFingerprint();
1526
- const magicLinkResult = await convex.mutation("workspaces:createMagicLink", {
1527
- email,
1528
- fingerprint,
1529
- });
1530
- // Send magic link via email
1531
- const verifyUrl = `https://apiclaw.nordsym.com/verify?token=${magicLinkResult.token}`;
1532
- const emailResponse = await fetch('https://api.resend.com/emails', {
1533
- method: 'POST',
1534
- headers: {
1535
- 'Authorization': `Bearer ${process.env.RESEND_API_KEY}`,
1536
- 'Content-Type': 'application/json',
1537
- },
1538
- body: JSON.stringify({
1539
- from: 'APIClaw <noreply@apiclaw.nordsym.com>',
1540
- to: email,
1541
- subject: 'Verify your APIClaw workspace',
1542
- html: `<p>Click to verify: <a href="${verifyUrl}">${verifyUrl}</a></p><p>Expires in 15 minutes.</p>`
1543
- })
1544
- });
1545
- if (!emailResponse.ok) {
1546
- const errorData = await emailResponse.text();
1547
- throw new Error(`Failed to send verification email: ${errorData}`);
1548
- }
1549
- return {
1550
- content: [{
1551
- type: 'text',
1552
- text: JSON.stringify({
1553
- status: 'pending_verification',
1554
- message: 'Workspace created! Check your email for verification link.',
1555
- email,
1556
- expires_in_minutes: 15,
1557
- next_step: 'Check your email, click the verification link, then run check_workspace_status',
1558
- }, null, 2)
1559
- }]
1560
- };
1561
- }
1562
- catch (error) {
1563
- return {
1564
- content: [{
1565
- type: 'text',
1566
- text: JSON.stringify({
1567
- status: 'error',
1568
- error: error instanceof Error ? error.message : 'Registration failed',
1569
- }, null, 2)
1570
- }],
1571
- isError: true
1572
- };
1573
- }
1574
- }
1575
- case 'check_workspace_status': {
1576
- // Check if we have a local session
1577
- const session = readSession();
1578
- if (!session) {
1579
- return {
1580
- content: [{
1581
- type: 'text',
1582
- text: JSON.stringify({
1583
- status: 'not_authenticated',
1584
- message: 'No active session. Use register_owner to authenticate.',
1585
- }, null, 2)
1586
- }]
1587
- };
1588
- }
1589
- try {
1590
- const result = await convex.query("workspaces:getWorkspaceStatus", {
1591
- sessionToken: session.sessionToken,
1592
- });
1593
- if (!result.authenticated) {
1594
- clearSession();
1595
- workspaceContext = null;
1596
- return {
1597
- content: [{
1598
- type: 'text',
1599
- text: JSON.stringify({
1600
- status: 'session_expired',
1601
- message: 'Session expired. Use register_owner to re-authenticate.',
1602
- }, null, 2)
1603
- }]
1604
- };
1605
- }
1606
- // Update global context
1607
- workspaceContext = {
1608
- sessionToken: session.sessionToken,
1609
- workspaceId: session.workspaceId,
1610
- email: result.email ?? '',
1611
- tier: result.tier ?? 'free',
1612
- usageRemaining: result.usageRemaining ?? 0,
1613
- status: result.status ?? 'unknown',
1614
- };
1615
- return {
1616
- content: [{
1617
- type: 'text',
1618
- text: JSON.stringify({
1619
- status: 'success',
1620
- workspace: {
1621
- email: result.email,
1622
- status: result.status,
1623
- tier: result.tier,
1624
- usage: {
1625
- count: result.usageCount,
1626
- limit: result.usageLimit === -1 ? 'unlimited' : result.usageLimit,
1627
- remaining: result.usageRemaining === -1 ? 'unlimited' : result.usageRemaining,
1628
- },
1629
- hasStripe: result.hasStripe,
1630
- createdAt: result.createdAt ? new Date(result.createdAt).toISOString() : undefined,
1631
- },
1632
- }, null, 2)
1633
- }]
1634
- };
1635
- }
1636
- catch (error) {
1637
- return {
1638
- content: [{
1639
- type: 'text',
1640
- text: JSON.stringify({
1641
- status: 'error',
1642
- error: error instanceof Error ? error.message : 'Failed to check status',
1643
- }, null, 2)
1644
- }],
1645
- isError: true
1646
- };
1647
- }
1648
- }
1649
- case 'remind_owner': {
1650
- const session = readSession();
1651
- if (!session) {
1652
- return {
1653
- content: [{
1654
- type: 'text',
1655
- text: JSON.stringify({
1656
- status: 'error',
1657
- error: 'No workspace found. Use register_owner first.',
1658
- }, null, 2)
1659
- }],
1660
- isError: true
1661
- };
1662
- }
1663
- try {
1664
- // Check current status
1665
- const result = await convex.query("workspaces:getWorkspaceStatus", {
1666
- sessionToken: session.sessionToken,
1667
- });
1668
- if (result.authenticated && result.status === 'active') {
1669
- return {
1670
- content: [{
1671
- type: 'text',
1672
- text: JSON.stringify({
1673
- status: 'already_verified',
1674
- message: 'Workspace is already verified and active!',
1675
- email: result.email,
1676
- }, null, 2)
1677
- }]
1678
- };
1679
- }
1680
- // Create new magic link
1681
- const fingerprint = getMachineFingerprint();
1682
- const magicLinkResult = await convex.mutation("workspaces:createMagicLink", {
1683
- email: session.email,
1684
- fingerprint,
1685
- });
1686
- // TODO: Agent 2 will implement actual email sending
1687
- const verifyUrl = `https://apiclaw.nordsym.com/verify?token=${magicLinkResult.token}`;
1688
- return {
1689
- content: [{
1690
- type: 'text',
1691
- text: JSON.stringify({
1692
- status: 'reminder_sent',
1693
- message: 'New verification link created.',
1694
- email: session.email,
1695
- verification_url: verifyUrl,
1696
- expires_in_minutes: 15,
1697
- note: 'Email sending will be implemented by Agent 2',
1698
- }, null, 2)
1699
- }]
1700
- };
1701
- }
1702
- catch (error) {
1703
- return {
1704
- content: [{
1705
- type: 'text',
1706
- text: JSON.stringify({
1707
- status: 'error',
1708
- error: error instanceof Error ? error.message : 'Failed to send reminder',
1709
- }, null, 2)
1710
- }],
1711
- isError: true
1712
- };
1713
- }
1714
- }
1715
- // Metered Billing Tools
1716
- case 'setup_metered_billing': {
1717
- const { email, success_url, cancel_url } = args;
1718
- if (!email) {
1719
- return {
1720
- content: [{
1721
- type: 'text',
1722
- text: JSON.stringify({ status: 'error', error: 'Email is required' }, null, 2)
1723
- }],
1724
- isError: true
1725
- };
1726
- }
1727
- // Create or get customer
1728
- const customerResult = await getOrCreateCustomer(email, email);
1729
- if ('error' in customerResult) {
1730
- return {
1731
- content: [{
1732
- type: 'text',
1733
- text: JSON.stringify({ status: 'error', error: customerResult.error }, null, 2)
1734
- }],
1735
- isError: true
1736
- };
1737
- }
1738
- // Create checkout session for metered subscription
1739
- const checkoutResult = await createMeteredCheckoutSession(email, success_url || 'https://apiclaw.nordsym.com/billing/success', cancel_url || 'https://apiclaw.nordsym.com/billing/cancel');
1740
- if ('error' in checkoutResult) {
1741
- return {
1742
- content: [{
1743
- type: 'text',
1744
- text: JSON.stringify({ status: 'error', error: checkoutResult.error }, null, 2)
1745
- }],
1746
- isError: true
1747
- };
1748
- }
1749
- return {
1750
- content: [{
1751
- type: 'text',
1752
- text: JSON.stringify({
1753
- status: 'checkout_ready',
1754
- message: 'Complete checkout to activate pay-per-call billing',
1755
- checkout_url: checkoutResult.url,
1756
- session_id: checkoutResult.sessionId,
1757
- customer_id: customerResult.customerId,
1758
- pricing: {
1759
- per_call: '$0.002',
1760
- billing_period: 'monthly',
1761
- billed_at: 'end of period based on usage'
1762
- }
1763
- }, null, 2)
1764
- }]
1765
- };
1766
- }
1767
- case 'get_usage_summary': {
1768
- const { subscription_id } = args;
1769
- if (!subscription_id) {
1770
- return {
1771
- content: [{
1772
- type: 'text',
1773
- text: JSON.stringify({ status: 'error', error: 'subscription_id is required' }, null, 2)
1774
- }],
1775
- isError: true
1776
- };
1777
- }
1778
- const usage = await getUsageSummary(subscription_id);
1779
- if ('error' in usage) {
1780
- return {
1781
- content: [{
1782
- type: 'text',
1783
- text: JSON.stringify({ status: 'error', error: usage.error }, null, 2)
1784
- }],
1785
- isError: true
1786
- };
1787
- }
1788
- return {
1789
- content: [{
1790
- type: 'text',
1791
- text: JSON.stringify({
1792
- status: 'success',
1793
- billing_period: {
1794
- start: new Date(usage.period.start * 1000).toISOString(),
1795
- end: new Date(usage.period.end * 1000).toISOString()
1796
- },
1797
- usage: {
1798
- total_calls: usage.totalCalls,
1799
- price_per_call: METERED_BILLING.pricePerCall,
1800
- estimated_cost: `$${usage.totalCost.toFixed(4)}`
1801
- }
1802
- }, null, 2)
1803
- }]
1804
- };
1805
- }
1806
- case 'estimate_cost': {
1807
- const { call_count } = args;
1808
- if (!call_count || call_count < 0) {
1809
- return {
1810
- content: [{
1811
- type: 'text',
1812
- text: JSON.stringify({ status: 'error', error: 'Valid call_count is required' }, null, 2)
1813
- }],
1814
- isError: true
1815
- };
1816
- }
1817
- const estimate = estimateCost(call_count);
1818
- return {
1819
- content: [{
1820
- type: 'text',
1821
- text: JSON.stringify({
1822
- status: 'success',
1823
- estimate: {
1824
- calls: estimate.calls,
1825
- price_per_call: `$${estimate.pricePerCall}`,
1826
- total_cost: `$${estimate.totalCost.toFixed(4)}`,
1827
- currency: estimate.currency
1828
- },
1829
- examples: {
1830
- '100 calls': `$${(100 * METERED_BILLING.pricePerCall).toFixed(2)}`,
1831
- '1,000 calls': `$${(1000 * METERED_BILLING.pricePerCall).toFixed(2)}`,
1832
- '10,000 calls': `$${(10000 * METERED_BILLING.pricePerCall).toFixed(2)}`
1833
- }
1834
- }, null, 2)
1835
- }]
1836
- };
1837
- }
1838
- // ============================================
1839
- // CHAIN MANAGEMENT TOOLS
1840
- // ============================================
1841
- case 'get_chain_status': {
1842
- const chainId = args?.chain_id;
1843
- if (!chainId) {
1844
- return {
1845
- content: [{
1846
- type: 'text',
1847
- text: JSON.stringify({
1848
- status: 'error',
1849
- error: 'chain_id is required'
1850
- }, null, 2)
1851
- }],
1852
- isError: true
1853
- };
1854
- }
1855
- const chainStatus = await getChainStatus(chainId);
1856
- if (chainStatus.status === 'not_found') {
1857
- return {
1858
- content: [{
1859
- type: 'text',
1860
- text: JSON.stringify({
1861
- status: 'error',
1862
- error: `Chain not found: ${chainId}`,
1863
- hint: 'Chain states expire after 1 hour. The chain may have completed or expired.'
1864
- }, null, 2)
1865
- }],
1866
- isError: true
1867
- };
1868
- }
1869
- return {
1870
- content: [{
1871
- type: 'text',
1872
- text: JSON.stringify({
1873
- status: 'success',
1874
- chain: {
1875
- chainId: chainStatus.chainId,
1876
- executionStatus: chainStatus.status,
1877
- ...(chainStatus.result ? {
1878
- result: {
1879
- success: chainStatus.result.success,
1880
- completedSteps: chainStatus.result.completedSteps,
1881
- totalLatencyMs: chainStatus.result.totalLatencyMs,
1882
- totalCost: chainStatus.result.totalCost,
1883
- finalResult: chainStatus.result.finalResult,
1884
- error: chainStatus.result.error,
1885
- canResume: chainStatus.result.canResume,
1886
- resumeToken: chainStatus.result.resumeToken,
1887
- }
1888
- } : {})
1889
- }
1890
- }, null, 2)
1891
- }]
1892
- };
1893
- }
1894
- case 'resume_chain': {
1895
- const resumeToken = args?.resume_token;
1896
- const overrides = args?.overrides;
1897
- const originalChain = args?.original_chain;
1898
- if (!resumeToken) {
1899
- return {
1900
- content: [{
1901
- type: 'text',
1902
- text: JSON.stringify({
1903
- status: 'error',
1904
- error: 'resume_token is required'
1905
- }, null, 2)
1906
- }],
1907
- isError: true
1908
- };
1909
- }
1910
- // Check workspace access
1911
- const access = checkWorkspaceAccess();
1912
- if (!access.allowed) {
1913
- return {
1914
- content: [{
1915
- type: 'text',
1916
- text: JSON.stringify({
1917
- status: 'error',
1918
- error: access.error,
1919
- hint: 'Use register_owner to authenticate your workspace.',
1920
- }, null, 2)
1921
- }],
1922
- isError: true
1923
- };
1924
- }
1925
- try {
1926
- // Note: The resume_chain function requires the original chain definition
1927
- // In practice, you'd store this or require the caller to provide it
1928
- if (!originalChain) {
1929
- return {
1930
- content: [{
1931
- type: 'text',
1932
- text: JSON.stringify({
1933
- status: 'error',
1934
- error: 'original_chain is required to resume. Please provide the original chain definition.',
1935
- hint: 'Pass original_chain: [...] with the same chain array used in the failed execution.'
1936
- }, null, 2)
1937
- }],
1938
- isError: true
1939
- };
1940
- }
1941
- const chainDefinition = {
1942
- steps: originalChain,
1943
- };
1944
- const chainCredentials = {
1945
- userId: DEFAULT_AGENT_ID,
1946
- customerKeys: {},
1947
- };
1948
- const customerKey = args?.customer_key;
1949
- if (customerKey) {
1950
- chainCredentials.customerKeys = { default: customerKey };
1951
- }
1952
- const result = await resumeChain(resumeToken, chainDefinition, chainCredentials, {}, // inputs
1953
- overrides, { verbose: false });
1954
- return {
1955
- content: [{
1956
- type: 'text',
1957
- text: JSON.stringify({
1958
- status: result.success ? 'success' : 'error',
1959
- mode: 'chain_resumed',
1960
- chainId: result.chainId,
1961
- steps: result.trace.map(t => ({
1962
- id: t.stepId,
1963
- status: t.success ? 'completed' : 'failed',
1964
- result: t.output,
1965
- error: t.error,
1966
- latencyMs: t.latencyMs,
1967
- })),
1968
- finalResult: result.finalResult,
1969
- totalLatencyMs: result.totalLatencyMs,
1970
- totalCost: result.totalCost,
1971
- ...(result.error ? {
1972
- error: result.error,
1973
- canResume: result.canResume,
1974
- resumeToken: result.resumeToken,
1975
- } : {}),
1976
- }, null, 2)
1977
- }],
1978
- isError: !result.success
1979
- };
1980
- }
1981
- catch (error) {
1982
- return {
1983
- content: [{
1984
- type: 'text',
1985
- text: JSON.stringify({
1986
- status: 'error',
1987
- error: error instanceof Error ? error.message : String(error),
1988
- }, null, 2)
1989
- }],
1990
- isError: true
1991
- };
1992
- }
1993
- }
1994
- default:
1995
- return {
1996
- content: [
1997
- {
1998
- type: 'text',
1999
- text: JSON.stringify({
2000
- status: 'error',
2001
- message: `Unknown tool: ${name}`
2002
- }, null, 2)
2003
- }
2004
- ],
2005
- isError: true
2006
- };
2007
- }
2008
- }
2009
- catch (error) {
2010
- return {
2011
- content: [
2012
- {
2013
- type: 'text',
2014
- text: JSON.stringify({
2015
- status: 'error',
2016
- message: error instanceof Error ? error.message : 'Unknown error'
2017
- }, null, 2)
2018
- }
2019
- ],
2020
- isError: true
2021
- };
2022
- }
2023
- });
2024
- // Start server
2025
- async function main() {
2026
- // Check for CLI mode
2027
- if (process.argv.includes('--cli') || process.argv.includes('-c')) {
2028
- const { startCLI } = await import('./cli.js');
2029
- await startCLI();
2030
- return;
2031
- }
2032
- const transport = new StdioServerTransport();
2033
- await server.connect(transport);
2034
- trackStartup();
2035
- // Validate session on startup
2036
- const hasValidSession = await validateSession();
2037
- // Welcome message with onboarding
2038
- console.error(`
2039
- 🦞 APIClaw v1.1.5 — The API Layer for AI Agents
2040
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2041
-
2042
- ✓ 19,000+ APIs indexed
2043
- ✓ 23 categories
2044
- ✓ 9 direct-call providers ready
2045
- ${hasValidSession ? `✓ Authenticated as ${workspaceContext?.email}` : '⚠ Not authenticated - use register_owner'}
2046
-
2047
- Quick Start:
2048
- ${!hasValidSession ? 'register_owner({ email: "you@example.com" }) # First, authenticate\n ' : ''}discover_apis("send SMS to Sweden")
2049
- discover_apis("search the web")
2050
- call_api({ provider: "brave_search", ... })
2051
-
2052
- Direct Call (no API key needed):
2053
- list_connected()
2054
-
2055
- Interactive CLI mode:
2056
- npx @nordsym/apiclaw --cli
2057
-
2058
- Docs: https://apiclaw.nordsym.com
2059
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2060
- `);
2061
- }
2062
- main().catch(console.error);