@apiosk/mcp 1.3.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,256 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
4
+
5
+ const DEFAULT_CONNECT_HEADER_NAME = "X-Apiosk-Connect-Token";
6
+ const DEFAULT_GATEWAY_URL = "https://gateway.apiosk.com";
7
+ const DEFAULT_DASHBOARD_URL = "https://apiosk.com";
8
+ const DEFAULT_CONTROL_PLANE_URL = "https://mcp.apiosk.com";
9
+ const DEFAULT_CHAIN_ID = 8453;
10
+ const DEFAULT_RPC_URL = "https://mainnet.base.org";
11
+ const DEFAULT_USDC_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
12
+
13
+ function trimString(value) {
14
+ return String(value || "").trim();
15
+ }
16
+
17
+ function defaultApioskDir(env = process.env) {
18
+ const configured = trimString(env.APIOSK_HOME);
19
+ if (configured) return configured;
20
+
21
+ const home = trimString(env.HOME) || os.homedir();
22
+ return path.join(home, ".apiosk");
23
+ }
24
+
25
+ function normalizeWalletAddress(value, { optional = false } = {}) {
26
+ const normalized = trimString(value).toLowerCase();
27
+ if (!normalized && optional) return "";
28
+ if (!/^0x[a-f0-9]{40}$/.test(normalized)) {
29
+ throw new Error("Wallet address must use a 0x-prefixed 40-byte hex address.");
30
+ }
31
+ return normalized;
32
+ }
33
+
34
+ function normalizeConnectToken(value, { optional = false } = {}) {
35
+ const normalized = trimString(value);
36
+ if (!normalized && optional) return "";
37
+ if (!/^aw_[A-Za-z0-9_:-]+$/.test(normalized)) {
38
+ throw new Error("Connect token must match the Apiosk managed-token format.");
39
+ }
40
+ return normalized;
41
+ }
42
+
43
+ async function safeChmod(filePath, mode) {
44
+ try {
45
+ await chmod(filePath, mode);
46
+ } catch {
47
+ // Best effort only; not all environments support chmod.
48
+ }
49
+ }
50
+
51
+ async function writeAtomic(filePath, value) {
52
+ const dir = path.dirname(filePath);
53
+ const tempFile = `${filePath}.${process.pid}.${Date.now()}.tmp`;
54
+
55
+ await mkdir(dir, { recursive: true });
56
+ await writeFile(tempFile, value, "utf8");
57
+ await safeChmod(tempFile, 0o600);
58
+ await rename(tempFile, filePath);
59
+ await safeChmod(filePath, 0o600);
60
+ }
61
+
62
+ export function createApioskLocalConfigPaths(env = process.env) {
63
+ const apioskDir = defaultApioskDir(env);
64
+ return {
65
+ apioskDir,
66
+ configFile: path.join(apioskDir, "config.json"),
67
+ connectEnvFile: path.join(apioskDir, "connect.env"),
68
+ };
69
+ }
70
+
71
+ export async function readLocalApioskConfig(env = process.env) {
72
+ const { configFile } = createApioskLocalConfigPaths(env);
73
+
74
+ try {
75
+ const raw = await readFile(configFile, "utf8");
76
+ const parsed = JSON.parse(raw);
77
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
78
+ return null;
79
+ }
80
+ return parsed;
81
+ } catch (error) {
82
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
83
+ return null;
84
+ }
85
+ throw new Error(
86
+ `Could not read the local Apiosk config: ${
87
+ error instanceof Error ? error.message : String(error)
88
+ }`
89
+ );
90
+ }
91
+ }
92
+
93
+ export async function saveLocalApioskConfig(input = {}, env = process.env) {
94
+ const paths = createApioskLocalConfigPaths(env);
95
+ const existing = (await readLocalApioskConfig(env)) || {};
96
+ const gatewayUrl = trimString(input.gateway_url || DEFAULT_GATEWAY_URL).replace(/\/+$/, "");
97
+ const chainId = Number.parseInt(String(input.chain_id || DEFAULT_CHAIN_ID), 10) || DEFAULT_CHAIN_ID;
98
+ const walletAddress = normalizeWalletAddress(input.agent_wallet_address || input.wallet_address, {
99
+ optional: true,
100
+ });
101
+ const connectToken = normalizeConnectToken(input.connect_token, { optional: true });
102
+ const connectAuthorization = trimString(
103
+ input.connect_authorization || (connectToken ? `Bearer ${connectToken}` : "")
104
+ );
105
+ const connectHeaderName = trimString(input.connect_header_name || DEFAULT_CONNECT_HEADER_NAME);
106
+ const dailyLimitUsdc = Number.isFinite(Number(input.daily_limit_usdc))
107
+ ? Number(input.daily_limit_usdc)
108
+ : 100;
109
+ const perRequestLimitUsdc = Number.isFinite(Number(input.per_request_limit_usdc))
110
+ ? Number(input.per_request_limit_usdc)
111
+ : 1;
112
+
113
+ if (!connectToken && !connectAuthorization) {
114
+ throw new Error("A connect token or connect authorization value is required.");
115
+ }
116
+
117
+ const config = {
118
+ rpc_url: DEFAULT_RPC_URL,
119
+ chain_id: chainId,
120
+ usdc_contract: DEFAULT_USDC_CONTRACT,
121
+ gateway_url: gatewayUrl,
122
+ agent_wallet_address: walletAddress || null,
123
+ daily_limit_usdc: dailyLimitUsdc,
124
+ per_request_limit_usdc: perRequestLimitUsdc,
125
+ connect_token: connectToken || null,
126
+ connect_authorization: connectAuthorization || null,
127
+ connect_header_name: connectHeaderName || DEFAULT_CONNECT_HEADER_NAME,
128
+ control_plane_url:
129
+ trimString(existing.control_plane_url || env.APIOSK_CONTROL_PLANE_URL || DEFAULT_CONTROL_PLANE_URL).replace(
130
+ /\/+$/,
131
+ ""
132
+ ) || null,
133
+ dashboard_url:
134
+ trimString(existing.dashboard_url || env.APIOSK_DASHBOARD_URL || DEFAULT_DASHBOARD_URL).replace(/\/+$/, "") ||
135
+ null,
136
+ dashboard_session_token: trimString(existing.dashboard_session_token) || null,
137
+ dashboard_session_email: trimString(existing.dashboard_session_email) || null,
138
+ dashboard_session_expires_at: Number.isFinite(Number(existing.dashboard_session_expires_at))
139
+ ? Number(existing.dashboard_session_expires_at)
140
+ : null,
141
+ };
142
+
143
+ const connectEnv = [
144
+ `APIO_GATEWAY_URL=${gatewayUrl}`,
145
+ `APIO_CHAIN_ID=${chainId}`,
146
+ `APIO_AGENT_WALLET_ADDRESS=${walletAddress}`,
147
+ `APIO_CONNECT_TOKEN=${connectToken}`,
148
+ `APIO_CONNECT_AUTHORIZATION=${connectAuthorization}`,
149
+ `APIO_CONNECT_HEADER_NAME=${connectHeaderName || DEFAULT_CONNECT_HEADER_NAME}`,
150
+ `APIO_WALLET_DAILY_LIMIT_USDC=${dailyLimitUsdc}`,
151
+ `APIO_WALLET_PER_TX_LIMIT_USDC=${perRequestLimitUsdc}`,
152
+ ].join("\n");
153
+
154
+ await writeAtomic(paths.configFile, `${JSON.stringify(config, null, 2)}\n`);
155
+ await writeAtomic(paths.connectEnvFile, `${connectEnv}\n`);
156
+
157
+ return {
158
+ paths,
159
+ config,
160
+ };
161
+ }
162
+
163
+ export async function saveLocalApioskDashboardSession(input = {}, env = process.env) {
164
+ const paths = createApioskLocalConfigPaths(env);
165
+ const existing = (await readLocalApioskConfig(env)) || {};
166
+ const sessionToken = trimString(input.dashboard_session_token || input.session_token);
167
+
168
+ if (!sessionToken) {
169
+ throw new Error("A dashboard session token is required.");
170
+ }
171
+
172
+ const expiresAtRaw = input.dashboard_session_expires_at ?? input.expires_at;
173
+ const expiresAt = Number(expiresAtRaw);
174
+ const config = {
175
+ ...existing,
176
+ control_plane_url:
177
+ trimString(
178
+ input.control_plane_url || existing.control_plane_url || env.APIOSK_CONTROL_PLANE_URL || DEFAULT_CONTROL_PLANE_URL
179
+ ).replace(/\/+$/, "") || null,
180
+ dashboard_url:
181
+ trimString(input.dashboard_url || existing.dashboard_url || env.APIOSK_DASHBOARD_URL || DEFAULT_DASHBOARD_URL).replace(/\/+$/, "") ||
182
+ null,
183
+ dashboard_session_token: sessionToken,
184
+ dashboard_session_email:
185
+ trimString(input.dashboard_session_email || input.email || existing.dashboard_session_email) || null,
186
+ dashboard_session_expires_at: Number.isFinite(expiresAt)
187
+ ? expiresAt
188
+ : Number.isFinite(Number(existing.dashboard_session_expires_at))
189
+ ? Number(existing.dashboard_session_expires_at)
190
+ : null,
191
+ };
192
+
193
+ await writeAtomic(paths.configFile, `${JSON.stringify(config, null, 2)}\n`);
194
+
195
+ return {
196
+ paths,
197
+ config,
198
+ };
199
+ }
200
+
201
+ export function parseConnectString(raw) {
202
+ const lines = String(raw || "").split(/\r?\n/);
203
+ const parsed = {
204
+ gateway_url: "",
205
+ chain_id: DEFAULT_CHAIN_ID,
206
+ wallet_address: "",
207
+ connect_token: "",
208
+ connect_authorization: "",
209
+ connect_header_name: DEFAULT_CONNECT_HEADER_NAME,
210
+ daily_limit_usdc: 100,
211
+ per_request_limit_usdc: 1,
212
+ };
213
+
214
+ for (const line of lines) {
215
+ const trimmed = line.trim();
216
+ if (!trimmed || trimmed.startsWith("#")) continue;
217
+
218
+ const normalizedLine = trimmed.startsWith("export ") ? trimmed.slice(7) : trimmed;
219
+ const separatorIndex = normalizedLine.indexOf("=");
220
+ if (separatorIndex === -1) continue;
221
+
222
+ const key = normalizedLine.slice(0, separatorIndex).trim();
223
+ let value = normalizedLine.slice(separatorIndex + 1).trim();
224
+ if (
225
+ (value.startsWith('"') && value.endsWith('"')) ||
226
+ (value.startsWith("'") && value.endsWith("'"))
227
+ ) {
228
+ value = value.slice(1, -1);
229
+ }
230
+
231
+ if (key === "APIO_GATEWAY_URL") parsed.gateway_url = value;
232
+ if (key === "APIO_CHAIN_ID") parsed.chain_id = Number.parseInt(value, 10) || DEFAULT_CHAIN_ID;
233
+ if (key === "APIO_AGENT_WALLET_ADDRESS") parsed.wallet_address = value;
234
+ if (key === "APIO_CONNECT_TOKEN") parsed.connect_token = value;
235
+ if (key === "APIO_CONNECT_AUTHORIZATION") parsed.connect_authorization = value;
236
+ if (key === "APIO_CONNECT_HEADER_NAME") parsed.connect_header_name = value || DEFAULT_CONNECT_HEADER_NAME;
237
+ if (key === "APIO_WALLET_DAILY_LIMIT_USDC") parsed.daily_limit_usdc = Number(value) || 100;
238
+ if (key === "APIO_WALLET_PER_TX_LIMIT_USDC") parsed.per_request_limit_usdc = Number(value) || 1;
239
+ }
240
+
241
+ if (!parsed.gateway_url || !parsed.wallet_address || !parsed.connect_token) {
242
+ throw new Error("Connect string is missing one of: APIO_GATEWAY_URL, APIO_AGENT_WALLET_ADDRESS, APIO_CONNECT_TOKEN.");
243
+ }
244
+
245
+ return {
246
+ gateway_url: trimString(parsed.gateway_url).replace(/\/+$/, ""),
247
+ chain_id: parsed.chain_id || DEFAULT_CHAIN_ID,
248
+ wallet_address: normalizeWalletAddress(parsed.wallet_address),
249
+ connect_token: normalizeConnectToken(parsed.connect_token),
250
+ connect_authorization:
251
+ trimString(parsed.connect_authorization) || `Bearer ${normalizeConnectToken(parsed.connect_token)}`,
252
+ connect_header_name: trimString(parsed.connect_header_name) || DEFAULT_CONNECT_HEADER_NAME,
253
+ daily_limit_usdc: parsed.daily_limit_usdc,
254
+ per_request_limit_usdc: parsed.per_request_limit_usdc,
255
+ };
256
+ }