44
55namespace Laravel \Boost \Mcp \Tools ;
66
7- use Illuminate \Console \Command ;
8- use Illuminate \Support \Facades \Artisan ;
97use Illuminate \Support \Facades \Cache ;
10- use Illuminate \Support \Str ;
8+ use Illuminate \Support \Facades \DB ;
9+ use Illuminate \Support \Facades \Log ;
10+ use Illuminate \Support \Facades \Schema ;
11+ use Laravel \Boost \Mcp \Tools \DatabaseSchema \SchemaDriverFactory ;
1112use Laravel \Mcp \Server \Tool ;
1213use Laravel \Mcp \Server \Tools \Annotations \IsReadOnly ;
1314use Laravel \Mcp \Server \Tools \ToolInputSchema ;
1415use Laravel \Mcp \Server \Tools \ToolResult ;
15- use Symfony \Component \Console \Output \BufferedOutput ;
1616
1717#[IsReadOnly()]
1818class DatabaseSchema extends Tool
@@ -28,6 +28,10 @@ public function schema(ToolInputSchema $schema): ToolInputSchema
2828 ->description ('Name of the database connection to dump (defaults to app \'s default connection, often not needed) ' )
2929 ->required (false );
3030
31+ $ schema ->string ('filter ' )
32+ ->description ('Filter the tables by name ' )
33+ ->required (false );
34+
3135 return $ schema ;
3236 }
3337
@@ -37,35 +41,132 @@ public function schema(ToolInputSchema $schema): ToolInputSchema
3741 public function handle (array $ arguments ): ToolResult
3842 {
3943 $ connection = $ arguments ['database ' ] ?? config ('database.default ' );
40- $ cacheKey = "boost:mcp:database-schema: {$ connection }" ;
44+ $ filter = $ arguments ['filter ' ] ?? '' ;
45+ $ cacheKey = "boost:mcp:database-schema: {$ connection }: {$ filter }" ;
46+
47+ $ schema = Cache::remember ($ cacheKey , 20 , function () use ($ connection , $ filter ) {
48+ return $ this ->getDatabaseStructure ($ connection , $ filter );
49+ });
50+
51+ return ToolResult::json ($ schema );
52+ }
53+
54+ protected function getDatabaseStructure (?string $ connection , string $ filter = '' ): array
55+ {
56+ $ structure = [
57+ 'engine ' => DB ::connection ($ connection )->getDriverName (),
58+ 'tables ' => $ this ->getAllTablesStructure ($ connection , $ filter ),
59+ 'global ' => $ this ->getGlobalStructure ($ connection ),
60+ ];
61+
62+ return $ structure ;
63+ }
4164
42- // We can't cache for long in case the user rolls back, edits a migration
43- // then migrates, and gets the schema again
44- $ schema = Cache::remember ($ cacheKey , 20 , function () use ($ arguments ) {
45- $ filename = 'tmp_ ' .Str::random (40 ).'.sql ' ;
46- $ path = database_path ("schema/ {$ filename }" );
65+ protected function getAllTablesStructure (?string $ connection , string $ filter = '' ): array
66+ {
67+ $ structures = [];
4768
48- $ artisanArgs = ['--path ' => $ path ];
69+ foreach ($ this ->getAllTables ($ connection ) as $ table ) {
70+ $ tableName = $ table ['name ' ];
4971
50- // Respect optional connection name
51- if (! empty ($ arguments ['database ' ])) {
52- $ artisanArgs ['--database ' ] = $ arguments ['database ' ];
72+ if ($ filter && ! str_contains (strtolower ($ tableName ), strtolower ($ filter ))) {
73+ continue ;
5374 }
5475
55- $ output = new BufferedOutput ;
56- $ result = Artisan::call ('schema:dump ' , $ artisanArgs , $ output );
57- if ($ result !== Command::SUCCESS ) {
58- return ToolResult::error ('Failed to dump database schema: ' .$ output ->fetch ());
76+ $ structures [$ tableName ] = $ this ->getTableStructure ($ connection , $ tableName );
77+ }
78+
79+ return $ structures ;
80+ }
81+
82+ protected function getAllTables (?string $ connection ): array
83+ {
84+ return Schema::connection ($ connection )->getTables ();
85+ }
86+
87+ protected function getTableStructure (?string $ connection , string $ tableName ): array
88+ {
89+ $ driver = SchemaDriverFactory::make ($ connection );
90+
91+ try {
92+ $ columns = $ this ->getTableColumns ($ connection , $ tableName );
93+ $ indexes = $ this ->getTableIndexes ($ connection , $ tableName );
94+ $ foreignKeys = $ this ->getTableForeignKeys ($ connection , $ tableName );
95+ $ triggers = $ driver ->getTriggers ($ tableName );
96+ $ checkConstraints = $ driver ->getCheckConstraints ($ tableName );
97+
98+ return [
99+ 'columns ' => $ columns ,
100+ 'indexes ' => $ indexes ,
101+ 'foreign_keys ' => $ foreignKeys ,
102+ 'triggers ' => $ triggers ,
103+ 'check_constraints ' => $ checkConstraints ,
104+ ];
105+ } catch (\Exception $ e ) {
106+ Log::error ('Failed to get table structure for: ' .$ tableName , [
107+ 'error ' => $ e ->getMessage (),
108+ 'trace ' => $ e ->getTraceAsString (),
109+ ]);
110+
111+ return [
112+ 'error ' => 'Failed to get structure: ' .$ e ->getMessage (),
113+ ];
114+ }
115+ }
116+
117+ protected function getTableColumns (?string $ connection , string $ tableName ): array
118+ {
119+ $ columns = Schema::connection ($ connection )->getColumnListing ($ tableName );
120+ $ columnDetails = [];
121+
122+ foreach ($ columns as $ column ) {
123+ $ columnDetails [$ column ] = [
124+ 'type ' => Schema::connection ($ connection )->getColumnType ($ tableName , $ column ),
125+ ];
126+ }
127+
128+ return $ columnDetails ;
129+ }
130+
131+ protected function getTableIndexes (?string $ connection , string $ tableName ): array
132+ {
133+ try {
134+ $ indexes = Schema::connection ($ connection )->getIndexes ($ tableName );
135+ $ indexDetails = [];
136+
137+ foreach ($ indexes as $ index ) {
138+ $ indexDetails [$ index ['name ' ]] = [
139+ 'columns ' => $ index ['columns ' ],
140+ 'type ' => $ index ['type ' ] ?? null ,
141+ 'is_unique ' => $ index ['unique ' ] ?? false ,
142+ 'is_primary ' => $ index ['primary ' ] ?? false ,
143+ ];
59144 }
60145
61- $ schemaContent = file_get_contents ($ path );
146+ return $ indexDetails ;
147+ } catch (\Exception $ e ) {
148+ return [];
149+ }
150+ }
62151
63- // Clean up temp file
64- unlink ($ path );
152+ protected function getTableForeignKeys (?string $ connection , string $ tableName ): array
153+ {
154+ try {
155+ return Schema::connection ($ connection )->getForeignKeys ($ tableName );
156+ } catch (\Exception $ e ) {
157+ return [];
158+ }
159+ }
65160
66- return $ schemaContent ;
67- });
161+ protected function getGlobalStructure (?string $ connection ): array
162+ {
163+ $ driver = SchemaDriverFactory::make ($ connection );
68164
69- return ToolResult::text ($ schema );
165+ return [
166+ 'views ' => $ driver ->getViews (),
167+ 'stored_procedures ' => $ driver ->getStoredProcedures (),
168+ 'functions ' => $ driver ->getFunctions (),
169+ 'sequences ' => $ driver ->getSequences (),
170+ ];
70171 }
71- }
172+ }
0 commit comments