import { z } from 'zod';
import type { MCPTool, MCPToolHandler } from '../types.js';
import { getToolContext, getApiBaseUrl } from '../utils/api.js';
export const memoSearchTool: MCPTool = {
name: 'memo_search',
description: 'Searches for memos in the database. Supports content-based search and channel filtering. When no channel is specified, the currently active channel is used. Returns results in JSON format with memo information and content.',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Query string to search memo content (partial match)'
},
channelIds: {
type: 'array',
items: {
type: 'string'
},
description: 'List of channel IDs to search in (defaults to currently active channel if omitted)'
},
limit: {
type: 'number',
description: 'Maximum number of results to return (default: 20)',
minimum: 1,
maximum: 100
}
},
required: []
}
};
export const handleMemoSearch: MCPToolHandler = async (args: any) => {
try {
const schema = z.object({
query: z.string().optional(),
channelIds: z.array(z.string()).optional(),
limit: z.number().min(1).max(100).default(20)
});
const { query, channelIds, limit } = schema.parse(args);
const toolContext = await getToolContext();
let targetChannelIds = channelIds;
// Use active channel if no channel IDs are specified
if (!targetChannelIds || targetChannelIds.length === 0) {
const activeChannelId = toolContext.activeChannelId;
if (activeChannelId) {
targetChannelIds = [activeChannelId];
}
}
const url = new URL(`${getApiBaseUrl()}/api/memos/search`);
if (query) {
url.searchParams.set('query', query);
}
if (targetChannelIds && targetChannelIds.length > 0) {
targetChannelIds.forEach(channelId => {
url.searchParams.append('channelIds', channelId);
});
}
url.searchParams.set('limit', limit.toString());
url.searchParams.set('includeHidden', 'true');
// Don't set includeSecret as AI assistant cannot access secret memos
const response = await fetch(url.toString(), {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({})) as { error?: string };
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: false,
error: `API Error: ${response.statusText} - ${errorData.error || 'Unknown error'}`,
results: []
})
}
]
};
}
const result = await response.json() as { memos: any[] };
// Filter out secret memos (as a safety measure)
const filteredMemos = result.memos.filter(memo => !memo.isSecret);
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: true,
count: filteredMemos.length,
searchQuery: query,
filters: {
channelIds: targetChannelIds || [],
includeHidden: true
},
results: filteredMemos
})
}
]
};
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: false,
error: `Error occurred during memo search: ${errorMessage}`,
results: []
})
}
]
};
}
};