@ontos-ai/knowhere-mcp 0.2.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/LICENSE +21 -0
- package/README.md +202 -0
- package/dist/chunk-ZITSXYWR.mjs +319 -0
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +350 -0
- package/dist/index.mjs +8 -0
- package/dist/stdio.d.mts +1 -0
- package/dist/stdio.d.ts +1 -0
- package/dist/stdio.js +943 -0
- package/dist/stdio.mjs +612 -0
- package/package.json +69 -0
package/dist/stdio.js
ADDED
|
@@ -0,0 +1,943 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/index.ts
|
|
27
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
28
|
+
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
29
|
+
var import_knowhere_sdk = require("@ontos-ai/knowhere-sdk");
|
|
30
|
+
var z = __toESM(require("zod/v4"));
|
|
31
|
+
var parsingParamsSchema = z.object({
|
|
32
|
+
model: z.enum(["base", "advanced"]).optional(),
|
|
33
|
+
ocrEnabled: z.boolean().optional(),
|
|
34
|
+
kbDir: z.string().optional(),
|
|
35
|
+
docType: z.enum(["auto", "pdf", "docx", "txt", "md"]).optional(),
|
|
36
|
+
smartTitleParse: z.boolean().optional(),
|
|
37
|
+
summaryImage: z.boolean().optional(),
|
|
38
|
+
summaryTable: z.boolean().optional(),
|
|
39
|
+
summaryTxt: z.boolean().optional(),
|
|
40
|
+
addFragDesc: z.string().optional()
|
|
41
|
+
}).optional();
|
|
42
|
+
var objectOutputSchema = {
|
|
43
|
+
result: z.record(z.string(), z.unknown())
|
|
44
|
+
};
|
|
45
|
+
async function createKnowhereMcpServer(options) {
|
|
46
|
+
const client = options?.client ?? new import_knowhere_sdk.Knowhere({
|
|
47
|
+
authTokenProvider: options?.authTokenProvider,
|
|
48
|
+
baseURL: options?.baseURL
|
|
49
|
+
});
|
|
50
|
+
const knowledge = options?.cacheDirectory === void 0 ? client.knowledge : client.knowledge.withCacheDirectory(options.cacheDirectory);
|
|
51
|
+
if (options?.recoverPendingJobsOnStart !== false) {
|
|
52
|
+
await knowledge.recoverPendingAsyncParseJobs();
|
|
53
|
+
}
|
|
54
|
+
const server = new import_mcp.McpServer({
|
|
55
|
+
name: "knowhere-local-knowledge",
|
|
56
|
+
version: import_knowhere_sdk.VERSION
|
|
57
|
+
});
|
|
58
|
+
const permission = options?.permission ?? "full_access";
|
|
59
|
+
const hasWritePermission = permission === "full_access";
|
|
60
|
+
if (hasWritePermission) {
|
|
61
|
+
server.registerTool(
|
|
62
|
+
"knowhere_parse_url",
|
|
63
|
+
{
|
|
64
|
+
description: "Blocking parse: submit a remote URL to Knowhere, wait for completion, then cache the parse result locally for outline/read/grep/search tools.",
|
|
65
|
+
inputSchema: {
|
|
66
|
+
url: z.string().url(),
|
|
67
|
+
namespace: z.string().optional(),
|
|
68
|
+
localDocumentId: z.string().optional(),
|
|
69
|
+
dataId: z.string().optional(),
|
|
70
|
+
parsingParams: parsingParamsSchema
|
|
71
|
+
},
|
|
72
|
+
outputSchema: objectOutputSchema
|
|
73
|
+
},
|
|
74
|
+
async (input) => createToolResult(
|
|
75
|
+
await knowledge.parse({
|
|
76
|
+
url: input.url,
|
|
77
|
+
namespace: input.namespace,
|
|
78
|
+
localDocumentId: input.localDocumentId,
|
|
79
|
+
dataId: input.dataId,
|
|
80
|
+
...toFlatParsingParams(input.parsingParams)
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
);
|
|
84
|
+
server.registerTool(
|
|
85
|
+
"knowhere_parse_file",
|
|
86
|
+
{
|
|
87
|
+
description: "Blocking parse: submit a local file path available to this MCP process, wait for completion, then cache the parse result locally.",
|
|
88
|
+
inputSchema: {
|
|
89
|
+
file: z.string().describe("Local file path available to this MCP server process."),
|
|
90
|
+
fileName: z.string().optional(),
|
|
91
|
+
namespace: z.string().optional(),
|
|
92
|
+
localDocumentId: z.string().optional(),
|
|
93
|
+
dataId: z.string().optional(),
|
|
94
|
+
parsingParams: parsingParamsSchema
|
|
95
|
+
},
|
|
96
|
+
outputSchema: objectOutputSchema
|
|
97
|
+
},
|
|
98
|
+
async (input) => createToolResult(
|
|
99
|
+
await knowledge.parse({
|
|
100
|
+
file: input.file,
|
|
101
|
+
fileName: input.fileName,
|
|
102
|
+
namespace: input.namespace,
|
|
103
|
+
localDocumentId: input.localDocumentId,
|
|
104
|
+
dataId: input.dataId,
|
|
105
|
+
...toFlatParsingParams(input.parsingParams)
|
|
106
|
+
})
|
|
107
|
+
)
|
|
108
|
+
);
|
|
109
|
+
server.registerTool(
|
|
110
|
+
"knowhere_async_parse_url",
|
|
111
|
+
{
|
|
112
|
+
description: "Start parsing a remote URL through Knowhere and return immediately with the parse job. Poll with knowhere_async_get_job_status; completed tracked jobs are cached locally automatically.",
|
|
113
|
+
inputSchema: {
|
|
114
|
+
url: z.string().url(),
|
|
115
|
+
namespace: z.string().optional(),
|
|
116
|
+
localDocumentId: z.string().optional(),
|
|
117
|
+
dataId: z.string().optional(),
|
|
118
|
+
parsingParams: parsingParamsSchema
|
|
119
|
+
},
|
|
120
|
+
outputSchema: objectOutputSchema
|
|
121
|
+
},
|
|
122
|
+
async (input) => createToolResult(
|
|
123
|
+
await knowledge.startParse({
|
|
124
|
+
url: input.url,
|
|
125
|
+
namespace: input.namespace,
|
|
126
|
+
localDocumentId: input.localDocumentId,
|
|
127
|
+
dataId: input.dataId,
|
|
128
|
+
...toFlatParsingParams(input.parsingParams)
|
|
129
|
+
})
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
server.registerTool(
|
|
133
|
+
"knowhere_async_parse_file",
|
|
134
|
+
{
|
|
135
|
+
description: "Start parsing a local file path available to this MCP process, upload it if needed, and return immediately with the parse job. Poll with knowhere_async_get_job_status; completed tracked jobs are cached locally automatically.",
|
|
136
|
+
inputSchema: {
|
|
137
|
+
file: z.string().describe("Local file path available to this MCP server process."),
|
|
138
|
+
fileName: z.string().optional(),
|
|
139
|
+
namespace: z.string().optional(),
|
|
140
|
+
localDocumentId: z.string().optional(),
|
|
141
|
+
dataId: z.string().optional(),
|
|
142
|
+
parsingParams: parsingParamsSchema
|
|
143
|
+
},
|
|
144
|
+
outputSchema: objectOutputSchema
|
|
145
|
+
},
|
|
146
|
+
async (input) => createToolResult(
|
|
147
|
+
await knowledge.startParse({
|
|
148
|
+
file: input.file,
|
|
149
|
+
fileName: input.fileName,
|
|
150
|
+
namespace: input.namespace,
|
|
151
|
+
localDocumentId: input.localDocumentId,
|
|
152
|
+
dataId: input.dataId,
|
|
153
|
+
...toFlatParsingParams(input.parsingParams)
|
|
154
|
+
})
|
|
155
|
+
)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
server.registerTool(
|
|
159
|
+
"knowhere_async_get_job_status",
|
|
160
|
+
{
|
|
161
|
+
description: "Fetch the current status for a Knowhere parse job. If the job was started by an async parse tool and is done, this also caches the result locally for outline/read/grep/search.",
|
|
162
|
+
inputSchema: {
|
|
163
|
+
jobId: z.string()
|
|
164
|
+
},
|
|
165
|
+
outputSchema: objectOutputSchema
|
|
166
|
+
},
|
|
167
|
+
async (input) => createToolResult(await knowledge.getJobStatus(input.jobId))
|
|
168
|
+
);
|
|
169
|
+
server.registerTool(
|
|
170
|
+
"knowhere_async_cache_job_result",
|
|
171
|
+
{
|
|
172
|
+
description: "Manually load a completed Knowhere parse job result and cache it locally. Usually not needed for jobs started by async parse tools because knowhere_async_get_job_status auto-caches them when done.",
|
|
173
|
+
inputSchema: {
|
|
174
|
+
jobId: z.string(),
|
|
175
|
+
localDocumentId: z.string().optional(),
|
|
176
|
+
verifyChecksum: z.boolean().optional()
|
|
177
|
+
},
|
|
178
|
+
outputSchema: objectOutputSchema
|
|
179
|
+
},
|
|
180
|
+
async (input) => createToolResult(
|
|
181
|
+
await knowledge.cacheJobResult({
|
|
182
|
+
jobId: input.jobId,
|
|
183
|
+
localDocumentId: input.localDocumentId,
|
|
184
|
+
verifyChecksum: input.verifyChecksum
|
|
185
|
+
})
|
|
186
|
+
)
|
|
187
|
+
);
|
|
188
|
+
server.registerTool(
|
|
189
|
+
"knowhere_list_documents",
|
|
190
|
+
{
|
|
191
|
+
description: "List parse results cached locally by this SDK-backed MCP server.",
|
|
192
|
+
inputSchema: {},
|
|
193
|
+
outputSchema: objectOutputSchema
|
|
194
|
+
},
|
|
195
|
+
async () => createToolResult({ documents: await knowledge.listDocuments() })
|
|
196
|
+
);
|
|
197
|
+
if (hasWritePermission) {
|
|
198
|
+
server.registerTool(
|
|
199
|
+
"knowhere_delete_document",
|
|
200
|
+
{
|
|
201
|
+
description: "Archive, or soft-delete, a published Knowhere document through the Knowhere API. Provide documentId directly, or localDocumentId for a cached parse result that has a server documentId.",
|
|
202
|
+
inputSchema: {
|
|
203
|
+
documentId: z.string().optional(),
|
|
204
|
+
localDocumentId: z.string().optional()
|
|
205
|
+
},
|
|
206
|
+
outputSchema: objectOutputSchema
|
|
207
|
+
},
|
|
208
|
+
async (input) => createToolResult(await archiveDocument({ client, knowledge, params: input }))
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
server.registerTool(
|
|
212
|
+
"knowhere_get_document_outline",
|
|
213
|
+
{
|
|
214
|
+
description: "Return the local outline for a cached parsed document.",
|
|
215
|
+
inputSchema: {
|
|
216
|
+
localDocumentId: z.string()
|
|
217
|
+
},
|
|
218
|
+
outputSchema: objectOutputSchema
|
|
219
|
+
},
|
|
220
|
+
async (input) => createToolResult(await knowledge.getDocumentOutline(input.localDocumentId))
|
|
221
|
+
);
|
|
222
|
+
server.registerTool(
|
|
223
|
+
"knowhere_read_chunks",
|
|
224
|
+
{
|
|
225
|
+
description: "Read exact chunks from a cached local parse result.",
|
|
226
|
+
inputSchema: {
|
|
227
|
+
localDocumentId: z.string(),
|
|
228
|
+
sectionPath: z.string().optional(),
|
|
229
|
+
startChunk: z.number().int().positive().optional(),
|
|
230
|
+
endChunk: z.number().int().positive().optional(),
|
|
231
|
+
chunkId: z.string().optional(),
|
|
232
|
+
chunkType: z.enum(["text", "image", "table"]).optional(),
|
|
233
|
+
limit: z.number().int().positive().optional()
|
|
234
|
+
},
|
|
235
|
+
outputSchema: objectOutputSchema
|
|
236
|
+
},
|
|
237
|
+
async (input) => createToolResult(await knowledge.readChunks(input))
|
|
238
|
+
);
|
|
239
|
+
server.registerTool(
|
|
240
|
+
"knowhere_grep_chunks",
|
|
241
|
+
{
|
|
242
|
+
description: "Run grep-style literal or regex matching against cached local chunks.",
|
|
243
|
+
inputSchema: {
|
|
244
|
+
localDocumentId: z.string(),
|
|
245
|
+
pattern: z.string(),
|
|
246
|
+
isRegex: z.boolean().optional(),
|
|
247
|
+
isCaseSensitive: z.boolean().optional(),
|
|
248
|
+
maxResults: z.number().int().positive().optional(),
|
|
249
|
+
chunkType: z.enum(["text", "image", "table"]).optional(),
|
|
250
|
+
sectionPathPrefix: z.string().optional(),
|
|
251
|
+
contextChars: z.number().int().nonnegative().optional()
|
|
252
|
+
},
|
|
253
|
+
outputSchema: objectOutputSchema
|
|
254
|
+
},
|
|
255
|
+
async (input) => createToolResult(await knowledge.grepChunks(input))
|
|
256
|
+
);
|
|
257
|
+
server.registerTool(
|
|
258
|
+
"knowhere_search",
|
|
259
|
+
{
|
|
260
|
+
description: "Search published Knowhere documents with the Knowhere API retrieval query. localDocumentIds only map returned server document IDs back to local cache IDs when available.",
|
|
261
|
+
inputSchema: {
|
|
262
|
+
query: z.string(),
|
|
263
|
+
namespace: z.string().optional(),
|
|
264
|
+
topK: z.number().int().positive().optional(),
|
|
265
|
+
localDocumentIds: z.array(z.string()).optional(),
|
|
266
|
+
useAgentic: z.boolean().optional()
|
|
267
|
+
},
|
|
268
|
+
outputSchema: objectOutputSchema
|
|
269
|
+
},
|
|
270
|
+
async (input) => createToolResult(await knowledge.search(input))
|
|
271
|
+
);
|
|
272
|
+
return server;
|
|
273
|
+
}
|
|
274
|
+
async function runKnowhereMcpServer(options) {
|
|
275
|
+
const server = await createKnowhereMcpServer(options);
|
|
276
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
277
|
+
await server.connect(transport);
|
|
278
|
+
}
|
|
279
|
+
function createToolResult(result) {
|
|
280
|
+
const structuredContent = { result };
|
|
281
|
+
return {
|
|
282
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent, null, 2) }],
|
|
283
|
+
structuredContent
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function toFlatParsingParams(parsingParams) {
|
|
287
|
+
if (!parsingParams) {
|
|
288
|
+
return {};
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
model: parsingParams.model,
|
|
292
|
+
ocr: parsingParams.ocrEnabled,
|
|
293
|
+
docType: parsingParams.docType,
|
|
294
|
+
smartTitleParse: parsingParams.smartTitleParse,
|
|
295
|
+
summaryImage: parsingParams.summaryImage,
|
|
296
|
+
summaryTable: parsingParams.summaryTable,
|
|
297
|
+
summaryTxt: parsingParams.summaryTxt,
|
|
298
|
+
addFragDesc: parsingParams.addFragDesc,
|
|
299
|
+
kbDir: parsingParams.kbDir
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
async function archiveDocument(params) {
|
|
303
|
+
const archiveTarget = await resolveArchiveTarget(params.knowledge, params.params);
|
|
304
|
+
const document = await params.client.documents.archive(archiveTarget.documentId);
|
|
305
|
+
return {
|
|
306
|
+
document,
|
|
307
|
+
localDocumentId: archiveTarget.localDocumentId
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
async function resolveArchiveTarget(knowledge, params) {
|
|
311
|
+
if (params.documentId) {
|
|
312
|
+
return {
|
|
313
|
+
documentId: params.documentId,
|
|
314
|
+
localDocumentId: params.localDocumentId
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
if (!params.localDocumentId) {
|
|
318
|
+
throw new import_knowhere_sdk.ValidationError("documentId or localDocumentId is required");
|
|
319
|
+
}
|
|
320
|
+
const document = await findLocalDocument(knowledge, params.localDocumentId);
|
|
321
|
+
if (!document) {
|
|
322
|
+
throw new Error(`Local Knowhere document not found: ${params.localDocumentId}`);
|
|
323
|
+
}
|
|
324
|
+
if (!document.documentId) {
|
|
325
|
+
throw new Error(`Local Knowhere document has no server documentId: ${params.localDocumentId}`);
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
documentId: document.documentId,
|
|
329
|
+
localDocumentId: document.localDocumentId
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
async function findLocalDocument(knowledge, localDocumentId) {
|
|
333
|
+
const documents = await knowledge.listDocuments();
|
|
334
|
+
return documents.find((document) => document.localDocumentId === localDocumentId);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/auth.ts
|
|
338
|
+
var import_child_process = require("child_process");
|
|
339
|
+
var import_crypto = __toESM(require("crypto"));
|
|
340
|
+
var import_http = require("http");
|
|
341
|
+
var import_os = __toESM(require("os"));
|
|
342
|
+
var import_path = __toESM(require("path"));
|
|
343
|
+
var import_promises = require("fs/promises");
|
|
344
|
+
var import_knowhere_sdk2 = require("@ontos-ai/knowhere-sdk");
|
|
345
|
+
var DEFAULT_DASHBOARD_URL = "https://knowhereto.ai";
|
|
346
|
+
var AUTH_FILE_ENV = "KNOWHERE_MCP_AUTH_FILE";
|
|
347
|
+
var DASHBOARD_URL_ENV = "KNOWHERE_DASHBOARD_URL";
|
|
348
|
+
var API_BASE_URL_ENV = "KNOWHERE_BASE_URL";
|
|
349
|
+
var API_KEY_ENV = "KNOWHERE_API_KEY";
|
|
350
|
+
var TOKEN_REFRESH_SKEW_MS = 5 * 60 * 1e3;
|
|
351
|
+
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
352
|
+
var RANDOM_BYTE_LENGTH = 32;
|
|
353
|
+
var DEFAULT_CLIENT_NAME = "knowhere-cli";
|
|
354
|
+
var DEFAULT_PERMISSION = "full_access";
|
|
355
|
+
var PERMISSION_VALUES = /* @__PURE__ */ new Set(["read_only", "full_access"]);
|
|
356
|
+
var McpCredentialManager = class {
|
|
357
|
+
authFilePath;
|
|
358
|
+
refreshPromise;
|
|
359
|
+
constructor(authFilePath = getDefaultAuthFilePath()) {
|
|
360
|
+
this.authFilePath = authFilePath;
|
|
361
|
+
}
|
|
362
|
+
getAuthFilePath() {
|
|
363
|
+
return this.authFilePath;
|
|
364
|
+
}
|
|
365
|
+
async getStatus() {
|
|
366
|
+
if (process.env[API_KEY_ENV]) {
|
|
367
|
+
return {
|
|
368
|
+
source: "api_key",
|
|
369
|
+
authFilePath: this.authFilePath,
|
|
370
|
+
apiBaseUrl: process.env[API_BASE_URL_ENV],
|
|
371
|
+
permission: DEFAULT_PERMISSION
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
const storedAuth = await this.readAuth();
|
|
375
|
+
if (!storedAuth) {
|
|
376
|
+
return { source: "none", authFilePath: this.authFilePath };
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
source: "stored_login",
|
|
380
|
+
authFilePath: this.authFilePath,
|
|
381
|
+
dashboardUrl: storedAuth.dashboardUrl,
|
|
382
|
+
apiBaseUrl: process.env[API_BASE_URL_ENV] ?? storedAuth.apiBaseUrl,
|
|
383
|
+
permission: storedAuth.permission,
|
|
384
|
+
refreshTokenExpiresAt: storedAuth.refreshTokenExpiresAt,
|
|
385
|
+
accessTokenExpiresAt: storedAuth.accessTokenExpiresAt
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
async hasStoredLogin() {
|
|
389
|
+
return await this.readAuth() !== void 0;
|
|
390
|
+
}
|
|
391
|
+
async getAccessToken() {
|
|
392
|
+
const storedAuth = await this.readAuth();
|
|
393
|
+
if (!storedAuth) {
|
|
394
|
+
throw new import_knowhere_sdk2.ValidationError(
|
|
395
|
+
'Knowhere MCP is not logged in. Run "npx -y @ontos-ai/knowhere-mcp login" or set KNOWHERE_API_KEY.'
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
if (isUsableAccessToken(storedAuth)) {
|
|
399
|
+
return storedAuth.accessToken;
|
|
400
|
+
}
|
|
401
|
+
if (!this.refreshPromise) {
|
|
402
|
+
this.refreshPromise = this.refreshAccessToken(storedAuth).finally(() => {
|
|
403
|
+
this.refreshPromise = void 0;
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
return this.refreshPromise;
|
|
407
|
+
}
|
|
408
|
+
async login(options = {}) {
|
|
409
|
+
const dashboardUrl = normalizeDashboardUrl(
|
|
410
|
+
options.dashboardUrl ?? process.env[DASHBOARD_URL_ENV] ?? DEFAULT_DASHBOARD_URL
|
|
411
|
+
);
|
|
412
|
+
const apiBaseUrl = options.baseURL ?? process.env[API_BASE_URL_ENV];
|
|
413
|
+
const codeVerifier = createRandomToken();
|
|
414
|
+
const state = createRandomToken();
|
|
415
|
+
const codeChallenge = createPkceChallenge(codeVerifier);
|
|
416
|
+
const callback = await createLoopbackCallback(state);
|
|
417
|
+
try {
|
|
418
|
+
const loginUrl = buildLoginUrl({
|
|
419
|
+
dashboardUrl,
|
|
420
|
+
redirectUri: callback.redirectUri,
|
|
421
|
+
state,
|
|
422
|
+
codeChallenge,
|
|
423
|
+
clientName: options.clientName ?? DEFAULT_CLIENT_NAME
|
|
424
|
+
});
|
|
425
|
+
options.onLoginUrl?.(loginUrl);
|
|
426
|
+
if (options.openBrowser !== false) {
|
|
427
|
+
openBrowser(loginUrl);
|
|
428
|
+
}
|
|
429
|
+
const code = await callback.waitForCode();
|
|
430
|
+
const tokenResponse = await requestToken(dashboardUrl, {
|
|
431
|
+
grant_type: "authorization_code",
|
|
432
|
+
code,
|
|
433
|
+
code_verifier: codeVerifier,
|
|
434
|
+
client_name: options.clientName ?? DEFAULT_CLIENT_NAME
|
|
435
|
+
});
|
|
436
|
+
const storedAuth = buildStoredAuth({
|
|
437
|
+
dashboardUrl,
|
|
438
|
+
apiBaseUrl,
|
|
439
|
+
tokenResponse,
|
|
440
|
+
previous: await this.readAuth()
|
|
441
|
+
});
|
|
442
|
+
await this.writeAuth(storedAuth);
|
|
443
|
+
return {
|
|
444
|
+
authFilePath: this.authFilePath,
|
|
445
|
+
dashboardUrl,
|
|
446
|
+
apiBaseUrl,
|
|
447
|
+
permission: tokenResponse.permission,
|
|
448
|
+
refreshTokenExpiresAt: tokenResponse.refreshTokenExpiresAt
|
|
449
|
+
};
|
|
450
|
+
} finally {
|
|
451
|
+
await callback.close();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
async logout() {
|
|
455
|
+
const storedAuth = await this.readAuth();
|
|
456
|
+
let revokeError;
|
|
457
|
+
if (storedAuth) {
|
|
458
|
+
try {
|
|
459
|
+
await requestRevoke(storedAuth.dashboardUrl, storedAuth.refreshToken);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
revokeError = error instanceof Error ? error.message : "Failed to revoke MCP login";
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
await (0, import_promises.rm)(this.authFilePath, { force: true });
|
|
465
|
+
return {
|
|
466
|
+
authFilePath: this.authFilePath,
|
|
467
|
+
hadStoredLogin: storedAuth !== void 0,
|
|
468
|
+
revokeError
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
async resolveBaseURL() {
|
|
472
|
+
return process.env[API_BASE_URL_ENV] ?? (await this.readAuth())?.apiBaseUrl;
|
|
473
|
+
}
|
|
474
|
+
async refreshAccessToken(storedAuth) {
|
|
475
|
+
const tokenResponse = await requestToken(storedAuth.dashboardUrl, {
|
|
476
|
+
grant_type: "refresh_token",
|
|
477
|
+
refresh_token: storedAuth.refreshToken
|
|
478
|
+
});
|
|
479
|
+
const refreshedAuth = buildStoredAuth({
|
|
480
|
+
dashboardUrl: storedAuth.dashboardUrl,
|
|
481
|
+
apiBaseUrl: storedAuth.apiBaseUrl,
|
|
482
|
+
tokenResponse,
|
|
483
|
+
previous: storedAuth
|
|
484
|
+
});
|
|
485
|
+
await this.writeAuth(refreshedAuth);
|
|
486
|
+
return refreshedAuth.accessToken ?? "";
|
|
487
|
+
}
|
|
488
|
+
async readAuth() {
|
|
489
|
+
try {
|
|
490
|
+
const rawAuth = await (0, import_promises.readFile)(this.authFilePath, "utf8");
|
|
491
|
+
return parseStoredAuth(JSON.parse(rawAuth));
|
|
492
|
+
} catch (error) {
|
|
493
|
+
if (isFileMissingError(error)) {
|
|
494
|
+
return void 0;
|
|
495
|
+
}
|
|
496
|
+
throw error;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async writeAuth(storedAuth) {
|
|
500
|
+
await (0, import_promises.mkdir)(import_path.default.dirname(this.authFilePath), { recursive: true, mode: 448 });
|
|
501
|
+
await (0, import_promises.writeFile)(this.authFilePath, `${JSON.stringify(storedAuth, null, 2)}
|
|
502
|
+
`, {
|
|
503
|
+
mode: 384
|
|
504
|
+
});
|
|
505
|
+
await (0, import_promises.chmod)(this.authFilePath, 384);
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
function getDefaultAuthFilePath() {
|
|
509
|
+
return process.env[AUTH_FILE_ENV] ?? import_path.default.join(import_os.default.homedir(), ".knowhere-node-sdk", "mcp", "auth.json");
|
|
510
|
+
}
|
|
511
|
+
function isUsableAccessToken(storedAuth) {
|
|
512
|
+
if (!storedAuth.accessToken || !storedAuth.accessTokenExpiresAt) {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
const expiresAt = Date.parse(storedAuth.accessTokenExpiresAt);
|
|
516
|
+
return Number.isFinite(expiresAt) && expiresAt - Date.now() > TOKEN_REFRESH_SKEW_MS;
|
|
517
|
+
}
|
|
518
|
+
function buildStoredAuth({
|
|
519
|
+
dashboardUrl,
|
|
520
|
+
apiBaseUrl,
|
|
521
|
+
tokenResponse,
|
|
522
|
+
previous
|
|
523
|
+
}) {
|
|
524
|
+
const now = /* @__PURE__ */ new Date();
|
|
525
|
+
const accessTokenExpiresAt = new Date(
|
|
526
|
+
now.getTime() + tokenResponse.expiresInSeconds * 1e3
|
|
527
|
+
).toISOString();
|
|
528
|
+
const refreshToken = tokenResponse.refreshToken ?? previous?.refreshToken;
|
|
529
|
+
if (!refreshToken) {
|
|
530
|
+
throw new Error("Knowhere MCP token response did not include a refresh token");
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
dashboardUrl,
|
|
534
|
+
apiBaseUrl,
|
|
535
|
+
permission: tokenResponse.permission,
|
|
536
|
+
refreshToken,
|
|
537
|
+
refreshTokenExpiresAt: tokenResponse.refreshTokenExpiresAt ?? previous?.refreshTokenExpiresAt,
|
|
538
|
+
accessToken: tokenResponse.accessToken,
|
|
539
|
+
accessTokenExpiresAt,
|
|
540
|
+
createdAt: previous?.createdAt ?? now.toISOString(),
|
|
541
|
+
updatedAt: now.toISOString()
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
function parseStoredAuth(value) {
|
|
545
|
+
if (!isRecord(value)) {
|
|
546
|
+
throw new Error("Invalid Knowhere MCP auth file");
|
|
547
|
+
}
|
|
548
|
+
const dashboardUrl = readRequiredString(value, "dashboardUrl");
|
|
549
|
+
const refreshToken = readRequiredString(value, "refreshToken");
|
|
550
|
+
const createdAt = readRequiredString(value, "createdAt");
|
|
551
|
+
const updatedAt = readRequiredString(value, "updatedAt");
|
|
552
|
+
return {
|
|
553
|
+
dashboardUrl,
|
|
554
|
+
apiBaseUrl: readOptionalString(value, "apiBaseUrl"),
|
|
555
|
+
permission: normalizePermission(readOptionalString(value, "permission")),
|
|
556
|
+
refreshToken,
|
|
557
|
+
refreshTokenExpiresAt: readOptionalString(value, "refreshTokenExpiresAt"),
|
|
558
|
+
accessToken: readOptionalString(value, "accessToken"),
|
|
559
|
+
accessTokenExpiresAt: readOptionalString(value, "accessTokenExpiresAt"),
|
|
560
|
+
createdAt,
|
|
561
|
+
updatedAt
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function readRequiredString(value, key) {
|
|
565
|
+
const field = value[key];
|
|
566
|
+
if (typeof field !== "string" || field.length === 0) {
|
|
567
|
+
throw new Error(`Invalid Knowhere MCP auth file: ${key} is required`);
|
|
568
|
+
}
|
|
569
|
+
return field;
|
|
570
|
+
}
|
|
571
|
+
function readOptionalString(value, key) {
|
|
572
|
+
const field = value[key];
|
|
573
|
+
return typeof field === "string" && field.length > 0 ? field : void 0;
|
|
574
|
+
}
|
|
575
|
+
function readRequiredPermission(value, key) {
|
|
576
|
+
const field = value[key];
|
|
577
|
+
if (typeof field !== "string" || field.length === 0) {
|
|
578
|
+
throw new Error(`Invalid Knowhere MCP token response: ${key} is required`);
|
|
579
|
+
}
|
|
580
|
+
if (!PERMISSION_VALUES.has(field)) {
|
|
581
|
+
throw new Error(`Invalid Knowhere MCP token response: ${key} is invalid`);
|
|
582
|
+
}
|
|
583
|
+
return field;
|
|
584
|
+
}
|
|
585
|
+
function createPkceChallenge(codeVerifier) {
|
|
586
|
+
return import_crypto.default.createHash("sha256").update(codeVerifier).digest("base64url");
|
|
587
|
+
}
|
|
588
|
+
function buildLoginUrl({
|
|
589
|
+
dashboardUrl,
|
|
590
|
+
redirectUri,
|
|
591
|
+
state,
|
|
592
|
+
codeChallenge,
|
|
593
|
+
clientName
|
|
594
|
+
}) {
|
|
595
|
+
const url = new URL("/mcp/login", dashboardUrl);
|
|
596
|
+
url.searchParams.set("redirect_uri", redirectUri);
|
|
597
|
+
url.searchParams.set("state", state);
|
|
598
|
+
url.searchParams.set("code_challenge", codeChallenge);
|
|
599
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
600
|
+
url.searchParams.set("client_name", clientName);
|
|
601
|
+
return url.toString();
|
|
602
|
+
}
|
|
603
|
+
async function createLoopbackCallback(expectedState) {
|
|
604
|
+
let resolveCode;
|
|
605
|
+
let rejectCode;
|
|
606
|
+
let isSettled = false;
|
|
607
|
+
const codePromise = new Promise((resolve, reject) => {
|
|
608
|
+
resolveCode = resolve;
|
|
609
|
+
rejectCode = reject;
|
|
610
|
+
});
|
|
611
|
+
const timeout = setTimeout(() => {
|
|
612
|
+
if (!isSettled) {
|
|
613
|
+
isSettled = true;
|
|
614
|
+
rejectCode?.(new Error("Timed out waiting for Dashboard login callback"));
|
|
615
|
+
}
|
|
616
|
+
}, LOGIN_TIMEOUT_MS);
|
|
617
|
+
const server = (0, import_http.createServer)((request, response) => {
|
|
618
|
+
handleCallbackRequest({
|
|
619
|
+
request,
|
|
620
|
+
response,
|
|
621
|
+
expectedState,
|
|
622
|
+
resolveCode: (code) => {
|
|
623
|
+
if (!isSettled) {
|
|
624
|
+
isSettled = true;
|
|
625
|
+
clearTimeout(timeout);
|
|
626
|
+
resolveCode?.(code);
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
rejectCode: (error) => {
|
|
630
|
+
if (!isSettled) {
|
|
631
|
+
isSettled = true;
|
|
632
|
+
clearTimeout(timeout);
|
|
633
|
+
rejectCode?.(error);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
await new Promise((resolve, reject) => {
|
|
639
|
+
server.once("error", reject);
|
|
640
|
+
server.listen(0, "127.0.0.1", () => resolve());
|
|
641
|
+
});
|
|
642
|
+
const address = server.address();
|
|
643
|
+
if (!address || typeof address === "string") {
|
|
644
|
+
throw new Error("Failed to start loopback login callback server");
|
|
645
|
+
}
|
|
646
|
+
return {
|
|
647
|
+
redirectUri: `http://127.0.0.1:${address.port}/callback`,
|
|
648
|
+
waitForCode: () => codePromise,
|
|
649
|
+
close: () => new Promise((resolve, reject) => {
|
|
650
|
+
clearTimeout(timeout);
|
|
651
|
+
server.close((error) => {
|
|
652
|
+
if (error) {
|
|
653
|
+
reject(error);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
resolve();
|
|
657
|
+
});
|
|
658
|
+
})
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
function handleCallbackRequest({
|
|
662
|
+
request,
|
|
663
|
+
response,
|
|
664
|
+
expectedState,
|
|
665
|
+
resolveCode,
|
|
666
|
+
rejectCode
|
|
667
|
+
}) {
|
|
668
|
+
const requestUrl = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
669
|
+
if (requestUrl.pathname !== "/callback") {
|
|
670
|
+
response.writeHead(404);
|
|
671
|
+
response.end("Not found");
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const code = requestUrl.searchParams.get("code");
|
|
675
|
+
const callbackError = requestUrl.searchParams.get("error");
|
|
676
|
+
const state = requestUrl.searchParams.get("state");
|
|
677
|
+
if (callbackError) {
|
|
678
|
+
response.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
|
|
679
|
+
response.end("Knowhere MCP login was denied. You can close this window.");
|
|
680
|
+
rejectCode(new Error(`Dashboard login failed: ${callbackError}`));
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (!code || state !== expectedState) {
|
|
684
|
+
response.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
|
|
685
|
+
response.end("Knowhere MCP login failed. You can close this window.");
|
|
686
|
+
rejectCode(new Error("Dashboard login callback did not include a valid code and state"));
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
690
|
+
response.end(
|
|
691
|
+
"<!doctype html><title>Knowhere MCP</title><p>Knowhere MCP login complete. You can close this window.</p>"
|
|
692
|
+
);
|
|
693
|
+
resolveCode(code);
|
|
694
|
+
}
|
|
695
|
+
async function requestToken(dashboardUrl, body) {
|
|
696
|
+
const response = await fetch(new URL("/api/mcp/token", dashboardUrl), {
|
|
697
|
+
method: "POST",
|
|
698
|
+
headers: { "Content-Type": "application/json" },
|
|
699
|
+
body: JSON.stringify(body)
|
|
700
|
+
});
|
|
701
|
+
const responseBody = await readJsonResponse(response);
|
|
702
|
+
if (!response.ok) {
|
|
703
|
+
throw new Error(getResponseMessage(responseBody, "Knowhere MCP token request failed"));
|
|
704
|
+
}
|
|
705
|
+
return parseTokenResponse(responseBody);
|
|
706
|
+
}
|
|
707
|
+
async function requestRevoke(dashboardUrl, refreshToken) {
|
|
708
|
+
const response = await fetch(new URL("/api/mcp/revoke", dashboardUrl), {
|
|
709
|
+
method: "POST",
|
|
710
|
+
headers: { "Content-Type": "application/json" },
|
|
711
|
+
body: JSON.stringify({ refresh_token: refreshToken })
|
|
712
|
+
});
|
|
713
|
+
const responseBody = await readJsonResponse(response);
|
|
714
|
+
if (!response.ok) {
|
|
715
|
+
throw new Error(getResponseMessage(responseBody, "Knowhere MCP revoke request failed"));
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
async function readJsonResponse(response) {
|
|
719
|
+
try {
|
|
720
|
+
return await response.json();
|
|
721
|
+
} catch {
|
|
722
|
+
return void 0;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function parseTokenResponse(value) {
|
|
726
|
+
if (!isRecord(value)) {
|
|
727
|
+
throw new Error("Invalid Knowhere MCP token response");
|
|
728
|
+
}
|
|
729
|
+
const accessToken = readRequiredString(value, "accessToken");
|
|
730
|
+
const tokenType = readRequiredString(value, "tokenType");
|
|
731
|
+
const expiresInSeconds = value.expiresInSeconds;
|
|
732
|
+
const permission = readRequiredPermission(value, "permission");
|
|
733
|
+
if (tokenType !== "Bearer") {
|
|
734
|
+
throw new Error("Invalid Knowhere MCP token type");
|
|
735
|
+
}
|
|
736
|
+
if (typeof expiresInSeconds !== "number" || !Number.isFinite(expiresInSeconds)) {
|
|
737
|
+
throw new Error("Invalid Knowhere MCP token expiry");
|
|
738
|
+
}
|
|
739
|
+
return {
|
|
740
|
+
accessToken,
|
|
741
|
+
expiresInSeconds,
|
|
742
|
+
permission,
|
|
743
|
+
refreshToken: readOptionalString(value, "refreshToken"),
|
|
744
|
+
refreshTokenExpiresAt: readOptionalString(value, "refreshTokenExpiresAt"),
|
|
745
|
+
tokenType
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function getResponseMessage(responseBody, fallback) {
|
|
749
|
+
if (isRecord(responseBody) && typeof responseBody.message === "string") {
|
|
750
|
+
return responseBody.message;
|
|
751
|
+
}
|
|
752
|
+
return fallback;
|
|
753
|
+
}
|
|
754
|
+
function normalizeDashboardUrl(value) {
|
|
755
|
+
const url = new URL(value);
|
|
756
|
+
url.pathname = "/";
|
|
757
|
+
url.search = "";
|
|
758
|
+
url.hash = "";
|
|
759
|
+
return url.toString();
|
|
760
|
+
}
|
|
761
|
+
function normalizePermission(value) {
|
|
762
|
+
if (value && PERMISSION_VALUES.has(value)) {
|
|
763
|
+
return value;
|
|
764
|
+
}
|
|
765
|
+
return DEFAULT_PERMISSION;
|
|
766
|
+
}
|
|
767
|
+
function openBrowser(url) {
|
|
768
|
+
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
769
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
770
|
+
const child = (0, import_child_process.spawn)(command, args, {
|
|
771
|
+
detached: true,
|
|
772
|
+
stdio: "ignore"
|
|
773
|
+
});
|
|
774
|
+
child.unref();
|
|
775
|
+
}
|
|
776
|
+
function createRandomToken() {
|
|
777
|
+
return import_crypto.default.randomBytes(RANDOM_BYTE_LENGTH).toString("base64url");
|
|
778
|
+
}
|
|
779
|
+
function isRecord(value) {
|
|
780
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
781
|
+
}
|
|
782
|
+
function isFileMissingError(error) {
|
|
783
|
+
return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/stdio.ts
|
|
787
|
+
async function main() {
|
|
788
|
+
const [command, ...args] = process.argv.slice(2);
|
|
789
|
+
switch (command) {
|
|
790
|
+
case void 0:
|
|
791
|
+
case "serve":
|
|
792
|
+
await runKnowhereMcpServerWithAuth();
|
|
793
|
+
return;
|
|
794
|
+
case "login":
|
|
795
|
+
await runLogin(args);
|
|
796
|
+
return;
|
|
797
|
+
case "logout":
|
|
798
|
+
await runLogout();
|
|
799
|
+
return;
|
|
800
|
+
case "status":
|
|
801
|
+
await runStatus();
|
|
802
|
+
return;
|
|
803
|
+
case "--help":
|
|
804
|
+
case "-h":
|
|
805
|
+
case "help":
|
|
806
|
+
printHelp();
|
|
807
|
+
return;
|
|
808
|
+
default:
|
|
809
|
+
throw new Error(`Unknown knowhere-mcp command: ${command}`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
async function runKnowhereMcpServerWithAuth() {
|
|
813
|
+
const credentialManager = new McpCredentialManager();
|
|
814
|
+
const status = await credentialManager.getStatus();
|
|
815
|
+
if (process.env.KNOWHERE_API_KEY) {
|
|
816
|
+
await runKnowhereMcpServer({ permission: status.permission });
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
await runKnowhereMcpServer({
|
|
820
|
+
authTokenProvider: () => credentialManager.getAccessToken(),
|
|
821
|
+
baseURL: await credentialManager.resolveBaseURL(),
|
|
822
|
+
permission: status.permission,
|
|
823
|
+
recoverPendingJobsOnStart: status.source === "stored_login"
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
async function runLogin(args) {
|
|
827
|
+
const options = parseLoginArgs(args);
|
|
828
|
+
const credentialManager = new McpCredentialManager(options.authFilePath);
|
|
829
|
+
const result = await credentialManager.login({
|
|
830
|
+
dashboardUrl: options.dashboardUrl,
|
|
831
|
+
baseURL: options.baseURL,
|
|
832
|
+
openBrowser: options.openBrowser,
|
|
833
|
+
clientName: options.clientName,
|
|
834
|
+
onLoginUrl: (url) => {
|
|
835
|
+
console.log(`Open this URL to log in to Knowhere:
|
|
836
|
+
${url}`);
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
console.log(`Knowhere MCP login saved to ${result.authFilePath}`);
|
|
840
|
+
console.log(`Permission: ${result.permission}`);
|
|
841
|
+
if (result.refreshTokenExpiresAt) {
|
|
842
|
+
console.log(`Refresh token expires at ${result.refreshTokenExpiresAt}`);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
async function runLogout() {
|
|
846
|
+
const credentialManager = new McpCredentialManager();
|
|
847
|
+
const result = await credentialManager.logout();
|
|
848
|
+
if (result.revokeError) {
|
|
849
|
+
console.error(
|
|
850
|
+
`Knowhere MCP login was removed locally, but revoke failed: ${result.revokeError}`
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
console.log(
|
|
854
|
+
result.hadStoredLogin ? `Knowhere MCP login removed from ${result.authFilePath}` : `No Knowhere MCP login found at ${result.authFilePath}`
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
async function runStatus() {
|
|
858
|
+
const credentialManager = new McpCredentialManager();
|
|
859
|
+
const status = await credentialManager.getStatus();
|
|
860
|
+
console.log(formatStatus(status));
|
|
861
|
+
}
|
|
862
|
+
function parseLoginArgs(args) {
|
|
863
|
+
const options = {
|
|
864
|
+
openBrowser: true
|
|
865
|
+
};
|
|
866
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
867
|
+
const arg = args[index];
|
|
868
|
+
switch (arg) {
|
|
869
|
+
case "--dashboard-url":
|
|
870
|
+
options.dashboardUrl = readFlagValue(args, index, arg);
|
|
871
|
+
index += 1;
|
|
872
|
+
break;
|
|
873
|
+
case "--base-url":
|
|
874
|
+
options.baseURL = readFlagValue(args, index, arg);
|
|
875
|
+
index += 1;
|
|
876
|
+
break;
|
|
877
|
+
case "--auth-file":
|
|
878
|
+
options.authFilePath = readFlagValue(args, index, arg);
|
|
879
|
+
index += 1;
|
|
880
|
+
break;
|
|
881
|
+
case "--client-name":
|
|
882
|
+
options.clientName = readFlagValue(args, index, arg);
|
|
883
|
+
index += 1;
|
|
884
|
+
break;
|
|
885
|
+
case "--no-open":
|
|
886
|
+
options.openBrowser = false;
|
|
887
|
+
break;
|
|
888
|
+
default:
|
|
889
|
+
throw new Error(`Unknown login option: ${arg}`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return options;
|
|
893
|
+
}
|
|
894
|
+
function readFlagValue(args, index, flag) {
|
|
895
|
+
const value = args[index + 1];
|
|
896
|
+
if (!value || value.startsWith("--")) {
|
|
897
|
+
throw new Error(`${flag} requires a value`);
|
|
898
|
+
}
|
|
899
|
+
return value;
|
|
900
|
+
}
|
|
901
|
+
function formatStatus(status) {
|
|
902
|
+
switch (status.source) {
|
|
903
|
+
case "api_key":
|
|
904
|
+
return [
|
|
905
|
+
"Knowhere MCP is authenticated with KNOWHERE_API_KEY",
|
|
906
|
+
`Auth file: ${status.authFilePath}`,
|
|
907
|
+
`Permission: ${status.permission ?? "full_access"}`
|
|
908
|
+
].join("\n");
|
|
909
|
+
case "stored_login":
|
|
910
|
+
return [
|
|
911
|
+
"Knowhere MCP is authenticated with dashboard login",
|
|
912
|
+
`Auth file: ${status.authFilePath}`,
|
|
913
|
+
`Dashboard: ${status.dashboardUrl ?? "unknown"}`,
|
|
914
|
+
`API base URL: ${status.apiBaseUrl ?? "default"}`,
|
|
915
|
+
`Permission: ${status.permission ?? "full_access"}`,
|
|
916
|
+
`Refresh token expires: ${status.refreshTokenExpiresAt ?? "unknown"}`,
|
|
917
|
+
`Access token expires: ${status.accessTokenExpiresAt ?? "not cached"}`
|
|
918
|
+
].join("\n");
|
|
919
|
+
case "none":
|
|
920
|
+
return `Knowhere MCP is not logged in
|
|
921
|
+
Run: npx -y @ontos-ai/knowhere-mcp login
|
|
922
|
+
Auth file: ${status.authFilePath}`;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
function printHelp() {
|
|
926
|
+
console.log(`Usage:
|
|
927
|
+
knowhere-mcp Run the stdio MCP server
|
|
928
|
+
knowhere-mcp serve Run the stdio MCP server
|
|
929
|
+
knowhere-mcp login Log in through the Knowhere dashboard
|
|
930
|
+
knowhere-mcp status Show local MCP auth status
|
|
931
|
+
knowhere-mcp logout Remove and revoke local MCP login
|
|
932
|
+
|
|
933
|
+
Login options:
|
|
934
|
+
--dashboard-url <url> Dashboard URL, defaults to KNOWHERE_DASHBOARD_URL or https://knowhereto.ai
|
|
935
|
+
--base-url <url> Knowhere API URL, defaults to KNOWHERE_BASE_URL or SDK default
|
|
936
|
+
--auth-file <path> Override local auth file path
|
|
937
|
+
--client-name <name> Label shown in dashboard token records
|
|
938
|
+
--no-open Print login URL without opening a browser`);
|
|
939
|
+
}
|
|
940
|
+
main().catch((error) => {
|
|
941
|
+
console.error(error);
|
|
942
|
+
process.exit(1);
|
|
943
|
+
});
|