import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import type { DocumentIndex } from "../../utils/processor/types.js";
import type {
EnrichedDocument,
VaultManager,
} from "../../utils/VaultManager.js";
import type { ObsidianContentQueryParams } from "./params.js";
async function getDocumentContent(
vaultManager: VaultManager,
filename: string,
excerptLength?: number,
): Promise<Partial<EnrichedDocument>> {
const doc = await vaultManager.getDocumentInfo(filename, {
includeStats: true,
});
if (!doc) return {};
if (excerptLength) {
doc.content =
doc.content.substring(0, excerptLength) +
(doc.content.length > excerptLength ? "..." : "");
}
return doc;
}
function formatDocument(
doc: DocumentIndex | EnrichedDocument,
includeContent: boolean,
excerptLength?: number,
) {
const hasContentProperty =
"content" in doc && typeof doc.content === "string";
// content 필드를 생성하는 로직을 명확하게 분리
const createContentObject = () => {
if (includeContent && hasContentProperty) {
// FullContentSchema 형태
const excerpt =
excerptLength && doc.content?.length > excerptLength
? `${doc.content?.substring(0, excerptLength)}...`
: doc.content;
return {
full: doc.content,
excerpt: excerpt,
};
} else {
// PreviewContentSchema 형태
return {
preview: "(Content not loaded)",
note: "Full content available with includeContent=true",
};
}
};
console.log(doc);
return {
filename: doc.filePath.split("/").pop() || doc.filePath,
fullPath: doc.filePath,
metadata: {
title: doc.frontmatter.title || "Untitled",
tags: doc.frontmatter.tags || [],
},
stats:
"stats" in doc && doc.stats
? doc.stats
: {
contentLength: doc.contentLength,
hasContent: hasContentProperty,
wordCount: 0,
},
content: createContentObject(), // 항상 객체를 반환
};
}
export async function searchDocuments(
vaultManager: VaultManager,
params: ObsidianContentQueryParams,
): Promise<CallToolResult> {
await vaultManager.initialize();
const searchResults = await vaultManager.searchDocuments(
params.keyword || "",
);
if (params.quiet) {
return {
isError: false,
content: [
{
type: "text",
text: JSON.stringify({
found: searchResults.length,
filenames: searchResults.map(
(doc) => doc.filePath.split("/").pop() || doc.filePath,
),
}),
},
],
};
}
const documentsData = await Promise.all(
searchResults.map(async (doc) => {
if (params.includeContent) {
const fullDoc = await getDocumentContent(
vaultManager,
doc.filePath,
params.excerptLength,
);
return formatDocument(
{ ...doc, ...fullDoc },
true,
params.excerptLength,
);
}
return formatDocument(doc, false);
}),
);
return {
isError: false,
content: [
{
type: "text",
text: JSON.stringify(
{
query: params.keyword,
found: documentsData.length,
total_in_vault: (await vaultManager.getAllDocuments()).length,
documents: documentsData,
},
null,
2,
),
},
],
};
}
export async function readSpecificFile(
vaultManager: VaultManager,
params: ObsidianContentQueryParams,
): Promise<CallToolResult> {
await vaultManager.initialize();
const doc = await vaultManager.getDocumentInfo(params.filename ?? "", {
includeStats: true,
includeBacklinks: true,
});
if (!doc) {
return {
isError: true,
content: [
{
type: "text",
text: JSON.stringify(
{ error: `Document not found: ${params.filename}` },
null,
2,
),
},
],
};
}
return {
isError: false,
content: [{ type: "text", text: JSON.stringify(doc, null, 2) }],
};
}
export async function listAllDocuments(
vaultManager: VaultManager,
params: ObsidianContentQueryParams,
): Promise<CallToolResult> {
await vaultManager.initialize();
const allDocuments = await vaultManager.getAllDocuments();
const limitedDocs = allDocuments.slice(0, params.limit || 50);
if (params.quiet) {
return {
isError: false,
content: [
{
type: "text",
text: JSON.stringify({
total_documents: allDocuments.length,
filenames: allDocuments.map(
(doc) => doc.filePath.split("/").pop() || doc.filePath,
),
}),
},
],
};
}
const documentsOverview = await Promise.all(
limitedDocs.map(async (doc) => {
if (params.includeContent) {
const fullDoc = await getDocumentContent(
vaultManager,
doc.filePath,
200,
);
return formatDocument({ ...doc, ...fullDoc }, true, 200);
}
return formatDocument(doc, false);
}),
);
return {
isError: false,
content: [
{
type: "text",
text: JSON.stringify(
{
vault_overview: {
total_documents: allDocuments.length,
showing: limitedDocs.length,
},
documents: documentsOverview,
},
null,
2,
),
},
],
};
}
export async function statsAllDocuments(
vaultManager: VaultManager,
): Promise<CallToolResult> {
await vaultManager.initialize();
const stats = vaultManager.getStats();
return {
isError: false,
content: [{ type: "text", text: JSON.stringify(stats, null, 2) }],
};
}