@hiai-gg/docsmint 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +171 -0
  2. package/README.md +348 -0
  3. package/backend/src/lib/logger.ts +18 -0
  4. package/backend/src/lib/redis-factory.ts +40 -0
  5. package/backend/src/lib/storage-factory.ts +56 -0
  6. package/frontend/src/lib/components/editor/shared-document.ts +237 -0
  7. package/frontend/src/lib/extensions/context.ts +60 -0
  8. package/frontend/src/lib/extensions/doc-tabs.ts +18 -0
  9. package/frontend/src/lib/extensions/resolve.ts +48 -0
  10. package/frontend/src/lib/extensions/types.ts +202 -0
  11. package/frontend/src/lib/hosts/DocsmintSharedDocumentHost.svelte +65 -0
  12. package/frontend/src/lib/hosts/HiaiDocsDashboardHost.svelte +1007 -0
  13. package/frontend/src/lib/hosts/HiaiDocsExtensionProvider.svelte +20 -0
  14. package/frontend/src/lib/hosts/HiaiDocsSearchHost.svelte +996 -0
  15. package/frontend/src/lib/hosts/index.ts +25 -0
  16. package/frontend/src/lib/index.ts +65 -0
  17. package/frontend/src/lib/stores/doc-tab-registry.svelte.ts +68 -0
  18. package/package.json +178 -0
  19. package/packages/cli/src/client.ts +271 -0
  20. package/packages/cli/src/commands/config.ts +47 -0
  21. package/packages/cli/src/commands/create.ts +35 -0
  22. package/packages/cli/src/commands/delete.ts +37 -0
  23. package/packages/cli/src/commands/export.ts +36 -0
  24. package/packages/cli/src/commands/folders.ts +88 -0
  25. package/packages/cli/src/commands/history.ts +55 -0
  26. package/packages/cli/src/commands/list.ts +61 -0
  27. package/packages/cli/src/commands/read.ts +38 -0
  28. package/packages/cli/src/commands/restore.ts +30 -0
  29. package/packages/cli/src/commands/search.ts +56 -0
  30. package/packages/cli/src/commands/snapshot.ts +35 -0
  31. package/packages/cli/src/commands/update.ts +54 -0
  32. package/packages/cli/src/config.ts +83 -0
  33. package/packages/cli/src/format.ts +153 -0
  34. package/packages/cli/src/index.ts +73 -0
  35. package/packages/db/src/client.ts +20 -0
  36. package/packages/db/src/index.ts +5 -0
  37. package/packages/db/src/schema.ts +692 -0
  38. package/packages/db/src/with-tenant.ts +75 -0
  39. package/packages/mcp-server/src/client.ts +172 -0
  40. package/packages/mcp-server/src/index.ts +109 -0
  41. package/packages/mcp-server/src/tools/create-document.ts +32 -0
  42. package/packages/mcp-server/src/tools/create-folder.ts +24 -0
  43. package/packages/mcp-server/src/tools/create-snapshot.ts +30 -0
  44. package/packages/mcp-server/src/tools/export-document.ts +22 -0
  45. package/packages/mcp-server/src/tools/get-document.ts +20 -0
  46. package/packages/mcp-server/src/tools/list-documents.ts +42 -0
  47. package/packages/mcp-server/src/tools/list-folders.ts +25 -0
  48. package/packages/mcp-server/src/tools/search.ts +42 -0
  49. package/packages/mcp-server/src/tools/update-document.ts +30 -0
  50. package/packages/mcp-server/src/tools/version-history.ts +32 -0
  51. package/packages/mcp-server/src/types.ts +126 -0
  52. package/packages/sdk/dist/client.d.ts +187 -0
  53. package/packages/sdk/dist/client.js +568 -0
  54. package/packages/sdk/dist/index.d.ts +3 -0
  55. package/packages/sdk/dist/index.js +1 -0
  56. package/packages/sdk/dist/types.d.ts +391 -0
  57. package/packages/sdk/dist/types.js +8 -0
@@ -0,0 +1,75 @@
1
+ import { sql } from "drizzle-orm";
2
+ import { db } from "./client";
3
+
4
+ export interface TenantContext {
5
+ userId: string;
6
+ role: "admin" | "user" | "none";
7
+ workspaceId?: string;
8
+ source?: "personal" | "external";
9
+ actorRole?: "owner" | "admin" | "editor" | "viewer";
10
+ }
11
+
12
+ export const ZERO_UUID = "00000000-0000-0000-0000-000000000000";
13
+
14
+ /**
15
+ * Build an admin TenantContext.
16
+ *
17
+ * `ownerId` SHOULD be passed explicitly by the caller (typically resolved
18
+ * from the hiai-docs `config.OWNER_ID` via the `tenant.ts` middleware).
19
+ * If omitted, falls back to `process.env.OWNER_ID` so this package can
20
+ * remain dependency-free while still working stand-alone.
21
+ */
22
+ export function adminTenantContext(ownerId?: string): TenantContext {
23
+ const resolved = ownerId ?? process.env.OWNER_ID;
24
+ if (!resolved) {
25
+ console.warn(
26
+ "[hiai-docs/db] adminTenantContext: ownerId not provided and OWNER_ID env not set, using empty string",
27
+ );
28
+ }
29
+ return {
30
+ userId: resolved ?? "",
31
+ role: "admin",
32
+ };
33
+ }
34
+
35
+ export function shareGuestTenantContext(ownerId: string): TenantContext {
36
+ return {
37
+ userId: ownerId,
38
+ role: "user",
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Run `fn` inside a `db.transaction(...)` with the per-request RLS
44
+ * GUCs (`app.current_user_id`, `app.current_user_role`) installed
45
+ * on the transaction's connection.
46
+ *
47
+ * The transaction pins a single pooled connection for the duration
48
+ * of `fn`, so every query inside `fn` runs on the same connection
49
+ * where the GUCs were installed. This works around the
50
+ * `postgres-js` connection-pool round-robin: a single
51
+ * `set_config(..., false)` outside a transaction would land on a
52
+ * different connection than the route handler's first query, and
53
+ * RLS would fail closed.
54
+ *
55
+ * GUCs use `set_config(..., true)` (transaction-local), so they
56
+ * automatically reset when the transaction commits/rolls back and
57
+ * cannot leak into the next request that reuses the connection.
58
+ */
59
+ export async function withTenant<T>(
60
+ ctx: TenantContext,
61
+ fn: (tx: Parameters<Parameters<typeof db.transaction>[0]>[0]) => Promise<T>,
62
+ ): Promise<T> {
63
+ return db.transaction(async (tx) => {
64
+ await tx.execute(
65
+ sql`SELECT set_config('app.current_user_id', ${ctx.userId}, true)`,
66
+ );
67
+ await tx.execute(
68
+ sql`SELECT set_config('app.current_user_role', ${ctx.role}, true)`,
69
+ );
70
+ await tx.execute(
71
+ sql`SELECT set_config('app.current_workspace_id', ${ctx.workspaceId ?? ""}, true)`,
72
+ );
73
+ return fn(tx);
74
+ });
75
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * REST client for hiai-docs.
3
+ *
4
+ * Reads configuration from environment:
5
+ * HIAI_DOCS_URL — base URL (default: http://localhost:50700)
6
+ * HIAI_DOCS_API_KEY — bearer token for the API
7
+ *
8
+ * Bun-native. Uses global `fetch`. Throws on non-2xx responses with the
9
+ * error message extracted from the response body when possible.
10
+ */
11
+
12
+ import type { ExportResponse } from "./types.js";
13
+
14
+ const DEFAULT_BASE_URL = "http://localhost:50700";
15
+
16
+ function readConfig() {
17
+ const baseUrl = (process.env.HIAI_DOCS_URL ?? DEFAULT_BASE_URL).replace(
18
+ /\/+$/,
19
+ "",
20
+ );
21
+ const apiKey = process.env.HIAI_DOCS_API_KEY ?? "";
22
+ return { baseUrl, apiKey };
23
+ }
24
+
25
+ export class HiaiDocsError extends Error {
26
+ constructor(
27
+ message: string,
28
+ readonly status: number,
29
+ readonly body: unknown,
30
+ ) {
31
+ super(message);
32
+ this.name = "HiaiDocsError";
33
+ }
34
+ }
35
+
36
+ type QueryValue = string | number | boolean | string[] | undefined;
37
+
38
+ async function request<T>(
39
+ method: string,
40
+ path: string,
41
+ options: { query?: Record<string, QueryValue>; body?: unknown } = {},
42
+ ): Promise<T> {
43
+ const { baseUrl, apiKey } = readConfig();
44
+
45
+ const url = new URL(path.startsWith("/") ? path : `/${path}`, `${baseUrl}/`);
46
+ if (options.query) {
47
+ for (const [key, value] of Object.entries(options.query)) {
48
+ if (value === undefined) continue;
49
+ if (Array.isArray(value)) {
50
+ if (value.length > 0) url.searchParams.set(key, value.join(","));
51
+ } else {
52
+ url.searchParams.set(key, String(value));
53
+ }
54
+ }
55
+ }
56
+
57
+ const headers: Record<string, string> = {
58
+ Accept: "application/json",
59
+ };
60
+ if (apiKey) {
61
+ headers["Authorization"] = `Bearer ${apiKey}`;
62
+ }
63
+
64
+ let body: BodyInit | undefined;
65
+ if (options.body !== undefined) {
66
+ headers["Content-Type"] = "application/json";
67
+ body = JSON.stringify(options.body);
68
+ }
69
+
70
+ const response = await fetch(url, { method, headers, body });
71
+
72
+ const contentType = response.headers.get("content-type") ?? "";
73
+ const isJson = contentType.includes("application/json");
74
+ const payload: unknown = isJson
75
+ ? await response.json().catch(() => null)
76
+ : await response.text().catch(() => null);
77
+
78
+ if (!response.ok) {
79
+ const message =
80
+ (isJson && payload && typeof payload === "object" && "error" in payload
81
+ ? String((payload as { error: unknown }).error)
82
+ : typeof payload === "string" && payload.length > 0
83
+ ? payload
84
+ : `HTTP ${response.status} ${response.statusText}`) ||
85
+ `HTTP ${response.status}`;
86
+ throw new HiaiDocsError(message, response.status, payload);
87
+ }
88
+
89
+ return payload as T;
90
+ }
91
+
92
+ function joinId(...segments: string[]): string {
93
+ return segments
94
+ .map((s) => encodeURIComponent(s))
95
+ .join("/");
96
+ }
97
+
98
+ export const client = {
99
+ search(params: {
100
+ query: string;
101
+ folder?: string;
102
+ tags?: string[];
103
+ limit?: number;
104
+ }) {
105
+ return request("GET", "/api/search", {
106
+ query: {
107
+ q: params.query,
108
+ folder: params.folder,
109
+ tags: params.tags,
110
+ limit: params.limit,
111
+ },
112
+ });
113
+ },
114
+
115
+ getDocument(id: string) {
116
+ return request("GET", `/api/${joinId("documents", id)}`);
117
+ },
118
+
119
+ createDocument(input: { title: string; content?: string; folderId?: string }) {
120
+ return request("POST", "/api/documents", { body: input });
121
+ },
122
+
123
+ updateDocument(id: string, input: { title?: string; content?: string }) {
124
+ return request("PATCH", `/api/${joinId("documents", id)}`, { body: input });
125
+ },
126
+
127
+ listDocuments(params: {
128
+ folderId?: string;
129
+ tag?: string;
130
+ page?: number;
131
+ limit?: number;
132
+ }) {
133
+ return request("GET", "/api/documents", { query: params });
134
+ },
135
+
136
+ listFolders(params: { parentId?: string }) {
137
+ return request("GET", "/api/folders", { query: { parentId: params.parentId } });
138
+ },
139
+
140
+ createFolder(input: { name: string; parentId?: string }) {
141
+ return request("POST", "/api/folders", { body: input });
142
+ },
143
+
144
+ createSnapshot(
145
+ documentId: string,
146
+ input: { label: string; description?: string },
147
+ ) {
148
+ return request(
149
+ "POST",
150
+ `/api/${joinId("documents", documentId, "versions")}`,
151
+ { body: input },
152
+ );
153
+ },
154
+
155
+ getVersionHistory(documentId: string, onlySnapshots?: boolean) {
156
+ return request(
157
+ "GET",
158
+ `/api/${joinId("documents", documentId, "versions")}`,
159
+ { query: { onlySnapshots } },
160
+ );
161
+ },
162
+
163
+ async exportDocument(id: string): Promise<ExportResponse> {
164
+ const markdown = await request<string>(
165
+ "GET",
166
+ `/api/${joinId("documents", id, "export")}`,
167
+ );
168
+ return { markdown };
169
+ },
170
+ };
171
+
172
+ export type HiaiDocsClient = typeof client;
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * hiai-docs MCP Server
4
+ *
5
+ * Exposes hiai-docs operations as MCP tools via the stdio transport.
6
+ *
7
+ * Environment:
8
+ * HIAI_DOCS_URL — base URL of the hiai-docs API
9
+ * (default: http://localhost:50700)
10
+ * HIAI_DOCS_API_KEY — bearer token used for Authorization header
11
+ */
12
+
13
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
+ import { HiaiDocsError } from "./client.js";
16
+ import type { ZodRawShape } from "zod";
17
+
18
+ import * as search from "./tools/search.js";
19
+ import * as getDocument from "./tools/get-document.js";
20
+ import * as createDocument from "./tools/create-document.js";
21
+ import * as updateDocument from "./tools/update-document.js";
22
+ import * as listDocuments from "./tools/list-documents.js";
23
+ import * as listFolders from "./tools/list-folders.js";
24
+ import * as createFolder from "./tools/create-folder.js";
25
+ import * as createSnapshot from "./tools/create-snapshot.js";
26
+ import * as versionHistory from "./tools/version-history.js";
27
+ import * as exportDocument from "./tools/export-document.js";
28
+
29
+ type ToolHandler = (args: Record<string, unknown>) => Promise<unknown>;
30
+
31
+ interface McpToolResult {
32
+ content: Array<{ type: "text"; text: string }>;
33
+ isError?: boolean;
34
+ }
35
+
36
+ const server = new McpServer({
37
+ name: "hiai-docs",
38
+ version: "0.3.0",
39
+ });
40
+
41
+ /**
42
+ * Wraps a tool handler to convert errors into MCP-formatted responses.
43
+ * The SDK's tool callback returns CallToolResult; we return either success
44
+ * with JSON-stringified content or an error with `isError: true`.
45
+ */
46
+ function wrapHandler(
47
+ name: string,
48
+ handler: ToolHandler,
49
+ ): (args: Record<string, unknown>) => Promise<McpToolResult> {
50
+ return async (args) => {
51
+ try {
52
+ const result = await handler(args);
53
+ return {
54
+ content: [
55
+ {
56
+ type: "text" as const,
57
+ text: JSON.stringify(result, null, 2),
58
+ },
59
+ ],
60
+ };
61
+ } catch (err) {
62
+ if (err instanceof HiaiDocsError) {
63
+ return {
64
+ isError: true,
65
+ content: [
66
+ {
67
+ type: "text" as const,
68
+ text: `hiai-docs API error (${err.status}): ${err.message}`,
69
+ },
70
+ ],
71
+ };
72
+ }
73
+ const message = err instanceof Error ? err.message : String(err);
74
+ return {
75
+ isError: true,
76
+ content: [
77
+ {
78
+ type: "text" as const,
79
+ text: `Tool '${name}' failed: ${message}`,
80
+ },
81
+ ],
82
+ };
83
+ }
84
+ };
85
+ }
86
+
87
+ function register<Args>(
88
+ name: string,
89
+ description: string,
90
+ inputSchema: ZodRawShape,
91
+ handler: (args: Args) => Promise<unknown>,
92
+ ): void {
93
+ server.tool(name, description, inputSchema as never, wrapHandler(name, handler as ToolHandler) as never);
94
+ }
95
+
96
+ register(search.definition.name, search.definition.description, search.definition.inputSchema as ZodRawShape, search.handler);
97
+ register(getDocument.definition.name, getDocument.definition.description, getDocument.definition.inputSchema as ZodRawShape, getDocument.handler);
98
+ register(createDocument.definition.name, createDocument.definition.description, createDocument.definition.inputSchema as ZodRawShape, createDocument.handler);
99
+ register(updateDocument.definition.name, updateDocument.definition.description, updateDocument.definition.inputSchema as ZodRawShape, updateDocument.handler);
100
+ register(listDocuments.definition.name, listDocuments.definition.description, listDocuments.definition.inputSchema as ZodRawShape, listDocuments.handler);
101
+ register(listFolders.definition.name, listFolders.definition.description, listFolders.definition.inputSchema as ZodRawShape, listFolders.handler);
102
+ register(createFolder.definition.name, createFolder.definition.description, createFolder.definition.inputSchema as ZodRawShape, createFolder.handler);
103
+ register(createSnapshot.definition.name, createSnapshot.definition.description, createSnapshot.definition.inputSchema as ZodRawShape, createSnapshot.handler);
104
+ register(versionHistory.definition.name, versionHistory.definition.description, versionHistory.definition.inputSchema as ZodRawShape, versionHistory.handler);
105
+ register(exportDocument.definition.name, exportDocument.definition.description, exportDocument.definition.inputSchema as ZodRawShape, exportDocument.handler);
106
+
107
+ const transport = new StdioServerTransport();
108
+
109
+ await server.connect(transport);
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+ import { client } from "../client.js";
3
+ import type { DocumentDetail } from "../types.js";
4
+
5
+ export const definition = {
6
+ name: "create_document",
7
+ description:
8
+ "Create a new document. Optionally provide initial markdown content and a folder ID.",
9
+ inputSchema: {
10
+ title: z.string().describe("Document title."),
11
+ content: z
12
+ .string()
13
+ .optional()
14
+ .describe("Initial markdown content for the document."),
15
+ folderId: z
16
+ .string()
17
+ .optional()
18
+ .describe("Optional folder ID to place the document in."),
19
+ },
20
+ } as const;
21
+
22
+ export interface CreateDocumentArgs {
23
+ title: string;
24
+ content?: string;
25
+ folderId?: string;
26
+ }
27
+
28
+ export async function handler(
29
+ args: CreateDocumentArgs,
30
+ ): Promise<DocumentDetail> {
31
+ return (await client.createDocument(args)) as DocumentDetail;
32
+ }
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+ import { client } from "../client.js";
3
+ import type { Folder } from "../types.js";
4
+
5
+ export const definition = {
6
+ name: "create_folder",
7
+ description: "Create a new folder, optionally nested under a parent folder.",
8
+ inputSchema: {
9
+ name: z.string().describe("Folder name."),
10
+ parentId: z
11
+ .string()
12
+ .optional()
13
+ .describe("Optional parent folder ID for nesting."),
14
+ },
15
+ } as const;
16
+
17
+ export interface CreateFolderArgs {
18
+ name: string;
19
+ parentId?: string;
20
+ }
21
+
22
+ export async function handler(args: CreateFolderArgs): Promise<Folder> {
23
+ return (await client.createFolder(args)) as Folder;
24
+ }
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+ import { client } from "../client.js";
3
+ import type { Version } from "../types.js";
4
+
5
+ export const definition = {
6
+ name: "create_snapshot",
7
+ description:
8
+ "Create a named snapshot (labelled version) of a document from its current content.",
9
+ inputSchema: {
10
+ documentId: z.string().describe("Document ID to snapshot."),
11
+ label: z
12
+ .string()
13
+ .describe("Short label for the snapshot (e.g. 'v1.0-release')."),
14
+ description: z
15
+ .string()
16
+ .optional()
17
+ .describe("Optional longer description of the snapshot."),
18
+ },
19
+ } as const;
20
+
21
+ export interface CreateSnapshotArgs {
22
+ documentId: string;
23
+ label: string;
24
+ description?: string;
25
+ }
26
+
27
+ export async function handler(args: CreateSnapshotArgs): Promise<Version> {
28
+ const { documentId, ...input } = args;
29
+ return (await client.createSnapshot(documentId, input)) as Version;
30
+ }
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+ import { client } from "../client.js";
3
+ import type { ExportResponse } from "../types.js";
4
+
5
+ export const definition = {
6
+ name: "export_document",
7
+ description:
8
+ "Export a document as markdown. Returns the rendered markdown content.",
9
+ inputSchema: {
10
+ id: z.string().describe("Document ID to export."),
11
+ },
12
+ } as const;
13
+
14
+ export interface ExportDocumentArgs {
15
+ id: string;
16
+ }
17
+
18
+ export async function handler(
19
+ args: ExportDocumentArgs,
20
+ ): Promise<ExportResponse> {
21
+ return (await client.exportDocument(args.id)) as ExportResponse;
22
+ }
@@ -0,0 +1,20 @@
1
+ import { z } from "zod";
2
+ import { client } from "../client.js";
3
+ import type { DocumentDetail } from "../types.js";
4
+
5
+ export const definition = {
6
+ name: "get_document",
7
+ description:
8
+ "Fetch a single document by ID. Returns full content, metadata, and tags.",
9
+ inputSchema: {
10
+ id: z.string().describe("Document ID."),
11
+ },
12
+ } as const;
13
+
14
+ export interface GetDocumentArgs {
15
+ id: string;
16
+ }
17
+
18
+ export async function handler(args: GetDocumentArgs): Promise<DocumentDetail> {
19
+ return (await client.getDocument(args.id)) as DocumentDetail;
20
+ }
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ import { client } from "../client.js";
3
+ import type { ListDocumentsResponse } from "../types.js";
4
+
5
+ export const definition = {
6
+ name: "list_documents",
7
+ description:
8
+ "List documents with pagination, optionally filtered by folder or tag.",
9
+ inputSchema: {
10
+ folderId: z
11
+ .string()
12
+ .optional()
13
+ .describe("Optional folder ID to filter by."),
14
+ tag: z.string().optional().describe("Optional tag ID to filter by."),
15
+ page: z
16
+ .number()
17
+ .int()
18
+ .positive()
19
+ .optional()
20
+ .describe("Page number (1-indexed, default 1)."),
21
+ limit: z
22
+ .number()
23
+ .int()
24
+ .positive()
25
+ .max(100)
26
+ .optional()
27
+ .describe("Items per page (default 20, max 100)."),
28
+ },
29
+ } as const;
30
+
31
+ export interface ListDocumentsArgs {
32
+ folderId?: string;
33
+ tag?: string;
34
+ page?: number;
35
+ limit?: number;
36
+ }
37
+
38
+ export async function handler(
39
+ args: ListDocumentsArgs,
40
+ ): Promise<ListDocumentsResponse> {
41
+ return (await client.listDocuments(args)) as ListDocumentsResponse;
42
+ }
@@ -0,0 +1,25 @@
1
+ import { z } from "zod";
2
+ import { client } from "../client.js";
3
+ import type { Folder } from "../types.js";
4
+
5
+ export const definition = {
6
+ name: "list_folders",
7
+ description:
8
+ "List folders, optionally scoped to a parent folder. Returns a flat list of immediate children.",
9
+ inputSchema: {
10
+ parentId: z
11
+ .string()
12
+ .optional()
13
+ .describe(
14
+ "Optional parent folder ID. Omit to list top-level (root) folders.",
15
+ ),
16
+ },
17
+ } as const;
18
+
19
+ export interface ListFoldersArgs {
20
+ parentId?: string;
21
+ }
22
+
23
+ export async function handler(args: ListFoldersArgs): Promise<Folder[]> {
24
+ return (await client.listFolders(args)) as Folder[];
25
+ }
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ import { client } from "../client.js";
3
+ import type { SearchResponse } from "../types.js";
4
+
5
+ export const definition = {
6
+ name: "search_documents",
7
+ description:
8
+ "Hybrid search across documents (full-text + semantic). Supports filtering by folder and tags.",
9
+ inputSchema: {
10
+ query: z.string().describe("Search query string."),
11
+ folder: z
12
+ .string()
13
+ .optional()
14
+ .describe("Optional folder ID to scope the search to."),
15
+ tags: z
16
+ .array(z.string())
17
+ .optional()
18
+ .describe("Optional tag IDs to filter by."),
19
+ limit: z
20
+ .number()
21
+ .int()
22
+ .positive()
23
+ .optional()
24
+ .describe("Maximum number of results to return (default 20)."),
25
+ },
26
+ } as const;
27
+
28
+ export type SearchArgs = {
29
+ query: string;
30
+ folder?: string;
31
+ tags?: string[];
32
+ limit?: number;
33
+ };
34
+
35
+ export async function handler(args: SearchArgs): Promise<SearchResponse> {
36
+ return (await client.search({
37
+ query: args.query,
38
+ folder: args.folder,
39
+ tags: args.tags,
40
+ limit: args.limit,
41
+ })) as SearchResponse;
42
+ }
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+ import { client } from "../client.js";
3
+ import type { DocumentDetail } from "../types.js";
4
+
5
+ export const definition = {
6
+ name: "update_document",
7
+ description:
8
+ "Update an existing document's title and/or content. The server creates a new version on each update.",
9
+ inputSchema: {
10
+ id: z.string().describe("Document ID to update."),
11
+ title: z.string().optional().describe("New title for the document."),
12
+ content: z
13
+ .string()
14
+ .optional()
15
+ .describe("New markdown content for the document."),
16
+ },
17
+ } as const;
18
+
19
+ export interface UpdateDocumentArgs {
20
+ id: string;
21
+ title?: string;
22
+ content?: string;
23
+ }
24
+
25
+ export async function handler(
26
+ args: UpdateDocumentArgs,
27
+ ): Promise<DocumentDetail> {
28
+ const { id, ...patch } = args;
29
+ return (await client.updateDocument(id, patch)) as DocumentDetail;
30
+ }
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+ import { client } from "../client.js";
3
+ import type { Version } from "../types.js";
4
+
5
+ export const definition = {
6
+ name: "get_version_history",
7
+ description:
8
+ "List the version history for a document. Optionally restrict to named snapshots.",
9
+ inputSchema: {
10
+ documentId: z
11
+ .string()
12
+ .describe("Document ID whose versions should be listed."),
13
+ onlySnapshots: z
14
+ .boolean()
15
+ .optional()
16
+ .describe(
17
+ "When true, only return named snapshots (skip auto-saved revisions).",
18
+ ),
19
+ },
20
+ } as const;
21
+
22
+ export interface VersionHistoryArgs {
23
+ documentId: string;
24
+ onlySnapshots?: boolean;
25
+ }
26
+
27
+ export async function handler(args: VersionHistoryArgs): Promise<Version[]> {
28
+ return (await client.getVersionHistory(
29
+ args.documentId,
30
+ args.onlySnapshots,
31
+ )) as Version[];
32
+ }