@toolprint/hypertool-mcp
Version:
HyperTool MCP proxy server for routing requests between clients and multiple underlying MCP servers
235 lines • 7.73 kB
JavaScript
/**
* Application registry management
*/
import { promises as fs } from "fs";
import { vol } from "memfs";
import { isTestMode } from "../../config/environment.js";
import { join } from "path";
import { homedir, platform } from "os";
export class AppRegistry {
registryPath;
registry = null;
fs;
basePath;
constructor(basePath = join(homedir(), ".toolprint/hypertool-mcp")) {
this.basePath = basePath;
this.registryPath = join(basePath, "apps/registry.json");
// Use memfs in test mode, real fs in production
this.fs = isTestMode() ? vol.promises : fs;
}
/**
* Initialize the registry with default applications
*/
createDefaultRegistry() {
return {
version: "1.0.0",
applications: {
"claude-desktop": {
name: "Claude Desktop",
enabled: true,
platforms: {
darwin: {
configPath: "~/Library/Application Support/Claude/claude_desktop_config.json",
format: "standard",
},
win32: {
configPath: "%APPDATA%\\Claude\\claude_desktop_config.json",
format: "standard",
},
},
detection: {
type: "directory",
path: this.getCurrentPlatform() === "darwin"
? "~/Library/Application Support/Claude"
: "%APPDATA%\\Claude",
},
},
cursor: {
name: "Cursor IDE",
enabled: true,
platforms: {
all: {
configPath: "~/.cursor/mcp.json",
format: "standard",
},
},
detection: {
type: "directory",
path: "~/.cursor",
},
},
"claude-code": {
name: "Claude Code",
enabled: true,
platforms: {
all: {
configPath: "~/.claude.json",
format: "custom",
transformer: "claude-code",
},
},
detection: {
type: "file",
path: "~/.claude.json",
},
},
},
};
}
/**
* Load the application registry
*/
async load() {
if (this.registry) {
return this.registry;
}
try {
const content = await this.fs.readFile(this.registryPath, "utf-8");
this.registry = JSON.parse(content);
return this.registry;
}
catch (error) {
// If file doesn't exist, create default registry
if (error.code === "ENOENT") {
this.registry = this.createDefaultRegistry();
await this.save();
return this.registry;
}
throw error;
}
}
/**
* Save the application registry
*/
async save() {
if (!this.registry) {
throw new Error("No registry loaded");
}
// Ensure directory exists
const dir = join(this.registryPath, "..");
await this.fs.mkdir(dir, { recursive: true });
// Write registry
await this.fs.writeFile(this.registryPath, JSON.stringify(this.registry, null, 2), "utf-8");
}
/**
* Get all enabled applications
*/
async getEnabledApplications() {
const registry = await this.load();
const enabled = {};
for (const [id, app] of Object.entries(registry.applications)) {
if (app.enabled) {
enabled[id] = app;
}
}
return enabled;
}
/**
* Get application by ID
*/
async getApplication(appId) {
const registry = await this.load();
return registry.applications[appId] || null;
}
/**
* Add or update an application
*/
async setApplication(appId, app) {
const registry = await this.load();
registry.applications[appId] = app;
await this.save();
}
/**
* Remove an application
*/
async removeApplication(appId) {
const registry = await this.load();
delete registry.applications[appId];
await this.save();
}
/**
* Get current platform (for testing support)
*/
getCurrentPlatform() {
// In test mode, check for simulated platform
if (isTestMode()) {
const testPlatform = global.__TEST_PLATFORM__;
if (testPlatform) {
return testPlatform;
}
}
return platform();
}
/**
* Get platform-specific configuration for an application
*/
getPlatformConfig(app) {
// Use test platform if available, otherwise use actual platform
const currentPlatform = this.getCurrentPlatform();
// Check for platform-specific config first
if (currentPlatform === "darwin" && app.platforms.darwin) {
return app.platforms.darwin;
}
else if (currentPlatform === "linux" && app.platforms.linux) {
return app.platforms.linux;
}
else if (currentPlatform === "win32" && app.platforms.win32) {
return app.platforms.win32;
}
// Fall back to 'all' if available
if (app.platforms.all) {
return app.platforms.all;
}
return null;
}
/**
* Resolve path with home directory and environment variables
*/
resolvePath(path) {
// Replace ~ with home directory (use test base directory in test mode)
if (path.startsWith("~")) {
const homeDir = isTestMode()
? this.basePath.replace("/.toolprint/hypertool-mcp", "")
: homedir();
path = join(homeDir, path.slice(1));
}
// Replace environment variables (Windows style %VAR%)
path = path.replace(/%([^%]+)%/g, (_, varName) => {
return process.env[varName] || "";
});
// Replace environment variables (Unix style $VAR or ${VAR})
path = path.replace(/\$\{?([A-Z_][A-Z0-9_]*)\}?/gi, (_, varName) => {
return process.env[varName] || "";
});
return path;
}
/**
* Detect if an application is installed
*/
async isApplicationInstalled(app) {
const detection = app.detection;
switch (detection.type) {
case "file":
case "directory":
if (!detection.path)
return false;
const resolvedPath = this.resolvePath(detection.path);
try {
await this.fs.access(resolvedPath);
return true;
}
catch {
return false;
}
case "project-local":
// Project-local apps are always "installed" if we're checking them
return true;
case "command":
// TODO: Implement command-based detection
return false;
default:
return false;
}
}
}
//# sourceMappingURL=registry.js.map