@hasna/connectors 1.3.20 → 1.3.22
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.
- package/bin/index.js +214 -8
- package/bin/mcp.js +223 -16
- package/bin/serve.js +1 -1
- package/dist/index.js +1 -1
- package/dist/server/auth.d.ts +4 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -1909,7 +1909,7 @@ var package_default;
|
|
|
1909
1909
|
var init_package = __esm(() => {
|
|
1910
1910
|
package_default = {
|
|
1911
1911
|
name: "@hasna/connectors",
|
|
1912
|
-
version: "1.3.
|
|
1912
|
+
version: "1.3.21",
|
|
1913
1913
|
description: "Open source connector library - Install API connectors with a single command",
|
|
1914
1914
|
type: "module",
|
|
1915
1915
|
bin: {
|
|
@@ -33029,8 +33029,29 @@ function redactSecrets(obj) {
|
|
|
33029
33029
|
}
|
|
33030
33030
|
return obj;
|
|
33031
33031
|
}
|
|
33032
|
+
function getOAuthTokenState(name) {
|
|
33033
|
+
const tokens = loadTokens(name);
|
|
33034
|
+
if (!tokens?.accessToken && !tokens?.refreshToken) {
|
|
33035
|
+
return { hasTokens: false, expired: false };
|
|
33036
|
+
}
|
|
33037
|
+
if (!tokens.expiresAt) {
|
|
33038
|
+
return { hasTokens: true, expired: false };
|
|
33039
|
+
}
|
|
33040
|
+
const now3 = Date.now();
|
|
33041
|
+
const isExpired = now3 >= tokens.expiresAt - 60000;
|
|
33042
|
+
const expiresIn = isExpired ? undefined : (() => {
|
|
33043
|
+
const ms = tokens.expiresAt - now3;
|
|
33044
|
+
const mins = Math.floor(ms / 60000);
|
|
33045
|
+
if (mins < 1)
|
|
33046
|
+
return "less than a minute";
|
|
33047
|
+
if (mins < 60)
|
|
33048
|
+
return `${mins}m`;
|
|
33049
|
+
return `${Math.floor(mins / 60)}h ${mins % 60}m`;
|
|
33050
|
+
})();
|
|
33051
|
+
return { hasTokens: true, expired: isExpired, expiresIn };
|
|
33052
|
+
}
|
|
33032
33053
|
function registerCommands4(program2) {
|
|
33033
|
-
program2.command("auth").argument("<connector>", "Connector name to configure auth for").option("-k, --key <value>", "API key or bearer token value (non-interactive)").option("-f, --field <field>", "Which field to set (for multi-field connectors)").option("--json", "Output as JSON", false).description("Configure authentication for a connector").action(async (connector, options) => {
|
|
33054
|
+
program2.command("auth").argument("<connector>", "Connector name to configure auth for").option("-k, --key <value>", "API key or bearer token value (non-interactive)").option("-f, --field <field>", "Which field to set (for multi-field connectors)").option("--json", "Output as JSON", false).option("--no-browser", "Print OAuth URL without opening a browser (agent-friendly)", false).option("--refresh", "Refresh expired OAuth tokens", false).option("--port <port>", "OAuth server port (default: 9876)", "9876").description("Configure authentication for a connector").action(async (connector, options) => {
|
|
33034
33055
|
const meta = getConnector(connector);
|
|
33035
33056
|
if (!meta) {
|
|
33036
33057
|
if (options.json) {
|
|
@@ -33044,6 +33065,39 @@ function registerCommands4(program2) {
|
|
|
33044
33065
|
}
|
|
33045
33066
|
const authType = getAuthType(connector);
|
|
33046
33067
|
const statusBefore = getAuthStatus(connector);
|
|
33068
|
+
if (options.refresh) {
|
|
33069
|
+
if (authType !== "oauth") {
|
|
33070
|
+
if (options.json) {
|
|
33071
|
+
console.log(JSON.stringify({ error: `${connector} does not use OAuth. Refresh is only available for OAuth connectors.` }));
|
|
33072
|
+
} else {
|
|
33073
|
+
console.log(chalk5.red(`${meta.displayName} does not use OAuth. Refresh is only available for OAuth connectors.`));
|
|
33074
|
+
}
|
|
33075
|
+
process.exit(1);
|
|
33076
|
+
return;
|
|
33077
|
+
}
|
|
33078
|
+
try {
|
|
33079
|
+
const tokens = await refreshOAuthToken(connector);
|
|
33080
|
+
if (options.json) {
|
|
33081
|
+
console.log(JSON.stringify({ success: true, connector, tokenType: tokens.tokenType, scope: tokens.scope, expiresAt: tokens.expiresAt }));
|
|
33082
|
+
} else {
|
|
33083
|
+
console.log(chalk5.green(`
|
|
33084
|
+
\u2713 Refreshed ${meta.displayName} tokens.`));
|
|
33085
|
+
console.log(chalk5.dim(` Expires: ${new Date(tokens.expiresAt).toLocaleString()}`));
|
|
33086
|
+
}
|
|
33087
|
+
process.exit(0);
|
|
33088
|
+
return;
|
|
33089
|
+
} catch (err) {
|
|
33090
|
+
if (options.json) {
|
|
33091
|
+
console.log(JSON.stringify({ error: `Refresh failed: ${err instanceof Error ? err.message : String(err)}` }));
|
|
33092
|
+
} else {
|
|
33093
|
+
console.log(chalk5.red(`
|
|
33094
|
+
\u2717 Refresh failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
33095
|
+
console.log(chalk5.dim("You may need to re-authenticate."));
|
|
33096
|
+
}
|
|
33097
|
+
process.exit(1);
|
|
33098
|
+
return;
|
|
33099
|
+
}
|
|
33100
|
+
}
|
|
33047
33101
|
if (!options.json) {
|
|
33048
33102
|
const statusLabel = statusBefore.configured ? chalk5.green("configured") : chalk5.red("not configured");
|
|
33049
33103
|
console.log(chalk5.bold(`
|
|
@@ -33051,6 +33105,14 @@ ${meta.displayName} \u2014 Auth Configuration
|
|
|
33051
33105
|
`));
|
|
33052
33106
|
console.log(` Auth type: ${authType === "oauth" ? "OAuth" : authType === "apikey" ? "API Key" : "Bearer Token"}`);
|
|
33053
33107
|
console.log(` Status: ${statusLabel}`);
|
|
33108
|
+
if (authType === "oauth" && statusBefore.configured) {
|
|
33109
|
+
const tokenState = getOAuthTokenState(connector);
|
|
33110
|
+
if (tokenState.hasTokens && !tokenState.expired) {
|
|
33111
|
+
console.log(` Token: ${chalk5.green("valid")} (expires in ${tokenState.expiresIn})`);
|
|
33112
|
+
} else if (tokenState.hasTokens && tokenState.expired) {
|
|
33113
|
+
console.log(` Token: ${chalk5.yellow("expired")} (run 'connectors auth ${connector} --refresh' or re-authenticate)`);
|
|
33114
|
+
}
|
|
33115
|
+
}
|
|
33054
33116
|
const envVars2 = getEnvVars(connector);
|
|
33055
33117
|
if (envVars2.length > 0) {
|
|
33056
33118
|
console.log(` Fields: ${envVars2.map((v) => v.variable).join(", ")}`);
|
|
@@ -33058,23 +33120,115 @@ ${meta.displayName} \u2014 Auth Configuration
|
|
|
33058
33120
|
console.log();
|
|
33059
33121
|
}
|
|
33060
33122
|
if (authType === "oauth") {
|
|
33123
|
+
const tokenState = getOAuthTokenState(connector);
|
|
33124
|
+
if (tokenState.hasTokens && !tokenState.expired) {
|
|
33125
|
+
if (options.json) {
|
|
33126
|
+
console.log(JSON.stringify({ connector, authType: "oauth", status: "authenticated", message: `Tokens are valid and not expired.` }));
|
|
33127
|
+
} else {
|
|
33128
|
+
console.log(chalk5.green(`
|
|
33129
|
+
\u2713 ${meta.displayName} is already authenticated (tokens valid, expires in ${tokenState.expiresIn}).`));
|
|
33130
|
+
console.log(chalk5.dim(`Use --refresh to renew tokens, or re-run auth to re-authenticate.`));
|
|
33131
|
+
}
|
|
33132
|
+
process.exit(0);
|
|
33133
|
+
return;
|
|
33134
|
+
}
|
|
33135
|
+
if (tokenState.hasTokens && tokenState.expired) {
|
|
33136
|
+
if (!options.json) {
|
|
33137
|
+
console.log(chalk5.dim("Tokens expired \u2014 attempting auto-refresh..."));
|
|
33138
|
+
}
|
|
33139
|
+
try {
|
|
33140
|
+
const tokens = await refreshOAuthToken(connector);
|
|
33141
|
+
if (options.json) {
|
|
33142
|
+
console.log(JSON.stringify({ connector, authType: "oauth", status: "refreshed", expiresAt: tokens.expiresAt }));
|
|
33143
|
+
} else {
|
|
33144
|
+
console.log(chalk5.green(`
|
|
33145
|
+
\u2713 Refreshed ${meta.displayName} tokens.`));
|
|
33146
|
+
console.log(chalk5.dim(` Expires: ${new Date(tokens.expiresAt).toLocaleString()}`));
|
|
33147
|
+
}
|
|
33148
|
+
process.exit(0);
|
|
33149
|
+
return;
|
|
33150
|
+
} catch {
|
|
33151
|
+
if (!options.json) {
|
|
33152
|
+
console.log(chalk5.yellow("Auto-refresh failed. Proceeding with OAuth flow..."));
|
|
33153
|
+
console.log();
|
|
33154
|
+
}
|
|
33155
|
+
}
|
|
33156
|
+
}
|
|
33061
33157
|
if (options.json) {
|
|
33158
|
+
const port2 = parseInt(options.port, 10) || 9876;
|
|
33159
|
+
const oauthUrl2 = `http://localhost:${port2}/oauth/${connector}/start`;
|
|
33062
33160
|
console.log(JSON.stringify({
|
|
33063
33161
|
connector,
|
|
33064
33162
|
authType: "oauth",
|
|
33065
|
-
message: "OAuth connectors require browser-based authentication. Use 'connectors serve' or pass --key to set tokens manually."
|
|
33163
|
+
message: "OAuth connectors require browser-based authentication. Use 'connectors serve' or pass --key to set tokens manually.",
|
|
33164
|
+
oauthUrl: oauthUrl2
|
|
33066
33165
|
}));
|
|
33067
33166
|
process.exit(0);
|
|
33068
33167
|
return;
|
|
33069
33168
|
}
|
|
33070
33169
|
console.log(chalk5.yellow("OAuth connectors require browser-based authentication."));
|
|
33071
33170
|
console.log();
|
|
33171
|
+
const port = parseInt(options.port, 10) || 9876;
|
|
33172
|
+
const oauthUrl = `http://localhost:${port}/oauth/${connector}/start`;
|
|
33072
33173
|
try {
|
|
33073
|
-
|
|
33174
|
+
if (!options.browser) {
|
|
33175
|
+
console.log(chalk5.dim(`Starting OAuth server on port ${port}...`));
|
|
33176
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
33177
|
+
const scriptPath = process.argv[1];
|
|
33178
|
+
const serverProc = spawn4("node", [scriptPath, "serve", "--port", String(port)], {
|
|
33179
|
+
detached: true,
|
|
33180
|
+
stdio: "ignore"
|
|
33181
|
+
});
|
|
33182
|
+
serverProc.unref();
|
|
33183
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
33184
|
+
try {
|
|
33185
|
+
await fetch(`http://localhost:${port}/api/connectors`);
|
|
33186
|
+
} catch {
|
|
33187
|
+
console.log(chalk5.red(`OAuth server failed to start on port ${port}. Is the port already in use?`));
|
|
33188
|
+
console.log(chalk5.dim("Free the port and try again, or use 'connectors serve' for the full dashboard."));
|
|
33189
|
+
process.exit(1);
|
|
33190
|
+
return;
|
|
33191
|
+
}
|
|
33192
|
+
console.log(chalk5.bold(`Open this URL to authenticate:
|
|
33193
|
+
`));
|
|
33194
|
+
console.log(` ${chalk5.cyan(oauthUrl)}
|
|
33195
|
+
`);
|
|
33196
|
+
console.log(chalk5.dim("Waiting for authentication to complete..."));
|
|
33197
|
+
const connectorsHome = getConnectorsHome();
|
|
33198
|
+
const connectorDirName = connector.startsWith("connect-") ? connector : `connect-${connector}`;
|
|
33199
|
+
const tokensPath = join20(connectorsHome, connectorDirName, "profiles", "default", "tokens.json");
|
|
33200
|
+
let attempts = 0;
|
|
33201
|
+
const maxAttempts = 360;
|
|
33202
|
+
while (attempts < maxAttempts) {
|
|
33203
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
33204
|
+
if (existsSync19(tokensPath)) {
|
|
33205
|
+
break;
|
|
33206
|
+
}
|
|
33207
|
+
attempts++;
|
|
33208
|
+
if (attempts % 6 === 0) {
|
|
33209
|
+
process.stdout.write(".");
|
|
33210
|
+
}
|
|
33211
|
+
}
|
|
33212
|
+
try {
|
|
33213
|
+
serverProc.kill("SIGTERM");
|
|
33214
|
+
} catch {}
|
|
33215
|
+
if (attempts >= maxAttempts) {
|
|
33216
|
+
console.log();
|
|
33217
|
+
console.log(chalk5.yellow("Timed out waiting for OAuth callback. The auth may still be in progress."));
|
|
33218
|
+
console.log(chalk5.dim(`Check ${tokensPath} for tokens.`));
|
|
33219
|
+
console.log(chalk5.dim("You can stop the server manually: lsof -ti :${port} | xargs kill"));
|
|
33220
|
+
process.exit(1);
|
|
33221
|
+
return;
|
|
33222
|
+
}
|
|
33223
|
+
console.log();
|
|
33224
|
+
console.log(chalk5.green(`
|
|
33225
|
+
\u2713 Connected! ${meta.displayName} is now authenticated.`));
|
|
33226
|
+
process.exit(0);
|
|
33227
|
+
return;
|
|
33228
|
+
}
|
|
33074
33229
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
|
|
33075
33230
|
console.log(chalk5.dim(`Starting temporary server on port ${port}...`));
|
|
33076
33231
|
await startServer2(port, { open: false, strict: true });
|
|
33077
|
-
const oauthUrl = `http://localhost:${port}/oauth/${connector}/start`;
|
|
33078
33232
|
console.log(chalk5.bold(`
|
|
33079
33233
|
Open this URL to authenticate:
|
|
33080
33234
|
`));
|
|
@@ -34271,7 +34425,7 @@ ${meta.displayName} operations:
|
|
|
34271
34425
|
}
|
|
34272
34426
|
process.exit(result.exitCode);
|
|
34273
34427
|
});
|
|
34274
|
-
program2.command("setup").argument("<name>", "Connector name to set up").option("-k, --key <value>", "API key or bearer token value").option("-f, --field <field>", "Which field to set (for multi-field connectors)").option("-o, --overwrite", "Overwrite existing installation", false).option("--json", "Output as JSON", false).description("Install, configure auth, and verify a connector in one step").action(async (name, options) => {
|
|
34428
|
+
program2.command("setup").argument("<name>", "Connector name to set up").option("-k, --key <value>", "API key or bearer token value").option("-f, --field <field>", "Which field to set (for multi-field connectors)").option("-o, --overwrite", "Overwrite existing installation", false).option("--json", "Output as JSON", false).option("--no-browser", "Print OAuth URL without opening a browser (agent-friendly)", false).description("Install, configure auth, and verify a connector in one step").action(async (name, options) => {
|
|
34275
34429
|
const meta = getConnector(name);
|
|
34276
34430
|
if (!meta) {
|
|
34277
34431
|
if (options.json) {
|
|
@@ -34346,11 +34500,63 @@ Setting up ${meta.displayName}...
|
|
|
34346
34500
|
return;
|
|
34347
34501
|
}
|
|
34348
34502
|
console.log(` ${chalk7.yellow("\u27F3")} OAuth authentication required \u2014 starting server...`);
|
|
34503
|
+
const port = 9876;
|
|
34504
|
+
const oauthUrl = `http://localhost:${port}/oauth/${name}/start`;
|
|
34505
|
+
if (!options.browser) {
|
|
34506
|
+
console.log(`
|
|
34507
|
+
${chalk7.bold("Open this URL to authenticate:")}`);
|
|
34508
|
+
console.log(` ${chalk7.cyan(oauthUrl)}
|
|
34509
|
+
`);
|
|
34510
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
34511
|
+
const { getConnectorsHome: getConnectorsHome2 } = await Promise.resolve().then(() => (init_database(), exports_database));
|
|
34512
|
+
const { existsSync: existsSync8 } = await import("fs");
|
|
34513
|
+
const { join: join8 } = await import("path");
|
|
34514
|
+
const scriptPath = process.argv[1];
|
|
34515
|
+
const serverProc = spawn4("node", [scriptPath, "serve", "--port", String(port)], {
|
|
34516
|
+
detached: true,
|
|
34517
|
+
stdio: "ignore"
|
|
34518
|
+
});
|
|
34519
|
+
serverProc.unref();
|
|
34520
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
34521
|
+
try {
|
|
34522
|
+
await fetch(`http://localhost:${port}/api/connectors`);
|
|
34523
|
+
} catch {
|
|
34524
|
+
console.log(` ${chalk7.red("\u2717")} OAuth server failed to start on port ${port}.`);
|
|
34525
|
+
process.exit(1);
|
|
34526
|
+
return;
|
|
34527
|
+
}
|
|
34528
|
+
console.log(chalk7.dim(" Waiting for authentication to complete..."));
|
|
34529
|
+
const connectorsHome = getConnectorsHome2();
|
|
34530
|
+
const connectorDirName = name.startsWith("connect-") ? name : `connect-${name}`;
|
|
34531
|
+
const tokensPath = join8(connectorsHome, connectorDirName, "profiles", "default", "tokens.json");
|
|
34532
|
+
let attempts = 0;
|
|
34533
|
+
const maxAttempts = 360;
|
|
34534
|
+
while (attempts < maxAttempts) {
|
|
34535
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
34536
|
+
if (existsSync8(tokensPath))
|
|
34537
|
+
break;
|
|
34538
|
+
attempts++;
|
|
34539
|
+
if (attempts % 6 === 0)
|
|
34540
|
+
process.stdout.write(".");
|
|
34541
|
+
}
|
|
34542
|
+
try {
|
|
34543
|
+
serverProc.kill("SIGTERM");
|
|
34544
|
+
} catch {}
|
|
34545
|
+
if (attempts >= maxAttempts) {
|
|
34546
|
+
console.log();
|
|
34547
|
+
console.log(chalk7.yellow(" Timed out waiting for OAuth callback."));
|
|
34548
|
+
process.exit(1);
|
|
34549
|
+
return;
|
|
34550
|
+
}
|
|
34551
|
+
console.log();
|
|
34552
|
+
console.log(` ${chalk7.green("\u2713")} ${meta.displayName} is now authenticated.`);
|
|
34553
|
+
authConfigured = true;
|
|
34554
|
+
process.exit(0);
|
|
34555
|
+
return;
|
|
34556
|
+
}
|
|
34349
34557
|
try {
|
|
34350
|
-
const port = 9876;
|
|
34351
34558
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
|
|
34352
34559
|
await startServer2(port, { open: false, strict: true });
|
|
34353
|
-
const oauthUrl = `http://localhost:${port}/oauth/${name}/start`;
|
|
34354
34560
|
console.log(`
|
|
34355
34561
|
${chalk7.bold("Open this URL to authenticate:")}`);
|
|
34356
34562
|
console.log(` ${chalk7.cyan(oauthUrl)}
|
package/bin/mcp.js
CHANGED
|
@@ -11443,7 +11443,7 @@ var package_default;
|
|
|
11443
11443
|
var init_package = __esm(() => {
|
|
11444
11444
|
package_default = {
|
|
11445
11445
|
name: "@hasna/connectors",
|
|
11446
|
-
version: "1.3.
|
|
11446
|
+
version: "1.3.21",
|
|
11447
11447
|
description: "Open source connector library - Install API connectors with a single command",
|
|
11448
11448
|
type: "module",
|
|
11449
11449
|
bin: {
|
|
@@ -24166,16 +24166,16 @@ __export(exports_runner, {
|
|
|
24166
24166
|
getConnectorCliPath: () => getConnectorCliPath,
|
|
24167
24167
|
buildEnvWithCredentials: () => buildEnvWithCredentials
|
|
24168
24168
|
});
|
|
24169
|
-
import { existsSync as
|
|
24170
|
-
import { join as
|
|
24169
|
+
import { existsSync as existsSync14, readdirSync as readdirSync8 } from "fs";
|
|
24170
|
+
import { join as join15, dirname as dirname7 } from "path";
|
|
24171
24171
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
24172
|
-
import { spawn } from "child_process";
|
|
24172
|
+
import { spawn as spawn2 } from "child_process";
|
|
24173
24173
|
function resolveConnectorsDir2() {
|
|
24174
|
-
const fromBin =
|
|
24175
|
-
if (
|
|
24174
|
+
const fromBin = join15(__dirname5, "..", "connectors");
|
|
24175
|
+
if (existsSync14(fromBin))
|
|
24176
24176
|
return fromBin;
|
|
24177
|
-
const fromSrc =
|
|
24178
|
-
if (
|
|
24177
|
+
const fromSrc = join15(__dirname5, "..", "..", "connectors");
|
|
24178
|
+
if (existsSync14(fromSrc))
|
|
24179
24179
|
return fromSrc;
|
|
24180
24180
|
return fromBin;
|
|
24181
24181
|
}
|
|
@@ -24222,9 +24222,9 @@ function buildEnvWithCredentials(connectorName, baseEnv) {
|
|
|
24222
24222
|
}
|
|
24223
24223
|
function getConnectorCliPath(name) {
|
|
24224
24224
|
const safeName = name.replace(/[^a-z0-9-]/g, "");
|
|
24225
|
-
const connectorDir =
|
|
24226
|
-
const cliPath =
|
|
24227
|
-
if (
|
|
24225
|
+
const connectorDir = join15(CONNECTORS_DIR2, `connect-${safeName}`);
|
|
24226
|
+
const cliPath = join15(connectorDir, "src", "cli", "index.ts");
|
|
24227
|
+
if (existsSync14(cliPath))
|
|
24228
24228
|
return cliPath;
|
|
24229
24229
|
return null;
|
|
24230
24230
|
}
|
|
@@ -24263,7 +24263,7 @@ function runLegacyConnectorCommand(name, args, timeoutMs = 30000) {
|
|
|
24263
24263
|
});
|
|
24264
24264
|
}
|
|
24265
24265
|
return new Promise((resolve) => {
|
|
24266
|
-
const proc =
|
|
24266
|
+
const proc = spawn2("bun", ["run", cliPath, ...args], {
|
|
24267
24267
|
timeout: timeoutMs,
|
|
24268
24268
|
env: buildEnvWithCredentials(name, process.env),
|
|
24269
24269
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -39916,7 +39916,9 @@ async function withWriteLock(connector, fn) {
|
|
|
39916
39916
|
|
|
39917
39917
|
// src/server/auth.ts
|
|
39918
39918
|
init_database();
|
|
39919
|
+
var FETCH_TIMEOUT = 1e4;
|
|
39919
39920
|
var oauthStateStore = new Map;
|
|
39921
|
+
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
39920
39922
|
var GOOGLE_SCOPES = {
|
|
39921
39923
|
gmail: [
|
|
39922
39924
|
"https://www.googleapis.com/auth/gmail.readonly",
|
|
@@ -40125,6 +40127,67 @@ function guessKeyField(name) {
|
|
|
40125
40127
|
}
|
|
40126
40128
|
return "apiKey";
|
|
40127
40129
|
}
|
|
40130
|
+
function getOAuthConfig(name) {
|
|
40131
|
+
const configDir = getConnectorConfigDir(name);
|
|
40132
|
+
const credentialsFile = join13(configDir, "credentials.json");
|
|
40133
|
+
if (existsSync12(credentialsFile)) {
|
|
40134
|
+
try {
|
|
40135
|
+
const creds = JSON.parse(readFileSync6(credentialsFile, "utf-8"));
|
|
40136
|
+
return { clientId: creds.clientId, clientSecret: creds.clientSecret };
|
|
40137
|
+
} catch {}
|
|
40138
|
+
}
|
|
40139
|
+
const config2 = loadProfileConfig(name);
|
|
40140
|
+
return {
|
|
40141
|
+
clientId: config2.clientId,
|
|
40142
|
+
clientSecret: config2.clientSecret
|
|
40143
|
+
};
|
|
40144
|
+
}
|
|
40145
|
+
function saveOAuthTokens(name, tokens) {
|
|
40146
|
+
const configDir = getConnectorConfigDir(name);
|
|
40147
|
+
const profile = getCurrentProfile2(name);
|
|
40148
|
+
const profileDir = join13(configDir, "profiles", profile);
|
|
40149
|
+
mkdirSync9(profileDir, { recursive: true });
|
|
40150
|
+
const tokensFile = join13(profileDir, "tokens.json");
|
|
40151
|
+
writeFileSync5(tokensFile, JSON.stringify(tokens, null, 2), { mode: 384 });
|
|
40152
|
+
}
|
|
40153
|
+
async function refreshOAuthToken(name) {
|
|
40154
|
+
return withWriteLock(name, () => _refreshOAuthToken(name));
|
|
40155
|
+
}
|
|
40156
|
+
async function _refreshOAuthToken(name) {
|
|
40157
|
+
const oauthConfig = getOAuthConfig(name);
|
|
40158
|
+
const currentTokens = loadTokens(name);
|
|
40159
|
+
if (!oauthConfig.clientId || !oauthConfig.clientSecret) {
|
|
40160
|
+
throw new Error("OAuth credentials not configured for " + name);
|
|
40161
|
+
}
|
|
40162
|
+
if (!currentTokens?.refreshToken) {
|
|
40163
|
+
throw new Error("No refresh token available. Please re-authenticate.");
|
|
40164
|
+
}
|
|
40165
|
+
const response = await fetch(GOOGLE_TOKEN_URL, {
|
|
40166
|
+
method: "POST",
|
|
40167
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
40168
|
+
body: new URLSearchParams({
|
|
40169
|
+
client_id: oauthConfig.clientId,
|
|
40170
|
+
client_secret: oauthConfig.clientSecret,
|
|
40171
|
+
refresh_token: currentTokens.refreshToken,
|
|
40172
|
+
grant_type: "refresh_token"
|
|
40173
|
+
}),
|
|
40174
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT)
|
|
40175
|
+
});
|
|
40176
|
+
if (!response.ok) {
|
|
40177
|
+
const error2 = await response.json().catch(() => ({ error: response.statusText }));
|
|
40178
|
+
throw new Error(`Token refresh failed: ${error2.error_description || error2.error}`);
|
|
40179
|
+
}
|
|
40180
|
+
const data = await response.json();
|
|
40181
|
+
const tokens = {
|
|
40182
|
+
accessToken: data.access_token,
|
|
40183
|
+
refreshToken: currentTokens.refreshToken,
|
|
40184
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
40185
|
+
tokenType: data.token_type,
|
|
40186
|
+
scope: data.scope || currentTokens.scope
|
|
40187
|
+
};
|
|
40188
|
+
saveOAuthTokens(name, tokens);
|
|
40189
|
+
return tokens;
|
|
40190
|
+
}
|
|
40128
40191
|
|
|
40129
40192
|
// src/mcp/tools/management.ts
|
|
40130
40193
|
function registerManagementTools(server, stripped) {
|
|
@@ -40269,6 +40332,10 @@ function registerManagementTools(server, stripped) {
|
|
|
40269
40332
|
}
|
|
40270
40333
|
// src/mcp/tools/auth.ts
|
|
40271
40334
|
init_zod();
|
|
40335
|
+
init_database();
|
|
40336
|
+
import { existsSync as existsSync13, readFileSync as readFileSync7 } from "fs";
|
|
40337
|
+
import { join as join14 } from "path";
|
|
40338
|
+
import { spawn } from "child_process";
|
|
40272
40339
|
function registerAuthTools(server, stripped) {
|
|
40273
40340
|
server.registerTool("connector_auth_status", {
|
|
40274
40341
|
title: "Connector Auth Status",
|
|
@@ -40343,6 +40410,146 @@ function registerAuthTools(server, stripped) {
|
|
|
40343
40410
|
};
|
|
40344
40411
|
}
|
|
40345
40412
|
});
|
|
40413
|
+
server.registerTool("connector_oauth", {
|
|
40414
|
+
title: "Connector OAuth Flow",
|
|
40415
|
+
description: "Start an OAuth authentication flow for a connector. Returns an authorization URL to open in a browser. For non-interactive/agent use, pass noBrowser: true to start a temporary server and wait for tokens automatically.",
|
|
40416
|
+
inputSchema: {
|
|
40417
|
+
name: exports_external.string(),
|
|
40418
|
+
noBrowser: exports_external.boolean().optional().describe("If true, starts a temporary OAuth server and waits for tokens. If false/omitted, returns the URL for browser-based auth."),
|
|
40419
|
+
port: exports_external.number().optional().describe("OAuth server port (default: 9876)"),
|
|
40420
|
+
refresh: exports_external.boolean().optional().describe("If true, attempt to refresh existing OAuth tokens instead of starting a new flow.")
|
|
40421
|
+
}
|
|
40422
|
+
}, async ({ name, noBrowser, port, refresh }) => {
|
|
40423
|
+
const meta = getConnector(name);
|
|
40424
|
+
if (!meta) {
|
|
40425
|
+
return {
|
|
40426
|
+
content: [{ type: "text", text: `Connector '${name}' not found. Use search_connectors or list_connectors to find available connectors.` }],
|
|
40427
|
+
isError: true
|
|
40428
|
+
};
|
|
40429
|
+
}
|
|
40430
|
+
const authType = getAuthType(name);
|
|
40431
|
+
if (authType !== "oauth") {
|
|
40432
|
+
return {
|
|
40433
|
+
content: [{ type: "text", text: `${meta.displayName} does not use OAuth. Use configure_auth instead to set an API key.` }],
|
|
40434
|
+
isError: true
|
|
40435
|
+
};
|
|
40436
|
+
}
|
|
40437
|
+
if (refresh) {
|
|
40438
|
+
try {
|
|
40439
|
+
const tokens2 = await refreshOAuthToken(name);
|
|
40440
|
+
return {
|
|
40441
|
+
content: [{
|
|
40442
|
+
type: "text",
|
|
40443
|
+
text: JSON.stringify({ success: true, connector: name, action: "refreshed", expiresAt: tokens2.expiresAt, scope: tokens2.scope }, null, 2)
|
|
40444
|
+
}]
|
|
40445
|
+
};
|
|
40446
|
+
} catch (err) {
|
|
40447
|
+
return {
|
|
40448
|
+
content: [{
|
|
40449
|
+
type: "text",
|
|
40450
|
+
text: `Refresh failed: ${err instanceof Error ? err.message : String(err)}. You may need to re-authenticate with a new OAuth flow.`
|
|
40451
|
+
}],
|
|
40452
|
+
isError: true
|
|
40453
|
+
};
|
|
40454
|
+
}
|
|
40455
|
+
}
|
|
40456
|
+
const tokens = loadTokens(name);
|
|
40457
|
+
if (tokens?.accessToken && tokens?.refreshToken) {
|
|
40458
|
+
if (tokens.expiresAt && Date.now() < tokens.expiresAt - 60000) {
|
|
40459
|
+
const mins = Math.floor((tokens.expiresAt - Date.now()) / 60000);
|
|
40460
|
+
const expiresIn = mins < 60 ? `${mins}m` : `${Math.floor(mins / 60)}h ${mins % 60}m`;
|
|
40461
|
+
return {
|
|
40462
|
+
content: [{
|
|
40463
|
+
type: "text",
|
|
40464
|
+
text: JSON.stringify({ connector: name, status: "already_authenticated", expiresIn, message: "Tokens are valid. Use refresh: true to renew or re-authenticate to get new tokens." }, null, 2)
|
|
40465
|
+
}]
|
|
40466
|
+
};
|
|
40467
|
+
}
|
|
40468
|
+
}
|
|
40469
|
+
const serverPort = port || 9876;
|
|
40470
|
+
const oauthUrl = `http://localhost:${serverPort}/oauth/${name}/start`;
|
|
40471
|
+
if (noBrowser) {
|
|
40472
|
+
const connectorsHome = getConnectorsHome();
|
|
40473
|
+
const connectorDirName = name.startsWith("connect-") ? name : `connect-${name}`;
|
|
40474
|
+
const tokensPath = join14(connectorsHome, connectorDirName, "profiles", "default", "tokens.json");
|
|
40475
|
+
let serverRunning = false;
|
|
40476
|
+
try {
|
|
40477
|
+
await fetch(`http://localhost:${serverPort}/api/connectors`);
|
|
40478
|
+
serverRunning = true;
|
|
40479
|
+
} catch {}
|
|
40480
|
+
if (!serverRunning) {
|
|
40481
|
+
const scriptPath = process.argv[1];
|
|
40482
|
+
const serverProc = spawn("node", [scriptPath, "serve", "--port", String(serverPort)], {
|
|
40483
|
+
detached: true,
|
|
40484
|
+
stdio: "ignore"
|
|
40485
|
+
});
|
|
40486
|
+
serverProc.unref();
|
|
40487
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
40488
|
+
}
|
|
40489
|
+
let attempts = 0;
|
|
40490
|
+
const maxAttempts = 120;
|
|
40491
|
+
while (attempts < maxAttempts) {
|
|
40492
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
40493
|
+
if (existsSync13(tokensPath)) {
|
|
40494
|
+
break;
|
|
40495
|
+
}
|
|
40496
|
+
attempts++;
|
|
40497
|
+
}
|
|
40498
|
+
if (attempts >= maxAttempts) {
|
|
40499
|
+
return {
|
|
40500
|
+
content: [{
|
|
40501
|
+
type: "text",
|
|
40502
|
+
text: JSON.stringify({
|
|
40503
|
+
connector: name,
|
|
40504
|
+
status: "waiting",
|
|
40505
|
+
oauthUrl,
|
|
40506
|
+
message: `OAuth server is running on port ${serverPort}. Open the URL to authenticate. Tokens will be saved to ${tokensPath}.`
|
|
40507
|
+
}, null, 2)
|
|
40508
|
+
}]
|
|
40509
|
+
};
|
|
40510
|
+
}
|
|
40511
|
+
try {
|
|
40512
|
+
const tokenData = JSON.parse(readFileSync7(tokensPath, "utf-8"));
|
|
40513
|
+
return {
|
|
40514
|
+
content: [{
|
|
40515
|
+
type: "text",
|
|
40516
|
+
text: JSON.stringify({
|
|
40517
|
+
success: true,
|
|
40518
|
+
connector: name,
|
|
40519
|
+
status: "authenticated",
|
|
40520
|
+
tokenType: tokenData.tokenType || "Bearer",
|
|
40521
|
+
scope: tokenData.scope
|
|
40522
|
+
}, null, 2)
|
|
40523
|
+
}]
|
|
40524
|
+
};
|
|
40525
|
+
} catch {
|
|
40526
|
+
return {
|
|
40527
|
+
content: [{
|
|
40528
|
+
type: "text",
|
|
40529
|
+
text: JSON.stringify({
|
|
40530
|
+
connector: name,
|
|
40531
|
+
status: "tokens_file_found",
|
|
40532
|
+
message: "Token file was created but could not be parsed."
|
|
40533
|
+
}, null, 2)
|
|
40534
|
+
}],
|
|
40535
|
+
isError: true
|
|
40536
|
+
};
|
|
40537
|
+
}
|
|
40538
|
+
} else {
|
|
40539
|
+
return {
|
|
40540
|
+
content: [{
|
|
40541
|
+
type: "text",
|
|
40542
|
+
text: JSON.stringify({
|
|
40543
|
+
connector: name,
|
|
40544
|
+
status: "auth_required",
|
|
40545
|
+
authType: "oauth",
|
|
40546
|
+
oauthUrl,
|
|
40547
|
+
message: `Open this URL in a browser to authenticate. After completing the OAuth flow, tokens will be saved automatically.`
|
|
40548
|
+
}, null, 2)
|
|
40549
|
+
}]
|
|
40550
|
+
};
|
|
40551
|
+
}
|
|
40552
|
+
});
|
|
40346
40553
|
}
|
|
40347
40554
|
// src/mcp/tools/operations.ts
|
|
40348
40555
|
init_zod();
|
|
@@ -40621,7 +40828,7 @@ function listWorkflows(db) {
|
|
|
40621
40828
|
}
|
|
40622
40829
|
|
|
40623
40830
|
// src/lib/workflow-runner.ts
|
|
40624
|
-
import { spawn as
|
|
40831
|
+
import { spawn as spawn3 } from "child_process";
|
|
40625
40832
|
async function runStep(step, previousOutput) {
|
|
40626
40833
|
return new Promise((resolve) => {
|
|
40627
40834
|
const args = [...step.args ?? []];
|
|
@@ -40629,7 +40836,7 @@ async function runStep(step, previousOutput) {
|
|
|
40629
40836
|
args.push("--input", previousOutput.trim().slice(0, 4096));
|
|
40630
40837
|
}
|
|
40631
40838
|
const cmdArgs = ["run", step.connector, step.command, ...args, "--format", "json"];
|
|
40632
|
-
const proc =
|
|
40839
|
+
const proc = spawn3("connectors", cmdArgs, { shell: false });
|
|
40633
40840
|
let output = "";
|
|
40634
40841
|
proc.stdout.on("data", (d) => {
|
|
40635
40842
|
output += d.toString();
|
|
@@ -40671,11 +40878,11 @@ async function runWorkflow(workflow) {
|
|
|
40671
40878
|
}
|
|
40672
40879
|
|
|
40673
40880
|
// src/lib/scheduler.ts
|
|
40674
|
-
import { spawn as
|
|
40881
|
+
import { spawn as spawn4 } from "child_process";
|
|
40675
40882
|
async function runConnectorCommand2(connector, command, args) {
|
|
40676
40883
|
return new Promise((resolve) => {
|
|
40677
40884
|
const cmdArgs = [connector, command, ...args, "--format", "json"];
|
|
40678
|
-
const proc =
|
|
40885
|
+
const proc = spawn4("connectors", ["run", ...cmdArgs], { shell: false });
|
|
40679
40886
|
let output = "";
|
|
40680
40887
|
proc.stdout.on("data", (d) => {
|
|
40681
40888
|
output += d.toString();
|
package/bin/serve.js
CHANGED
|
@@ -15160,7 +15160,7 @@ var githubConnector = defineConnector({
|
|
|
15160
15160
|
// package.json
|
|
15161
15161
|
var package_default = {
|
|
15162
15162
|
name: "@hasna/connectors",
|
|
15163
|
-
version: "1.3.
|
|
15163
|
+
version: "1.3.21",
|
|
15164
15164
|
description: "Open source connector library - Install API connectors with a single command",
|
|
15165
15165
|
type: "module",
|
|
15166
15166
|
bin: {
|
package/dist/index.js
CHANGED
|
@@ -4969,7 +4969,7 @@ var githubConnector = defineConnector({
|
|
|
4969
4969
|
// package.json
|
|
4970
4970
|
var package_default = {
|
|
4971
4971
|
name: "@hasna/connectors",
|
|
4972
|
-
version: "1.3.
|
|
4972
|
+
version: "1.3.21",
|
|
4973
4973
|
description: "Open source connector library - Install API connectors with a single command",
|
|
4974
4974
|
type: "module",
|
|
4975
4975
|
bin: {
|
package/dist/server/auth.d.ts
CHANGED
|
@@ -28,6 +28,10 @@ export interface OAuthTokens {
|
|
|
28
28
|
* Get the auth type for a connector by parsing its CLAUDE.md
|
|
29
29
|
*/
|
|
30
30
|
export declare function getAuthType(name: string): AuthType;
|
|
31
|
+
/**
|
|
32
|
+
* Load OAuth tokens for a connector
|
|
33
|
+
*/
|
|
34
|
+
export declare function loadTokens(name: string): OAuthTokens | null;
|
|
31
35
|
/**
|
|
32
36
|
* Get the full auth status for a connector
|
|
33
37
|
*/
|