filemaker-data-api-mcp
Version:
Model Context Protocol (MCP) server providing FileMaker Data API integration with database introspection for AI agents. Compatible with Claude, Windsurf, Cursor, Cline, and other MCP-enabled assistants.
294 lines • 9.39 kB
JavaScript
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
import { loggers } from "./logger.js";
/**
* Connection Manager - Handles current connection state and switching
*/
export class ConnectionManager {
currentConnection = null;
connections = new Map();
defaultConnectionName = null;
configDir;
connectionsFile;
constructor(configDir = path.join(os.homedir(), ".filemaker-mcp")) {
loggers.connection("Initializing ConnectionManager");
this.configDir = configDir;
this.connectionsFile = path.join(configDir, "connections.json");
loggers.connection(`Config directory: ${configDir}`);
this.loadConnections();
}
/**
* Load connections from file
*/
loadConnections() {
if (fs.existsSync(this.connectionsFile)) {
try {
const data = JSON.parse(fs.readFileSync(this.connectionsFile, "utf-8"));
this.connections = new Map(Object.entries(data.connections || {}));
this.defaultConnectionName = data.defaultConnection || null;
loggers.connection(`Loaded ${this.connections.size} connection(s) from ${this.connectionsFile}`);
if (this.defaultConnectionName) {
loggers.connection(`Default connection: ${this.defaultConnectionName}`);
}
}
catch (error) {
loggers.connection(`Error loading connections file: ${error instanceof Error ? error.message : String(error)}`);
console.error("Error loading connections file:", error);
this.connections = new Map();
this.defaultConnectionName = null;
}
}
else {
loggers.connection("No connections file found, starting with empty connections");
}
}
/**
* Save connections to file
*/
saveConnections() {
if (!fs.existsSync(this.configDir)) {
fs.mkdirSync(this.configDir, { recursive: true });
}
const data = {
connections: Object.fromEntries(this.connections),
defaultConnection: this.defaultConnectionName,
};
fs.writeFileSync(this.connectionsFile, JSON.stringify(data, null, 2));
fs.chmodSync(this.connectionsFile, 0o600); // Restrict permissions for security
}
/**
* Validate connection configuration
*/
validateConnection(connection) {
const errors = [];
if (!connection.server || connection.server.trim() === "") {
errors.push("Server is required");
}
if (!connection.database || connection.database.trim() === "") {
errors.push("Database is required");
}
if (!connection.user || connection.user.trim() === "") {
errors.push("User is required");
}
if (!connection.password || connection.password.trim() === "") {
errors.push("Password is required");
}
if (connection.version && connection.version.trim() === "") {
errors.push("Version must be a valid string");
}
return {
valid: errors.length === 0,
errors,
};
}
/**
* Get the current active connection
*/
getCurrentConnection() {
return this.currentConnection;
}
/**
* Set the current connection directly
*/
setCurrentConnection(connection) {
const validation = this.validateConnection(connection);
if (!validation.valid) {
throw new Error(`Invalid connection: ${validation.errors.join(", ")}`);
}
this.currentConnection = connection;
}
/**
* Switch to a predefined connection by name
*/
switchToConnection(name) {
const connection = this.connections.get(name);
if (!connection) {
loggers.connection(`Connection "${name}" not found`);
throw new Error(`Connection "${name}" not found`);
}
this.currentConnection = connection;
loggers.connection(`Switched to connection: ${name} (${connection.database}@${connection.server})`);
}
/**
* Add a new predefined connection
*/
addConnection(name, connection) {
if (!name || name.trim() === "") {
throw new Error("Connection name is required");
}
// Check if connection already exists
if (this.connections.has(name)) {
throw new Error(`Connection "${name}" already exists`);
}
const validation = this.validateConnection(connection);
if (!validation.valid) {
throw new Error(`Invalid connection: ${validation.errors.join(", ")}`);
}
// Add name to connection object
const namedConnection = { ...connection, name };
this.connections.set(name, namedConnection);
this.saveConnections();
loggers.connection(`Connection "${name}" added: ${connection.database}@${connection.server}`);
}
/**
* Remove a predefined connection
*/
removeConnection(name) {
if (!this.connections.has(name)) {
throw new Error(`Connection "${name}" not found`);
}
this.connections.delete(name);
// If this was the default connection, clear the default
if (this.defaultConnectionName === name) {
this.defaultConnectionName = null;
}
// If this was the current connection, clear it
if (this.currentConnection?.name === name) {
this.currentConnection = null;
}
this.saveConnections();
}
/**
* Get a specific connection by name
*/
getConnection(name) {
return this.connections.get(name) || null;
}
/**
* Get connection details with password masked
*/
getConnectionMasked(name) {
const connection = this.connections.get(name);
if (!connection) {
return null;
}
return {
...connection,
password: "***",
};
}
/**
* List all available connections
*/
listConnections() {
return Array.from(this.connections.values());
}
/**
* List all connection names
*/
listConnectionNames() {
return Array.from(this.connections.keys());
}
/**
* Set the default connection
*/
setDefaultConnection(name) {
if (!this.connections.has(name)) {
throw new Error(`Connection "${name}" not found`);
}
this.defaultConnectionName = name;
this.saveConnections();
}
/**
* Get the default connection name
*/
getDefaultConnectionName() {
return this.defaultConnectionName;
}
/**
* Get the default connection
*/
getDefaultConnection() {
if (!this.defaultConnectionName) {
return null;
}
return this.connections.get(this.defaultConnectionName) || null;
}
/**
* Initialize with default connection if not already set
*/
initializeWithDefault() {
if (!this.currentConnection && this.defaultConnectionName) {
const defaultConn = this.connections.get(this.defaultConnectionName);
if (defaultConn) {
this.currentConnection = defaultConn;
}
}
}
/**
* Clear the current connection
*/
clearCurrentConnection() {
this.currentConnection = null;
}
/**
* Check if a connection exists
*/
connectionExists(name) {
return this.connections.has(name);
}
/**
* Get connection count
*/
getConnectionCount() {
return this.connections.size;
}
/**
* Export all connections (for backup/migration)
*/
exportConnections() {
return {
connections: Object.fromEntries(this.connections),
defaultConnection: this.defaultConnectionName,
};
}
/**
* Import connections (for restore/migration)
*/
importConnections(data) {
this.connections.clear();
for (const [name, connection] of Object.entries(data.connections || {})) {
const validation = this.validateConnection(connection);
if (validation.valid) {
this.connections.set(name, { ...connection, name });
}
}
if (data.defaultConnection && this.connections.has(data.defaultConnection)) {
this.defaultConnectionName = data.defaultConnection;
}
this.saveConnections();
}
/**
* Get connections file path
*/
getConnectionsFilePath() {
return this.connectionsFile;
}
/**
* Get config directory
*/
getConfigDir() {
return this.configDir;
}
}
/**
* Global singleton instance
*/
let globalConnectionManager = null;
/**
* Get or create the global connection manager instance
*/
export function getConnectionManager(configDir) {
if (!globalConnectionManager) {
globalConnectionManager = new ConnectionManager(configDir);
}
return globalConnectionManager;
}
/**
* Reset the global connection manager (useful for testing)
*/
export function resetConnectionManager() {
globalConnectionManager = null;
}
//# sourceMappingURL=connection.js.map