@khoinguyen2002/doc-mcp 1.0.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.
@@ -0,0 +1,13 @@
1
+ export declare const config: {
2
+ QDRANT_URL: string;
3
+ OPENROUTER_API_KEY: string;
4
+ EMBEDDING_MODEL_ID: string;
5
+ CHUNK_SIZE: number;
6
+ CHUNK_OVERLAP: number;
7
+ DOC_MCP_DRIVE_FOLDER_ID?: string | undefined;
8
+ DOC_MCP_GOOGLE_CLIENT_EMAIL?: string | undefined;
9
+ DOC_MCP_GOOGLE_PRIVATE_KEY?: string | undefined;
10
+ QDRANT_API_KEY?: string | undefined;
11
+ };
12
+ export type Config = typeof config;
13
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AA2BA,eAAO,MAAM,MAAM;;;;;;;;;;CAAe,CAAC;AACnC,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+ const schema = z.object({
3
+ DOC_MCP_DRIVE_FOLDER_ID: z.string().optional(),
4
+ DOC_MCP_GOOGLE_CLIENT_EMAIL: z.string().email().optional(),
5
+ DOC_MCP_GOOGLE_PRIVATE_KEY: z.string().optional(),
6
+ // Vector DB / Embeddings
7
+ QDRANT_URL: z.string().url().describe("The URL of your Qdrant instance"),
8
+ QDRANT_API_KEY: z.string().optional().describe("API Key for Qdrant Cloud (optional for local)"),
9
+ OPENROUTER_API_KEY: z.string().min(1),
10
+ EMBEDDING_MODEL_ID: z.string().default("nvidia/llama-nemotron-embed-vl-1b-v2:free"),
11
+ CHUNK_SIZE: z.coerce.number().int().positive().default(4000),
12
+ CHUNK_OVERLAP: z.coerce.number().int().nonnegative().default(500),
13
+ });
14
+ function loadConfig() {
15
+ const result = schema.safeParse(process.env);
16
+ if (!result.success) {
17
+ const missing = result.error.issues
18
+ .map((i) => ` ${i.path.join(".")}: ${i.message}`)
19
+ .join("\n");
20
+ throw new Error(`Invalid environment configuration for doc-mcp:\n${missing}`);
21
+ }
22
+ return result.data;
23
+ }
24
+ export const config = loadConfig();
@@ -0,0 +1,8 @@
1
+ export declare function initVectorDB(): Promise<void>;
2
+ export declare function embedText(text: string): Promise<number[]>;
3
+ export declare function upsertProjectDocument(projectId: string, text: string, metadata?: Record<string, any>): Promise<void>;
4
+ export declare function searchProjectMemory(projectId: string, query: string, topK?: number): Promise<any[]>;
5
+ export declare function deleteProjectDocument(projectId: string, fileId: string): Promise<void>;
6
+ export declare function checkProjectDocumentExists(projectId: string, fileId: string): Promise<boolean>;
7
+ export declare function getProjectDocumentMetadata(projectId: string): Promise<Record<string, string>>;
8
+ //# sourceMappingURL=vector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vector.d.ts","sourceRoot":"","sources":["../../src/db/vector.ts"],"names":[],"mappings":"AAOA,wBAAsB,YAAY,kBAqCjC;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAwB/D;AAED,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B9H;AAED,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,MAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CA4B5G;AAED,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAa5F;AAED,wBAAsB,0BAA0B,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAapG;AAED,wBAAsB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAwBnG"}
@@ -0,0 +1,166 @@
1
+ import { QdrantClient } from '@qdrant/js-client-rest';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import { config } from '../config.js';
4
+ let client = null;
5
+ const COLLECTION_NAME = 'project_memory';
6
+ export async function initVectorDB() {
7
+ if (!client) {
8
+ client = new QdrantClient({
9
+ url: config.QDRANT_URL,
10
+ apiKey: config.QDRANT_API_KEY,
11
+ });
12
+ console.error(`Connected to Qdrant at ${config.QDRANT_URL}`);
13
+ // Check if collection exists
14
+ const res = await client.getCollections();
15
+ const exists = res.collections.some(c => c.name === COLLECTION_NAME);
16
+ if (!exists) {
17
+ console.error(`Creating Qdrant collection: ${COLLECTION_NAME}`);
18
+ const dummyVector = await embedText("test");
19
+ const dimension = dummyVector.length;
20
+ await client.createCollection(COLLECTION_NAME, {
21
+ vectors: {
22
+ size: dimension,
23
+ distance: "Cosine",
24
+ },
25
+ });
26
+ await client.createPayloadIndex(COLLECTION_NAME, {
27
+ field_name: "projectId",
28
+ field_schema: "keyword",
29
+ });
30
+ await client.createPayloadIndex(COLLECTION_NAME, {
31
+ field_name: "file_id",
32
+ field_schema: "keyword",
33
+ });
34
+ await client.createPayloadIndex(COLLECTION_NAME, {
35
+ field_name: "source",
36
+ field_schema: "keyword",
37
+ });
38
+ console.error(`Collection ${COLLECTION_NAME} created with dimension ${dimension}.`);
39
+ }
40
+ }
41
+ }
42
+ export async function embedText(text) {
43
+ const response = await fetch("https://openrouter.ai/api/v1/embeddings", {
44
+ method: "POST",
45
+ headers: {
46
+ "Authorization": `Bearer ${config.OPENROUTER_API_KEY}`,
47
+ "Content-Type": "application/json"
48
+ },
49
+ body: JSON.stringify({
50
+ model: config.EMBEDDING_MODEL_ID,
51
+ input: text
52
+ })
53
+ });
54
+ if (!response.ok) {
55
+ const errText = await response.text();
56
+ throw new Error(`OpenRouter Embedding API failed: ${response.status} ${errText}`);
57
+ }
58
+ const json = await response.json();
59
+ if (!json.data || !json.data[0] || !json.data[0].embedding) {
60
+ throw new Error("Invalid response from OpenRouter Embedding API");
61
+ }
62
+ return json.data[0].embedding;
63
+ }
64
+ export async function upsertProjectDocument(projectId, text, metadata = {}) {
65
+ await initVectorDB();
66
+ if (!client)
67
+ throw new Error("Qdrant not initialized");
68
+ const vector = await embedText(text);
69
+ await client.upsert(COLLECTION_NAME, {
70
+ wait: true,
71
+ points: [
72
+ {
73
+ id: uuidv4(),
74
+ vector: vector,
75
+ payload: {
76
+ projectId,
77
+ text,
78
+ source: metadata.source || "user",
79
+ file_id: metadata.file_id || null,
80
+ modified_time: metadata.modified_time || null,
81
+ metadata: JSON.stringify(metadata),
82
+ createdAt: new Date().toISOString()
83
+ }
84
+ }
85
+ ]
86
+ });
87
+ console.error(`Upserted document chunk for project ${projectId}`);
88
+ }
89
+ export async function searchProjectMemory(projectId, query, topK = 3) {
90
+ await initVectorDB();
91
+ if (!client)
92
+ throw new Error("Qdrant not initialized");
93
+ const queryVector = await embedText(query);
94
+ const results = await client.search(COLLECTION_NAME, {
95
+ vector: queryVector,
96
+ limit: topK,
97
+ with_payload: true,
98
+ filter: {
99
+ must: [
100
+ {
101
+ key: "projectId",
102
+ match: {
103
+ value: projectId
104
+ }
105
+ }
106
+ ]
107
+ }
108
+ });
109
+ // Map to match LanceDB format expected by other tools
110
+ return results.map(r => ({
111
+ id: r.id,
112
+ vector: r.vector,
113
+ ...r.payload
114
+ }));
115
+ }
116
+ export async function deleteProjectDocument(projectId, fileId) {
117
+ await initVectorDB();
118
+ if (!client)
119
+ return;
120
+ await client.delete(COLLECTION_NAME, {
121
+ filter: {
122
+ must: [
123
+ { key: "projectId", match: { value: projectId } },
124
+ { key: "file_id", match: { value: fileId } }
125
+ ]
126
+ }
127
+ });
128
+ console.error(`Deleted old chunks from Qdrant for ${projectId} / ${fileId}`);
129
+ }
130
+ export async function checkProjectDocumentExists(projectId, fileId) {
131
+ await initVectorDB();
132
+ if (!client)
133
+ return false;
134
+ const res = await client.count(COLLECTION_NAME, {
135
+ filter: {
136
+ must: [
137
+ { key: "projectId", match: { value: projectId } },
138
+ { key: "file_id", match: { value: fileId } }
139
+ ]
140
+ }
141
+ });
142
+ return res.count > 0;
143
+ }
144
+ export async function getProjectDocumentMetadata(projectId) {
145
+ await initVectorDB();
146
+ if (!client)
147
+ return {};
148
+ const res = await client.scroll(COLLECTION_NAME, {
149
+ filter: {
150
+ must: [
151
+ { key: "projectId", match: { value: projectId } },
152
+ { key: "source", match: { value: "google_drive" } }
153
+ ]
154
+ },
155
+ limit: 10000,
156
+ with_payload: ["file_id", "modified_time"],
157
+ with_vector: false
158
+ });
159
+ const fileMap = {};
160
+ for (const r of res.points) {
161
+ if (r.payload && r.payload.file_id && r.payload.modified_time) {
162
+ fileMap[r.payload.file_id] = r.payload.modified_time;
163
+ }
164
+ }
165
+ return fileMap;
166
+ }
@@ -0,0 +1,2 @@
1
+ export declare function syncProjectDriveFiles(projectId: string, onSyncMessage?: (msg: string) => void): Promise<void>;
2
+ //# sourceMappingURL=driveSync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driveSync.d.ts","sourceRoot":"","sources":["../../src/hooks/driveSync.ts"],"names":[],"mappings":"AAeA,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GACpC,OAAO,CAAC,IAAI,CAAC,CAyFf"}
@@ -0,0 +1,88 @@
1
+ import { getProjectDocumentMetadata, deleteProjectDocument, upsertProjectDocument, } from "../db/vector.js";
2
+ import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
3
+ // Remove childLogger dependency to fully decouple from core
4
+ // import { childLogger } from "@workspace/core";
5
+ import { config } from "../config.js";
6
+ const log = {
7
+ info: (obj, msg) => console.log(`[driveSync] ${msg}`, obj),
8
+ error: (obj, msg) => console.error(`[driveSync] ${msg}`, obj)
9
+ };
10
+ export async function syncProjectDriveFiles(projectId, onSyncMessage) {
11
+ const dbMeta = await getProjectDocumentMetadata(projectId);
12
+ const fileIds = Object.keys(dbMeta);
13
+ if (fileIds.length === 0)
14
+ return;
15
+ log.info({ projectId, fileCount: fileIds.length }, "Checking Drive files for updates...");
16
+ const { google } = await import("googleapis");
17
+ const clientEmail = config.DOC_MCP_GOOGLE_CLIENT_EMAIL;
18
+ let privateKey = config.DOC_MCP_GOOGLE_PRIVATE_KEY;
19
+ if (!clientEmail || !privateKey)
20
+ return;
21
+ if (privateKey.startsWith('"') && privateKey.endsWith('"')) {
22
+ privateKey = privateKey.slice(1, -1);
23
+ }
24
+ privateKey = privateKey.replace(/\\n/g, "\n");
25
+ const auth = new google.auth.JWT({
26
+ email: clientEmail,
27
+ key: privateKey,
28
+ scopes: ["https://www.googleapis.com/auth/drive.readonly"],
29
+ });
30
+ const drive = google.drive({ version: "v3", auth });
31
+ let updatedCount = 0;
32
+ for (const fileId of fileIds) {
33
+ try {
34
+ const fileInfo = await drive.files.get({
35
+ fileId: fileId,
36
+ fields: "id, name, modifiedTime, trashed",
37
+ supportsAllDrives: true,
38
+ });
39
+ if (fileInfo.data.trashed) {
40
+ log.info({ fileId, projectId }, "File trashed on Drive, deleting from VectorDB...");
41
+ await deleteProjectDocument(projectId, fileId);
42
+ continue;
43
+ }
44
+ const driveModifiedTime = fileInfo.data.modifiedTime || "";
45
+ const dbModifiedTime = dbMeta[fileId];
46
+ if (driveModifiedTime !== dbModifiedTime) {
47
+ if (onSyncMessage) {
48
+ onSyncMessage(`🔄 Syncing updated file: ${fileInfo.data.name}...`);
49
+ }
50
+ log.info({ fileId, projectId }, "File updated on Drive, syncing...");
51
+ await deleteProjectDocument(projectId, fileId);
52
+ const res = await drive.files.export({
53
+ fileId: fileId,
54
+ mimeType: "text/plain",
55
+ });
56
+ const content = res.data;
57
+ if (typeof content === "string" && content.trim() !== "") {
58
+ const splitter = new RecursiveCharacterTextSplitter({
59
+ chunkSize: config.CHUNK_SIZE,
60
+ chunkOverlap: config.CHUNK_OVERLAP,
61
+ });
62
+ const chunks = await splitter.splitText(content);
63
+ for (const chunk of chunks) {
64
+ await upsertProjectDocument(projectId, chunk, {
65
+ title: fileInfo.data.name,
66
+ file_id: fileId,
67
+ source: "google_drive",
68
+ modified_time: driveModifiedTime,
69
+ });
70
+ }
71
+ updatedCount++;
72
+ }
73
+ }
74
+ }
75
+ catch (err) {
76
+ if (err.code === 404) {
77
+ log.info({ fileId, projectId }, "File not found on Drive, deleting from VectorDB...");
78
+ await deleteProjectDocument(projectId, fileId);
79
+ }
80
+ else {
81
+ log.error({ fileId, err: err.message }, "Error syncing drive file");
82
+ }
83
+ }
84
+ }
85
+ if (updatedCount > 0 && onSyncMessage) {
86
+ onSyncMessage(`✅ Synced ${updatedCount} files from Google Drive.`);
87
+ }
88
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export {};
2
+ // The doc-agent is now an MCP Server.
3
+ // Use `npx doc-agent` or `node dist/mcp-server.js` to run it.
4
+ // We no longer export tools directly as JS functions.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=mcp-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":""}
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { listDriveFiles, readDriveDocument, } from "./tools/driveTools.js";
6
+ import { saveAgentNote, searchKnowledge } from "./tools/knowledgeTools.js";
7
+ import { config } from "./config.js";
8
+ const DRIVE_FOLDER_ID = config.DOC_MCP_DRIVE_FOLDER_ID;
9
+ if (!DRIVE_FOLDER_ID) {
10
+ console.error("Missing DOC_MCP_DRIVE_FOLDER_ID environment variable. The doc-agent requires a target folder ID.");
11
+ process.exit(1);
12
+ }
13
+ const server = new McpServer({
14
+ name: "doc-agent",
15
+ version: "1.0.0",
16
+ });
17
+ // Register tools
18
+ server.registerTool("list_drive_files", {
19
+ description: "List and search for Google Drive documents in the configured folder.",
20
+ inputSchema: {
21
+ keyword: z
22
+ .string()
23
+ .optional()
24
+ .describe("Optional keyword to search for in document titles"),
25
+ },
26
+ }, async ({ keyword }) => {
27
+ const res = await listDriveFiles(keyword);
28
+ if (!res.success) {
29
+ return {
30
+ content: [{ type: "text", text: `Error: ${res.error}` }],
31
+ isError: true,
32
+ };
33
+ }
34
+ return {
35
+ content: [{ type: "text", text: JSON.stringify(res.results, null, 2) }],
36
+ };
37
+ });
38
+ server.registerTool("read_drive_document", {
39
+ description: "Read the content of a specific Google Drive document. The document will also be automatically ingested into vector memory for future semantic search.",
40
+ inputSchema: {
41
+ fileId: z.string().describe("The Google Drive file ID to read"),
42
+ },
43
+ }, async ({ fileId }) => {
44
+ const res = await readDriveDocument(fileId);
45
+ if (!res.success) {
46
+ return {
47
+ content: [{ type: "text", text: `Error: ${res.error}` }],
48
+ isError: true,
49
+ };
50
+ }
51
+ return {
52
+ content: [{ type: "text", text: res.content || "No content found." }],
53
+ };
54
+ });
55
+ server.registerTool("save_agent_note", {
56
+ description: "Save an agent note, thought, or summary directly into the vector memory.",
57
+ inputSchema: {
58
+ content: z.string().describe("The note or knowledge content to store"),
59
+ },
60
+ }, async ({ content }) => {
61
+ const res = await saveAgentNote(content);
62
+ if (!res.success) {
63
+ return {
64
+ content: [{ type: "text", text: `Error: ${res.error}` }],
65
+ isError: true,
66
+ };
67
+ }
68
+ return {
69
+ content: [{ type: "text", text: res.message || "Saved successfully" }],
70
+ };
71
+ });
72
+ server.registerTool("search_knowledge", {
73
+ description: "Search the folder's vector memory for relevant context or knowledge.",
74
+ inputSchema: {
75
+ query: z.string().describe("The search query"),
76
+ topK: z
77
+ .number()
78
+ .optional()
79
+ .describe("Number of results to return (default: 3)"),
80
+ },
81
+ }, async ({ query, topK }) => {
82
+ const res = await searchKnowledge(query, topK);
83
+ if (!res.success) {
84
+ return {
85
+ content: [{ type: "text", text: `Error: ${res.error}` }],
86
+ isError: true,
87
+ };
88
+ }
89
+ return {
90
+ content: [
91
+ {
92
+ type: "text",
93
+ text: typeof res.results === "string"
94
+ ? res.results
95
+ : JSON.stringify(res.results),
96
+ },
97
+ ],
98
+ };
99
+ });
100
+ // Start the server
101
+ async function run() {
102
+ const transport = new StdioServerTransport();
103
+ await server.connect(transport);
104
+ console.error("doc-agent MCP server running on stdio");
105
+ }
106
+ run().catch((error) => {
107
+ console.error("Fatal error running server:", error);
108
+ process.exit(1);
109
+ });
@@ -0,0 +1,35 @@
1
+ export declare function listDriveFiles(keyword?: string): Promise<{
2
+ success: boolean;
3
+ results: import("googleapis").drive_v3.Schema$File[];
4
+ error?: undefined;
5
+ } | {
6
+ success: boolean;
7
+ error: any;
8
+ results?: undefined;
9
+ }>;
10
+ export declare function syncSingleDocument(fileId: string, folderId: string): Promise<{
11
+ synced: boolean;
12
+ content: string;
13
+ driveModifiedTime: string;
14
+ } | {
15
+ synced: boolean;
16
+ driveModifiedTime: string;
17
+ content?: undefined;
18
+ }>;
19
+ export declare function readDriveDocument(fileId: string): Promise<{
20
+ success: boolean;
21
+ content: string;
22
+ error?: undefined;
23
+ } | {
24
+ success: boolean;
25
+ error: any;
26
+ content?: undefined;
27
+ }>;
28
+ export declare function syncFolderState(folderId: string): Promise<{
29
+ success: boolean;
30
+ error?: undefined;
31
+ } | {
32
+ success: boolean;
33
+ error: any;
34
+ }>;
35
+ //# sourceMappingURL=driveTools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driveTools.d.ts","sourceRoot":"","sources":["../../src/tools/driveTools.ts"],"names":[],"mappings":"AAiCA,wBAAsB,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM;;;;;;;;GAoCpD;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;GA6CxE;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM;;;;;;;;GAsCrD;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM;;;;;;GAuCrD"}
@@ -0,0 +1,166 @@
1
+ import { google } from "googleapis";
2
+ import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
3
+ import { config } from "../config.js";
4
+ import { upsertProjectDocument, getProjectDocumentMetadata, deleteProjectDocument, } from "../db/vector.js";
5
+ function getDriveClient() {
6
+ const clientEmail = config.DOC_MCP_GOOGLE_CLIENT_EMAIL;
7
+ let privateKey = config.DOC_MCP_GOOGLE_PRIVATE_KEY;
8
+ if (!clientEmail || !privateKey) {
9
+ throw new Error("Google Drive credentials not configured. Please set DOC_MCP_GOOGLE_CLIENT_EMAIL and DOC_MCP_GOOGLE_PRIVATE_KEY in .env");
10
+ }
11
+ if (privateKey.startsWith('"') && privateKey.endsWith('"')) {
12
+ privateKey = privateKey.slice(1, -1);
13
+ }
14
+ privateKey = privateKey.replace(/\\n/g, "\n");
15
+ const auth = new google.auth.JWT({
16
+ email: clientEmail,
17
+ key: privateKey,
18
+ scopes: ["https://www.googleapis.com/auth/drive.readonly"],
19
+ });
20
+ return google.drive({ version: "v3", auth });
21
+ }
22
+ export async function listDriveFiles(keyword) {
23
+ const folderId = process.env.DOC_MCP_DRIVE_FOLDER_ID;
24
+ if (!folderId) {
25
+ return {
26
+ success: false,
27
+ error: "DOC_MCP_DRIVE_FOLDER_ID is not configured for this agent.",
28
+ };
29
+ }
30
+ try {
31
+ const drive = getDriveClient();
32
+ let q = "mimeType = 'application/vnd.google-apps.document'";
33
+ q = `'${folderId}' in parents and ${q}`;
34
+ if (keyword) {
35
+ q = `name contains '${keyword}' and ${q}`;
36
+ }
37
+ const res = await drive.files.list({
38
+ q,
39
+ fields: "files(id, name, description)",
40
+ spaces: "drive",
41
+ pageSize: 50,
42
+ supportsAllDrives: true,
43
+ includeItemsFromAllDrives: true,
44
+ });
45
+ const files = res.data.files;
46
+ if (!files || files.length === 0) {
47
+ return { success: true, results: [] };
48
+ }
49
+ return { success: true, results: files };
50
+ }
51
+ catch (err) {
52
+ return { success: false, error: err.message };
53
+ }
54
+ }
55
+ export async function syncSingleDocument(fileId, folderId) {
56
+ const drive = getDriveClient();
57
+ const fileInfo = await drive.files.get({
58
+ fileId,
59
+ fields: "id, name, modifiedTime",
60
+ supportsAllDrives: true,
61
+ });
62
+ const driveModifiedTime = fileInfo.data.modifiedTime || "";
63
+ const dbMetaMap = await getProjectDocumentMetadata(folderId);
64
+ const dbModifiedTime = dbMetaMap[fileId];
65
+ if (!dbModifiedTime || dbModifiedTime !== driveModifiedTime) {
66
+ if (dbModifiedTime) {
67
+ await deleteProjectDocument(folderId, fileId);
68
+ }
69
+ const res = await drive.files.export({
70
+ fileId: fileId,
71
+ mimeType: "text/plain",
72
+ });
73
+ const content = res.data;
74
+ if (typeof content !== "string" || content.trim() === "") {
75
+ throw new Error("Empty or invalid file content");
76
+ }
77
+ const splitter = new RecursiveCharacterTextSplitter({
78
+ chunkSize: config.CHUNK_SIZE,
79
+ chunkOverlap: config.CHUNK_OVERLAP,
80
+ });
81
+ const chunks = await splitter.splitText(content);
82
+ for (const chunk of chunks) {
83
+ await upsertProjectDocument(folderId, chunk, {
84
+ title: fileInfo.data.name || "Untitled Google Doc",
85
+ source: "google_drive",
86
+ file_id: fileId,
87
+ modified_time: driveModifiedTime,
88
+ });
89
+ }
90
+ return { synced: true, content, driveModifiedTime };
91
+ }
92
+ return { synced: false, driveModifiedTime };
93
+ }
94
+ export async function readDriveDocument(fileId) {
95
+ const folderId = process.env.DOC_MCP_DRIVE_FOLDER_ID;
96
+ if (!folderId) {
97
+ return {
98
+ success: false,
99
+ error: "DOC_MCP_DRIVE_FOLDER_ID is not configured for this agent.",
100
+ };
101
+ }
102
+ try {
103
+ const result = await syncSingleDocument(fileId, folderId);
104
+ // If not synced just now, we need to fetch content to return to the user
105
+ let content = result.content;
106
+ if (!content) {
107
+ const drive = getDriveClient();
108
+ const res = await drive.files.export({
109
+ fileId: fileId,
110
+ mimeType: "text/plain",
111
+ });
112
+ content = typeof res.data === "string" ? res.data : "";
113
+ }
114
+ let finalContent = content;
115
+ const MAX_CHARS = 10000;
116
+ if (finalContent && finalContent.length > MAX_CHARS) {
117
+ finalContent =
118
+ finalContent.substring(0, MAX_CHARS) +
119
+ "\n\n... [Content truncated due to length. The full document has been automatically ingested into Vector Memory. Use search_knowledge to query specific details.]";
120
+ }
121
+ return {
122
+ success: true,
123
+ content: finalContent || "Empty file",
124
+ };
125
+ }
126
+ catch (err) {
127
+ return { success: false, error: err.message };
128
+ }
129
+ }
130
+ export async function syncFolderState(folderId) {
131
+ try {
132
+ const drive = getDriveClient();
133
+ let q = "mimeType = 'application/vnd.google-apps.document'";
134
+ q = `'${folderId}' in parents and ${q}`;
135
+ const res = await drive.files.list({
136
+ q,
137
+ fields: "files(id, name, modifiedTime)",
138
+ spaces: "drive",
139
+ pageSize: 100,
140
+ supportsAllDrives: true,
141
+ includeItemsFromAllDrives: true,
142
+ });
143
+ const driveFiles = res.data.files || [];
144
+ const dbMetaMap = await getProjectDocumentMetadata(folderId);
145
+ // Sync updated or new files
146
+ for (const file of driveFiles) {
147
+ if (!file.id)
148
+ continue;
149
+ const dbModTime = dbMetaMap[file.id];
150
+ if (!dbModTime || dbModTime !== file.modifiedTime) {
151
+ await syncSingleDocument(file.id, folderId);
152
+ }
153
+ }
154
+ // Delete removed files from DB
155
+ for (const dbFileId of Object.keys(dbMetaMap)) {
156
+ if (!driveFiles.find(f => f.id === dbFileId)) {
157
+ await deleteProjectDocument(folderId, dbFileId);
158
+ }
159
+ }
160
+ return { success: true };
161
+ }
162
+ catch (err) {
163
+ console.error("Auto-sync failed:", err.message);
164
+ return { success: false, error: err.message };
165
+ }
166
+ }
@@ -0,0 +1,19 @@
1
+ export declare function saveAgentNote(content: string): Promise<{
2
+ success: boolean;
3
+ error: string;
4
+ message?: undefined;
5
+ } | {
6
+ success: boolean;
7
+ message: string;
8
+ error?: undefined;
9
+ }>;
10
+ export declare function searchKnowledge(query: string, topK?: number): Promise<{
11
+ success: boolean;
12
+ error: string;
13
+ results?: undefined;
14
+ } | {
15
+ success: boolean;
16
+ results: string;
17
+ error?: undefined;
18
+ }>;
19
+ //# sourceMappingURL=knowledgeTools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledgeTools.d.ts","sourceRoot":"","sources":["../../src/tools/knowledgeTools.ts"],"names":[],"mappings":"AAGA,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM;;;;;;;;GAelD;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,MAAU;;;;;;;;GAuBpE"}