UNPKG

@osiris-ai/cli

Version:

CLI for Osiris SDK - Create and manage MCP servers with authentication

1,572 lines (1,459 loc) 143 kB
#!/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