node-server #10
@@ -40,6 +40,30 @@ function isBinaryContent(buf: Buffer): boolean {
|
|||||||
return nonPrintable / sample.length > 0.1;
|
return nonPrintable / sample.length > 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Map common file extensions to MIME types */
|
||||||
|
const MIME_TYPES: Record<string, string> = {
|
||||||
|
pdf: 'application/pdf',
|
||||||
|
zip: 'application/zip',
|
||||||
|
gz: 'application/gzip',
|
||||||
|
tar: 'application/x-tar',
|
||||||
|
png: 'image/png',
|
||||||
|
jpg: 'image/jpeg',
|
||||||
|
jpeg: 'image/jpeg',
|
||||||
|
gif: 'image/gif',
|
||||||
|
webp: 'image/webp',
|
||||||
|
svg: 'image/svg+xml',
|
||||||
|
mp3: 'audio/mpeg',
|
||||||
|
mp4: 'video/mp4',
|
||||||
|
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
|
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||||
|
};
|
||||||
|
|
||||||
|
function getMimeType(filePath: string): string {
|
||||||
|
const ext = filePath.split('.').pop()?.toLowerCase() ?? '';
|
||||||
|
return MIME_TYPES[ext] ?? 'application/octet-stream';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register all file operation tools
|
* Register all file operation tools
|
||||||
*/
|
*/
|
||||||
@@ -151,7 +175,7 @@ export async function registerFileOperationTools(server: ObsidianMCPServer): Pro
|
|||||||
// T031: Read note tool
|
// T031: Read note tool
|
||||||
server.registerTool(
|
server.registerTool(
|
||||||
'obsidian_read_note',
|
'obsidian_read_note',
|
||||||
'Read the content of a note from the Obsidian vault. Specify either the note name (file) or full path (path). For large files (e.g. PDFs), use max_chars and offset to read in chunks and avoid exceeding context limits. Binary files (ZIP, images, compiled files, etc.) are automatically detected and returned as a base64-encoded string prefixed with "BASE64:" — the client must base64-decode the value to recover the original binary content.',
|
'Read the content of a note from the Obsidian vault. Specify either the note name (file) or full path (path). For large files (e.g. PDFs), use max_chars and offset to read in chunks and avoid exceeding context limits. Binary files (ZIP, images, PDFs, etc.) are automatically detected and returned as an MCP embedded resource with a uri, mimeType, and base64-encoded blob field instead of plain text.',
|
||||||
{
|
{
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -174,7 +198,7 @@ export async function registerFileOperationTools(server: ObsidianMCPServer): Pro
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
createToolHandler(
|
createToolHandler(
|
||||||
'Read the content of a note. Binary files are returned as a base64-encoded string prefixed with "BASE64:" — decode it to recover the original binary content.',
|
'Read the content of a note. Binary files are returned as an MCP embedded resource (type: "resource") with uri, mimeType, and a base64-encoded blob field.',
|
||||||
{
|
{
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -209,13 +233,21 @@ export async function registerFileOperationTools(server: ObsidianMCPServer): Pro
|
|||||||
const result = await executeObsidianCommandBinary('read', cmdArgs);
|
const result = await executeObsidianCommandBinary('read', cmdArgs);
|
||||||
handleCLIResult(result, { operation: 'read_note', identifier: sanitized.file || sanitized.path });
|
handleCLIResult(result, { operation: 'read_note', identifier: sanitized.file || sanitized.path });
|
||||||
|
|
||||||
// Detect binary content from the raw buffer and return as base64
|
// Detect binary content from the raw buffer and return as MCP resource
|
||||||
if (result.stdoutBuffer && isBinaryContent(result.stdoutBuffer)) {
|
if (result.stdoutBuffer && isBinaryContent(result.stdoutBuffer)) {
|
||||||
|
const identifier = sanitized.file || sanitized.path as string;
|
||||||
|
const vaultName = process.env.OBSIDIAN_VAULT ?? 'vault';
|
||||||
|
const uri = `obsidian://${encodeURIComponent(vaultName)}/${encodeURIComponent(identifier)}`;
|
||||||
|
const mimeType = getMimeType(identifier);
|
||||||
return {
|
return {
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'resource' as const,
|
||||||
text: `BASE64:${result.stdoutBuffer.toString('base64')}`,
|
resource: {
|
||||||
|
uri,
|
||||||
|
mimeType,
|
||||||
|
blob: result.stdoutBuffer.toString('base64'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ export interface ToolInput {
|
|||||||
* Successful tool output
|
* Successful tool output
|
||||||
*/
|
*/
|
||||||
export interface ToolOutput {
|
export interface ToolOutput {
|
||||||
content: Array<{
|
content: Array<
|
||||||
type: 'text';
|
| { type: 'text'; text: string }
|
||||||
text: string;
|
| { type: 'resource'; resource: { uri: string; mimeType?: string; blob: string } }
|
||||||
}>;
|
>;
|
||||||
isError?: false;
|
isError?: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user