Skip to main content
Glama

MCP Server for Splunk

Apache 2.0
16
  • Apple
  • Linux
tools.mdโ€ข33.5 kB
# MCP Tools Best Practices Guide ## Table of Contents - [Overview](#overview) - [Tool Definition Best Practices](#tool-definition-best-practices) - [Input Schema Design](#input-schema-design) - [Output and Content Types](#output-and-content-types) - [Error Handling](#error-handling) - [Tool Annotations](#tool-annotations) - [Security Considerations](#security-considerations) - [Performance Optimization](#performance-optimization) - [Testing and Validation](#testing-and-validation) - [Documentation Standards](#documentation-standards) - [Implementation Examples](#implementation-examples) ## Overview Tools are a core primitive in the Model Context Protocol (MCP) that allow servers to expose executable functionality to clients and LLMs. Unlike resources which are application-controlled, tools are **model-controlled**, meaning the LLM can decide when and how to invoke them during conversations. ### Key Characteristics - Tools are invoked via JSON-RPC `tools/call` requests - Each tool has a unique name, description, and input schema - Tools return structured content with optional error handling - Tools support various content types: text, images, audio, and embedded resources ## Tool Definition Best Practices ### 1. Naming Conventions - **Use clear, descriptive names**: Choose names that immediately convey the tool's purpose - **Follow consistent patterns**: Use verb-noun patterns like `get_weather`, `create_file`, `analyze_data` - **Avoid abbreviations**: Prefer `calculate_sum` over `calc_sum` - **Use snake_case**: Follow the convention established in MCP specifications ```json // Good { "name": "get_weather_forecast", "description": "Get detailed weather forecast for a specific location" } // Avoid { "name": "weather", "description": "Weather stuff" } ``` ### 2. Comprehensive Descriptions - **Be specific and actionable**: Clearly explain what the tool does and when to use it - **Include context about limitations**: Mention any constraints or requirements - **Specify expected outcomes**: Describe what type of data will be returned ```json { "name": "analyze_csv", "description": "Analyze a CSV file and perform statistical operations like sum, average, and count on specified columns. Supports files up to 100MB with standard CSV formatting." } ``` ### 3. Tool Structure Template ```json { "name": "tool_name", "title": "Human-Readable Tool Title", "description": "Detailed description of functionality and use cases", "inputSchema": { "type": "object", "properties": { // Parameter definitions }, "required": ["param1", "param2"] }, "outputSchema": { "type": "object", "properties": { // Expected output structure } }, "annotations": { "title": "Tool Display Name", "readOnlyHint": false, "destructiveHint": false, "idempotentHint": false, "openWorldHint": true } } ``` ## Input Schema Design ### 1. Use Proper JSON Schema Types - **String validation**: Include patterns, formats, and length constraints - **Number validation**: Set minimum, maximum, and type constraints - **Array handling**: Define item types and length limits - **Object nesting**: Keep complexity manageable ```json { "inputSchema": { "type": "object", "properties": { "location": { "type": "string", "description": "City name or coordinates (lat,lng)", "pattern": "^[a-zA-Z\\s,.-]+$|^-?\\d+\\.\\d+,-?\\d+\\.\\d+$", "minLength": 2, "maxLength": 100 }, "days": { "type": "integer", "description": "Number of forecast days", "minimum": 1, "maximum": 14, "default": 7 }, "units": { "type": "string", "enum": ["metric", "imperial", "kelvin"], "description": "Temperature units", "default": "metric" } }, "required": ["location"] } } ``` ### 2. Parameter Best Practices - **Provide clear descriptions**: Each parameter should have a meaningful description - **Set reasonable defaults**: Include default values where appropriate - **Use enums for limited options**: Constrain choices with enum values - **Validate input ranges**: Set min/max values for numeric inputs ### 3. Required vs Optional Parameters - **Minimize required parameters**: Only mark truly essential parameters as required - **Provide sensible defaults**: Optional parameters should have reasonable defaults - **Document parameter interactions**: Explain how parameters work together ## Output and Content Types ### 1. Content Type Selection #### Text Content ```json { "type": "text", "text": "Structured text response with clear formatting" } ``` #### Image Content (Base64) ```json { "type": "image", "data": "base64-encoded-image-data", "mimeType": "image/png" } ``` #### Audio Content (Base64) ```json { "type": "audio", "data": "base64-encoded-audio-data", "mimeType": "audio/wav" } ``` #### Embedded Resources ```json { "type": "resource", "resource": { "uri": "file:///path/to/resource", "title": "Resource Title", "mimeType": "text/plain", "text": "Resource content" } } ``` #### Resource Links ```json { "type": "resource_link", "uri": "file:///path/to/resource", "name": "resource.txt", "description": "Link to external resource", "mimeType": "text/plain" } ``` ### 2. Structured Content with Output Schemas Use output schemas to validate and structure returned data: ```json { "outputSchema": { "type": "object", "properties": { "temperature": { "type": "number", "description": "Temperature in specified units" }, "conditions": { "type": "string", "description": "Weather conditions description" }, "humidity": { "type": "number", "minimum": 0, "maximum": 100, "description": "Humidity percentage" } }, "required": ["temperature", "conditions"] } } ``` ### 3. Return Multiple Content Types Tools can return multiple content items in a single response: ```json { "content": [ { "type": "text", "text": "Analysis complete. Temperature: 22ยฐC, Humidity: 65%" }, { "type": "image", "data": "base64-weather-chart", "mimeType": "image/png" } ], "structuredContent": { "temperature": 22, "humidity": 65, "conditions": "partly cloudy" } } ``` ## Error Handling ### 1. Proper Error Response Structure Always include the `isError` flag when tool execution fails: ```typescript try { const result = await performOperation(); return { content: [ { type: "text", text: `Operation successful: ${result}` } ] }; } catch (error) { return { isError: true, content: [ { type: "text", text: `Error: ${error.message}` } ] }; } ``` ### 2. Error Types and Handling #### Input Validation Errors ```json { "isError": true, "content": [ { "type": "text", "text": "Invalid input: 'location' parameter is required and must be a non-empty string" } ] } ``` #### External Service Errors ```json { "isError": true, "content": [ { "type": "text", "text": "Failed to fetch weather data: API rate limit exceeded. Please try again in 60 seconds." } ] } ``` #### Business Logic Errors ```json { "isError": true, "content": [ { "type": "text", "text": "Cannot delete file: File is currently in use by another process" } ] } ``` ### 3. Error Recovery Guidance Provide actionable error messages that help users understand how to fix issues: ```typescript if (!fs.existsSync(filePath)) { return { isError: true, content: [ { type: "text", text: `File not found: ${filePath}. Please check the file path and ensure the file exists. Use absolute paths for better reliability.` } ] }; } ``` ## Tool Annotations ### 1. Annotation Types and Usage #### readOnlyHint Indicates tools that don't modify their environment: ```json { "annotations": { "title": "Weather Information", "readOnlyHint": true, "openWorldHint": true } } ``` #### destructiveHint Marks tools that may perform destructive operations: ```json { "annotations": { "title": "Delete File", "readOnlyHint": false, "destructiveHint": true, "idempotentHint": true, "openWorldHint": false } } ``` #### idempotentHint Indicates tools that can be safely called multiple times: ```json { "annotations": { "title": "Create Directory", "readOnlyHint": false, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false } } ``` #### openWorldHint Indicates interaction with external systems: ```json { "annotations": { "title": "Web Search", "readOnlyHint": true, "openWorldHint": true } } ``` ### 2. Annotation Combinations | Tool Type | readOnly | destructive | idempotent | openWorld | Example | |-----------|----------|-------------|------------|-----------|---------| | Data Query | true | false | true | false | `get_database_record` | | Web API | true | false | true | true | `search_web` | | File Creation | false | false | true | false | `create_directory` | | File Deletion | false | true | true | false | `delete_file` | | Data Analysis | true | false | true | false | `analyze_csv` | ## Security Considerations ### 1. Input Validation - **Sanitize all inputs**: Never trust user-provided data - **Validate file paths**: Prevent directory traversal attacks - **Check permissions**: Ensure proper access controls - **Rate limiting**: Implement appropriate throttling ```typescript function validateFilePath(path: string): boolean { // Prevent directory traversal if (path.includes('..') || path.includes('~')) { return false; } // Ensure path is within allowed directories const allowedPaths = ['/tmp', '/workspace']; return allowedPaths.some(allowed => path.startsWith(allowed)); } ``` ### 2. Authentication and Authorization - **Validate credentials**: Check authentication tokens - **Implement role-based access**: Control tool access by user role - **Audit tool usage**: Log tool invocations for security monitoring ### 3. Data Protection - **Encrypt sensitive data**: Protect data in transit and at rest - **Avoid logging secrets**: Don't include sensitive data in logs - **Sanitize outputs**: Remove sensitive information from responses ## Performance Optimization ### 1. Response Time Optimization - **Cache frequently accessed data**: Implement intelligent caching - **Use connection pooling**: Reuse database/API connections - **Implement timeouts**: Set reasonable operation timeouts - **Optimize data processing**: Use efficient algorithms and data structures ### 2. Resource Management - **Stream large data**: Use streaming for large files/datasets - **Limit response sizes**: Prevent memory exhaustion - **Clean up resources**: Properly dispose of file handles, connections ```typescript async function processLargeFile(filePath: string): Promise<CallToolResult> { const maxSize = 10 * 1024 * 1024; // 10MB limit const stats = await fs.promises.stat(filePath); if (stats.size > maxSize) { return { isError: true, content: [ { type: "text", text: `File too large: ${stats.size} bytes. Maximum size is ${maxSize} bytes.` } ] }; } // Process file... } ``` ### 3. Pagination Support Implement pagination for tools that return large datasets: ```json { "inputSchema": { "type": "object", "properties": { "query": {"type": "string"}, "page": {"type": "integer", "minimum": 1, "default": 1}, "pageSize": {"type": "integer", "minimum": 1, "maximum": 100, "default": 20} } } } ``` ## Testing and Validation ### 1. Unit Testing Test each tool's core functionality: ```typescript describe('WeatherTool', () => { it('should return weather data for valid location', async () => { const result = await weatherTool.call({ location: 'New York', units: 'metric' }); expect(result.isError).toBe(false); expect(result.content).toHaveLength(1); expect(result.content[0].type).toBe('text'); }); it('should handle invalid location gracefully', async () => { const result = await weatherTool.call({ location: '' }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Invalid input'); }); }); ``` ### 2. Integration Testing Test tool interactions with external services: ```typescript describe('WeatherTool Integration', () => { it('should handle API rate limiting', async () => { // Mock rate limit response mockApiResponse(429, { error: 'Rate limit exceeded' }); const result = await weatherTool.call({ location: 'New York' }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('rate limit'); }); }); ``` ### 3. Schema Validation Validate input and output schemas: ```typescript import Ajv from 'ajv'; const ajv = new Ajv(); function validateToolInput(tool: Tool, input: any): boolean { const validate = ajv.compile(tool.inputSchema); return validate(input); } function validateToolOutput(tool: Tool, output: any): boolean { if (tool.outputSchema) { const validate = ajv.compile(tool.outputSchema); return validate(output.structuredContent); } return true; } ``` ## Documentation Standards ### 1. Tool Documentation Template ```markdown ## Tool Name: get_weather_forecast ### Description Retrieves detailed weather forecast information for a specified location using the OpenWeatherMap API. ### Parameters - `location` (string, required): City name, coordinates (lat,lng), or zip code - `days` (integer, optional): Number of forecast days (1-14, default: 7) - `units` (string, optional): Temperature units ('metric'|'imperial'|'kelvin', default: 'metric') ### Returns - Weather forecast data including temperature, conditions, humidity, and wind information - Structured JSON data with forecast for each day - Optional weather map image if available ### Error Conditions - Invalid location format - API rate limit exceeded - Network connectivity issues - Unsupported location (outside API coverage) ### Examples ```json { "location": "New York", "days": 5, "units": "metric" } ``` ### Annotations - Read-only: Yes - Open world: Yes (accesses external weather API) - Idempotent: Yes ``` ### 2. Code Documentation Include comprehensive inline documentation: ```typescript /** * Retrieves weather forecast for a specified location * * @param request - Tool call request containing location and options * @returns Promise<CallToolResult> Weather forecast data or error * * @throws {Error} When location parameter is missing or invalid * @throws {Error} When external API is unavailable * * @example * ```typescript * const result = await getWeatherForecast({ * location: "London", * days: 3, * units: "metric" * }); * ``` */ async function getWeatherForecast( request: CallToolRequest ): Promise<CallToolResult> { // Implementation... } ``` ## Implementation Examples ### 1. Simple Calculator Tool ```typescript import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { CallToolRequestSchema, CallToolResult } from "@modelcontextprotocol/sdk/types.js"; const server = new Server({ name: "calculator-server", version: "1.0.0" }, { capabilities: { tools: {} } }); server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === "calculate") { const { operation, a, b } = request.params.arguments; try { let result: number; switch (operation) { case "add": result = a + b; break; case "subtract": result = a - b; break; case "multiply": result = a * b; break; case "divide": if (b === 0) { throw new Error("Division by zero is not allowed"); } result = a / b; break; default: throw new Error(`Unsupported operation: ${operation}`); } return { content: [ { type: "text", text: `${a} ${operation} ${b} = ${result}` } ], structuredContent: { operation, operands: [a, b], result } }; } catch (error) { return { isError: true, content: [ { type: "text", text: `Calculation error: ${error.message}` } ] }; } } throw new Error(`Unknown tool: ${request.params.name}`); }); ``` ## Conclusion Following these best practices will help you create robust, secure, and user-friendly MCP tools that integrate seamlessly with LLM applications. Remember to: 1. **Design with the user in mind**: Clear names, descriptions, and error messages 2. **Implement proper security**: Validate inputs, sanitize outputs, control access 3. **Handle errors gracefully**: Provide actionable error messages 4. **Optimize for performance**: Cache data, limit response sizes, use timeouts 5. **Test thoroughly**: Unit tests, integration tests, edge cases 6. **Document comprehensively**: Code comments, API documentation, examples By following these guidelines, your tools will be reliable, maintainable, and provide excellent user experiences for LLM interactions. ## MCP Resources for Splunk Enterprise ### Understanding Resources vs Tools in Splunk Context **MCP Resources** are application-controlled data sources that provide context to LLMs, while **Tools** are model-controlled executable functions. For Splunk enterprise environments, resources should expose configuration data, search results, and knowledge objects that LLMs can use for analysis and decision-making. ## ๐Ÿข **Multi-Tenant Resources with Client-Specific Configurations** ### **The Challenge: Client-Configured Splunk Connections** When clients provide their own Splunk configurations, you face these key challenges: 1. **Dynamic Resource Discovery**: Resources vary per client's Splunk instance 2. **Security Isolation**: Each client should only access their own data 3. **Connection Management**: Efficient handling of multiple Splunk connections 4. **URI Scoping**: Resources must be scoped to specific clients ### **Solution Architecture: Client-Scoped Resources** #### **1. Client Identity & Security** ```python # Client identity based on configuration hash @dataclass class ClientIdentity: client_id: str # Hash-based secure identifier session_id: str # MCP session ID config_hash: str # Deterministic config hash splunk_host: str # For auditing created_at: float # For cleanup # Example client ID generation def create_client_identity(client_config: Dict) -> str: config_str = f"{host}|{port}|{username}|{scheme}" config_hash = hashlib.sha256(config_str.encode()).hexdigest()[:16] return f"client_{config_hash}_{session_id[:8]}" ``` #### **2. Client-Scoped URI Patterns** ```python # Multi-tenant resource URIs CLIENT_SCOPED_PATTERNS = { "config": "splunk://client/{client_id}/config/{config_file}", "health": "splunk://client/{client_id}/health/{component}/status", "search": "splunk://client/{client_id}/search/results/{search_id}", "schema": "splunk://client/{client_id}/schema/index/{index_name}" } # Example client-scoped URIs "splunk://client/abc123def/config/indexes.conf" "splunk://client/abc123def/health/indexer/status" "splunk://client/xyz789ghi/search/results/1234567890.12345" ``` #### **3. Dynamic Resource Discovery** Resources are discovered **per-client** based on their Splunk instance: ```python async def list_resources(ctx: Context) -> List[Dict[str, Any]]: """Dynamically discover client-specific resources""" # 1. Extract client config from MCP context client_config = get_client_config_from_context(ctx) if not client_config: return [] # No resources without client config # 2. Create secure client identity identity = create_client_identity(ctx, client_config) # 3. Establish client-specific Splunk connection service = get_splunk_service(client_config) # 4. Discover available resources for this client resources = [] # Configuration files available on this instance for config_file in ["indexes.conf", "props.conf", "transforms.conf"]: uri = f"splunk://client/{identity.client_id}/config/{config_file}" resources.append({ "uri": uri, "name": f"{config_file} ({identity.splunk_host})", "description": f"Configuration for client {identity.client_id}", "mimeType": "text/plain" }) # Health monitoring for client's components for component in ["indexer", "search", "forwarder"]: uri = f"splunk://client/{identity.client_id}/health/{component}/status" resources.append({ "uri": uri, "name": f"Health: {component} ({identity.splunk_host})", "description": f"{component} health for client {identity.client_id}", "mimeType": "application/json" }) return resources ``` #### **4. Secure Resource Access Control** ```python async def read_resource(ctx: Context, uri: str) -> str: """Read resource with client isolation""" # 1. Extract client config and create identity client_config = get_client_config_from_context(ctx) identity = create_client_identity(ctx, client_config) # 2. Validate URI belongs to this client if identity.client_id not in uri: raise SecurityError(f"Access denied: URI {uri} not owned by client") # 3. Get client-specific Splunk connection service = get_splunk_service(client_config) # 4. Read resource content with client's connection content = await get_resource_content(service, uri, identity) # 5. Audit log access logger.info(f"Resource accessed by {identity.client_id}: {uri}") return content ``` ### **Connection Management & Performance** #### **1. Connection Pooling per Client** ```python class ClientConnectionManager: def __init__(self): self._connections: Dict[str, client.Service] = {} self._client_identities: Dict[str, ClientIdentity] = {} async def get_client_connection(self, ctx: Context, client_config: Dict) -> Tuple[ClientIdentity, client.Service]: """Get or create client-specific connection""" identity = create_client_identity(ctx, client_config) # Reuse existing connection if available if identity.client_id in self._connections: return identity, self._connections[identity.client_id] # Create new connection service = get_splunk_service(client_config) self._connections[identity.client_id] = service self._client_identities[identity.client_id] = identity return identity, service ``` #### **2. Security Validation** ```python def validate_client_config(config: Dict[str, Any]): """Validate client config for security""" required_fields = ['splunk_host', 'splunk_username', 'splunk_password'] for field in required_fields: if not config.get(field): raise SecurityError(f"Required field missing: {field}") # Prevent injection attacks host = config['splunk_host'] if not isinstance(host, str) or not host.strip(): raise SecurityError("Invalid host format") # Port validation port = config.get('splunk_port', 8089) if not isinstance(port, int) or port < 1 or port > 65535: raise SecurityError("Invalid port") ``` ### **Resource Implementation Examples** #### **1. Client-Scoped Configuration Resource** ```python class SplunkConfigResource(ClientScopedResource): def __init__(self): super().__init__( base_uri_template="splunk://client/{client_id}/config/{config_file}", name="Splunk Configuration", description="Client-specific configuration files" ) async def get_content(self, ctx: Context, uri: str) -> str: # Extract client config and validate access client_config = self._get_client_config_from_context(ctx) identity, service = await self.client_manager.get_client_connection(ctx, client_config) # Validate URI ownership if identity.client_id not in uri: raise SecurityError("Access denied") # Extract config file from URI config_file = uri.split("/")[-1] # e.g., "indexes.conf" # Read from client's Splunk instance config_name = config_file.split('.')[0] # Remove .conf response = service.get(f"configs/conf-{config_name}") raw_config = response.body.read() # Format with client context formatted = f""" # Splunk Configuration: {config_file} # Client: {identity.client_id} # Host: {identity.splunk_host} # Retrieved: {datetime.utcnow().isoformat()}Z {raw_config} """ return formatted ``` #### **2. Client-Scoped Health Resource** ```python class SplunkHealthResource(ClientScopedResource): def __init__(self): super().__init__( base_uri_template="splunk://client/{client_id}/health/{component}/status", name="Health Status", supports_subscriptions=True # Real-time updates ) async def get_content(self, ctx: Context, uri: str) -> str: identity, service = await self._get_client_connection(ctx) # Extract component from URI component = uri.split("/")[-2] # "indexer", "search", etc. # Get health data from client's Splunk info = service.info() health_data = { "client_id": identity.client_id, "component": component, "timestamp": time.time(), "splunk_info": { "version": info.get("version"), "server_name": info.get("serverName"), "license_state": info.get("licenseState") }, "status": "healthy" } return json.dumps(health_data, indent=2) ``` ### **MCP Server Integration** #### **Register Resources with Server Capabilities** ```python # Declare full resource capabilities { "capabilities": { "resources": { "subscribe": True, # Individual resource subscriptions "listChanged": True # Resource list change notifications } } } # Register resource handlers with FastMCP @app.list_resources() async def list_resources_handler(ctx: Context): """Handle resources/list requests with client scoping""" handler = get_resource_handler() return await handler.list_resources(ctx) @app.read_resource() async def read_resource_handler(ctx: Context, uri: str): """Handle resources/read with security validation""" handler = get_resource_handler() content = await handler.read_resource(ctx, uri) return { "contents": [{ "uri": uri, "mimeType": "text/plain", "text": content }] } ``` ## ๐Ÿ”’ **Security Best Practices** ### **1. Client Isolation** - **Hash-based identities**: Deterministic but opaque client IDs - **URI validation**: Ensure clients only access their own resources - **Connection pooling**: Separate connections per client - **Audit logging**: Track all resource access ### **2. Configuration Security** - **Input validation**: Sanitize all client-provided configs - **Credential protection**: Never log passwords or tokens - **Connection limits**: Prevent resource exhaustion - **Timeout management**: Clean up idle connections ### **3. Content Security** - **Data sanitization**: Remove sensitive info from resource content - **Size limits**: Prevent memory exhaustion from large configs - **Rate limiting**: Protect against abuse - **Error handling**: Don't leak internal information ### Core Splunk Resource Categories #### 1. Configuration Resources ```python # Splunk configuration files as resources SPLUNK_CONFIG_RESOURCES = [ { "uri": "splunk://client/{client_id}/config/indexes.conf", "name": "Index Configuration", "description": "Current index settings, retention policies, and storage paths", "mimeType": "text/plain", "capabilities": ["subscribe", "listChanged"] }, { "uri": "splunk://client/{client_id}/config/props.conf", "name": "Props Configuration", "description": "Field extraction, parsing rules, and data preprocessing settings", "mimeType": "text/plain" }, { "uri": "splunk://client/{client_id}/config/server.conf", "name": "Server Configuration", "description": "Server-level settings and cluster configuration", "mimeType": "text/plain" } ] ``` #### 2. Knowledge Object Resources ```python # Splunk knowledge objects as contextual resources KNOWLEDGE_OBJECT_RESOURCES = [ { "uriTemplate": "splunk://client/{client_id}/knowledge/datamodel/{model_name}", "name": "Data Model Schema", "description": "CIM-compliant data model definitions and field mappings", "mimeType": "application/json" }, { "uri": "splunk://client/{client_id}/knowledge/eventtypes", "name": "Event Types Library", "description": "Categorized event type definitions for log classification", "mimeType": "application/json" }, { "uri": "splunk://client/{client_id}/knowledge/macros", "name": "Search Macros", "description": "Reusable search macro definitions and parameters", "mimeType": "application/json" } ] ``` ### Resource URI Patterns for Splunk ```python # Recommended URI patterns for Splunk resources SPLUNK_URI_PATTERNS = { # Configuration resources (client-scoped) "config": "splunk://client/{client_id}/config/{config_file}", "app_config": "splunk://client/{client_id}/apps/{app_name}/config/{config_file}", # Knowledge objects (client-scoped) "datamodel": "splunk://client/{client_id}/knowledge/datamodel/{model_name}", "eventtypes": "splunk://client/{client_id}/knowledge/eventtypes/{eventtype_name}", "macros": "splunk://client/{client_id}/knowledge/macros/{macro_name}", "lookups": "splunk://client/{client_id}/knowledge/lookups/{lookup_name}", # Search context (client-scoped) "search_results": "splunk://client/{client_id}/search/results/{search_id}", "saved_search": "splunk://client/{client_id}/search/saved/{search_name}/results", "recent_logs": "splunk://client/{client_id}/logs/recent/{index}?timeframe={timeframe}", # Monitoring data (client-scoped) "health": "splunk://client/{client_id}/health/{component}/status", "performance": "splunk://client/{client_id}/monitoring/performance/{metric_type}", "license": "splunk://client/{client_id}/monitoring/license/usage", # Dashboards (client-scoped) "dashboard_def": "splunk://client/{client_id}/dashboards/{dashboard_id}/definition", "dashboard_data": "splunk://client/{client_id}/dashboards/{dashboard_id}/data", # Apps and deployments (client-scoped) "app_manifest": "splunk://client/{client_id}/apps/{app_name}/manifest", "deployment_status": "splunk://client/{client_id}/deployment/status/{deployment_id}" } ``` ### Benefits of Multi-Tenant Splunk Resources 1. **Complete Data Isolation**: Each client only accesses their own Splunk environment 2. **Scalable Resource Management**: Connection pooling and efficient client handling 3. **Secure Access Control**: Hash-based client identity with URI validation 4. **Dynamic Resource Discovery**: Resources adapt to each client's Splunk capabilities 5. **Real-time Awareness**: Subscription capabilities for live updates per client 6. **Context-Aware Intelligence**: LLMs get client-specific context for accurate assistance ### Resource vs Tool Decision Matrix | Splunk Function | Resource | Tool | Reason | |----------------|----------|------|--------| | Configuration files | โœ… | โŒ | Static data for context | | Search execution | โŒ | โœ… | Action that modifies state | | Search results | โœ… | โŒ | Data for analysis context | | Dashboard definitions | โœ… | โŒ | Static structure for reference | | Health metrics | โœ… | โŒ | Monitoring data for context | | App deployment | โŒ | โœ… | Action that changes system | | Knowledge objects | โœ… | โŒ | Reference data for decisions | | Alert creation | โŒ | โœ… | Action that creates objects | ## Conclusion Following these best practices will help you create robust, secure, and user-friendly MCP tools that integrate seamlessly with LLM applications. Remember to: 1. **Design with the user in mind**: Clear names, descriptions, and error messages 2. **Implement proper security**: Validate inputs, sanitize outputs, control access 3. **Handle errors gracefully**: Provide actionable error messages 4. **Optimize for performance**: Cache data, limit response sizes, use timeouts 5. **Test thoroughly**: Unit tests, integration tests, edge cases 6. **Document comprehensively**: Code comments, API documentation, examples By following these guidelines, your tools will be reliable, maintainable, and provide excellent user experiences for LLM interactions.

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/deslicer/mcp-for-splunk'

If you have feedback or need assistance with the MCP directory API, please join our Discord server