@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/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
+ });