@hiai-gg/docsmint 0.10.0 → 1.0.2
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 +11 -11
- package/dist/backend/index.js +1 -1
- package/dist/mcp-cli.js +279 -171
- package/dist/mcp-server.js +282 -172
- package/package.json +1 -1
- package/packages/cli/src/index.ts +1 -1
- package/server.json +3 -3
package/dist/mcp-server.js
CHANGED
|
@@ -571,7 +571,7 @@ var RESOURCE_PERMISSIONS = new Set([
|
|
|
571
571
|
"write"
|
|
572
572
|
]);
|
|
573
573
|
// ../mcp-server/src/compact.ts
|
|
574
|
-
import { z as
|
|
574
|
+
import { z as z3 } from "zod";
|
|
575
575
|
|
|
576
576
|
// ../mcp-server/src/output-schemas.ts
|
|
577
577
|
import { z } from "zod";
|
|
@@ -850,140 +850,339 @@ var toolOutputSchemas = {
|
|
|
850
850
|
explore_graph: z.union([
|
|
851
851
|
variant("neighbors", operationOutputSchemas.get_related_documents),
|
|
852
852
|
variant("search", operationOutputSchemas.search_knowledge_graph)
|
|
853
|
+
]),
|
|
854
|
+
batch_documents: z.looseObject({
|
|
855
|
+
action: z.enum(["move", "set_category", "add_tag", "remove_tag", "trash", "restore", "refresh_index"]),
|
|
856
|
+
total: z.number().int().min(1).max(25),
|
|
857
|
+
succeeded: z.number().int().nonnegative(),
|
|
858
|
+
failed: z.number().int().nonnegative(),
|
|
859
|
+
aborted: z.boolean(),
|
|
860
|
+
results: z.array(z.union([
|
|
861
|
+
z.looseObject({ id: z.string().uuid(), status: z.literal("ok") }),
|
|
862
|
+
z.looseObject({ id: z.string().uuid(), status: z.literal("error"), error: z.looseObject({ status: z.number().int(), code: z.string(), message: z.string() }) }),
|
|
863
|
+
z.looseObject({ id: z.string().uuid(), status: z.literal("skipped") })
|
|
864
|
+
]))
|
|
865
|
+
}),
|
|
866
|
+
get_workspace_item: z.union([
|
|
867
|
+
variant("folder", folderSchema),
|
|
868
|
+
variant("category", categoryListItemSchema),
|
|
869
|
+
variant("tag", tagSchema)
|
|
853
870
|
])
|
|
854
871
|
};
|
|
855
872
|
|
|
873
|
+
// ../mcp-server/src/batch-workspace.ts
|
|
874
|
+
import { z as z2 } from "zod";
|
|
875
|
+
|
|
876
|
+
// ../mcp-server/src/client.ts
|
|
877
|
+
var DEFAULT_BASE_URL = "http://localhost:50700";
|
|
878
|
+
function readConfig() {
|
|
879
|
+
const baseUrl = (process.env.HIAI_DOCS_URL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
880
|
+
const apiKey = process.env.HIAI_DOCS_API_KEY;
|
|
881
|
+
return { baseUrl, apiKey: apiKey || undefined };
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
class HiaiDocsError extends Error {
|
|
885
|
+
status;
|
|
886
|
+
body;
|
|
887
|
+
code;
|
|
888
|
+
constructor(message, status, body, code = `http_${status}`) {
|
|
889
|
+
super(message);
|
|
890
|
+
this.status = status;
|
|
891
|
+
this.body = body;
|
|
892
|
+
this.code = code;
|
|
893
|
+
this.name = "HiaiDocsError";
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
function sanitizeMcpRequestContext(context) {
|
|
897
|
+
if (!context)
|
|
898
|
+
return;
|
|
899
|
+
const headers = new Headers(context.headers);
|
|
900
|
+
if (context.workspaceAssertion) {
|
|
901
|
+
headers.delete("Authorization");
|
|
902
|
+
headers.delete("Cookie");
|
|
903
|
+
}
|
|
904
|
+
return {
|
|
905
|
+
...context,
|
|
906
|
+
authorization: context.workspaceAssertion ? undefined : context.authorization,
|
|
907
|
+
cookie: context.workspaceAssertion ? undefined : context.cookie,
|
|
908
|
+
headers
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
function createMcpDocsClient(docsClient, requestContext) {
|
|
912
|
+
const context = sanitizeMcpRequestContext(requestContext);
|
|
913
|
+
return {
|
|
914
|
+
getFolder: (id) => docsClient.getFolder(id, context),
|
|
915
|
+
createTag: (input) => docsClient.createTag(input, context),
|
|
916
|
+
updateTag: (id, input) => docsClient.updateTag(id, input, context),
|
|
917
|
+
deleteTag: (id) => docsClient.deleteTag(id, context),
|
|
918
|
+
addTagToDocument: (documentId, tagId) => docsClient.addTagToDoc(documentId, tagId, context),
|
|
919
|
+
removeTagFromDocument: (documentId, tagId) => docsClient.removeTagFromDoc(documentId, tagId, context),
|
|
920
|
+
updateFolder: (id, input) => docsClient.updateFolder(id, input, context),
|
|
921
|
+
updateCategory: (id, input) => docsClient.updateCategory(id, input, context),
|
|
922
|
+
listTrash: () => docsClient.listTrash(context),
|
|
923
|
+
restoreTrashedDocument: (id) => docsClient.restoreTrashedDocument(id, context),
|
|
924
|
+
permanentlyDeleteDocument: (id) => docsClient.permanentlyDeleteDocument(id, context),
|
|
925
|
+
deleteDocument: (id) => docsClient.deleteDoc(id, context),
|
|
926
|
+
deleteFolder: (id) => docsClient.deleteFolder(id, context),
|
|
927
|
+
deleteCategory: (id) => docsClient.deleteCategory(id, context),
|
|
928
|
+
restoreDocumentVersion: (documentId, versionId) => docsClient.restoreVersion(documentId, versionId, context),
|
|
929
|
+
search: (params) => docsClient.search(params.query, {
|
|
930
|
+
folder: params.folder,
|
|
931
|
+
tags: params.tags?.join(","),
|
|
932
|
+
limit: params.limit
|
|
933
|
+
}, context),
|
|
934
|
+
getDocument: (id) => docsClient.getDoc(id, context),
|
|
935
|
+
createDocument: (input) => docsClient.createDoc({
|
|
936
|
+
...input,
|
|
937
|
+
folderId: input.folderId ?? undefined,
|
|
938
|
+
categoryId: input.categoryId ?? undefined
|
|
939
|
+
}, context),
|
|
940
|
+
updateDocument: (id, input) => docsClient.updateDoc(id, input, context),
|
|
941
|
+
listDocuments: (params) => docsClient.listDocs(params, context),
|
|
942
|
+
listFolders: (params) => docsClient.listFolders(params.parentId, context),
|
|
943
|
+
createFolder: (input) => docsClient.createFolder(input, context),
|
|
944
|
+
listCategories: () => docsClient.listCategories(context),
|
|
945
|
+
createCategory: (input) => docsClient.createCategory(input, context),
|
|
946
|
+
listTags: () => docsClient.listTags(context),
|
|
947
|
+
getRelatedDocuments: (documentId, limit) => docsClient.getRelatedDocuments(documentId, context, { limit }),
|
|
948
|
+
searchGraph: (input) => docsClient.searchGraph({ query: input.query, docIds: input.docIds, maxResults: input.limit }, context),
|
|
949
|
+
getDocumentIndexStatus: (documentId) => docsClient.getDocumentIndexStatus(documentId, context),
|
|
950
|
+
refreshDocumentIndex: (documentId) => docsClient.refreshDocumentIndex(documentId, context),
|
|
951
|
+
createSnapshot: (documentId, input) => docsClient.createSnapshot(documentId, input, context),
|
|
952
|
+
getVersionHistory: (documentId, onlySnapshots) => docsClient.listVersions(documentId, { onlySnapshots }, context),
|
|
953
|
+
exportDocument: async (id) => ({ markdown: await docsClient.exportDoc(id, context) })
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
function createDefaultDocsClient() {
|
|
957
|
+
const { baseUrl, apiKey } = readConfig();
|
|
958
|
+
return new DocsClient({ baseUrl, apiKey, retries: 1 });
|
|
959
|
+
}
|
|
960
|
+
var client = new Proxy({}, {
|
|
961
|
+
get(_target, property) {
|
|
962
|
+
const configured = createMcpDocsClient(createDefaultDocsClient());
|
|
963
|
+
const value = configured[property];
|
|
964
|
+
return typeof value === "function" ? value.bind(configured) : value;
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
// ../mcp-server/src/batch-workspace.ts
|
|
969
|
+
var uuid = z2.string().uuid().describe("UUID of an item visible to the active API key.");
|
|
970
|
+
var documentIds = z2.array(uuid).min(1).max(25).refine((ids) => new Set(ids).size === ids.length, "Document IDs must be unique.").describe("One to 25 distinct document UUIDs, explicitly selected by the user; no search expansion.");
|
|
971
|
+
var base = { documentIds };
|
|
972
|
+
var batchInput = z2.discriminatedUnion("action", [
|
|
973
|
+
z2.strictObject({ ...base, action: z2.literal("move").describe("Move each document to the same folder."), folderId: uuid.nullable().describe("Destination folder UUID, or null for no folder.") }),
|
|
974
|
+
z2.strictObject({ ...base, action: z2.literal("set_category").describe("Set one explicit category on every document."), categoryId: uuid.nullable().describe("Category UUID, or null to clear explicit category.") }),
|
|
975
|
+
z2.strictObject({ ...base, action: z2.literal("add_tag").describe("Assign one existing tag to every document."), tagId: uuid.describe("Existing tag UUID to assign to each document.") }),
|
|
976
|
+
z2.strictObject({ ...base, action: z2.literal("remove_tag").describe("Remove one tag assignment from every document."), tagId: uuid.describe("Existing tag UUID to unassign from each document.") }),
|
|
977
|
+
z2.strictObject({ ...base, action: z2.literal("trash").describe("Move every document to trash without permanent deletion.") }),
|
|
978
|
+
z2.strictObject({ ...base, action: z2.literal("restore").describe("Restore every selected document from trash.") }),
|
|
979
|
+
z2.strictObject({ ...base, action: z2.literal("refresh_index").describe("Request explicit indexing retry for every document.") })
|
|
980
|
+
]);
|
|
981
|
+
function required(method, name) {
|
|
982
|
+
if (!method)
|
|
983
|
+
throw new Error(`The injected legacy MCP client does not support ${name}; use a public DocsClient.`);
|
|
984
|
+
return method;
|
|
985
|
+
}
|
|
986
|
+
function registerBatchAndWorkspaceCapabilities(server, client2, wrap) {
|
|
987
|
+
async function batch(input) {
|
|
988
|
+
const results = [];
|
|
989
|
+
let aborted = false;
|
|
990
|
+
for (const id of input.documentIds) {
|
|
991
|
+
if (aborted) {
|
|
992
|
+
results.push({ id, status: "skipped" });
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
try {
|
|
996
|
+
switch (input.action) {
|
|
997
|
+
case "move":
|
|
998
|
+
await client2.updateDocument(id, { folderId: input.folderId });
|
|
999
|
+
break;
|
|
1000
|
+
case "set_category":
|
|
1001
|
+
await client2.updateDocument(id, { categoryId: input.categoryId });
|
|
1002
|
+
break;
|
|
1003
|
+
case "add_tag":
|
|
1004
|
+
await required(client2.addTagToDocument, "add_tag")(id, input.tagId);
|
|
1005
|
+
break;
|
|
1006
|
+
case "remove_tag":
|
|
1007
|
+
await required(client2.removeTagFromDocument, "remove_tag")(id, input.tagId);
|
|
1008
|
+
break;
|
|
1009
|
+
case "trash":
|
|
1010
|
+
await required(client2.deleteDocument, "trash")(id);
|
|
1011
|
+
break;
|
|
1012
|
+
case "restore":
|
|
1013
|
+
await required(client2.restoreTrashedDocument, "restore")(id);
|
|
1014
|
+
break;
|
|
1015
|
+
case "refresh_index":
|
|
1016
|
+
await client2.refreshDocumentIndex(id);
|
|
1017
|
+
break;
|
|
1018
|
+
}
|
|
1019
|
+
results.push({ id, status: "ok" });
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
const known = isDocsApiError(error) || error instanceof HiaiDocsError;
|
|
1022
|
+
const status = known ? error.status : 500;
|
|
1023
|
+
const code = known ? error.code : "INTERNAL_ERROR";
|
|
1024
|
+
results.push({ id, status: "error", error: { status, code, message: known ? error.message : "Document operation failed" } });
|
|
1025
|
+
if (status === 401 || status === 429)
|
|
1026
|
+
aborted = true;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
const succeeded = results.filter((result) => result.status === "ok").length;
|
|
1030
|
+
return { action: input.action, total: input.documentIds.length, succeeded, failed: results.filter((result) => result.status === "error").length, aborted, results };
|
|
1031
|
+
}
|
|
1032
|
+
server.registerTool("batch_documents", {
|
|
1033
|
+
description: "Use for multiple explicitly selected documents that need the same action; for one document prefer the matching single-document tool. Accepts 1\u201325 IDs for automation compatibility. Supports move, set_category, add_tag, remove_tag, trash, restore, or refresh_index. Processes sequentially through the existing API with a permission check per document and a per-ID result; partial success is possible. Stops after authentication or rate-limit failure. Never expands a search result or permanently purges documents; version restore is separate.",
|
|
1034
|
+
inputSchema: batchInput,
|
|
1035
|
+
outputSchema: toolOutputSchemas.batch_documents,
|
|
1036
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }
|
|
1037
|
+
}, wrap("batch_documents", batch, toolOutputSchemas.batch_documents));
|
|
1038
|
+
server.registerTool("get_workspace_item", {
|
|
1039
|
+
description: "Read one visible folder, category, or tag by UUID. Folder uses its direct API getter; category and tag lookup uses the complete permission-filtered lists. An absent or out-of-scope ID is reported as not found. Use list_workspace_structure to discover IDs first.",
|
|
1040
|
+
inputSchema: z2.strictObject({ kind: z2.enum(["folder", "category", "tag"]).describe("The workspace resource type to read by ID."), id: uuid }),
|
|
1041
|
+
outputSchema: toolOutputSchemas.get_workspace_item,
|
|
1042
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1043
|
+
}, wrap("get_workspace_item", async ({ kind, id }) => {
|
|
1044
|
+
if (kind === "folder")
|
|
1045
|
+
return { operation: kind, result: await required(client2.getFolder, "get_workspace_item(folder)")(id) };
|
|
1046
|
+
const listed = kind === "category" ? await client2.listCategories() : await client2.listTags();
|
|
1047
|
+
const result = Array.isArray(listed) ? listed.find((item) => item.id === id) : undefined;
|
|
1048
|
+
if (!result)
|
|
1049
|
+
throw new HiaiDocsError(`${kind} not found`, 404, null, "NOT_FOUND");
|
|
1050
|
+
return { operation: kind, result };
|
|
1051
|
+
}, toolOutputSchemas.get_workspace_item));
|
|
1052
|
+
}
|
|
1053
|
+
|
|
856
1054
|
// ../mcp-server/src/compact.ts
|
|
857
|
-
var
|
|
858
|
-
var text =
|
|
859
|
-
var title =
|
|
860
|
-
var content =
|
|
1055
|
+
var uuid2 = z3.string().uuid().describe("UUID returned by the corresponding list or read operation.");
|
|
1056
|
+
var text = z3.string().min(1).describe("Non-empty human-readable name or title.");
|
|
1057
|
+
var title = z3.string().min(1).max(500).describe("Document title, from 1 to 500 characters.");
|
|
1058
|
+
var content = z3.string().describe("Markdown document content; an empty string clears the content on update.");
|
|
861
1059
|
var read = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
862
1060
|
var write = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
863
1061
|
var destructive = { ...write, destructiveHint: true };
|
|
864
|
-
function
|
|
1062
|
+
function required2(value, name) {
|
|
865
1063
|
if (!value)
|
|
866
1064
|
throw new Error(`The injected legacy MCP client does not support ${name}; use a public DocsClient.`);
|
|
867
1065
|
return value;
|
|
868
1066
|
}
|
|
869
|
-
function registerCompactCapabilities(server,
|
|
1067
|
+
function registerCompactCapabilities(server, client2, wrap) {
|
|
870
1068
|
function register(name, description, inputSchema, annotations, handler) {
|
|
871
1069
|
server.registerTool(name, { description, inputSchema, outputSchema: toolOutputSchemas[name], annotations }, wrap(name, handler, toolOutputSchemas[name]));
|
|
872
1070
|
}
|
|
873
|
-
register("find_documents", "Find readable documents in the active scope. Use mode=list for paginated browsing by folder or tag UUID; use mode=search for hybrid text and semantic retrieval with tag names.",
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
]), read, async (input) => input.mode === "list" ? { operation: "list", result: await
|
|
877
|
-
register("read_document", "Read one document by UUID. Choose detail for content and metadata, markdown for portable export, or versions for saved revisions and snapshots. All views are read-only.",
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1071
|
+
register("find_documents", "Find readable documents in the active scope. Use mode=list for paginated browsing by folder or tag UUID; use mode=search for hybrid text and semantic retrieval with tag names.", z3.discriminatedUnion("mode", [
|
|
1072
|
+
z3.strictObject({ mode: z3.literal("list").describe("Select the list operation explicitly."), folderId: uuid2.optional(), tag: uuid2.optional(), page: z3.number().int().min(1).optional().describe("One-based page number; defaults to the API first page."), limit: z3.number().int().min(1).max(1000).optional().describe("Maximum documents per page, up to 1000.") }),
|
|
1073
|
+
z3.strictObject({ mode: z3.literal("search").describe("Select the search operation explicitly."), query: text.describe("Text to search in the original language."), folder: z3.string().optional().describe("Optional folder UUID filter for search."), tags: z3.array(z3.string()).optional().describe("Optional tag names; all supplied names must match."), limit: z3.number().int().min(1).max(100).optional().describe("Maximum results to return, up to 100.") })
|
|
1074
|
+
]), read, async (input) => input.mode === "list" ? { operation: "list", result: await client2.listDocuments(input) } : { operation: "search", result: await client2.search(input) });
|
|
1075
|
+
register("read_document", "Read one document by UUID. Choose detail for content and metadata, markdown for portable export, or versions for saved revisions and snapshots. All views are read-only.", z3.discriminatedUnion("view", [
|
|
1076
|
+
z3.strictObject({ view: z3.literal("detail").describe("Select the detail operation explicitly."), id: uuid2 }),
|
|
1077
|
+
z3.strictObject({ view: z3.literal("markdown").describe("Select the markdown operation explicitly."), id: uuid2 }),
|
|
1078
|
+
z3.strictObject({ view: z3.literal("versions").describe("Select the versions operation explicitly."), id: uuid2, onlySnapshots: z3.boolean().optional().describe("When true, return only named snapshots.") })
|
|
881
1079
|
]), read, async (input) => {
|
|
882
1080
|
if (input.view === "detail")
|
|
883
|
-
return { operation: "detail", result: await
|
|
1081
|
+
return { operation: "detail", result: await client2.getDocument(input.id) };
|
|
884
1082
|
if (input.view === "markdown")
|
|
885
|
-
return { operation: "markdown", result: await
|
|
886
|
-
return { operation: "versions", result: await
|
|
1083
|
+
return { operation: "markdown", result: await client2.exportDocument(input.id) };
|
|
1084
|
+
return { operation: "versions", result: await client2.getVersionHistory(input.id, input.onlySnapshots) };
|
|
887
1085
|
});
|
|
888
|
-
register("save_document", "Create a new document or update an existing
|
|
889
|
-
|
|
890
|
-
|
|
1086
|
+
register("save_document", "Create or update one document. Use action=create for a new document or action=update for an existing one; batch_documents handles the same placement change across multiple existing documents. Creation requires write access; content edits require edit access, and placement changes may require write access. Content or placement changes queue normal indexing.", z3.discriminatedUnion("action", [
|
|
1087
|
+
z3.strictObject({ action: z3.literal("create").describe("Select the create operation explicitly."), title: title.optional(), content: content.optional(), folderId: uuid2.optional(), categoryId: uuid2.nullable().optional().describe("Optional category UUID; null clears the explicit category.") }),
|
|
1088
|
+
z3.strictObject({ action: z3.literal("update").describe("Select the update operation explicitly."), id: uuid2, title: title.optional(), content: content.optional(), folderId: uuid2.nullable().optional().describe("Optional folder UUID; null clears the placement on update."), categoryId: uuid2.nullable().optional().describe("Optional category UUID; null clears the explicit category.") }).refine((value) => value.title !== undefined || value.content !== undefined || value.folderId !== undefined || value.categoryId !== undefined)
|
|
891
1089
|
]), destructive, async (input) => {
|
|
892
1090
|
if (input.action === "create") {
|
|
893
1091
|
const { action: _action2, ...data2 } = input;
|
|
894
|
-
return { operation: "create", result: await
|
|
1092
|
+
return { operation: "create", result: await client2.createDocument(data2) };
|
|
895
1093
|
}
|
|
896
1094
|
const { action: _action, id, ...data } = input;
|
|
897
|
-
return { operation: "update", result: await
|
|
1095
|
+
return { operation: "update", result: await client2.updateDocument(id, data) };
|
|
898
1096
|
});
|
|
899
|
-
register("delete_document", "Move
|
|
900
|
-
await
|
|
1097
|
+
register("delete_document", "Move one document to trash without purging content or versions. Use batch_documents(action=trash) for multiple explicit IDs. Requires write access and an explicit delete request; permanently_delete_document is a separate irreversible action.", z3.strictObject({ id: uuid2 }), destructive, async ({ id }) => {
|
|
1098
|
+
await required2(client2.deleteDocument, "delete_document")(id);
|
|
901
1099
|
return { id, deleted: true };
|
|
902
1100
|
});
|
|
903
|
-
register("list_workspace_structure", "List visible folders, categories, or tags in the active workspace or category scope. Set kind=folders to inspect root folders or children of parentId.",
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1101
|
+
register("list_workspace_structure", "List visible folders, categories, or tags in the active workspace or category scope. Set kind=folders to inspect root folders or children of parentId.", z3.discriminatedUnion("kind", [
|
|
1102
|
+
z3.strictObject({ kind: z3.literal("folders").describe("Select the folders operation explicitly."), parentId: uuid2.optional() }),
|
|
1103
|
+
z3.strictObject({ kind: z3.literal("categories").describe("Select the categories operation explicitly.") }),
|
|
1104
|
+
z3.strictObject({ kind: z3.literal("tags").describe("Select the tags operation explicitly.") })
|
|
907
1105
|
]), read, async (input) => {
|
|
908
1106
|
if (input.kind === "folders")
|
|
909
|
-
return { operation: "folders", result: await
|
|
1107
|
+
return { operation: "folders", result: await client2.listFolders({ parentId: input.parentId }) };
|
|
910
1108
|
if (input.kind === "categories")
|
|
911
|
-
return { operation: "categories", result: await
|
|
912
|
-
return { operation: "tags", result: await
|
|
1109
|
+
return { operation: "categories", result: await client2.listCategories() };
|
|
1110
|
+
return { operation: "tags", result: await client2.listTags() };
|
|
913
1111
|
});
|
|
914
|
-
register("save_folder", "Create or update a folder. action=update can rename or move an existing folder; the API enforces category boundaries and queues affected documents for reindexing.",
|
|
915
|
-
|
|
916
|
-
|
|
1112
|
+
register("save_folder", "Create or update a folder. action=update can rename or move an existing folder; the API enforces category boundaries and queues affected documents for reindexing.", z3.discriminatedUnion("action", [
|
|
1113
|
+
z3.strictObject({ action: z3.literal("create").describe("Select the create operation explicitly."), name: text.max(255), parentId: uuid2.nullable().optional().describe("Optional parent folder UUID; null selects the root."), categoryId: uuid2.nullable().optional().describe("Optional category UUID; null clears the explicit category.") }),
|
|
1114
|
+
z3.strictObject({ action: z3.literal("update").describe("Select the update operation explicitly."), id: uuid2, name: text.max(255).optional(), parentId: uuid2.nullable().optional().describe("Optional parent folder UUID; null selects the root."), categoryId: uuid2.nullable().optional().describe("Optional category UUID; null clears the explicit category."), order: z3.number().int().nonnegative().optional().describe("New non-negative display order.") }).refine((value) => value.name !== undefined || value.parentId !== undefined || value.categoryId !== undefined || value.order !== undefined)
|
|
917
1115
|
]), destructive, async (input) => {
|
|
918
1116
|
if (input.action === "create") {
|
|
919
1117
|
const { action: _action2, ...data2 } = input;
|
|
920
|
-
return { operation: "create", result: await
|
|
1118
|
+
return { operation: "create", result: await client2.createFolder(data2) };
|
|
921
1119
|
}
|
|
922
1120
|
const { action: _action, id, ...data } = input;
|
|
923
|
-
return { operation: "update", result: await
|
|
1121
|
+
return { operation: "update", result: await required2(client2.updateFolder, "update_folder")(id, data) };
|
|
924
1122
|
});
|
|
925
|
-
register("delete_folder", "Delete a folder while preserving its documents; child folders and documents are detached under the API rules. Requires write access.",
|
|
926
|
-
await
|
|
1123
|
+
register("delete_folder", "Delete a folder while preserving its documents; child folders and documents are detached under the API rules. Requires write access.", z3.strictObject({ id: uuid2 }), destructive, async ({ id }) => {
|
|
1124
|
+
await required2(client2.deleteFolder, "delete_folder")(id);
|
|
927
1125
|
return { id, deleted: true };
|
|
928
1126
|
});
|
|
929
|
-
register("save_category", "Create or update a workspace category. Full workspace write access is required; category-scoped credentials cannot manage categories.",
|
|
930
|
-
|
|
931
|
-
|
|
1127
|
+
register("save_category", "Create or update a workspace category. Full workspace write access is required; category-scoped credentials cannot manage categories.", z3.discriminatedUnion("action", [
|
|
1128
|
+
z3.strictObject({ action: z3.literal("create").describe("Select the create operation explicitly."), name: text.max(255) }),
|
|
1129
|
+
z3.strictObject({ action: z3.literal("update").describe("Select the update operation explicitly."), id: uuid2, name: text.max(255).optional(), order: z3.number().int().nonnegative().optional().describe("New non-negative display order.") }).refine((value) => value.name !== undefined || value.order !== undefined)
|
|
932
1130
|
]), destructive, async (input) => {
|
|
933
1131
|
if (input.action === "create")
|
|
934
|
-
return { operation: "create", result: await
|
|
1132
|
+
return { operation: "create", result: await client2.createCategory({ name: input.name }) };
|
|
935
1133
|
const { action: _action, id, ...data } = input;
|
|
936
|
-
return { operation: "update", result: await
|
|
1134
|
+
return { operation: "update", result: await required2(client2.updateCategory, "update_category")(id, data) };
|
|
937
1135
|
});
|
|
938
|
-
register("delete_category", "Delete a workspace category and detach its documents without deleting them. Requires full workspace write access; category-scoped credentials are denied.",
|
|
939
|
-
await
|
|
1136
|
+
register("delete_category", "Delete a workspace category and detach its documents without deleting them. Requires full workspace write access; category-scoped credentials are denied.", z3.strictObject({ id: uuid2 }), destructive, async ({ id }) => {
|
|
1137
|
+
await required2(client2.deleteCategory, "delete_category")(id);
|
|
940
1138
|
return { id, deleted: true };
|
|
941
1139
|
});
|
|
942
|
-
register("save_tag", "Create or update a workspace tag. Full workspace write access is required; renaming or recoloring a tag causes affected documents to be reindexed.",
|
|
943
|
-
|
|
944
|
-
|
|
1140
|
+
register("save_tag", "Create or update a workspace tag. Full workspace write access is required; renaming or recoloring a tag causes affected documents to be reindexed.", z3.discriminatedUnion("action", [
|
|
1141
|
+
z3.strictObject({ action: z3.literal("create").describe("Select the create operation explicitly."), name: text.max(100), color: z3.string().max(20).optional().describe("Optional tag color, at most 20 characters.") }),
|
|
1142
|
+
z3.strictObject({ action: z3.literal("update").describe("Select the update operation explicitly."), id: uuid2, name: text.max(100).optional(), color: z3.string().max(20).optional().describe("Optional tag color, at most 20 characters.") }).refine((value) => value.name !== undefined || value.color !== undefined)
|
|
945
1143
|
]), destructive, async (input) => {
|
|
946
1144
|
if (input.action === "create")
|
|
947
|
-
return { operation: "create", result: await
|
|
1145
|
+
return { operation: "create", result: await required2(client2.createTag, "create_tag")({ name: input.name, color: input.color }) };
|
|
948
1146
|
const { action: _action, id, ...data } = input;
|
|
949
|
-
return { operation: "update", result: await
|
|
1147
|
+
return { operation: "update", result: await required2(client2.updateTag, "update_tag")(id, data) };
|
|
950
1148
|
});
|
|
951
|
-
register("delete_tag", "Delete a workspace tag and remove its document assignments. Full workspace write access is required; documents themselves remain.",
|
|
952
|
-
await
|
|
1149
|
+
register("delete_tag", "Delete a workspace tag and remove its document assignments. Full workspace write access is required; documents themselves remain.", z3.strictObject({ id: uuid2 }), destructive, async ({ id }) => {
|
|
1150
|
+
await required2(client2.deleteTag, "delete_tag")(id);
|
|
953
1151
|
return { id, deleted: true };
|
|
954
1152
|
});
|
|
955
|
-
register("set_document_tag", "Add or remove
|
|
1153
|
+
register("set_document_tag", "Add or remove an existing tag assignment on one document. Use batch_documents for the same tag action across multiple explicit IDs. Requires edit access to each document; action=remove preserves the tag itself.", z3.strictObject({ action: z3.enum(["add", "remove"]).describe("Add or remove exactly one tag assignment."), documentId: uuid2, tagId: uuid2 }), destructive, async ({ action, documentId, tagId }) => {
|
|
956
1154
|
if (action === "add") {
|
|
957
|
-
await
|
|
1155
|
+
await required2(client2.addTagToDocument, "add_tag_to_document")(documentId, tagId);
|
|
958
1156
|
return { operation: "add", result: { documentId, tagId, assigned: true } };
|
|
959
1157
|
}
|
|
960
|
-
await
|
|
1158
|
+
await required2(client2.removeTagFromDocument, "remove_tag_from_document")(documentId, tagId);
|
|
961
1159
|
return { operation: "remove", result: { documentId, tagId, removed: true } };
|
|
962
1160
|
});
|
|
963
|
-
register("create_snapshot", "Save a named snapshot of an existing document before a planned change. The snapshot can later be restored with restore_document_version.",
|
|
964
|
-
register("restore_document_version", "Restore content from a version or snapshot listed by read_document(view=versions). This changes current content and queues indexing; it does not restore a trashed document.",
|
|
965
|
-
register("explore_graph", "Explore graph relations from readable seed documents. mode=neighbors traverses one document without a query; mode=search ranks relations from one or more seed IDs with optional query text.",
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
]), read, async (input) => input.mode === "neighbors" ? { operation: "neighbors", result: await
|
|
969
|
-
register("get_document_index_status", "Read current indexing and pipeline status for one document without starting work. Use refresh_document_index only when an explicit retry is intended.",
|
|
970
|
-
register("refresh_document_index", "Queue an explicit asynchronous reindex for one document. Routine content and placement changes already schedule indexing; check get_document_index_status first.",
|
|
971
|
-
register("list_trash", "List soft-deleted documents visible to the active workspace or category scope. Requires read access and does not restore or purge anything.",
|
|
972
|
-
register("restore_trashed_document", "Return one
|
|
973
|
-
register("permanently_delete_document", "Irreversibly purge a document already in trash, including its version history. Use only on an explicit user request after checking list_trash.",
|
|
974
|
-
await
|
|
1161
|
+
register("create_snapshot", "Save a named snapshot of an existing document before a planned change. The snapshot can later be restored with restore_document_version.", z3.strictObject({ documentId: uuid2, label: text.max(200).describe("Snapshot label, from 1 to 200 characters."), description: z3.string().max(1000).optional().describe("Optional snapshot note, at most 1000 characters.") }), write, async ({ documentId, label, description }) => client2.createSnapshot(documentId, { label, description }));
|
|
1162
|
+
register("restore_document_version", "Restore content from a version or snapshot listed by read_document(view=versions). This changes current content and queues indexing; it does not restore a trashed document.", z3.strictObject({ documentId: uuid2, versionId: uuid2 }), destructive, async ({ documentId, versionId }) => required2(client2.restoreDocumentVersion, "restore_document_version")(documentId, versionId));
|
|
1163
|
+
register("explore_graph", "Explore graph relations from readable seed documents. mode=neighbors traverses one document without a query; mode=search ranks relations from one or more seed IDs with optional query text.", z3.discriminatedUnion("mode", [
|
|
1164
|
+
z3.strictObject({ mode: z3.literal("neighbors").describe("Select the neighbors operation explicitly."), documentId: uuid2, limit: z3.number().int().min(1).max(100).optional().describe("Maximum results to return, up to 100.") }),
|
|
1165
|
+
z3.strictObject({ mode: z3.literal("search").describe("Select the search operation explicitly."), docIds: z3.array(uuid2).min(1).max(50).describe("One to 50 authorized seed document UUIDs."), query: z3.string().max(2000).optional().describe("Optional graph relevance query, at most 2000 characters."), limit: z3.number().int().min(1).max(100).optional().describe("Maximum results to return, up to 100.") })
|
|
1166
|
+
]), read, async (input) => input.mode === "neighbors" ? { operation: "neighbors", result: await client2.getRelatedDocuments(input.documentId, input.limit) } : { operation: "search", result: await client2.searchGraph(input) });
|
|
1167
|
+
register("get_document_index_status", "Read current indexing and pipeline status for one document without starting work. Use refresh_document_index only when an explicit retry is intended.", z3.strictObject({ documentId: uuid2 }), read, async ({ documentId }) => client2.getDocumentIndexStatus(documentId));
|
|
1168
|
+
register("refresh_document_index", "Queue an explicit asynchronous reindex for one document; use batch_documents(action=refresh_index) for multiple explicit IDs. Routine content and placement changes already schedule indexing; check get_document_index_status first.", z3.strictObject({ documentId: uuid2 }), write, async ({ documentId }) => client2.refreshDocumentIndex(documentId));
|
|
1169
|
+
register("list_trash", "List soft-deleted documents visible to the active workspace or category scope. Requires read access and does not restore or purge anything.", z3.strictObject({}), read, async () => required2(client2.listTrash, "list_trash")());
|
|
1170
|
+
register("restore_trashed_document", "Return one document from trash to the active library; use batch_documents(action=restore) for multiple explicit IDs. This restores the document itself, unlike restore_document_version which changes content.", z3.strictObject({ id: uuid2 }), write, async ({ id }) => required2(client2.restoreTrashedDocument, "restore_trashed_document")(id));
|
|
1171
|
+
register("permanently_delete_document", "Irreversibly purge a document already in trash, including its version history. Use only on an explicit user request after checking list_trash.", z3.strictObject({ id: uuid2 }), destructive, async ({ id }) => {
|
|
1172
|
+
await required2(client2.permanentlyDeleteDocument, "permanently_delete_document")(id);
|
|
975
1173
|
return { id, deleted: true };
|
|
976
1174
|
});
|
|
1175
|
+
registerBatchAndWorkspaceCapabilities(server, client2, wrap);
|
|
977
1176
|
}
|
|
978
1177
|
|
|
979
1178
|
// ../mcp-server/src/prompts-resources.ts
|
|
980
|
-
import { z as
|
|
981
|
-
function registerPromptsAndResources(server,
|
|
1179
|
+
import { z as z4 } from "zod";
|
|
1180
|
+
function registerPromptsAndResources(server, client2) {
|
|
982
1181
|
server.registerPrompt("organize_workspace", {
|
|
983
1182
|
description: "Plan safe document organization using DocsMint categories and folders.",
|
|
984
|
-
argsSchema:
|
|
985
|
-
objective:
|
|
986
|
-
language:
|
|
1183
|
+
argsSchema: z4.object({
|
|
1184
|
+
objective: z4.string(),
|
|
1185
|
+
language: z4.string().optional()
|
|
987
1186
|
})
|
|
988
1187
|
}, ({ objective, language }) => ({
|
|
989
1188
|
messages: [
|
|
@@ -998,9 +1197,9 @@ function registerPromptsAndResources(server, client) {
|
|
|
998
1197
|
}));
|
|
999
1198
|
server.registerPrompt("research_workspace", {
|
|
1000
1199
|
description: "Research a question with hybrid search and GraphRAG while citing DocsMint document IDs.",
|
|
1001
|
-
argsSchema:
|
|
1002
|
-
question:
|
|
1003
|
-
language:
|
|
1200
|
+
argsSchema: z4.object({
|
|
1201
|
+
question: z4.string(),
|
|
1202
|
+
language: z4.string().optional()
|
|
1004
1203
|
})
|
|
1005
1204
|
}, ({ question, language }) => ({
|
|
1006
1205
|
messages: [
|
|
@@ -1037,9 +1236,9 @@ Use find_documents(mode=search) for normal retrieval. Use explore_graph only wit
|
|
|
1037
1236
|
}));
|
|
1038
1237
|
server.registerResource("workspace-catalog", "docsmint://workspace/catalog", {}, async (uri) => {
|
|
1039
1238
|
const [categories, folders, tags] = await Promise.all([
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1239
|
+
client2.listCategories(),
|
|
1240
|
+
client2.listFolders({}),
|
|
1241
|
+
client2.listTags()
|
|
1043
1242
|
]);
|
|
1044
1243
|
return {
|
|
1045
1244
|
contents: [
|
|
@@ -1053,97 +1252,6 @@ Use find_documents(mode=search) for normal retrieval. Use explore_graph only wit
|
|
|
1053
1252
|
});
|
|
1054
1253
|
}
|
|
1055
1254
|
|
|
1056
|
-
// ../mcp-server/src/client.ts
|
|
1057
|
-
var DEFAULT_BASE_URL = "http://localhost:50700";
|
|
1058
|
-
function readConfig() {
|
|
1059
|
-
const baseUrl = (process.env.HIAI_DOCS_URL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1060
|
-
const apiKey = process.env.HIAI_DOCS_API_KEY;
|
|
1061
|
-
return { baseUrl, apiKey: apiKey || undefined };
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
class HiaiDocsError extends Error {
|
|
1065
|
-
status;
|
|
1066
|
-
body;
|
|
1067
|
-
code;
|
|
1068
|
-
constructor(message, status, body, code = `http_${status}`) {
|
|
1069
|
-
super(message);
|
|
1070
|
-
this.status = status;
|
|
1071
|
-
this.body = body;
|
|
1072
|
-
this.code = code;
|
|
1073
|
-
this.name = "HiaiDocsError";
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
function sanitizeMcpRequestContext(context) {
|
|
1077
|
-
if (!context)
|
|
1078
|
-
return;
|
|
1079
|
-
const headers = new Headers(context.headers);
|
|
1080
|
-
if (context.workspaceAssertion) {
|
|
1081
|
-
headers.delete("Authorization");
|
|
1082
|
-
headers.delete("Cookie");
|
|
1083
|
-
}
|
|
1084
|
-
return {
|
|
1085
|
-
...context,
|
|
1086
|
-
authorization: context.workspaceAssertion ? undefined : context.authorization,
|
|
1087
|
-
cookie: context.workspaceAssertion ? undefined : context.cookie,
|
|
1088
|
-
headers
|
|
1089
|
-
};
|
|
1090
|
-
}
|
|
1091
|
-
function createMcpDocsClient(docsClient, requestContext) {
|
|
1092
|
-
const context = sanitizeMcpRequestContext(requestContext);
|
|
1093
|
-
return {
|
|
1094
|
-
createTag: (input) => docsClient.createTag(input, context),
|
|
1095
|
-
updateTag: (id, input) => docsClient.updateTag(id, input, context),
|
|
1096
|
-
deleteTag: (id) => docsClient.deleteTag(id, context),
|
|
1097
|
-
addTagToDocument: (documentId, tagId) => docsClient.addTagToDoc(documentId, tagId, context),
|
|
1098
|
-
removeTagFromDocument: (documentId, tagId) => docsClient.removeTagFromDoc(documentId, tagId, context),
|
|
1099
|
-
updateFolder: (id, input) => docsClient.updateFolder(id, input, context),
|
|
1100
|
-
updateCategory: (id, input) => docsClient.updateCategory(id, input, context),
|
|
1101
|
-
listTrash: () => docsClient.listTrash(context),
|
|
1102
|
-
restoreTrashedDocument: (id) => docsClient.restoreTrashedDocument(id, context),
|
|
1103
|
-
permanentlyDeleteDocument: (id) => docsClient.permanentlyDeleteDocument(id, context),
|
|
1104
|
-
deleteDocument: (id) => docsClient.deleteDoc(id, context),
|
|
1105
|
-
deleteFolder: (id) => docsClient.deleteFolder(id, context),
|
|
1106
|
-
deleteCategory: (id) => docsClient.deleteCategory(id, context),
|
|
1107
|
-
restoreDocumentVersion: (documentId, versionId) => docsClient.restoreVersion(documentId, versionId, context),
|
|
1108
|
-
search: (params) => docsClient.search(params.query, {
|
|
1109
|
-
folder: params.folder,
|
|
1110
|
-
tags: params.tags?.join(","),
|
|
1111
|
-
limit: params.limit
|
|
1112
|
-
}, context),
|
|
1113
|
-
getDocument: (id) => docsClient.getDoc(id, context),
|
|
1114
|
-
createDocument: (input) => docsClient.createDoc({
|
|
1115
|
-
...input,
|
|
1116
|
-
folderId: input.folderId ?? undefined,
|
|
1117
|
-
categoryId: input.categoryId ?? undefined
|
|
1118
|
-
}, context),
|
|
1119
|
-
updateDocument: (id, input) => docsClient.updateDoc(id, input, context),
|
|
1120
|
-
listDocuments: (params) => docsClient.listDocs(params, context),
|
|
1121
|
-
listFolders: (params) => docsClient.listFolders(params.parentId, context),
|
|
1122
|
-
createFolder: (input) => docsClient.createFolder(input, context),
|
|
1123
|
-
listCategories: () => docsClient.listCategories(context),
|
|
1124
|
-
createCategory: (input) => docsClient.createCategory(input, context),
|
|
1125
|
-
listTags: () => docsClient.listTags(context),
|
|
1126
|
-
getRelatedDocuments: (documentId, limit) => docsClient.getRelatedDocuments(documentId, context, { limit }),
|
|
1127
|
-
searchGraph: (input) => docsClient.searchGraph({ query: input.query, docIds: input.docIds, maxResults: input.limit }, context),
|
|
1128
|
-
getDocumentIndexStatus: (documentId) => docsClient.getDocumentIndexStatus(documentId, context),
|
|
1129
|
-
refreshDocumentIndex: (documentId) => docsClient.refreshDocumentIndex(documentId, context),
|
|
1130
|
-
createSnapshot: (documentId, input) => docsClient.createSnapshot(documentId, input, context),
|
|
1131
|
-
getVersionHistory: (documentId, onlySnapshots) => docsClient.listVersions(documentId, { onlySnapshots }, context),
|
|
1132
|
-
exportDocument: async (id) => ({ markdown: await docsClient.exportDoc(id, context) })
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
function createDefaultDocsClient() {
|
|
1136
|
-
const { baseUrl, apiKey } = readConfig();
|
|
1137
|
-
return new DocsClient({ baseUrl, apiKey, retries: 1 });
|
|
1138
|
-
}
|
|
1139
|
-
var client = new Proxy({}, {
|
|
1140
|
-
get(_target, property) {
|
|
1141
|
-
const configured = createMcpDocsClient(createDefaultDocsClient());
|
|
1142
|
-
const value = configured[property];
|
|
1143
|
-
return typeof value === "function" ? value.bind(configured) : value;
|
|
1144
|
-
}
|
|
1145
|
-
});
|
|
1146
|
-
|
|
1147
1255
|
// ../mcp-server/src/capabilities.ts
|
|
1148
1256
|
var capabilityCatalog = {
|
|
1149
1257
|
tools: [
|
|
@@ -1166,7 +1274,9 @@ var capabilityCatalog = {
|
|
|
1166
1274
|
"refresh_document_index",
|
|
1167
1275
|
"list_trash",
|
|
1168
1276
|
"restore_trashed_document",
|
|
1169
|
-
"permanently_delete_document"
|
|
1277
|
+
"permanently_delete_document",
|
|
1278
|
+
"batch_documents",
|
|
1279
|
+
"get_workspace_item"
|
|
1170
1280
|
],
|
|
1171
1281
|
prompts: ["organize_workspace", "research_workspace"],
|
|
1172
1282
|
resources: [
|
|
@@ -1219,7 +1329,7 @@ function registerDocsmintMcpCapabilities(server, client2) {
|
|
|
1219
1329
|
registerPromptsAndResources(server, client2);
|
|
1220
1330
|
}
|
|
1221
1331
|
function createDocsmintMcpServer(options = {}) {
|
|
1222
|
-
const server = new McpServer({ name: "docsmint", version: "0.
|
|
1332
|
+
const server = new McpServer({ name: "docsmint", version: "1.0.2" });
|
|
1223
1333
|
const client2 = options.docsClient ? createMcpDocsClient(options.docsClient, options.requestContext) : options.client ?? createMcpDocsClient(createDefaultDocsClient(), options.requestContext);
|
|
1224
1334
|
registerDocsmintMcpCapabilities(server, client2);
|
|
1225
1335
|
return server;
|
package/package.json
CHANGED
|
@@ -25,7 +25,7 @@ import { registerSearch } from './commands/search.js';
|
|
|
25
25
|
import { registerSnapshot } from './commands/snapshot.js';
|
|
26
26
|
import { registerUpdate } from './commands/update.js';
|
|
27
27
|
|
|
28
|
-
const VERSION = '0.
|
|
28
|
+
const VERSION = '1.0.2';
|
|
29
29
|
|
|
30
30
|
const program = new Command();
|
|
31
31
|
program.name('docsmint').description('CLI for the DocsMint knowledge workspace').version(VERSION);
|