@osiris-ai/cli
Version:
CLI for Osiris SDK - Create and manage MCP servers with authentication
1,572 lines (1,459 loc) • 143 kB
JavaScript
#!/usr/bin/env node
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
// src/index.ts
import { Command } from "commander";
import { intro, outro, cancel, isCancel, spinner as spinner6 } from "@clack/prompts";
import pc13 from "picocolors";
import { spawn as spawn2 } from "child_process";
import { existsSync as existsSync3 } from "fs";
import { join as join4 } from "path";
// src/commands/register.ts
import { confirm, note, spinner } from "@clack/prompts";
import pc from "picocolors";
// src/api/hub-client.ts
import axios from "axios";
import { config } from "dotenv";
config();
var HubApiClient = class {
client;
baseUrl;
constructor(baseUrl) {
this.baseUrl = baseUrl || "https://api.osirislabs.xyz/v1";
this.client = axios.create({
baseURL: this.baseUrl,
headers: {
"Content-Type": "application/json"
},
timeout: 3e4
});
this.client.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.data) {
const errorData = error.response.data;
throw new Error(
errorData.error ?? errorData.message ?? `HTTP ${error.response.status}: ${error.response.statusText}`
);
}
if (error.code === "ECONNREFUSED") {
throw new Error(
`Cannot connect to Legion Hub at ${this.baseUrl}. Is the server running?`
);
}
if (error.code === "ENOTFOUND") {
throw new Error(
`Cannot resolve Legion Hub URL: ${this.baseUrl}. Check your LEGION_HUB_URL environment variable.`
);
}
throw new Error(error.message || "Network error");
}
);
}
async createPackage(data, accessToken) {
const response = await this.client.post(`${this.baseUrl}/packages`, data, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.data.status === "FAILED" || !response.data.data) {
throw new Error(response.data.error || "Failed to create package");
}
return response.data.data;
}
async listPackages(accessToken) {
const response = await this.client.get(`${this.baseUrl}/packages/package/my-packages`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.data.status === "FAILED" || !response.data.data) {
throw new Error(response.data.error || "Failed to list packages");
}
return response.data.data;
}
async listClients(accessToken) {
const response = await this.client.get(`${this.baseUrl}/hub/oauth-clients?limit=100&offset=0`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.data.status === "FAILED" || !response.data.data) {
throw new Error(response.data.error || "Failed to list clients");
}
return response.data.data;
}
async getAuthUrl() {
const response = await this.client.get(`${this.baseUrl}/users/auth/url?type=github&authType=cli`);
if (response.data.status === "FAILED" || !response.data.data) {
throw new Error(response.data.error || "Failed to create authentication url");
}
return response.data.data?.url;
}
async getAuthScopes(clientId) {
const response = await this.client.get(`${this.baseUrl}/hub/auth/scopes?serviceClient=${clientId}`);
if (response.data.status === "FAILED" || !response.data.data) {
throw new Error(response.data.error || "Failed to get authentication scopes");
}
return response.data.data;
}
async getAuthHubUrl(clientType, name, scopes, accessToken) {
const url = new URL(`${this.baseUrl}/hub/auth/url`);
url.searchParams.set("type", clientType);
url.searchParams.set("authType", "cli");
url.searchParams.set("name", name);
url.searchParams.set("redirectUri", "http://localhost:6725/hub/callback");
scopes.forEach((scope) => url.searchParams.append("scopes", scope));
const response = await this.client.get(url.toString(), {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.data.status === "FAILED" || !response.data.data) {
throw new Error(response.data.error || "Failed to get authentication url");
}
return response.data.data?.url;
}
async getPackage(packageId, accessToken) {
const response = await this.client.get(`${this.baseUrl}/packages/${packageId}/auth-scopes`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.data.status === "FAILED" || !response.data.data) {
throw new Error(response.data.error || "Failed to get package");
}
return response.data.data;
}
async getUserAuth(accessToken) {
const response = await this.client.get(`${this.baseUrl}/hub/auth/user`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.data.status === "FAILED" || !response.data.data) {
throw new Error(response.data.error || "Failed to get user auth");
}
return response.data.data;
}
async listDeployments(accessToken) {
const response = await this.client.get(`${this.baseUrl}/packages/packages/user/deployments`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.data.status === "FAILED" || !response.data.data) {
throw new Error(response.data.error || "Failed to list deployments");
}
return response.data.data;
}
async updateDeployment(deploymentId, data, accessToken) {
const response = await this.client.patch(`${this.baseUrl}/packages/deployments/${deploymentId}/update-policy`, data, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.data.status === "FAILED" || !response.data.data) {
throw new Error(response.data.error || "Failed to update deployment");
}
return response.data.data;
}
async publishPackage(packageId, version, serviceConnections, accessToken) {
try {
await this.client.post(`${this.baseUrl}/packages/install`, {
packageId,
version
}, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
} catch (error) {
if (error.message !== "Package version already installed") {
throw error;
}
}
const deployedPackage = await this.client.post(`${this.baseUrl}/packages/deploy`, {
packageId,
version,
url: "http://localhost:3000",
authData: {},
serviceConnections
}, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (deployedPackage.data.status === "FAILED" || !deployedPackage.data.data) {
throw new Error(deployedPackage.data.error || "Failed to deploy package");
}
return deployedPackage.data.data;
}
async authorizePackage(clientId, redirectUri, scopes, policy, deploymentId, accessToken) {
try {
try {
const response = await this.client.post(`${this.baseUrl}/hub/authorize`, {
clientId,
redirectUri,
responseType: "code",
scopes,
policy,
state: deploymentId
}, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.status === 200) {
return true;
}
return false;
} catch (error) {
if (error.message !== "Invalid client ID") {
throw error;
}
throw error;
}
return false;
} catch (error) {
return false;
}
}
async getDeployment(deploymentId, clientId, clientSecret) {
const response = await this.client.get(`${this.baseUrl}/package/deployments/${deploymentId}`, {
headers: {
"client-id": clientId,
"client-secret": clientSecret
}
});
if (response.data.status === "FAILED" || !response.data.data) {
throw new Error(response.data.error || "Failed to get deployment");
}
return response.data.data;
}
/**
* Register a new user and create OAuth client in one flow
*/
async register(data) {
try {
const userResponse = await this.client.post("/users", {
name: data.name,
email: data.email,
role: "admin"
});
if (userResponse.data.status === "FAILED") {
throw new Error(userResponse.data.error || "Failed to create user");
}
const { user, tokens } = userResponse.data.data;
try {
const oauthClient = await this.createOAuthClient(
{
name: data.oauthClientName,
redirectUris: data.redirectUris,
metadata: {
purpose: "Osiris CLI generated client",
createdVia: "cli-registration",
createdAt: (/* @__PURE__ */ new Date()).toISOString()
}
},
tokens.accessToken
);
return {
user,
tokens,
oauthClient
};
} catch (oauthError) {
return {
user,
tokens
// oauthClient will be undefined - to be created manually
};
}
} catch (error) {
if (error instanceof Error) {
if (error.message.includes("User already exists") || error.message.includes("already registered")) {
throw new Error(
"An account with this email already exists. Please use a different email."
);
}
if (error.message.includes("invalid email")) {
throw new Error("The provided email address is invalid.");
}
if (error.message.includes("redirect") || error.message.includes("callback")) {
throw new Error(
"One or more callback URLs are invalid. Please check the format."
);
}
}
throw error;
}
}
/**
* Create a new user
*/
async createUser(data) {
const response = await this.client.post("/users", {
name: data.name,
email: data.email,
role: data.role || "developer"
});
if (response.data.status === "FAILED") {
throw new Error(response.data.error || "Failed to create user");
}
return response.data.data;
}
async getUser(authToken) {
const response = await this.client.get("/users", {
headers: {
Authorization: `Bearer ${authToken}`
}
});
if (response.data.status === "FAILED") {
throw new Error(response.data.error || "Failed to get user");
}
return response.data.data;
}
/**
* Create a new OAuth client (requires authentication)
*/
async createOAuthClient(data, authToken) {
const response = await this.client.post(
"/hub/oauth-clients",
data,
{
headers: {
Authorization: `Bearer ${authToken}`
}
}
);
if (response.data.status === "FAILED") {
throw new Error(response.data.error || "Failed to create OAuth client");
}
return response.data.data;
}
/**
* Get available authentication methods
*/
async getAuthMethods() {
const response = await this.client.get("/hub/auth");
if (response.data.status === "FAILED") {
throw new Error(response.data.error || "Failed to get auth methods");
}
return response.data.data || [];
}
async createSecretConnection(serviceClientId, secret, name, accessToken) {
try {
const response = await this.client.post("/hub/secret/create", {
serviceClientId,
secret,
name
}, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.data.status === "FAILED") {
return {
success: false,
error: response.data.error || "Failed to create secret connection"
};
}
return {
success: true,
data: response.data.data
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error occurred"
};
}
}
/**
* Create an embedded wallet connection
*/
async createWallet(name, accounts, policy, accessToken) {
try {
const response = await this.client.post("/hub/wallet/create", {
name,
accounts,
policy: policy || void 0
}, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.data.status === "FAILED") {
return {
success: false,
error: response.data.error || "Failed to create wallet"
};
}
return {
success: true,
data: response.data.data
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error occurred"
};
}
}
async addWalletAccount(walletId, accountId, accounts, accessToken) {
try {
const response = await this.client.post("/hub/wallet/add-account", {
walletId,
accountId,
accounts
}, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.data.status === "FAILED") {
return {
success: false,
error: response.data.error || "Failed to add wallet account"
};
}
return {
success: true,
data: response.data.data
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error occurred"
};
}
}
async updateWalletPolicy(walletId, policy, accessToken) {
try {
const response = await this.client.patch("/hub/wallet/update-policy", {
id: walletId,
policy
}, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.data.status === "FAILED") {
return {
success: false,
error: response.data.error || "Failed to update wallet policy"
};
}
return {
success: true,
data: response.data.data
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error occurred"
};
}
}
/**
* Validate the connection to the hub
*/
async healthCheck() {
try {
const res = await this.client.get("/health");
return true;
} catch {
return false;
}
}
/**
* Get API status and version info
*/
async getStatus() {
const response = await this.client.get("/health");
return response.data;
}
};
// src/utils/config.ts
import { join } from "path";
import { homedir } from "os";
import { readFile, writeFile, mkdir } from "fs/promises";
import { existsSync } from "fs";
var CONFIG_DIR = join(homedir(), ".osiris");
var CONFIG_PATH = join(CONFIG_DIR, "config.json");
async function loadConfig() {
try {
if (!existsSync(CONFIG_PATH)) {
return null;
}
const configData = await readFile(CONFIG_PATH, "utf-8");
return JSON.parse(configData);
} catch (error) {
console.error("Failed to load config:", error);
return null;
}
}
async function saveConfig(config2) {
try {
if (!existsSync(CONFIG_DIR)) {
await mkdir(CONFIG_DIR, { recursive: true });
}
await writeFile(CONFIG_PATH, JSON.stringify(config2, null, 2));
} catch (error) {
throw new Error(`Failed to save config: ${error instanceof Error ? error.message : String(error)}`);
}
}
async function updateConfig(updates) {
const existingConfig = await loadConfig();
if (!existingConfig) {
throw new Error("No existing config found. Please run 'pnpm @osiris-ai/cli register' first.");
}
const newConfig = {
...existingConfig,
...updates,
user: {
...existingConfig.user,
...updates.user
},
oauthClient: [
...updates.oauthClient || []
]
};
await saveConfig(newConfig);
}
// src/commands/register.ts
import open from "open";
// src/proxy-server/server.ts
import { serve } from "@hono/node-server";
import { Hono } from "hono";
// src/utils/deferred.ts
var deferred = () => {
let resolve;
let reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
// src/proxy-server/server.ts
var startCallbackServer = async () => {
const { promise, resolve, reject } = deferred();
const app = new Hono();
app.get("/", (c) => {
const query = c.req.query();
const accessToken = query.accessToken;
const refreshToken = query.refreshToken;
if (!accessToken || !refreshToken) {
return c.json({ error: "Missing access token or refresh token" }, 400);
}
resolve({ accessToken, refreshToken });
return c.html(`<html><body style="font: 16px sans-serif">
<p>\u2705 Logged in. You may return to the terminal.</p>
</body></html>`);
});
app.get("/hub/callback", (c) => {
resolve({ accessToken: "", refreshToken: "" });
return c.html(`<html><body style="font: 16px sans-serif">
<p>\u2705 Connected Authentication. You may return to the terminal.</p>
</body></html>`);
});
const server = serve({ fetch: app.fetch, hostname: "127.0.0.1", port: 6725 });
await new Promise((r) => server.on("listening", r));
const port = server.address().port;
const timer = setTimeout(() => reject(new Error("Your request timed out")), 5 * 6e4);
return {
port,
onAuth: promise.finally(() => clearTimeout(timer)),
close: () => server.close()
};
};
// src/commands/register.ts
async function registerCommand() {
const hubClient = new HubApiClient();
const existingConfig = await loadConfig();
if (existingConfig?.user) {
note(
`${pc.yellow("\u26A0\uFE0F User Already Registered")}
${pc.dim("Current User:")}
${pc.dim("Name:")} ${existingConfig.user.name}
${pc.dim("Email:")} ${existingConfig.user.email}
${pc.dim("User ID:")} ${existingConfig.user.id}
Continuing will replace your current user configuration.`,
"Existing User Found"
);
const replaceUser = await confirm({
message: "Continue with registering a new user?",
initialValue: false
});
if (!replaceUser) {
note(
`${pc.green("\u2705 Registration cancelled")}
Your existing user configuration remains unchanged.`,
"Registration Cancelled"
);
return;
}
}
const isConnected = await hubClient.healthCheck();
if (!isConnected) {
note(
`${pc.red("\u274C Connection Failed")}
Cannot connect to Osiris Hub. Please check:
\u2022 Hub server is running
\u2022 OSIRIS_HUB_URL environment variable is correct
\u2022 Network connectivity
Current hub URL: ${process.env.OSIRIS_HUB_URL || "https://api.osirislabs.xyz/v1"}`,
"Hub Connection Error"
);
throw new Error("Cannot connect to Osiris Hub");
}
const { port, onAuth, close } = await startCallbackServer();
let authUrl = "";
try {
const urlSpinner = spinner();
urlSpinner.start("Getting authentication URL...");
authUrl = await hubClient.getAuthUrl();
urlSpinner.stop("\u2705 Authentication URL received");
} catch (error) {
note(
`${pc.red("\u274C Failed to get authentication URL")}
Error: ${error instanceof Error ? error.message : String(error)}
Please check:
\u2022 Hub server is running
\u2022 OSIRIS_HUB_URL environment variable is correct
\u2022 Network connectivity
Current hub URL: ${process.env.OSIRIS_HUB_URL || "https://api.osirislabs.xyz/v1"}`,
"Authentication Error"
);
await close();
return;
}
const openBrowser = await confirm({
message: "Would you like to open the authentication URL in your browser?",
initialValue: true
});
if (openBrowser) {
try {
await open(authUrl);
note(
`${pc.green("\u2705 Browser opened")}
Please complete the authentication process in your browser.`,
"Authentication in Progress"
);
} catch (error) {
note(
`${pc.yellow("\u26A0\uFE0F Could not open browser")}
Please manually open this URL in your browser:
${pc.green(authUrl)}`,
"Manual Authentication Required"
);
}
} else {
note(
`${pc.green("\u2705 Authentication URL")}
Please open this URL in your browser:
${pc.green(authUrl)}`,
"Manual Authentication Required"
);
}
const authSpinner = spinner();
authSpinner.start("Waiting for authentication...");
try {
const authTokens = await onAuth;
authSpinner.stop("\u2705 Authentication successful");
if (!authTokens) {
note(
`${pc.red("\u274C Authentication failed")}
No authentication tokens received. Please try again.`,
"Authentication Error"
);
await close();
return;
}
const userSpinner = spinner();
userSpinner.start("Retrieving user information...");
try {
const user = await hubClient.getUser(authTokens.accessToken);
userSpinner.stop("\u2705 User information retrieved");
const config2 = {
user: {
id: user.id,
name: user.name,
email: user.email,
tokens: {
accessToken: authTokens.accessToken,
refreshToken: authTokens.refreshToken
}
},
oauthClient: [],
hubUrl: process.env.OSIRIS_HUB_URL || "https://api.osirislabs.xyz/v1"
};
await saveConfig(config2);
note(
`${pc.green("\u{1F389} Registration Successful!")}
${pc.cyan("User Details:")}
${pc.dim("User ID:")} ${user.id}
${pc.dim("Name:")} ${user.name}
${pc.dim("Email:")} ${user.email}
${pc.dim("Role:")} ${user.role}
${pc.green("\u2705 Configuration saved to:")} ${pc.dim(CONFIG_PATH)}
${pc.cyan("Next Steps:")}
\u2022 Run ${pc.green("create-client")} to create an OAuth client
\u2022 Run ${pc.green("create-mcp")} to create your first MCP project`,
"Registration Complete"
);
} catch (error) {
userSpinner.stop("\u274C Failed to retrieve user information");
note(
`${pc.red("\u274C Failed to retrieve user information")}
Error: ${error instanceof Error ? error.message : String(error)}
Please try again or check your authentication.`,
"User Information Error"
);
await close();
return;
}
} catch (error) {
authSpinner.stop("\u274C Authentication failed");
note(
`${pc.red("\u274C Authentication failed")}
Error: ${error instanceof Error ? error.message : String(error)}
Please try again.`,
"Authentication Error"
);
await close();
return;
}
await close();
}
// src/commands/create-mcp.ts
import { text as text2, select, confirm as confirm2, note as note2, spinner as spinner2, multiselect } from "@clack/prompts";
import pc2 from "picocolors";
import { join as join2 } from "path";
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
// src/utils/dependency-map.ts
var DATABASE_ADAPTERS = {
memory: {
name: "Memory (Development)",
dependencies: {},
devDependencies: {}
},
postgres: {
name: "PostgreSQL",
dependencies: {
"@osiris-ai/postgres-sdk": "^0.1.1",
"pg": "^8.15.16"
}
},
mongodb: {
name: "MongoDB",
dependencies: {
"@osiris-ai/mongodb-sdk": "^0.1.1",
"mongodb": "^6.17.0"
}
},
redis: {
name: "Redis",
dependencies: {
"@osiris-ai/redis-sdk": "^0.1.1",
"ioredis": "^5.6.1"
}
},
sqlite: {
name: "SQLite",
dependencies: {
"@osiris-ai/sqlite-sdk": "^0.1.1",
"sqlite3": "^5.1.7"
}
}
};
function getDependenciesForDatabase(adapter) {
const config2 = DATABASE_ADAPTERS[adapter];
if (!config2) {
throw new Error(`Unknown database adapter: ${adapter}`);
}
return config2;
}
// src/templates/project-files.ts
function generateProjectFiles(config2) {
const files = {};
const dbDeps = getDependenciesForDatabase(config2.databaseAdapter);
const ext = config2.useTypeScript ? "ts" : "js";
files["package.json"] = generatePackageJson(config2, dbDeps);
files[".env"] = generateEnvFile(config2);
files[".env.example"] = generateEnvExampleFile();
files["README.md"] = generateReadme(config2);
if (config2.useTypeScript) files["tsconfig.json"] = generateTsConfig();
files[".gitignore"] = generateGitIgnore();
files["osiris.json"] = generateOsirisJson(config2);
files[`index.${ext}`] = genIndex(config2);
files[`client.${ext}`] = genClient(config2);
files[`tools/hello-world.${ext}`] = genTool(config2);
files[`prompts/hello-world.${ext}`] = genPrompt(config2);
files[`resources/hello-world.${ext}`] = genResource(config2);
files[`schema/index.${ext}`] = genSchema(config2);
return files;
}
function generateOsirisJson(config2) {
return JSON.stringify({
name: config2.name,
id: config2.id,
description: config2.description,
hubUrl: config2.hubUrl,
packageId: config2.packageId,
serviceClients: config2.serviceClients,
baseUrl: config2.url ?? "http://localhost:3000",
user: {
name: config2.userName,
email: config2.userEmail,
role: "publisher"
}
}, null, 2);
}
function generatePackageJson(config2, dbDeps) {
const pkg = {
id: config2.id,
version: "1.0.0",
description: config2.description || "MCP server built with Osiris SDK",
type: "module",
main: config2.useTypeScript ? "dist/index.js" : "index.js",
scripts: {
dev: config2.useTypeScript ? "tsx index.ts" : "nodemon index.js",
build: config2.useTypeScript ? "tsc" : "echo 'No build step needed for JavaScript'",
start: config2.useTypeScript ? "node dist/index.js" : "node index.js",
"type-check": config2.useTypeScript ? "tsc --noEmit" : "echo 'No type checking for JavaScript'",
test: "echo 'No tests specified' && exit 0"
},
keywords: ["mcp", "osiris", "server", "authentication"],
author: `${config2.userName} <${config2.userEmail}>`,
license: "MIT",
dependencies: {
"@osiris-ai/sdk": "^0.1.8",
"@modelcontextprotocol/sdk": "^1.11.4",
"dotenv": "^16.3.1",
"zod": "^3.25.67",
"@noble/curves": "^1.9.2",
...dbDeps.dependencies
},
devDependencies: {
"@types/node": "^24.0.8",
"tsx": "^4.19.4",
...config2.useTypeScript ? { "typescript": "^5.8.3" } : {},
...dbDeps.devDependencies
}
};
return JSON.stringify(pkg, null, 2);
}
function generateEnvFile(config2) {
return `# Osiris Hub Configuration
HUB_BASE_URL=${config2.hubUrl}
# OAuth Credentials
OAUTH_CLIENT_ID=${config2.clientId}
OAUTH_CLIENT_SECRET=${config2.clientSecret}
# MCP Server Configuration
PORT=3000
NODE_ENV=development
# Database Configuration
DATABASE_ADAPTER=${config2.databaseAdapter}
${config2.databaseAdapter === "postgres" ? `DATABASE_URL=postgresql://username:password@localhost:5432/${config2.name}` : ""}
${config2.databaseAdapter === "mongodb" ? `MONGODB_URL=mongodb://localhost:27017/${config2.name}` : ""}
${config2.databaseAdapter === "redis" ? `REDIS_URL=redis://localhost:6379` : ""}
${config2.databaseAdapter === "sqlite" ? `SQLITE_PATH=./data/${config2.name}.db` : ""}
# Security
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
`;
}
function generateEnvExampleFile() {
return `# Osiris Hub Configuration
HUB_BASE_URL=https://api.osirislabs.xyz/v1
# OAuth Credentials (get these from your Hub)
OAUTH_CLIENT_ID=your-oauth-client-id
OAUTH_CLIENT_SECRET=your-oauth-client-secret
# MCP Server Configuration
PORT=3000
NODE_ENV=development
# Database Configuration
DATABASE_ADAPTER=memory
# DATABASE_URL=your-database-connection-string
# Security
JWT_SECRET=your-super-secret-jwt-key
`;
}
function generateTsConfig() {
return JSON.stringify({
compilerOptions: {
target: "ES2022",
module: "ESNext",
moduleResolution: "Node",
allowSyntheticDefaultImports: true,
esModuleInterop: true,
allowImportingTsExtensions: false,
moduleDetection: "force",
noEmit: false,
composite: false,
strict: true,
skipLibCheck: true,
forceConsistentCasingInFileNames: true,
resolveJsonModule: true,
isolatedModules: true,
verbatimModuleSyntax: false,
outDir: "./dist",
rootDir: "./"
},
include: [
"**/*.ts"
],
exclude: [
"dist",
"node_modules",
"**/*.test.ts"
]
}, null, 2);
}
function generateGitIgnore() {
return `# Dependencies
node_modules/
pnpm-lock.yaml
package-lock.json
yarn.lock
# Build outputs
dist/
build/
# Environment variables
.env
.env.local
# Database files
*.db
*.sqlite
data/
# Logs
logs/
*.log
npm-debug.log*
pnpm-debug.log*
# Runtime data
pids/
*.pid
*.seed
*.pid.lock
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Temporary files
tmp/
temp/
`;
}
function generateReadme(config2) {
return `# ${config2.name}
${config2.description || "MCP server built with Osiris SDK"}
## Features
- \u2705 Built with Model Context Protocol (MCP) SDK
- \u{1F510} Integrated authentication via Osiris Hub
- \u{1F4CA} ${getDependenciesForDatabase(config2.databaseAdapter).name} database
- ${config2.useTypeScript ? "\u{1F537} TypeScript" : "\u{1F4DD} JavaScript"}
- \u{1F6E0}\uFE0F Example tools, resources, prompts, and schemas
## Quick Start
1. **Install dependencies:**
\`\`\`bash
pnpm install
\`\`\`
2. **Configure environment:**
\`\`\`bash
cp .env.example .env
# Edit .env with your configuration
\`\`\`
3. **Start development server:**
\`\`\`bash
pnpm run dev
\`\`\`
## Configuration
### Environment Variables
- \`HUB_BASE_URL\` - Your Osiris Hub URL
- \`OAUTH_CLIENT_ID\` - OAuth client ID from Hub
- \`OAUTH_CLIENT_SECRET\` - OAuth client secret from Hub
- \`DATABASE_ADAPTER\` - Database type (${config2.databaseAdapter})
### Database Setup
${config2.databaseAdapter === "postgres" ? `
#### PostgreSQL
1. Install PostgreSQL
2. Create a database: \`createdb ${config2.name}\`
3. Set \`DATABASE_URL\` in your .env file
` : ""}${config2.databaseAdapter === "mongodb" ? `
#### MongoDB
1. Install MongoDB
2. Start MongoDB service
3. Set \`MONGODB_URL\` in your .env file (optional, defaults to localhost)
` : ""}${config2.databaseAdapter === "redis" ? `
#### Redis
1. Install Redis
2. Start Redis service
3. Set \`REDIS_URL\` in your .env file (optional, defaults to localhost)
` : ""}${config2.databaseAdapter === "sqlite" ? `
#### SQLite
SQLite database will be created automatically at the path specified in \`SQLITE_PATH\`.
` : ""}${config2.databaseAdapter === "memory" ? `
#### Memory Database
No setup required. Data is stored in memory (development only).
**\u26A0\uFE0F Warning:** Data will be lost when the server restarts.
` : ""}
## Development
### Available Scripts
- \`pnpm run dev\` - Start development server with hot reload
- \`pnpm run build\` - Build for production${config2.useTypeScript ? " (TypeScript \u2192 JavaScript)" : ""}
- \`pnpm run start\` - Start production server
- \`pnpm run type-check\` - ${config2.useTypeScript ? "Run TypeScript type checking" : "No-op for JavaScript projects"}
### Project Structure
\`\`\`
${config2.name}/
\u251C\u2500\u2500 index.${config2.useTypeScript ? "ts" : "js"} # Main MCP server entry point
\u251C\u2500\u2500 client.${config2.useTypeScript ? "ts" : "js"} # MCP client setup and configuration
\u251C\u2500\u2500 tools/
\u2502 \u2514\u2500\u2500 hello-world.${config2.useTypeScript ? "ts" : "js"} # Simple hello world tool
\u251C\u2500\u2500 resources/
\u2502 \u2514\u2500\u2500 hello-world.${config2.useTypeScript ? "ts" : "js"} # Hello world resource
\u251C\u2500\u2500 prompts/
\u2502 \u2514\u2500\u2500 hello-world.${config2.useTypeScript ? "ts" : "js"} # Hello world prompt template
\u251C\u2500\u2500 schema/
\u2502 \u2514\u2500\u2500 index.${config2.useTypeScript ? "ts" : "js"} # Zod schema definitions
\u251C\u2500\u2500 .env # Environment variables
\u251C\u2500\u2500 .env.example # Environment template
\u2514\u2500\u2500 package.json # Dependencies and scripts
\`\`\`
## MCP Components
### Tools
#### Hello World Tool
A simple greeting tool that demonstrates basic MCP tool functionality.
**Usage:**
\`\`\`json
{
"name": "hello_world",
"arguments": {
"name": "Alice"
}
}
\`\`\`
### Resources
#### Hello World Resource
Provides a simple JSON resource demonstrating MCP resource functionality.
**URI:** \`hello://world\`
### Prompts
#### Hello World Prompt
Generates prompts about specified topics.
**Arguments:**
- \`topic\` (optional): Topic to ask about (default: "osiris")
## Adding New Components
### Adding a Tool
Create a new file in \`tools/\` and export the tool registration function:
\`\`\`${config2.useTypeScript ? "typescript" : "javascript"}
${config2.useTypeScript ? "import" : "const"} { McpServer } ${config2.useTypeScript ? "from" : "= require("} '@modelcontextprotocol/sdk/server/mcp.js'${config2.useTypeScript ? ";" : ");"}
${config2.useTypeScript ? "import" : "const"} { z } ${config2.useTypeScript ? "from" : "= require("} 'zod'${config2.useTypeScript ? ";" : ");"}
${config2.useTypeScript ? "export function" : "function"} registerMyTool(server${config2.useTypeScript ? ": McpServer" : ""}) {
const MyToolSchema = z.object({
param1: z.string().describe('Parameter description')
});
server.tool('my_tool', 'Description of what this tool does', MyToolSchema, async ({ param1 }) => {
// Your tool logic here
return {
content: [{
type: 'text',
text: \`Tool result: \${param1}\`
}]
};
});
}${config2.useTypeScript ? "" : "\n\nmodule.exports = { registerMyTool };"}
\`\`\`
Then register it in \`client.${config2.useTypeScript ? "ts" : "js"}\`.
### Adding a Resource
Create a new file in \`resources/\` and export the resource registration function:
\`\`\`${config2.useTypeScript ? "typescript" : "javascript"}
${config2.useTypeScript ? "import" : "const"} { McpServer } ${config2.useTypeScript ? "from" : "= require("} '@modelcontextprotocol/sdk/server/mcp.js'${config2.useTypeScript ? ";" : ");"}
${config2.useTypeScript ? "export function" : "function"} registerMyResource(server${config2.useTypeScript ? ": McpServer" : ""}) {
server.resource('my://resource', 'My Resource', 'Resource description', async () => {
return {
contents: [{
uri: 'my://resource',
mimeType: 'application/json',
text: JSON.stringify({ data: 'Resource data' })
}]
};
});
}${config2.useTypeScript ? "" : "\n\nmodule.exports = { registerMyResource };"}
\`\`\`
## Authentication
This server uses Osiris Hub for authentication. Make sure your Hub is configured and your OAuth credentials are correct.
## Deployment
1. Build the project:
\`\`\`bash
pnpm run build
\`\`\`
2. Set production environment variables
3. Start the server:
\`\`\`bash
NODE_ENV=production pnpm run start
\`\`\`
## Support
- \u{1F4D6} [MCP Documentation](https://modelcontextprotocol.io/docs)
- \u{1F4D6} [Osiris SDK Documentation](https://docs.osirislabs.xyz)
- \u{1F4AC} [Community Discord](https://discord.gg/osirislabs)
- \u{1F41B} [Issue Tracker](https://github.com/osirislabs/sdk/issues)
---
Built with \u2764\uFE0F using [Model Context Protocol](https://modelcontextprotocol.io) and [Osiris SDK](https://osirislabs.xyz)
`;
}
function adapterImport(adapter, useTs) {
const map = {
postgres: ["PostgresDatabaseAdapter", "@osiris-ai/postgres-sdk"],
mongodb: ["MongoDBDatabaseAdapter", "@osiris-ai/mongodb-sdk"],
redis: ["RedisDatabaseAdapter", "@osiris-ai/redis-sdk"],
sqlite: ["SQLiteDatabaseAdapter", "@osiris-ai/sqlite-sdk"],
memory: ["MemoryDatabaseAdapter", "@osiris-ai/sdk"]
};
const [cls, pkg] = map[adapter] || ["MemoryDatabaseAdapter", "@osiris-ai/sdk"];
return useTs ? `import { ${cls} } from '${pkg}';` : `const { ${cls} } = require('${pkg}');`;
}
function genIndex(config2) {
const cls = "HelloWorldMCP";
const importDb = adapterImport(config2.databaseAdapter, config2.useTypeScript);
const isTs = config2.useTypeScript;
const nl = "\n";
const imports = isTs ? `import { createMcpServer } from '@osiris-ai/sdk';${nl}${importDb}${nl}import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';` + nl + "import { config as dotenv } from 'dotenv';" + nl + `import { ${cls} } from './client.js';` + nl + "dotenv();" : `const { createMcpServer } = require('@osiris-ai/sdk');${nl}${importDb}${nl}const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');` + nl + "const { config: dotenv } = require('dotenv');" + nl + `const { ${cls} } = require('./client.js');` + nl + "dotenv();";
const adapterInstance = {
postgres: "new PostgresDatabaseAdapter({ connectionString: process.env.DATABASE_URL })",
mongodb: "new MongoDBDatabaseAdapter({ connectionString: process.env.DATABASE_URL })",
redis: "new RedisDatabaseAdapter({ connectionString: process.env.DATABASE_URL })",
sqlite: 'new SQLiteDatabaseAdapter({ filename: process.env.DATABASE_URL || "./data/db.sqlite" })',
memory: "new MemoryDatabaseAdapter()"
};
return imports + nl + "async function start()" + (isTs ? ": Promise<void>" : "") + " {" + nl + " const hub = process.env.HUB_BASE_URL || 'https://api.osirislabs.xyz/v1';" + nl + ' const clientId = process.env.OAUTH_CLIENT_ID || "";' + nl + ' const clientSecret = process.env.OAUTH_CLIENT_SECRET || "";' + nl + ' const port = parseInt(process.env.PORT || "3000", 10);' + nl + nl + ` const hello = new ${cls}(hub);` + nl + nl + " await createMcpServer({" + nl + ` name: '${config2.name}',` + nl + " version: '0.0.1'," + nl + " auth: {" + nl + " useHub: true," + nl + " hubConfig: { baseUrl: hub, clientId, clientSecret }," + nl + ` database: ${adapterInstance[config2.databaseAdapter] || "new MemoryDatabaseAdapter()"},` + nl + " }," + nl + " server: {" + nl + " port," + nl + " mcpPath: '/mcp'," + nl + " callbackBasePath: '/callback'," + nl + " baseUrl: 'http://localhost:3000'," + nl + " logger: (m" + (isTs ? ": string" : "") + ") => console.log(m)," + nl + " }," + nl + " configure: (s" + (isTs ? ": McpServer" : "") + ") => hello.configureServer(s)," + nl + " });" + nl + nl + ` console.log('\u{1F680} ${config2.name} running on port', port);` + nl + "}" + nl + "start().catch((err) => {" + nl + ' console.error("Failed to start:", err);' + nl + " process.exit(1);" + nl + "});";
}
function genClient(config2) {
const imports = config2.useTypeScript ? `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { registerHelloTool } from './tools/hello-world.js';
import { registerHelloPrompt } from './prompts/hello-world.js';
import { registerHelloResource } from './resources/hello-world.js';` : `const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
const { registerHelloTool } = require('./tools/hello-world.js');
const { registerHelloPrompt } = require('./prompts/hello-world.js');
const { registerHelloResource } = require('./resources/hello-world.js');`;
const classDeclaration = config2.useTypeScript ? `export class HelloWorldMCP {
private hubBaseUrl: string;
constructor(hubBaseUrl: string) {
this.hubBaseUrl = hubBaseUrl;
}
configureServer(server: McpServer): void {
registerHelloTool(server);
registerHelloPrompt(server);
registerHelloResource(server);
}
}` : `class HelloWorldMCP {
constructor(hubBaseUrl) {
this.hubBaseUrl = hubBaseUrl;
}
configureServer(server) {
registerHelloTool(server);
registerHelloPrompt(server);
registerHelloResource(server);
}
}
module.exports = { HelloWorldMCP };`;
return `${imports}
${classDeclaration}
`;
}
function genSchema(config2) {
const importLine = config2.useTypeScript ? `import { z } from 'zod';` : `const { z } = require('zod');`;
const exportOrConst = config2.useTypeScript ? "export const" : "const";
const moduleExport = config2.useTypeScript ? "" : "\nmodule.exports = { HelloToolSchema, HelloPromptSchema };";
return `${importLine}
${exportOrConst} HelloToolSchema = {
name: z.string().default('World').describe('Name to greet'),
};
${exportOrConst} HelloPromptSchema = {
topic: z.string(),
};
${moduleExport}
`;
}
function genTool(config2) {
const importLine = config2.useTypeScript ? `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { HelloToolSchema } from '../schema/index.js';` : `const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
const { HelloToolSchema } = require('../schema/index.js');`;
const exportPrefix = config2.useTypeScript ? "export function" : "function";
const moduleExport = config2.useTypeScript ? "" : "\nmodule.exports = { registerHelloTool };";
return `${importLine}
${exportPrefix} registerHelloTool(server${config2.useTypeScript ? ": McpServer" : ""}) {
server.tool('hello_world', 'Say hello', HelloToolSchema, async ({ name }) => {
const greeting = '\u{1F44B} Hello, ' + (name || 'World') + '!';
return {
content: [ { type: 'text', text: greeting } ]
};
});
}
${moduleExport}
`;
}
function genPrompt(config2) {
const importLine = config2.useTypeScript ? `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { HelloPromptSchema } from '../schema/index.js';` : `const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
const { HelloPromptSchema } = require('../schema/index.js');`;
const exportPrefix = config2.useTypeScript ? "export function" : "function";
const moduleExport = config2.useTypeScript ? "" : "\nmodule.exports = { registerHelloPrompt };";
return `${importLine}
${exportPrefix} registerHelloPrompt(server${config2.useTypeScript ? ": McpServer" : ""}) {
server.prompt('hello_world', 'Provide a hello prompt', HelloPromptSchema, async ({ topic }) => {
const promptText = 'Tell me something cool about ' + (topic || 'Osiris') + '.';
return {
description: 'Hello prompt about ' + (topic || 'Osiris'),
messages: [ { role: 'assistant', content: { type: 'text', text: promptText } } ]
};
});
}
${moduleExport}
`;
}
function genResource(config2) {
const importLine = config2.useTypeScript ? `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';` : `const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');`;
const exportPrefix = config2.useTypeScript ? "export function" : "function";
const moduleExport = config2.useTypeScript ? "" : "\nmodule.exports = { registerHelloResource };";
return `${importLine}
${exportPrefix} registerHelloResource(server${config2.useTypeScript ? ": McpServer" : ""}) {
server.resource('hello://world', 'Hello World', {
uri: 'hello://world',
name: "Hello World",
}, async () => {
return {
contents: [{ uri: 'hello://world', mimeType: 'application/json', text: JSON.stringify({ hello: 'world' }) }]
};
});
}
${moduleExport}
`;
}
// src/utils/package-manager.ts
import { spawn } from "child_process";
function detectPackageManager() {
const userAgent = process.env.npm_config_user_agent;
if (userAgent) {
if (userAgent.includes("yarn")) return "yarn";
if (userAgent.includes("pnpm")) return "pnpm";
}
try {
if (__require("fs").existsSync("pnpm-lock.yaml")) return "pnpm";
if (__require("fs").existsSync("yarn.lock")) return "yarn";
} catch {
}
return "npm";
}
function getInstallCommand(packageManager) {
switch (packageManager) {
case "yarn":
return ["yarn", "install"];
case "pnpm":
return ["pnpm", "install"];
default:
return ["npm", "install"];
}
}
async function installDependencies(projectPath, packageManager) {
return new Promise((resolve, reject) => {
const [command, ...args] = getInstallCommand(packageManager);
const child = spawn(command, args, {
cwd: projectPath,
stdio: "pipe",
// Capture output
shell: process.platform === "win32"
});
let stdout = "";
let stderr = "";
child.stdout?.on("data", (data) => {
stdout += data.toString();
});
child.stderr?.on("data", (data) => {
stderr += data.toString();
});
child.on("close", (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Package installation failed with code ${code}:
${stderr || stdout}`));
}
});
child.on("error", (error) => {
reject(new Error(`Failed to start package manager: ${error.message}`));
});
});
}
async function isPackageManagerAvailable(packageManager) {
return new Promise((resolve) => {
const child = spawn(packageManager, ["--version"], {
stdio: "pipe",
shell: process.platform === "win32"
});
child.on("close", (code) => {
resolve(code === 0);
});
child.on("error", () => {
resolve(false);
});
});
}
async function validatePackageManager(packageManager) {
const available = await isPackageManagerAvailable(packageManager);
if (available) {
return { available: true, alternatives: [] };
}
const allManagers = ["npm", "yarn", "pnpm"];
const alternatives = [];
for (const manager of allManagers) {
if (manager !== packageManager && await isPackageManagerAvailable(manager)) {
alternatives.push(manager);
}
}
return { available: false, alternatives };
}
function getPackageManagerConfig(packageManager) {
const scripts = {
npm: {
"install": "npm install",
"start": "npm start",
"dev": "npm run dev",
"build": "npm run build"
},
yarn: {
"install": "yarn install",
"start": "yarn start",
"dev": "yarn dev",
"build": "yarn build"
},
pnpm: {
"install": "pnpm install",
"start": "pnpm start",
"dev": "pnpm dev",
"build": "pnpm build"
}
};
const lockFiles = {
npm: "package-lock.json",
yarn: "yarn.lock",
pnpm: "pnpm-lock.yaml"
};
return {
lockFile: lockFiles[packageManager],
nodeModules: "node_modules",
scripts: scripts[packageManager]
};
}
// src/commands/create-mcp.ts
var getClientName = (clientId) => {
const names = {
"google": "Google (Gmail, Drive, Calendar, Sheets)",
"github": "GitHub (Repositories, Issues, PRs)",
"discord": "Discord (Server management, messaging)",
"linear": "Linear (Project management)",
"notion": "Notion (Databases, pages)",
"slack": "Slack (Team communication)",
"turnkey": "Turnkey (Embedded wallets)",
"postgres": "PostgreSQL Database"
};
return names[clientId] || clientId;
};
async function createMcpCommand() {
const config2 = await loadConfig();
if (!config2?.user) {
note2(
`${pc2.red("\u274C No User Found")}
You need to register a user first before creating MCP projects.
${pc2.cyan("Next Steps:")}
\u2022 Run ${pc2.green("register")} to create a user account first`,
"User Registration Required"
);
throw new Error("User registration required. Please run 'register' first.");
}
if (!config2.oauthClient || config2.oauthClient.length === 0) {
note2(
`${pc2.red("\u274C No OAuth Clients Found")}
You need to create an OAuth client before creating MCP projects.
${pc2.cyan("Next Steps:")}
\u2022 Run ${pc2.green("create-client")} to create an OAuth client first`,
"OAuth Client Required"
);
throw new Error("OAuth client required. Please run 'create-client' first.");
}
const hubClient = new HubApiClient();
const isConnected = await hubClient.healthCheck();
if (!isConnected) {
note2(
`${pc2.red("\u274C Connection Failed")}
Cannot connect to Osiris Hub. Please check:
\u2022 Hub server is running
\u2022 OSIRIS_HUB_URL environment variable is correct
\u2022 Network connectivity
Current hub URL: ${config2.hubUrl}`,
"Hub Connection Error"
);
throw new Error("Cannot connect to Osiris Hub");
}
note2(
`${pc2.cyan("Current User:")}
${pc2.dim("Name:")} ${config2.user.name}
${pc2.dim("Email:")} ${config2.user.email}
${pc2.dim("Available OAuth Clients:")} ${config2.oauthClient.length}`,
"MCP Creation Setup"
);
const selectedOAuthClient = await select({
message: "Choose an OAuth client to use:",
options: config2.oauthClient.map((client, index) => ({
value: index.toString(),
label: `${client.name} (${client.clientId})`
}))
});
if (typeof selectedOAuthClient !== "string") {
throw new Error("OAuth client selection required");
}
const oauthClient = config2.oauthClient[parseInt(selectedOAuthClient)];
note2(
`${pc2.green("\u2705 OAuth Client Selected")}
${pc2.dim("Client ID:")} ${oauthClient.clientId}
${pc2.dim("Client Name:")} ${oauthClient.name}`,
"OAuth Configuration"
);
const mcpName = await text2({
message: "What is your MCP name?",
placeholder: "My Awesome MCP",
validate(value) {
if (!value || value.trim().length === 0) {
return "MCP name is required";
}
if (value.trim().length < 2) {
return "MCP name must be at least 2 characters long";
}
if (value.trim().length > 100) {
return "MCP name must be less than 100 characters";
}
if (!/^[a-zA-Z0-9\s\-_]+$/.test(value.trim())) {
return "MCP name can only contain letters, numbers, spaces, hyphens, and underscores";
}
}
});
if (typeof mcpName !== "string") {
throw new Error("MCP name is required");
}
const projectName = mcpName.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z