search-content.ts•4.73 kB
/**
* MCP Tool: tableau_search_content
*
* Purpose: Search across all Tableau content (workbooks, views, data sources, projects)
* Parameters:
* - searchTerm (required): Search query text
* - contentType (optional): Filter by content type
* Returns: Array of search results with id, name, type, contentUrl
*/
import { z } from 'zod';
import { TableauClient } from '../tableau-client.js';
// Zod schema for input validation
export const SearchContentArgsSchema = z.object({
searchTerm: z.string().min(1).describe('Search query text (required)'),
contentType: z.enum(['workbook', 'view', 'datasource', 'project']).optional().describe('Optional: Filter by content type'),
});
export type SearchContentArgs = z.infer<typeof SearchContentArgsSchema>;
/**
* Handler for tableau_search_content tool
*
* Searches across all Tableau content types (workbooks, views, data sources, projects).
* Supports filtering by specific content type.
*
* @param args - Tool arguments (searchTerm, contentType)
* @param tableauClient - Initialized Tableau client instance
* @returns Formatted response with search results or error
*/
export async function searchContentHandler(
args: SearchContentArgs,
tableauClient: TableauClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
try {
// Validate searchTerm is provided
if (!args.searchTerm || args.searchTerm.trim() === '') {
return {
content: [{
type: 'text',
text: 'Error: searchTerm is required and cannot be empty.'
}]
};
}
// Call TableauClient to search content
const results = await tableauClient.searchContent(
args.searchTerm,
args.contentType
);
// Format response for LLM consumption
if (results.length === 0) {
const typeFilter = args.contentType ? ` of type '${args.contentType}'` : '';
return {
content: [{
type: 'text',
text: `No results found for search term '${args.searchTerm}'${typeFilter}.`
}]
};
}
// Group results by content type for better readability
const resultsByType: Record<string, typeof results> = {};
results.forEach(result => {
if (!resultsByType[result.type]) {
resultsByType[result.type] = [];
}
resultsByType[result.type].push(result);
});
// Create a summary with count and details grouped by type
const typeFilter = args.contentType ? ` (filtered by: ${args.contentType})` : '';
let summary = `Found ${results.length} result(s) for '${args.searchTerm}'${typeFilter}:\n\n`;
// Add results grouped by type
Object.entries(resultsByType).forEach(([type, items]) => {
summary += `\n${type.toUpperCase()}S (${items.length}):\n`;
items.forEach((item, index) => {
summary += `\n${index + 1}. ${item.name}\n` +
` ID: ${item.id}\n` +
` Content URL: ${item.contentUrl}\n` +
` Type: ${item.type}`;
});
summary += '\n';
});
// Also include raw JSON data for structured access
const jsonData = JSON.stringify(results, null, 2);
return {
content: [
{
type: 'text',
text: summary
},
{
type: 'text',
text: `\n\nRaw JSON data:\n${jsonData}`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
// Provide helpful error messages
if (errorMessage.includes('invalid') && args.contentType) {
return {
content: [{
type: 'text',
text: `Error: Invalid content type '${args.contentType}'. Valid types are: workbook, view, datasource, project.`
}]
};
}
return {
content: [{
type: 'text',
text: `Error searching content: ${errorMessage}`
}]
};
}
}
// Tool metadata for MCP server registration
export const searchContentTool = {
name: 'tableau_search_content',
description: 'Search across all Tableau content including workbooks, views, data sources, and projects. Supports filtering by content type. Returns matching items with ID, name, type, and content URL. Use this to discover content when you don\'t know the exact ID.',
inputSchema: {
type: 'object',
properties: {
searchTerm: {
type: 'string',
description: 'Required: Search query text to find matching content'
},
contentType: {
type: 'string',
enum: ['workbook', 'view', 'datasource', 'project'],
description: 'Optional: Filter results by content type (workbook, view, datasource, or project)'
}
},
required: ['searchTerm']
}
};