@ontos-ai/knowhere-mcp 0.2.0 → 0.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.
- package/README.md +6 -4
- package/dist/stdio.js +47 -47
- package/dist/stdio.mjs +47 -47
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,10 +41,12 @@ npx -y @ontos-ai/knowhere-mcp logout
|
|
|
41
41
|
|
|
42
42
|
`knowhere-mcp status` shows the stored Permission for the current login.
|
|
43
43
|
|
|
44
|
-
Set `
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
Set `KNOWHERE_BASE_URL` (or `--base-url`) to the Knowhere site base URL when
|
|
45
|
+
using a non-default environment, e.g. `https://staging.knowhereto.ai`; it
|
|
46
|
+
defaults to `https://knowhereto.ai`. The API and login routes are derived from
|
|
47
|
+
it. `KNOWHERE_API_KEY` is still supported as a manual fallback and takes
|
|
48
|
+
precedence over the local dashboard login. API-key authentication runs with
|
|
49
|
+
full access.
|
|
48
50
|
|
|
49
51
|
## Connect From MCP Hosts
|
|
50
52
|
|
package/dist/stdio.js
CHANGED
|
@@ -342,17 +342,26 @@ var import_os = __toESM(require("os"));
|
|
|
342
342
|
var import_path = __toESM(require("path"));
|
|
343
343
|
var import_promises = require("fs/promises");
|
|
344
344
|
var import_knowhere_sdk2 = require("@ontos-ai/knowhere-sdk");
|
|
345
|
-
var
|
|
345
|
+
var DEFAULT_BASE_URL = "https://knowhereto.ai";
|
|
346
346
|
var AUTH_FILE_ENV = "KNOWHERE_MCP_AUTH_FILE";
|
|
347
|
-
var
|
|
348
|
-
var API_BASE_URL_ENV = "KNOWHERE_BASE_URL";
|
|
347
|
+
var BASE_URL_ENV = "KNOWHERE_BASE_URL";
|
|
349
348
|
var API_KEY_ENV = "KNOWHERE_API_KEY";
|
|
350
349
|
var TOKEN_REFRESH_SKEW_MS = 5 * 60 * 1e3;
|
|
351
350
|
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
352
351
|
var RANDOM_BYTE_LENGTH = 32;
|
|
353
|
-
var DEFAULT_CLIENT_NAME = "knowhere-
|
|
352
|
+
var DEFAULT_CLIENT_NAME = "knowhere-mcp";
|
|
354
353
|
var DEFAULT_PERMISSION = "full_access";
|
|
355
354
|
var PERMISSION_VALUES = /* @__PURE__ */ new Set(["read_only", "full_access"]);
|
|
355
|
+
function resolveMcpUrls(base) {
|
|
356
|
+
const normalized = base.replace(/\/+$/, "");
|
|
357
|
+
return {
|
|
358
|
+
base: normalized,
|
|
359
|
+
api: `${normalized}/api`,
|
|
360
|
+
mcpLogin: `${normalized}/mcp/login`,
|
|
361
|
+
token: `${normalized}/api/oauth/token`,
|
|
362
|
+
revoke: `${normalized}/api/oauth/revoke`
|
|
363
|
+
};
|
|
364
|
+
}
|
|
356
365
|
var McpCredentialManager = class {
|
|
357
366
|
authFilePath;
|
|
358
367
|
refreshPromise;
|
|
@@ -367,7 +376,7 @@ var McpCredentialManager = class {
|
|
|
367
376
|
return {
|
|
368
377
|
source: "api_key",
|
|
369
378
|
authFilePath: this.authFilePath,
|
|
370
|
-
|
|
379
|
+
baseUrl: process.env[BASE_URL_ENV],
|
|
371
380
|
permission: DEFAULT_PERMISSION
|
|
372
381
|
};
|
|
373
382
|
}
|
|
@@ -378,8 +387,7 @@ var McpCredentialManager = class {
|
|
|
378
387
|
return {
|
|
379
388
|
source: "stored_login",
|
|
380
389
|
authFilePath: this.authFilePath,
|
|
381
|
-
|
|
382
|
-
apiBaseUrl: process.env[API_BASE_URL_ENV] ?? storedAuth.apiBaseUrl,
|
|
390
|
+
baseUrl: process.env[BASE_URL_ENV] ?? storedAuth.baseUrl,
|
|
383
391
|
permission: storedAuth.permission,
|
|
384
392
|
refreshTokenExpiresAt: storedAuth.refreshTokenExpiresAt,
|
|
385
393
|
accessTokenExpiresAt: storedAuth.accessTokenExpiresAt
|
|
@@ -406,17 +414,17 @@ var McpCredentialManager = class {
|
|
|
406
414
|
return this.refreshPromise;
|
|
407
415
|
}
|
|
408
416
|
async login(options = {}) {
|
|
409
|
-
const
|
|
410
|
-
options.
|
|
417
|
+
const baseUrl = normalizeBaseUrl(
|
|
418
|
+
options.baseUrl ?? process.env[BASE_URL_ENV] ?? DEFAULT_BASE_URL
|
|
411
419
|
);
|
|
412
|
-
const
|
|
420
|
+
const urls = resolveMcpUrls(baseUrl);
|
|
413
421
|
const codeVerifier = createRandomToken();
|
|
414
422
|
const state = createRandomToken();
|
|
415
423
|
const codeChallenge = createPkceChallenge(codeVerifier);
|
|
416
424
|
const callback = await createLoopbackCallback(state);
|
|
417
425
|
try {
|
|
418
426
|
const loginUrl = buildLoginUrl({
|
|
419
|
-
|
|
427
|
+
loginEndpoint: urls.mcpLogin,
|
|
420
428
|
redirectUri: callback.redirectUri,
|
|
421
429
|
state,
|
|
422
430
|
codeChallenge,
|
|
@@ -427,23 +435,21 @@ var McpCredentialManager = class {
|
|
|
427
435
|
openBrowser(loginUrl);
|
|
428
436
|
}
|
|
429
437
|
const code = await callback.waitForCode();
|
|
430
|
-
const tokenResponse = await requestToken(
|
|
438
|
+
const tokenResponse = await requestToken(urls.token, {
|
|
431
439
|
grant_type: "authorization_code",
|
|
432
440
|
code,
|
|
433
441
|
code_verifier: codeVerifier,
|
|
434
442
|
client_name: options.clientName ?? DEFAULT_CLIENT_NAME
|
|
435
443
|
});
|
|
436
444
|
const storedAuth = buildStoredAuth({
|
|
437
|
-
|
|
438
|
-
apiBaseUrl,
|
|
445
|
+
baseUrl,
|
|
439
446
|
tokenResponse,
|
|
440
447
|
previous: await this.readAuth()
|
|
441
448
|
});
|
|
442
449
|
await this.writeAuth(storedAuth);
|
|
443
450
|
return {
|
|
444
451
|
authFilePath: this.authFilePath,
|
|
445
|
-
|
|
446
|
-
apiBaseUrl,
|
|
452
|
+
baseUrl,
|
|
447
453
|
permission: tokenResponse.permission,
|
|
448
454
|
refreshTokenExpiresAt: tokenResponse.refreshTokenExpiresAt
|
|
449
455
|
};
|
|
@@ -456,7 +462,7 @@ var McpCredentialManager = class {
|
|
|
456
462
|
let revokeError;
|
|
457
463
|
if (storedAuth) {
|
|
458
464
|
try {
|
|
459
|
-
await requestRevoke(storedAuth.
|
|
465
|
+
await requestRevoke(resolveMcpUrls(storedAuth.baseUrl).revoke, storedAuth.refreshToken);
|
|
460
466
|
} catch (error) {
|
|
461
467
|
revokeError = error instanceof Error ? error.message : "Failed to revoke MCP login";
|
|
462
468
|
}
|
|
@@ -469,16 +475,20 @@ var McpCredentialManager = class {
|
|
|
469
475
|
};
|
|
470
476
|
}
|
|
471
477
|
async resolveBaseURL() {
|
|
472
|
-
|
|
478
|
+
const envBase = process.env[BASE_URL_ENV];
|
|
479
|
+
if (envBase) {
|
|
480
|
+
return resolveMcpUrls(envBase).api;
|
|
481
|
+
}
|
|
482
|
+
const storedAuth = await this.readAuth();
|
|
483
|
+
return storedAuth ? resolveMcpUrls(storedAuth.baseUrl).api : void 0;
|
|
473
484
|
}
|
|
474
485
|
async refreshAccessToken(storedAuth) {
|
|
475
|
-
const tokenResponse = await requestToken(storedAuth.
|
|
486
|
+
const tokenResponse = await requestToken(resolveMcpUrls(storedAuth.baseUrl).token, {
|
|
476
487
|
grant_type: "refresh_token",
|
|
477
488
|
refresh_token: storedAuth.refreshToken
|
|
478
489
|
});
|
|
479
490
|
const refreshedAuth = buildStoredAuth({
|
|
480
|
-
|
|
481
|
-
apiBaseUrl: storedAuth.apiBaseUrl,
|
|
491
|
+
baseUrl: storedAuth.baseUrl,
|
|
482
492
|
tokenResponse,
|
|
483
493
|
previous: storedAuth
|
|
484
494
|
});
|
|
@@ -516,8 +526,7 @@ function isUsableAccessToken(storedAuth) {
|
|
|
516
526
|
return Number.isFinite(expiresAt) && expiresAt - Date.now() > TOKEN_REFRESH_SKEW_MS;
|
|
517
527
|
}
|
|
518
528
|
function buildStoredAuth({
|
|
519
|
-
|
|
520
|
-
apiBaseUrl,
|
|
529
|
+
baseUrl,
|
|
521
530
|
tokenResponse,
|
|
522
531
|
previous
|
|
523
532
|
}) {
|
|
@@ -530,8 +539,7 @@ function buildStoredAuth({
|
|
|
530
539
|
throw new Error("Knowhere MCP token response did not include a refresh token");
|
|
531
540
|
}
|
|
532
541
|
return {
|
|
533
|
-
|
|
534
|
-
apiBaseUrl,
|
|
542
|
+
baseUrl,
|
|
535
543
|
permission: tokenResponse.permission,
|
|
536
544
|
refreshToken,
|
|
537
545
|
refreshTokenExpiresAt: tokenResponse.refreshTokenExpiresAt ?? previous?.refreshTokenExpiresAt,
|
|
@@ -545,13 +553,12 @@ function parseStoredAuth(value) {
|
|
|
545
553
|
if (!isRecord(value)) {
|
|
546
554
|
throw new Error("Invalid Knowhere MCP auth file");
|
|
547
555
|
}
|
|
548
|
-
const
|
|
556
|
+
const baseUrl = readRequiredString(value, "baseUrl");
|
|
549
557
|
const refreshToken = readRequiredString(value, "refreshToken");
|
|
550
558
|
const createdAt = readRequiredString(value, "createdAt");
|
|
551
559
|
const updatedAt = readRequiredString(value, "updatedAt");
|
|
552
560
|
return {
|
|
553
|
-
|
|
554
|
-
apiBaseUrl: readOptionalString(value, "apiBaseUrl"),
|
|
561
|
+
baseUrl,
|
|
555
562
|
permission: normalizePermission(readOptionalString(value, "permission")),
|
|
556
563
|
refreshToken,
|
|
557
564
|
refreshTokenExpiresAt: readOptionalString(value, "refreshTokenExpiresAt"),
|
|
@@ -586,13 +593,13 @@ function createPkceChallenge(codeVerifier) {
|
|
|
586
593
|
return import_crypto.default.createHash("sha256").update(codeVerifier).digest("base64url");
|
|
587
594
|
}
|
|
588
595
|
function buildLoginUrl({
|
|
589
|
-
|
|
596
|
+
loginEndpoint,
|
|
590
597
|
redirectUri,
|
|
591
598
|
state,
|
|
592
599
|
codeChallenge,
|
|
593
600
|
clientName
|
|
594
601
|
}) {
|
|
595
|
-
const url = new URL(
|
|
602
|
+
const url = new URL(loginEndpoint);
|
|
596
603
|
url.searchParams.set("redirect_uri", redirectUri);
|
|
597
604
|
url.searchParams.set("state", state);
|
|
598
605
|
url.searchParams.set("code_challenge", codeChallenge);
|
|
@@ -692,8 +699,8 @@ function handleCallbackRequest({
|
|
|
692
699
|
);
|
|
693
700
|
resolveCode(code);
|
|
694
701
|
}
|
|
695
|
-
async function requestToken(
|
|
696
|
-
const response = await fetch(
|
|
702
|
+
async function requestToken(tokenUrl, body) {
|
|
703
|
+
const response = await fetch(tokenUrl, {
|
|
697
704
|
method: "POST",
|
|
698
705
|
headers: { "Content-Type": "application/json" },
|
|
699
706
|
body: JSON.stringify(body)
|
|
@@ -704,8 +711,8 @@ async function requestToken(dashboardUrl, body) {
|
|
|
704
711
|
}
|
|
705
712
|
return parseTokenResponse(responseBody);
|
|
706
713
|
}
|
|
707
|
-
async function requestRevoke(
|
|
708
|
-
const response = await fetch(
|
|
714
|
+
async function requestRevoke(revokeUrl, refreshToken) {
|
|
715
|
+
const response = await fetch(revokeUrl, {
|
|
709
716
|
method: "POST",
|
|
710
717
|
headers: { "Content-Type": "application/json" },
|
|
711
718
|
body: JSON.stringify({ refresh_token: refreshToken })
|
|
@@ -751,12 +758,12 @@ function getResponseMessage(responseBody, fallback) {
|
|
|
751
758
|
}
|
|
752
759
|
return fallback;
|
|
753
760
|
}
|
|
754
|
-
function
|
|
761
|
+
function normalizeBaseUrl(value) {
|
|
755
762
|
const url = new URL(value);
|
|
756
763
|
url.pathname = "/";
|
|
757
764
|
url.search = "";
|
|
758
765
|
url.hash = "";
|
|
759
|
-
return url.toString();
|
|
766
|
+
return url.toString().replace(/\/+$/, "");
|
|
760
767
|
}
|
|
761
768
|
function normalizePermission(value) {
|
|
762
769
|
if (value && PERMISSION_VALUES.has(value)) {
|
|
@@ -827,8 +834,7 @@ async function runLogin(args) {
|
|
|
827
834
|
const options = parseLoginArgs(args);
|
|
828
835
|
const credentialManager = new McpCredentialManager(options.authFilePath);
|
|
829
836
|
const result = await credentialManager.login({
|
|
830
|
-
|
|
831
|
-
baseURL: options.baseURL,
|
|
837
|
+
baseUrl: options.baseUrl,
|
|
832
838
|
openBrowser: options.openBrowser,
|
|
833
839
|
clientName: options.clientName,
|
|
834
840
|
onLoginUrl: (url) => {
|
|
@@ -866,12 +872,8 @@ function parseLoginArgs(args) {
|
|
|
866
872
|
for (let index = 0; index < args.length; index += 1) {
|
|
867
873
|
const arg = args[index];
|
|
868
874
|
switch (arg) {
|
|
869
|
-
case "--dashboard-url":
|
|
870
|
-
options.dashboardUrl = readFlagValue(args, index, arg);
|
|
871
|
-
index += 1;
|
|
872
|
-
break;
|
|
873
875
|
case "--base-url":
|
|
874
|
-
options.
|
|
876
|
+
options.baseUrl = readFlagValue(args, index, arg);
|
|
875
877
|
index += 1;
|
|
876
878
|
break;
|
|
877
879
|
case "--auth-file":
|
|
@@ -910,8 +912,7 @@ function formatStatus(status) {
|
|
|
910
912
|
return [
|
|
911
913
|
"Knowhere MCP is authenticated with dashboard login",
|
|
912
914
|
`Auth file: ${status.authFilePath}`,
|
|
913
|
-
`
|
|
914
|
-
`API base URL: ${status.apiBaseUrl ?? "default"}`,
|
|
915
|
+
`Base URL: ${status.baseUrl ?? "unknown"}`,
|
|
915
916
|
`Permission: ${status.permission ?? "full_access"}`,
|
|
916
917
|
`Refresh token expires: ${status.refreshTokenExpiresAt ?? "unknown"}`,
|
|
917
918
|
`Access token expires: ${status.accessTokenExpiresAt ?? "not cached"}`
|
|
@@ -931,8 +932,7 @@ function printHelp() {
|
|
|
931
932
|
knowhere-mcp logout Remove and revoke local MCP login
|
|
932
933
|
|
|
933
934
|
Login options:
|
|
934
|
-
--
|
|
935
|
-
--base-url <url> Knowhere API URL, defaults to KNOWHERE_BASE_URL or SDK default
|
|
935
|
+
--base-url <url> Knowhere site base URL, defaults to KNOWHERE_BASE_URL or https://knowhereto.ai
|
|
936
936
|
--auth-file <path> Override local auth file path
|
|
937
937
|
--client-name <name> Label shown in dashboard token records
|
|
938
938
|
--no-open Print login URL without opening a browser`);
|
package/dist/stdio.mjs
CHANGED
|
@@ -11,17 +11,26 @@ import os from "os";
|
|
|
11
11
|
import path from "path";
|
|
12
12
|
import { mkdir, readFile, rm, writeFile, chmod } from "fs/promises";
|
|
13
13
|
import { ValidationError } from "@ontos-ai/knowhere-sdk";
|
|
14
|
-
var
|
|
14
|
+
var DEFAULT_BASE_URL = "https://knowhereto.ai";
|
|
15
15
|
var AUTH_FILE_ENV = "KNOWHERE_MCP_AUTH_FILE";
|
|
16
|
-
var
|
|
17
|
-
var API_BASE_URL_ENV = "KNOWHERE_BASE_URL";
|
|
16
|
+
var BASE_URL_ENV = "KNOWHERE_BASE_URL";
|
|
18
17
|
var API_KEY_ENV = "KNOWHERE_API_KEY";
|
|
19
18
|
var TOKEN_REFRESH_SKEW_MS = 5 * 60 * 1e3;
|
|
20
19
|
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
21
20
|
var RANDOM_BYTE_LENGTH = 32;
|
|
22
|
-
var DEFAULT_CLIENT_NAME = "knowhere-
|
|
21
|
+
var DEFAULT_CLIENT_NAME = "knowhere-mcp";
|
|
23
22
|
var DEFAULT_PERMISSION = "full_access";
|
|
24
23
|
var PERMISSION_VALUES = /* @__PURE__ */ new Set(["read_only", "full_access"]);
|
|
24
|
+
function resolveMcpUrls(base) {
|
|
25
|
+
const normalized = base.replace(/\/+$/, "");
|
|
26
|
+
return {
|
|
27
|
+
base: normalized,
|
|
28
|
+
api: `${normalized}/api`,
|
|
29
|
+
mcpLogin: `${normalized}/mcp/login`,
|
|
30
|
+
token: `${normalized}/api/oauth/token`,
|
|
31
|
+
revoke: `${normalized}/api/oauth/revoke`
|
|
32
|
+
};
|
|
33
|
+
}
|
|
25
34
|
var McpCredentialManager = class {
|
|
26
35
|
authFilePath;
|
|
27
36
|
refreshPromise;
|
|
@@ -36,7 +45,7 @@ var McpCredentialManager = class {
|
|
|
36
45
|
return {
|
|
37
46
|
source: "api_key",
|
|
38
47
|
authFilePath: this.authFilePath,
|
|
39
|
-
|
|
48
|
+
baseUrl: process.env[BASE_URL_ENV],
|
|
40
49
|
permission: DEFAULT_PERMISSION
|
|
41
50
|
};
|
|
42
51
|
}
|
|
@@ -47,8 +56,7 @@ var McpCredentialManager = class {
|
|
|
47
56
|
return {
|
|
48
57
|
source: "stored_login",
|
|
49
58
|
authFilePath: this.authFilePath,
|
|
50
|
-
|
|
51
|
-
apiBaseUrl: process.env[API_BASE_URL_ENV] ?? storedAuth.apiBaseUrl,
|
|
59
|
+
baseUrl: process.env[BASE_URL_ENV] ?? storedAuth.baseUrl,
|
|
52
60
|
permission: storedAuth.permission,
|
|
53
61
|
refreshTokenExpiresAt: storedAuth.refreshTokenExpiresAt,
|
|
54
62
|
accessTokenExpiresAt: storedAuth.accessTokenExpiresAt
|
|
@@ -75,17 +83,17 @@ var McpCredentialManager = class {
|
|
|
75
83
|
return this.refreshPromise;
|
|
76
84
|
}
|
|
77
85
|
async login(options = {}) {
|
|
78
|
-
const
|
|
79
|
-
options.
|
|
86
|
+
const baseUrl = normalizeBaseUrl(
|
|
87
|
+
options.baseUrl ?? process.env[BASE_URL_ENV] ?? DEFAULT_BASE_URL
|
|
80
88
|
);
|
|
81
|
-
const
|
|
89
|
+
const urls = resolveMcpUrls(baseUrl);
|
|
82
90
|
const codeVerifier = createRandomToken();
|
|
83
91
|
const state = createRandomToken();
|
|
84
92
|
const codeChallenge = createPkceChallenge(codeVerifier);
|
|
85
93
|
const callback = await createLoopbackCallback(state);
|
|
86
94
|
try {
|
|
87
95
|
const loginUrl = buildLoginUrl({
|
|
88
|
-
|
|
96
|
+
loginEndpoint: urls.mcpLogin,
|
|
89
97
|
redirectUri: callback.redirectUri,
|
|
90
98
|
state,
|
|
91
99
|
codeChallenge,
|
|
@@ -96,23 +104,21 @@ var McpCredentialManager = class {
|
|
|
96
104
|
openBrowser(loginUrl);
|
|
97
105
|
}
|
|
98
106
|
const code = await callback.waitForCode();
|
|
99
|
-
const tokenResponse = await requestToken(
|
|
107
|
+
const tokenResponse = await requestToken(urls.token, {
|
|
100
108
|
grant_type: "authorization_code",
|
|
101
109
|
code,
|
|
102
110
|
code_verifier: codeVerifier,
|
|
103
111
|
client_name: options.clientName ?? DEFAULT_CLIENT_NAME
|
|
104
112
|
});
|
|
105
113
|
const storedAuth = buildStoredAuth({
|
|
106
|
-
|
|
107
|
-
apiBaseUrl,
|
|
114
|
+
baseUrl,
|
|
108
115
|
tokenResponse,
|
|
109
116
|
previous: await this.readAuth()
|
|
110
117
|
});
|
|
111
118
|
await this.writeAuth(storedAuth);
|
|
112
119
|
return {
|
|
113
120
|
authFilePath: this.authFilePath,
|
|
114
|
-
|
|
115
|
-
apiBaseUrl,
|
|
121
|
+
baseUrl,
|
|
116
122
|
permission: tokenResponse.permission,
|
|
117
123
|
refreshTokenExpiresAt: tokenResponse.refreshTokenExpiresAt
|
|
118
124
|
};
|
|
@@ -125,7 +131,7 @@ var McpCredentialManager = class {
|
|
|
125
131
|
let revokeError;
|
|
126
132
|
if (storedAuth) {
|
|
127
133
|
try {
|
|
128
|
-
await requestRevoke(storedAuth.
|
|
134
|
+
await requestRevoke(resolveMcpUrls(storedAuth.baseUrl).revoke, storedAuth.refreshToken);
|
|
129
135
|
} catch (error) {
|
|
130
136
|
revokeError = error instanceof Error ? error.message : "Failed to revoke MCP login";
|
|
131
137
|
}
|
|
@@ -138,16 +144,20 @@ var McpCredentialManager = class {
|
|
|
138
144
|
};
|
|
139
145
|
}
|
|
140
146
|
async resolveBaseURL() {
|
|
141
|
-
|
|
147
|
+
const envBase = process.env[BASE_URL_ENV];
|
|
148
|
+
if (envBase) {
|
|
149
|
+
return resolveMcpUrls(envBase).api;
|
|
150
|
+
}
|
|
151
|
+
const storedAuth = await this.readAuth();
|
|
152
|
+
return storedAuth ? resolveMcpUrls(storedAuth.baseUrl).api : void 0;
|
|
142
153
|
}
|
|
143
154
|
async refreshAccessToken(storedAuth) {
|
|
144
|
-
const tokenResponse = await requestToken(storedAuth.
|
|
155
|
+
const tokenResponse = await requestToken(resolveMcpUrls(storedAuth.baseUrl).token, {
|
|
145
156
|
grant_type: "refresh_token",
|
|
146
157
|
refresh_token: storedAuth.refreshToken
|
|
147
158
|
});
|
|
148
159
|
const refreshedAuth = buildStoredAuth({
|
|
149
|
-
|
|
150
|
-
apiBaseUrl: storedAuth.apiBaseUrl,
|
|
160
|
+
baseUrl: storedAuth.baseUrl,
|
|
151
161
|
tokenResponse,
|
|
152
162
|
previous: storedAuth
|
|
153
163
|
});
|
|
@@ -185,8 +195,7 @@ function isUsableAccessToken(storedAuth) {
|
|
|
185
195
|
return Number.isFinite(expiresAt) && expiresAt - Date.now() > TOKEN_REFRESH_SKEW_MS;
|
|
186
196
|
}
|
|
187
197
|
function buildStoredAuth({
|
|
188
|
-
|
|
189
|
-
apiBaseUrl,
|
|
198
|
+
baseUrl,
|
|
190
199
|
tokenResponse,
|
|
191
200
|
previous
|
|
192
201
|
}) {
|
|
@@ -199,8 +208,7 @@ function buildStoredAuth({
|
|
|
199
208
|
throw new Error("Knowhere MCP token response did not include a refresh token");
|
|
200
209
|
}
|
|
201
210
|
return {
|
|
202
|
-
|
|
203
|
-
apiBaseUrl,
|
|
211
|
+
baseUrl,
|
|
204
212
|
permission: tokenResponse.permission,
|
|
205
213
|
refreshToken,
|
|
206
214
|
refreshTokenExpiresAt: tokenResponse.refreshTokenExpiresAt ?? previous?.refreshTokenExpiresAt,
|
|
@@ -214,13 +222,12 @@ function parseStoredAuth(value) {
|
|
|
214
222
|
if (!isRecord(value)) {
|
|
215
223
|
throw new Error("Invalid Knowhere MCP auth file");
|
|
216
224
|
}
|
|
217
|
-
const
|
|
225
|
+
const baseUrl = readRequiredString(value, "baseUrl");
|
|
218
226
|
const refreshToken = readRequiredString(value, "refreshToken");
|
|
219
227
|
const createdAt = readRequiredString(value, "createdAt");
|
|
220
228
|
const updatedAt = readRequiredString(value, "updatedAt");
|
|
221
229
|
return {
|
|
222
|
-
|
|
223
|
-
apiBaseUrl: readOptionalString(value, "apiBaseUrl"),
|
|
230
|
+
baseUrl,
|
|
224
231
|
permission: normalizePermission(readOptionalString(value, "permission")),
|
|
225
232
|
refreshToken,
|
|
226
233
|
refreshTokenExpiresAt: readOptionalString(value, "refreshTokenExpiresAt"),
|
|
@@ -255,13 +262,13 @@ function createPkceChallenge(codeVerifier) {
|
|
|
255
262
|
return crypto.createHash("sha256").update(codeVerifier).digest("base64url");
|
|
256
263
|
}
|
|
257
264
|
function buildLoginUrl({
|
|
258
|
-
|
|
265
|
+
loginEndpoint,
|
|
259
266
|
redirectUri,
|
|
260
267
|
state,
|
|
261
268
|
codeChallenge,
|
|
262
269
|
clientName
|
|
263
270
|
}) {
|
|
264
|
-
const url = new URL(
|
|
271
|
+
const url = new URL(loginEndpoint);
|
|
265
272
|
url.searchParams.set("redirect_uri", redirectUri);
|
|
266
273
|
url.searchParams.set("state", state);
|
|
267
274
|
url.searchParams.set("code_challenge", codeChallenge);
|
|
@@ -361,8 +368,8 @@ function handleCallbackRequest({
|
|
|
361
368
|
);
|
|
362
369
|
resolveCode(code);
|
|
363
370
|
}
|
|
364
|
-
async function requestToken(
|
|
365
|
-
const response = await fetch(
|
|
371
|
+
async function requestToken(tokenUrl, body) {
|
|
372
|
+
const response = await fetch(tokenUrl, {
|
|
366
373
|
method: "POST",
|
|
367
374
|
headers: { "Content-Type": "application/json" },
|
|
368
375
|
body: JSON.stringify(body)
|
|
@@ -373,8 +380,8 @@ async function requestToken(dashboardUrl, body) {
|
|
|
373
380
|
}
|
|
374
381
|
return parseTokenResponse(responseBody);
|
|
375
382
|
}
|
|
376
|
-
async function requestRevoke(
|
|
377
|
-
const response = await fetch(
|
|
383
|
+
async function requestRevoke(revokeUrl, refreshToken) {
|
|
384
|
+
const response = await fetch(revokeUrl, {
|
|
378
385
|
method: "POST",
|
|
379
386
|
headers: { "Content-Type": "application/json" },
|
|
380
387
|
body: JSON.stringify({ refresh_token: refreshToken })
|
|
@@ -420,12 +427,12 @@ function getResponseMessage(responseBody, fallback) {
|
|
|
420
427
|
}
|
|
421
428
|
return fallback;
|
|
422
429
|
}
|
|
423
|
-
function
|
|
430
|
+
function normalizeBaseUrl(value) {
|
|
424
431
|
const url = new URL(value);
|
|
425
432
|
url.pathname = "/";
|
|
426
433
|
url.search = "";
|
|
427
434
|
url.hash = "";
|
|
428
|
-
return url.toString();
|
|
435
|
+
return url.toString().replace(/\/+$/, "");
|
|
429
436
|
}
|
|
430
437
|
function normalizePermission(value) {
|
|
431
438
|
if (value && PERMISSION_VALUES.has(value)) {
|
|
@@ -496,8 +503,7 @@ async function runLogin(args) {
|
|
|
496
503
|
const options = parseLoginArgs(args);
|
|
497
504
|
const credentialManager = new McpCredentialManager(options.authFilePath);
|
|
498
505
|
const result = await credentialManager.login({
|
|
499
|
-
|
|
500
|
-
baseURL: options.baseURL,
|
|
506
|
+
baseUrl: options.baseUrl,
|
|
501
507
|
openBrowser: options.openBrowser,
|
|
502
508
|
clientName: options.clientName,
|
|
503
509
|
onLoginUrl: (url) => {
|
|
@@ -535,12 +541,8 @@ function parseLoginArgs(args) {
|
|
|
535
541
|
for (let index = 0; index < args.length; index += 1) {
|
|
536
542
|
const arg = args[index];
|
|
537
543
|
switch (arg) {
|
|
538
|
-
case "--dashboard-url":
|
|
539
|
-
options.dashboardUrl = readFlagValue(args, index, arg);
|
|
540
|
-
index += 1;
|
|
541
|
-
break;
|
|
542
544
|
case "--base-url":
|
|
543
|
-
options.
|
|
545
|
+
options.baseUrl = readFlagValue(args, index, arg);
|
|
544
546
|
index += 1;
|
|
545
547
|
break;
|
|
546
548
|
case "--auth-file":
|
|
@@ -579,8 +581,7 @@ function formatStatus(status) {
|
|
|
579
581
|
return [
|
|
580
582
|
"Knowhere MCP is authenticated with dashboard login",
|
|
581
583
|
`Auth file: ${status.authFilePath}`,
|
|
582
|
-
`
|
|
583
|
-
`API base URL: ${status.apiBaseUrl ?? "default"}`,
|
|
584
|
+
`Base URL: ${status.baseUrl ?? "unknown"}`,
|
|
584
585
|
`Permission: ${status.permission ?? "full_access"}`,
|
|
585
586
|
`Refresh token expires: ${status.refreshTokenExpiresAt ?? "unknown"}`,
|
|
586
587
|
`Access token expires: ${status.accessTokenExpiresAt ?? "not cached"}`
|
|
@@ -600,8 +601,7 @@ function printHelp() {
|
|
|
600
601
|
knowhere-mcp logout Remove and revoke local MCP login
|
|
601
602
|
|
|
602
603
|
Login options:
|
|
603
|
-
--
|
|
604
|
-
--base-url <url> Knowhere API URL, defaults to KNOWHERE_BASE_URL or SDK default
|
|
604
|
+
--base-url <url> Knowhere site base URL, defaults to KNOWHERE_BASE_URL or https://knowhereto.ai
|
|
605
605
|
--auth-file <path> Override local auth file path
|
|
606
606
|
--client-name <name> Label shown in dashboard token records
|
|
607
607
|
--no-open Print login URL without opening a browser`);
|