(sourceId: string)
| 112 | * @returns Tool metadata with name, description, and Zod schema |
| 113 | */ |
| 114 | export function getExecuteSqlMetadata(sourceId: string): ToolMetadata { |
| 115 | const sourceIds = ConnectorManager.getAvailableSourceIds(); |
| 116 | const sourceConfig = ConnectorManager.getSourceConfig(sourceId)!; |
| 117 | const dbType = sourceConfig.type; |
| 118 | const isSingleSource = sourceIds.length === 1; |
| 119 | |
| 120 | // Get tool configuration from registry to extract readonly/max_rows |
| 121 | const registry = getToolRegistry(); |
| 122 | const toolConfig = registry.getBuiltinToolConfig(BUILTIN_TOOL_EXECUTE_SQL, sourceId); |
| 123 | const executeOptions = { |
| 124 | readonly: toolConfig?.readonly, |
| 125 | maxRows: toolConfig?.max_rows, |
| 126 | }; |
| 127 | |
| 128 | // Determine tool name based on single vs multi-source configuration |
| 129 | const toolName = isSingleSource ? "execute_sql" : `execute_sql_${normalizeSourceId(sourceId)}`; |
| 130 | |
| 131 | // Determine title (human-readable display name) |
| 132 | const title = isSingleSource |
| 133 | ? `Execute SQL (${dbType})` |
| 134 | : `Execute SQL on ${sourceId} (${dbType})`; |
| 135 | |
| 136 | // Determine description with more context. |
| 137 | // Prepend the user-provided `description` from the source config (if set) |
| 138 | // so AI clients reading the MCP tool list see the source's purpose first. |
| 139 | const userDescPrefix = buildSourceDescriptionPrefix(sourceConfig.description); |
| 140 | const readonlyNote = executeOptions.readonly ? " [READ-ONLY MODE]" : ""; |
| 141 | const maxRowsNote = executeOptions.maxRows ? ` (limited to ${executeOptions.maxRows} rows)` : ""; |
| 142 | const description = isSingleSource |
| 143 | ? `${userDescPrefix}Execute SQL queries on the ${dbType} database${readonlyNote}${maxRowsNote}` |
| 144 | : `${userDescPrefix}Execute SQL queries on the '${sourceId}' ${dbType} database${readonlyNote}${maxRowsNote}`; |
| 145 | |
| 146 | // Build annotations object with all standard MCP hints |
| 147 | const isReadonly = executeOptions.readonly === true; |
| 148 | const annotations = { |
| 149 | title, |
| 150 | readOnlyHint: isReadonly, |
| 151 | destructiveHint: !isReadonly, // Can be destructive if not readonly |
| 152 | // In readonly mode, queries are more predictable (though still not strictly idempotent due to data changes) |
| 153 | // In write mode, queries are definitely not idempotent |
| 154 | idempotentHint: false, |
| 155 | // Database operations are always against internal/closed systems, not open-world |
| 156 | openWorldHint: false, |
| 157 | }; |
| 158 | |
| 159 | return { |
| 160 | name: toolName, |
| 161 | description, |
| 162 | schema: executeSqlSchema, |
| 163 | annotations, |
| 164 | }; |
| 165 | } |
| 166 | |
| 167 | /** |
| 168 | * Get search_objects tool metadata for a specific source |
no test coverage detected