@@ -39,7 +39,6 @@ import {
3939import {
4040 effectiveRowLimit ,
4141 enrichQueryOutput ,
42- markdownCell ,
4342 maskOutput ,
4443 pagedQueryWarnings ,
4544 shouldMaskColumnValue
@@ -57,10 +56,6 @@ import {
5756 visibleGlossaryTargets
5857} from "./business-glossary.js" ;
5958import type { McpRequestContext } from "./context.js" ;
60- import {
61- mapperConstraintSummary ,
62- mapperIndexSummary
63- } from "./metadata-summary.js" ;
6459import { metadataSchema } from "./metadata-scope.js" ;
6560import { policyContext } from "./policy-context.js" ;
6661import {
@@ -136,6 +131,7 @@ import {
136131 lintReadOnlySql ,
137132 registerSqlLintTools
138133} from "./tools/sql-lint.js" ;
134+ import { registerSqlMapperContextTools } from "./tools/sql-mapper-context.js" ;
139135import { registerSqlProjectQueryTools } from "./tools/sql-project-query.js" ;
140136import { registerSqlQueryDocTools } from "./tools/sql-query-doc.js" ;
141137import { registerSqlValidationTools } from "./tools/sql-validation.js" ;
@@ -144,8 +140,7 @@ import {
144140 validateSnippetMetadata
145141} from "./tools/sql-snippet-metadata.js" ;
146142import {
147- resolveProjectQuerySource ,
148- type ProjectQuerySource
143+ resolveProjectQuerySource
149144} from "./tools/sql-snippet-source.js" ;
150145import {
151146 findPrepareQueryCandidates ,
@@ -331,66 +326,10 @@ export function createObMcpServer(
331326 registerSqlValidationTools ( registerTool , runtime , context ) ;
332327 registerSqlProjectQueryTools ( registerTool , runtime , context ) ;
333328 registerSqlQueryDocTools ( registerTool , runtime , context ) ;
329+ registerSqlMapperContextTools ( registerTool , runtime , context ) ;
334330
335331 registerSqlPrepareTools ( registerTool , runtime , context ) ;
336332
337- registerTool (
338- "ob_generate_mapper_context" ,
339- {
340- title : "Generate Mapper Context" ,
341- description : "Generate read-only table and column context for a MyBatis mapper, SQL file, or explicit table." ,
342- inputSchema : {
343- ...profileInput ,
344- schema : z . string ( ) . optional ( ) ,
345- table : z . string ( ) . optional ( ) ,
346- sql : z . string ( ) . min ( 1 ) . optional ( ) ,
347- filePath : z . string ( ) . min ( 1 ) . optional ( ) ,
348- line : z . number ( ) . int ( ) . positive ( ) . optional ( ) ,
349- format : z . enum ( [ "json" , "markdown" ] ) . optional ( ) . default ( "json" ) ,
350- maxTables : z . number ( ) . int ( ) . positive ( ) . max ( 10 ) . optional ( ) ,
351- maxColumns : z . number ( ) . int ( ) . positive ( ) . max ( 200 ) . optional ( )
352- }
353- } ,
354- async ( { profile, schema, table, sql, filePath, line, format = "json" , maxTables, maxColumns } ) => {
355- const selected = selectAuthorizedProfile ( runtime , profile , context ) ;
356- const source = sql || filePath ? resolveProjectQuerySource ( sql , filePath , line ) : undefined ;
357- const policy = source ? explainSqlPolicy ( source . sql , policyContext ( selected . config ) ) : undefined ;
358- const maxTableCount = Math . min ( maxTables ?? 5 , 10 ) ;
359- const allTableRefs = mapperContextTableRefs (
360- selected . config ,
361- schema ,
362- table ,
363- policy ?. tableRefs ?? [ ] ,
364- 50
365- ) ;
366- const tableRefs = allTableRefs . slice ( 0 , maxTableCount ) ;
367- if ( tableRefs . length === 0 ) {
368- throw new Error ( "Pass table, sql, or filePath so mapper context can identify at least one table." ) ;
369- }
370- const tables = [ ] ;
371- for ( const ref of tableRefs ) {
372- tables . push ( await buildMapperContextTable ( selected , ref . schema , ref . table , Math . min ( maxColumns ?? 80 , 200 ) ) ) ;
373- }
374- const payload = {
375- mode : selected . config . compatibility ,
376- source : source ?. source ,
377- sql : source ?. sql ,
378- policy,
379- tableCount : tables . length ,
380- tables,
381- warnings : uniqueStrings ( [
382- ...( source ?. source . warnings ?? [ ] ) ,
383- ...( allTableRefs . length > tableRefs . length ? [ "Table list is capped by maxTables." ] : [ ] )
384- ] ) ,
385- markdown : undefined as string | undefined
386- } ;
387- if ( format === "markdown" ) {
388- payload . markdown = renderMapperContextMarkdown ( payload ) ;
389- }
390- return toLimitedResult ( payload , selected . config ) ;
391- }
392- ) ;
393-
394333 registerTool (
395334 "ob_find_unknown_columns_in_sql" ,
396335 {
@@ -1887,152 +1826,6 @@ function uniqueTableRefs(refs: Array<{ schema?: string; table: string }>): Array
18871826 return output ;
18881827}
18891828
1890- function mapperContextTableRefs (
1891- config : ObConfig ,
1892- schema : string | undefined ,
1893- table : string | undefined ,
1894- sqlRefs : Array < { schema ?: string ; table : string } > ,
1895- maxTables : number
1896- ) : Array < { schema ?: string ; table : string } > {
1897- const refs : Array < { schema ?: string ; table : string } > = [ ] ;
1898- if ( table ?. trim ( ) ) {
1899- refs . push ( normalizedSnippetTableRef ( config , schema , table ) ) ;
1900- }
1901- for ( const ref of sqlRefs ) {
1902- refs . push ( normalizedSnippetTableRef ( config , ref . schema ?? schema , ref . table ) ) ;
1903- }
1904- return uniqueTableRefs ( refs ) . slice ( 0 , maxTables ) ;
1905- }
1906-
1907- async function buildMapperContextTable (
1908- selected : ReturnType < typeof selectRuntimeProfile > ,
1909- schema : string | undefined ,
1910- table : string ,
1911- maxColumns : number
1912- ) {
1913- const metadataRef = normalizedSnippetTableRef ( selected . config , schema , table ) ;
1914- assertTableAccess ( metadataRef . schema , metadataRef . table , policyContext ( selected . config ) ) ;
1915- const columns = filterColumnMetadata (
1916- await withQuerySlot ( selected , ( ) =>
1917- describeTable (
1918- selected . pool ,
1919- selected . config . compatibility ,
1920- metadataRef . table ,
1921- metadataRef . schema ,
1922- selected . config . queryTimeoutMs
1923- )
1924- ) ,
1925- selected . config . policy
1926- ) ;
1927- const constraints = filterColumnMetadata (
1928- await withQuerySlot ( selected , ( ) =>
1929- listConstraints (
1930- selected . pool ,
1931- selected . config . compatibility ,
1932- metadataRef . table ,
1933- metadataRef . schema ,
1934- selected . config . queryTimeoutMs
1935- )
1936- ) ,
1937- selected . config . policy
1938- ) ;
1939- const indexes = filterColumnMetadata (
1940- await withQuerySlot ( selected , ( ) =>
1941- listIndexes (
1942- selected . pool ,
1943- selected . config . compatibility ,
1944- metadataRef . table ,
1945- metadataRef . schema ,
1946- selected . config . queryTimeoutMs
1947- )
1948- ) ,
1949- selected . config . policy
1950- ) ;
1951- const compactColumns = compactSchemaColumns ( columns , constraints , selected . config , metadataRef . schema , metadataRef . table ) ;
1952- const columnLimit = Math . max ( 1 , Math . min ( maxColumns , 200 ) ) ;
1953- return {
1954- schema : metadataRef . schema ,
1955- table : metadataRef . table ,
1956- visibleColumnCount : compactColumns . length ,
1957- columnsTruncated : compactColumns . length > columnLimit ,
1958- columns : compactColumns . slice ( 0 , columnLimit ) ,
1959- primaryKeys : schemaMapPrimaryKeyColumns ( constraints ) ,
1960- uniqueKeys : uniqueKeyGroups ( constraints ) ,
1961- indexes : mapperIndexSummary ( indexes ) ,
1962- constraints : mapperConstraintSummary ( constraints ) ,
1963- notes : mapperContextNotes ( compactColumns )
1964- } ;
1965- }
1966-
1967- function mapperContextNotes ( columns : Array < { name : string ; roles : string [ ] ; masked : boolean } > ) : string [ ] {
1968- const notes : string [ ] = [ ] ;
1969- const maskedColumns = columns . filter ( ( column ) => column . masked ) . map ( ( column ) => column . name ) ;
1970- const timeColumns = columns . filter ( ( column ) => column . roles . includes ( "time" ) ) . map ( ( column ) => column . name ) ;
1971- const statusColumns = columns . filter ( ( column ) => column . roles . includes ( "status" ) ) . map ( ( column ) => column . name ) ;
1972- if ( maskedColumns . length > 0 ) {
1973- notes . push ( `Masked columns: ${ maskedColumns . slice ( 0 , 10 ) . join ( ", " ) } .` ) ;
1974- }
1975- if ( timeColumns . length > 0 ) {
1976- notes . push ( `Likely time columns: ${ timeColumns . slice ( 0 , 10 ) . join ( ", " ) } .` ) ;
1977- }
1978- if ( statusColumns . length > 0 ) {
1979- notes . push ( `Likely dimension/status columns: ${ statusColumns . slice ( 0 , 10 ) . join ( ", " ) } .` ) ;
1980- }
1981- return notes ;
1982- }
1983-
1984- function renderMapperContextMarkdown ( payload : {
1985- mode : ObConfig [ "compatibility" ] ;
1986- source ?: ProjectQuerySource ;
1987- sql ?: string ;
1988- tableCount : number ;
1989- tables : Array < Awaited < ReturnType < typeof buildMapperContextTable > > > ;
1990- warnings : string [ ] ;
1991- } ) : string {
1992- const lines = [ "# OceanBase Mapper Context" , "" , `Mode: ${ payload . mode } ` , `Tables: ${ payload . tableCount } ` , "" ] ;
1993- if ( payload . source ?. type === "file" ) {
1994- lines . push ( `Source: ${ payload . source . file } :${ payload . source . line } ` , "" ) ;
1995- }
1996- if ( payload . sql ) {
1997- lines . push ( "```sql" , payload . sql , "```" , "" ) ;
1998- }
1999- for ( const table of payload . tables ) {
2000- const title = `${ table . schema ? `${ table . schema } .` : "" } ${ table . table } ` ;
2001- lines . push ( `## ${ title } ` , "" ) ;
2002- if ( table . primaryKeys . length > 0 ) {
2003- lines . push ( `Primary key: ${ table . primaryKeys . join ( ", " ) } ` , "" ) ;
2004- }
2005- if ( table . indexes . length > 0 ) {
2006- lines . push ( `Indexes: ${ table . indexes . map ( ( index ) => `${ index . name } (${ index . columns . join ( ", " ) } )` ) . join ( "; " ) } ` , "" ) ;
2007- }
2008- lines . push ( "| Column | Type | Nullable | Roles | Masked | Comment |" ) ;
2009- lines . push ( "| --- | --- | --- | --- | --- | --- |" ) ;
2010- for ( const column of table . columns ) {
2011- lines . push ( [
2012- markdownCell ( column . name ) ,
2013- markdownCell ( column . type ?? "" ) ,
2014- column . nullable == null ? "" : column . nullable ? "YES" : "NO" ,
2015- markdownCell ( column . roles . join ( ", " ) ) ,
2016- column . masked ? "YES" : "NO" ,
2017- markdownCell ( column . comment ?? "" )
2018- ] . join ( " | " ) . replace ( / ^ / , "| " ) . replace ( / $ / , " |" ) ) ;
2019- }
2020- if ( table . columnsTruncated ) {
2021- lines . push ( "" , `More columns exist for ${ title } ; output was truncated by maxColumns.` ) ;
2022- }
2023- for ( const note of table . notes ) {
2024- lines . push ( "" , `Note: ${ note } ` ) ;
2025- }
2026- lines . push ( "" ) ;
2027- }
2028- if ( payload . warnings . length > 0 ) {
2029- lines . push ( "## Warnings" , "" ) ;
2030- lines . push ( ...payload . warnings . map ( ( warning ) => `- ${ warning } ` ) ) ;
2031- lines . push ( "" ) ;
2032- }
2033- return lines . join ( "\n" ) ;
2034- }
2035-
20361829async function findUnknownTableCandidates (
20371830 selected : ReturnType < typeof selectRuntimeProfile > ,
20381831 schema : string | undefined ,
0 commit comments