Skip to content

Commit 2553548

Browse files
authored
Merge pull request #90 from schemacrawler/mcp
Create new main for use from a Docker image
2 parents 50d7e63 + d3cf546 commit 2553548

File tree

9 files changed

+242
-191
lines changed

9 files changed

+242
-191
lines changed

schemacrawler-ai-mcpserver/src/main/bin/schemacrawler-ai.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
# SPDX-License-Identifier: CC-BY-NC-4.0
44

55
SC_DIR=/opt/schemacrawler
6-
java -cp "$SC_DIR"/lib/*:"$SC_DIR"/config schemacrawler.tools.ai.mcpserver.DockerMcpServer "$@"
6+
java -cp "$SC_DIR"/lib/*:"$SC_DIR"/config schemacrawler.tools.ai.mcpserver.McpServerMain "$@"

schemacrawler-ai-mcpserver/src/main/java/schemacrawler/tools/ai/mcpserver/DockerMcpServer.java

Lines changed: 0 additions & 28 deletions
This file was deleted.

schemacrawler-ai-mcpserver/src/main/java/schemacrawler/tools/ai/mcpserver/McpServerContext.java

Lines changed: 70 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,27 @@
55
import static us.fatehi.utility.Utility.trimToEmpty;
66

77
import java.nio.file.Path;
8-
import java.util.ArrayList;
9-
import java.util.List;
108
import java.util.logging.Level;
119
import schemacrawler.schemacrawler.InfoLevel;
10+
import schemacrawler.schemacrawler.LimitOptions;
11+
import schemacrawler.schemacrawler.LimitOptionsBuilder;
12+
import schemacrawler.schemacrawler.LoadOptions;
13+
import schemacrawler.schemacrawler.LoadOptionsBuilder;
14+
import schemacrawler.schemacrawler.SchemaCrawlerOptions;
15+
import schemacrawler.schemacrawler.SchemaCrawlerOptionsBuilder;
1216
import schemacrawler.tools.command.mcpserver.McpServerTransportType;
1317
import schemacrawler.tools.databaseconnector.EnvironmentalDatabaseConnectionSourceBuilder;
14-
import us.fatehi.utility.datasource.DatabaseConnectionSourceBuilder;
18+
import schemacrawler.tools.offline.jdbc.OfflineConnectionUtility;
19+
import us.fatehi.utility.LoggingConfig;
20+
import us.fatehi.utility.datasource.DatabaseConnectionSource;
1521
import us.fatehi.utility.ioresource.EnvironmentVariableAccessor;
1622

1723
/** Inner class that handles the MCP server setup. */
1824
public final class McpServerContext {
1925

2026
private final EnvironmentVariableAccessor envAccessor;
27+
private final McpServerTransportType transport;
28+
private final SchemaCrawlerOptions schemaCrawlerOptions;
2129

2230
/** Default constructor that uses System.getenv */
2331
public McpServerContext() {
@@ -31,60 +39,70 @@ public McpServerContext() {
3139
*/
3240
protected McpServerContext(final EnvironmentVariableAccessor envAccessor) {
3341
this.envAccessor = requireNonNull(envAccessor, "No environment accessor provided");
42+
43+
final Level logLevel = readLogLevel();
44+
new LoggingConfig(logLevel);
45+
46+
transport = readTransport();
47+
schemaCrawlerOptions = buildSchemaCrawlerOptions();
3448
}
3549

36-
/**
37-
* Builds the complete argument list from environment variables.
38-
*
39-
* @return List of command line arguments
40-
*/
41-
public String[] buildArguments() {
50+
public SchemaCrawlerOptions getSchemaCrawlerOptions() {
51+
return schemaCrawlerOptions;
52+
}
4253

43-
final List<String> arguments = new ArrayList<>();
54+
public McpServerTransportType mcpTransport() {
55+
return transport;
56+
}
4457

45-
final List<String> offlineDatabaseArgs = buildOfflineDatabaseArguments();
46-
if (offlineDatabaseArgs.size() > 0) {
47-
arguments.addAll(offlineDatabaseArgs);
48-
} else {
49-
final DatabaseConnectionSourceBuilder databaseConnectionSourceBuilder =
50-
EnvironmentalDatabaseConnectionSourceBuilder.builder(envAccessor);
51-
final List<String> connectionArguments = databaseConnectionSourceBuilder.toArguments();
58+
protected DatabaseConnectionSource buildCatalogDatabaseConnectionSource() {
5259

53-
arguments.addAll(connectionArguments);
60+
final String offlineDatabasePathString =
61+
trimToEmpty(envAccessor.getenv("SCHCRWLR_OFFLINE_DATABASE"));
62+
if (isBlank(offlineDatabasePathString)) {
63+
return buildOperationsDatabaseConnectionSource();
5464
}
5565

56-
addSchemaCrawlerArguments(arguments);
66+
final Path offlineDatabasePath = Path.of(offlineDatabasePathString);
67+
final DatabaseConnectionSource dbConnectionSource =
68+
OfflineConnectionUtility.newOfflineDatabaseConnectionSource(offlineDatabasePath);
69+
return dbConnectionSource;
70+
}
71+
72+
/**
73+
* Builds the complete argument list from environment variables.
74+
*
75+
* @return List of command line arguments
76+
*/
77+
protected DatabaseConnectionSource buildOperationsDatabaseConnectionSource() {
5778

58-
return arguments.toArray(new String[0]);
79+
final DatabaseConnectionSource databaseConnectionSource =
80+
EnvironmentalDatabaseConnectionSourceBuilder.builder(envAccessor).build();
81+
return databaseConnectionSource;
5982
}
6083

6184
/**
6285
* Adds SchemaCrawler specific arguments to the arguments list.
6386
*
6487
* @param arguments The list of arguments to add to
6588
*/
66-
protected void addSchemaCrawlerArguments(final List<String> arguments) {
67-
final String infoLevel = envAccessor.getenv("SCHCRWLR_INFO_LEVEL");
68-
arguments.add("--info-level");
69-
arguments.add(validInfoLevel(infoLevel).name());
70-
71-
final String logLevel = envAccessor.getenv("SCHCRWLR_LOG_LEVEL");
72-
arguments.add("--log-level");
73-
arguments.add(validLogLevel(logLevel).getName());
74-
75-
arguments.add("--routines");
76-
arguments.add(".*");
77-
arguments.add("--sequences");
78-
arguments.add(".*");
79-
arguments.add("--synonyms");
80-
arguments.add(".*");
81-
82-
arguments.add("--command");
83-
arguments.add("mcpserver");
84-
85-
final String transport = envAccessor.getenv("SCHCRWLR_MCP_SERVER_TRANSPORT");
86-
arguments.add("--transport");
87-
arguments.add(validTransport(transport).name());
89+
protected SchemaCrawlerOptions buildSchemaCrawlerOptions() {
90+
final InfoLevel infoLevel = readInfoLevel();
91+
92+
final LoadOptions loadOptions = LoadOptionsBuilder.builder().withInfoLevel(infoLevel).build();
93+
final LimitOptions limitOptions =
94+
LimitOptionsBuilder.builder()
95+
.includeAllRoutines()
96+
.includeAllSequences()
97+
.includeAllSynonyms()
98+
.includeAllTables()
99+
.build();
100+
101+
final SchemaCrawlerOptions schemaCrawlerOptions =
102+
SchemaCrawlerOptionsBuilder.newSchemaCrawlerOptions()
103+
.withLoadOptions(loadOptions)
104+
.withLimitOptions(limitOptions);
105+
return schemaCrawlerOptions;
88106
}
89107

90108
/**
@@ -93,13 +111,11 @@ protected void addSchemaCrawlerArguments(final List<String> arguments) {
93111
* @param value The info level string to check
94112
* @return InfoLevel Non-null value
95113
*/
96-
protected InfoLevel validInfoLevel(final String value) {
97-
final InfoLevel defaultValue = InfoLevel.standard;
114+
protected InfoLevel readInfoLevel() {
98115

99-
if (isBlank(value)) {
100-
return defaultValue;
101-
}
116+
final InfoLevel defaultValue = InfoLevel.standard;
102117
try {
118+
final String value = envAccessor.getenv("SCHCRWLR_INFO_LEVEL");
103119
InfoLevel infoLevel = InfoLevel.valueOfFromString(value);
104120
if (infoLevel == InfoLevel.unknown) {
105121
infoLevel = defaultValue;
@@ -116,9 +132,11 @@ protected InfoLevel validInfoLevel(final String value) {
116132
* @param value The log level string to check
117133
* @return Level Non-null value
118134
*/
119-
protected Level validLogLevel(final String value) {
135+
protected Level readLogLevel() {
136+
120137
final Level defaultValue = Level.INFO;
121138

139+
final String value = envAccessor.getenv("SCHCRWLR_LOG_LEVEL");
122140
if (isBlank(value)) {
123141
return defaultValue;
124142
}
@@ -135,9 +153,12 @@ protected Level validLogLevel(final String value) {
135153
* @param value The transport string to check
136154
* @return McpServerTransportType Non-null value
137155
*/
138-
protected McpServerTransportType validTransport(final String value) {
156+
protected McpServerTransportType readTransport() {
157+
requireNonNull(envAccessor, "No environmental accessor provided");
158+
139159
final McpServerTransportType defaultValue = McpServerTransportType.stdio;
140160

161+
final String value = envAccessor.getenv("SCHCRWLR_MCP_SERVER_TRANSPORT");
141162
if (isBlank(value)) {
142163
return defaultValue;
143164
}
@@ -151,27 +172,4 @@ protected McpServerTransportType validTransport(final String value) {
151172
return defaultValue;
152173
}
153174
}
154-
155-
private List<String> buildOfflineDatabaseArguments() {
156-
157-
final List<String> arguments = new ArrayList<>();
158-
159-
final String offlineDatabasePathString =
160-
trimToEmpty(envAccessor.getenv("SCHCRWLR_OFFLINE_DATABASE"));
161-
if (isBlank(offlineDatabasePathString)) {
162-
return arguments;
163-
}
164-
165-
final Path offlineDatabasePath = Path.of(offlineDatabasePathString);
166-
if (!offlineDatabasePath.toFile().exists() && !offlineDatabasePath.toFile().isFile()) {
167-
return arguments;
168-
}
169-
170-
arguments.add("--server");
171-
arguments.add("offline");
172-
arguments.add("--database");
173-
arguments.add(offlineDatabasePath.toAbsolutePath().toString());
174-
175-
return arguments;
176-
}
177175
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* SchemaCrawler AI
3+
* http://www.schemacrawler.com
4+
* Copyright (c) 2000-2025, Sualeh Fatehi <sualeh@hotmail.com>.
5+
* All rights reserved.
6+
* SPDX-License-Identifier: CC-BY-NC-4.0
7+
*/
8+
9+
package schemacrawler.tools.ai.mcpserver;
10+
11+
import static schemacrawler.tools.ai.mcpserver.McpServerUtility.startMcpServer;
12+
13+
import schemacrawler.schema.Catalog;
14+
import schemacrawler.schemacrawler.SchemaCrawlerOptions;
15+
import schemacrawler.tools.ai.mcpserver.server.ConfigurationManager;
16+
import schemacrawler.tools.ai.mcpserver.server.ConnectionService;
17+
import schemacrawler.tools.utility.SchemaCrawlerUtility;
18+
import us.fatehi.utility.datasource.DatabaseConnectionSource;
19+
20+
/**
21+
* Construct SchemaCrawler arguments from environment variables and run SchemaCrawler MCP Server.
22+
*/
23+
public class McpServerMain {
24+
25+
/**
26+
* Main method that reads environment variables, constructs arguments, and runs SchemaCrawler MCP
27+
* Server.
28+
*
29+
* @param args Command line arguments (will be combined with environment variable arguments)
30+
* @throws Exception If an error occurs during execution
31+
*/
32+
public static void main(final String[] args) throws Exception {
33+
// Read options from environmental variable
34+
final McpServerContext context = new McpServerContext();
35+
final SchemaCrawlerOptions schemaCrawlerOptions = context.getSchemaCrawlerOptions();
36+
final DatabaseConnectionSource connectionSource =
37+
context.buildCatalogDatabaseConnectionSource();
38+
// Obtain the database catalog
39+
final Catalog catalog = SchemaCrawlerUtility.getCatalog(connectionSource, schemaCrawlerOptions);
40+
ConnectionService.instantiate(connectionSource);
41+
ConfigurationManager.instantiate(catalog);
42+
// Start the MCP server
43+
startMcpServer(context.mcpTransport());
44+
}
45+
}

schemacrawler-ai-mcpserver/src/main/java/schemacrawler/tools/ai/mcpserver/server/ConfigurationManager.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import static java.util.Objects.requireNonNull;
1212

1313
import schemacrawler.schema.Catalog;
14-
import schemacrawler.tools.command.mcpserver.McpServerCommandOptions;
1514

1615
/**
1716
* Thread-safe singleton configuration manager for SchemaCrawler AI. Manages configuration settings
@@ -33,37 +32,30 @@ public static ConfigurationManager getInstance() {
3332
* Initializes the ConnectionService singleton. This method should be called exactly once during
3433
* application startup. Subsequent calls will throw an IllegalStateException.
3534
*
36-
* @param options Command options
3735
* @param catalog Database schema catalog
3836
* @param connection SQL connection
3937
* @throws IllegalStateException if the service has already been initialized
4038
*/
41-
public static void instantiate(final McpServerCommandOptions options, final Catalog catalog) {
39+
public static void instantiate(final Catalog catalog) {
4240
synchronized (lock) {
4341
if (instance != null) {
4442
throw new IllegalStateException("ConnectionService has already been initialized");
4543
}
46-
instance = new ConfigurationManager(options, catalog);
44+
instance = new ConfigurationManager(catalog);
4745
}
4846
}
4947

5048
private boolean isDryRun = false;
5149
private final Catalog catalog;
52-
private final McpServerCommandOptions options;
5350

54-
private ConfigurationManager(final McpServerCommandOptions options, final Catalog catalog) {
51+
private ConfigurationManager(final Catalog catalog) {
5552
this.catalog = requireNonNull(catalog, "No catalog provided");
56-
this.options = requireNonNull(options, "No options provided");
5753
}
5854

5955
public Catalog getCatalog() {
6056
return catalog;
6157
}
6258

63-
public McpServerCommandOptions getOptions() {
64-
return options;
65-
}
66-
6759
public boolean isDryRun() {
6860
return isDryRun;
6961
}

schemacrawler-ai-mcpserver/src/main/java/schemacrawler/tools/ai/mcpserver/server/ConnectionService.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,22 @@ public static ConnectionService getInstance() {
5151
* @throws IllegalStateException if the service has already been initialized
5252
*/
5353
public static void instantiate(final Connection connection) {
54+
final DatabaseConnectionSource dbConnectionSource = newDatabaseConnectionSource(connection);
55+
instantiate(dbConnectionSource);
56+
}
57+
58+
/**
59+
* Initializes the ConnectionService singleton. This method should be called exactly once during
60+
* application startup. Subsequent calls will throw an IllegalStateException.
61+
*
62+
* @param dbConnectionSource Database connection sources
63+
* @throws IllegalStateException if the service has already been initialized
64+
*/
65+
public static void instantiate(final DatabaseConnectionSource dbConnectionSource) {
5466
synchronized (lock) {
5567
if (instance != null) {
5668
throw new IllegalStateException("ConnectionService has already been initialized");
5769
}
58-
final DatabaseConnectionSource dbConnectionSource = newDatabaseConnectionSource(connection);
5970
instance = new ConnectionService(dbConnectionSource);
6071
}
6172
}

schemacrawler-ai-mcpserver/src/main/java/schemacrawler/tools/command/mcpserver/McpServerCommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public void checkAvailability() throws RuntimeException {
3737
@Override
3838
public void execute() {
3939
ConnectionService.instantiate(connection);
40-
ConfigurationManager.instantiate(commandOptions, catalog);
40+
ConfigurationManager.instantiate(catalog);
4141
startMcpServer(commandOptions.mcpTransport());
4242
}
4343

0 commit comments

Comments
 (0)