/**
* OssHub MCP Server
* Universal Object Storage MCP Server
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import express from "express";
import path from "path";
import { readFileSync } from "fs";
import { fileURLToPath } from "url";
import { ProviderManager } from "./providers/manager.js";
import { ProviderRegistry } from "./providers/interface.js";
import { resolveTransport, resolvePort, resolveSourceConfigs, redactSourceConfig } from "./config/env.js";
import { registerTools } from "./tools/index.js";
// Create __dirname equivalent for ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Load package.json to get version
let packageJson: { version: string };
try {
const packageJsonPath = path.join(__dirname, "..", "package.json");
packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
} catch {
packageJson = { version: "0.1.0" };
}
// Server info
export const SERVER_NAME = "OssHub MCP Server";
export const SERVER_VERSION = packageJson.version;
/**
* Generate ASCII art banner with version information
*/
export function generateBanner(version: string): string {
return `
___ _ _ _
/ _ \\ | | | | | |
| | | |___ ___ | |_| |_ _| |__
| | | / __/ __|| _ | | | | '_ \\
| |_| \\__ \\__ \\| | | | |_| | |_) |
\\___/|___/___/|_| |_|\\__,_|_.__/
v${version} - Universal Object Storage MCP Server
`;
}
/**
* Initialize and start the OssHub server
*/
export async function main(): Promise<void> {
try {
// Resolve source configurations
const sourceConfigsData = await resolveSourceConfigs();
if (!sourceConfigsData) {
const samples = ProviderRegistry.getAllSampleEndpoints();
const sampleFormats = Object.entries(samples)
.map(([id, endpoint]) => ` - ${id}: ${endpoint}`)
.join("\n");
console.error(`
ERROR: Storage source configuration is required.
Please provide configuration in one of these ways (in order of priority):
1. TOML config file: --config=path/to/osshub.toml or ./osshub.toml
2. Environment variables:
- OSSHUB_TYPE: Provider type (obs, oss, s3, cos, minio)
- OSSHUB_ENDPOINT: Service endpoint URL
- OSSHUB_ACCESS_KEY: Access Key ID
- OSSHUB_SECRET_KEY: Secret Access Key
- OSSHUB_REGION: Region (optional)
Example endpoint formats:
${sampleFormats}
Example TOML config (osshub.toml):
[[sources]]
id = "my_obs"
type = "obs"
endpoint = "https://obs.cn-east-2.myhuaweicloud.com"
access_key = "your-access-key"
secret_key = "your-secret-key"
region = "cn-east-2"
See documentation for more details on configuring storage sources.
`);
process.exit(1);
}
// Create provider manager and connect to storage service(s)
const providerManager = new ProviderManager();
const sources = sourceConfigsData.sources;
console.error(`Configuration source: ${sourceConfigsData.source}`);
console.error(`Connecting to ${sources.length} storage source(s)...`);
for (const source of sources) {
console.error(` - ${redactSourceConfig(source)}`);
}
await providerManager.connectWithSources(sources);
console.error("Connected to all storage sources");
// Create MCP server factory function
const createServer = () => {
const server = new McpServer({
name: SERVER_NAME,
version: SERVER_VERSION,
});
// Register tools
registerTools(server, sourceConfigsData.tools);
return server;
};
// Resolve transport type
const transportData = resolveTransport();
const port = transportData.type === "http" ? resolvePort().port : null;
// Print banner
console.error(generateBanner(SERVER_VERSION));
// Print source summary
console.error("Registered sources:");
for (const source of sources) {
console.error(` - ${source.id} (${source.type})`);
}
console.error("");
// Set up transport-specific server
if (transportData.type === "http") {
// HTTP transport: Start Express server with MCP endpoint
const app = express();
app.use(express.json());
// CORS and security headers
app.use((req, res, next) => {
const origin = req.headers.origin;
if (origin && !origin.startsWith("http://localhost") && !origin.startsWith("https://localhost")) {
return res.status(403).json({ error: "Forbidden origin" });
}
res.header("Access-Control-Allow-Origin", origin || "http://localhost");
res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
res.header("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
res.header("Access-Control-Allow-Credentials", "true");
if (req.method === "OPTIONS") {
return res.sendStatus(200);
}
next();
});
// Health check endpoint
app.get("/healthz", (req, res) => {
res.status(200).send("OK");
});
// Sources API endpoint
app.get("/api/sources", (req, res) => {
const sources = ProviderManager.getAllSourceConfigs().map((s) => ({
id: s.id,
type: s.type,
endpoint: s.endpoint,
region: s.region,
}));
res.json({ sources });
});
// MCP endpoint (stateless mode)
app.get("/mcp", (req, res) => {
res.status(405).json({
error: "Method Not Allowed",
message: "SSE streaming is not supported in stateless mode. Use POST requests.",
});
});
app.post("/mcp", async (req, res) => {
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
const server = createServer();
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("Error handling request:", error);
if (!res.headersSent) {
res.status(500).json({ error: "Internal server error" });
}
}
});
// Start HTTP server
app.listen(port, "0.0.0.0", () => {
console.error(`MCP server endpoint at http://0.0.0.0:${port}/mcp`);
console.error(`Health check at http://0.0.0.0:${port}/healthz`);
});
} else {
// STDIO transport
const server = createServer();
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP server running on stdio");
process.on("SIGINT", async () => {
console.error("Shutting down...");
await transport.close();
process.exit(0);
});
}
} catch (err) {
console.error("Fatal error:", err);
process.exit(1);
}
}