create-mcp-i-app
Version:
Bootstrap MCP applications with identity features (temporary - use create-mcpi-app after Oct 7)
171 lines (170 loc) • 8.18 kB
JavaScript
import fs from "fs-extra";
import path from "path";
import chalk from "chalk";
/**
* Apply identity preset to an XMCP project
* This enhances the upstream XMCP template with identity features
* If noIdentity is true, leaves the XMCP project unchanged
*/
export async function applyIdentityPreset(options) {
const { projectPath, projectName, transports, noIdentity = false } = options;
if (noIdentity) {
console.log(chalk.blue("📦 Keeping plain XMCP project (--no-identity flag)"));
return;
}
console.log(chalk.blue("🔐 Applying identity preset..."));
const srcDir = path.join(projectPath, "src");
// Check if upstream XMCP structure exists
const middlewarePath = path.join(srcDir, "middleware.ts");
const toolsDir = path.join(srcDir, "tools");
if (await fs.pathExists(middlewarePath)) {
// Enhance existing middleware.ts with identity features
console.log(chalk.gray("📝 Enhancing existing middleware.ts with identity features..."));
let middlewareContent = await fs.readFile(middlewarePath, "utf-8");
// Add identity imports at the top
const identityImports = `import { createMCPIRuntime } from "@kya-os/mcp-i";\n`;
// Add identity middleware before existing middleware
const identityMiddleware = `\n// XMCP-I Identity Middleware\nconst mcpiRuntime = await createMCPIRuntime({\n identity: {\n environment: process.env.NODE_ENV === "production" ? "production" : "development",\n devIdentityPath: ".mcp-i/identity.json",\n },\n wellKnown: {\n environment: process.env.NODE_ENV === "production" ? "production" : "development",\n baseUrl: process.env.BASE_URL || "http://localhost:3000",\n },\n});\n\n// Add identity verification to all requests\nexport const identityMiddleware = mcpiRuntime.middleware;\n\n`;
// Insert identity imports at the beginning
middlewareContent = identityImports + middlewareContent;
// Insert identity middleware before the last export
const lastExportIndex = middlewareContent.lastIndexOf("export");
if (lastExportIndex !== -1) {
middlewareContent =
middlewareContent.slice(0, lastExportIndex) +
identityMiddleware +
middlewareContent.slice(lastExportIndex);
}
else {
middlewareContent += identityMiddleware;
}
await fs.writeFile(middlewarePath, middlewareContent);
}
else {
// Fallback: create our own structure if upstream doesn't exist
console.log(chalk.yellow("⚠️ No upstream middleware.ts found, creating mcpi structure..."));
await fs.ensureDir(srcDir);
await fs.ensureDir(toolsDir);
// Create middleware.ts with identity features
const middlewareContent = `import { createMCPIRuntime } from "@kya-os/mcp-i";\n\n// Create XMCP-I server with identity features\nconst runtime = await createMCPIRuntime({\n identity: {\n environment: process.env.NODE_ENV === "production" ? "production" : "development",\n devIdentityPath: ".mcp-i/identity.json",\n },\n wellKnown: {\n environment: process.env.NODE_ENV === "production" ? "production" : "development",\n baseUrl: process.env.BASE_URL || "http://localhost:3000",\n },\n});\n\n// Export identity middleware\nexport const identityMiddleware = runtime.middleware;\n`;
await fs.writeFile(middlewarePath, middlewareContent);
}
// Enhance existing tools or create identity-aware tools
if (await fs.pathExists(toolsDir)) {
console.log(chalk.gray("🔧 Enhancing existing tools with identity features..."));
// Check for existing tools and enhance them
const toolFiles = await fs.readdir(toolsDir);
const tsFiles = toolFiles.filter((f) => f.endsWith(".ts") && f !== "index.ts");
if (tsFiles.length > 0) {
// Enhance existing tools
for (const toolFile of tsFiles) {
const toolPath = path.join(toolsDir, toolFile);
let toolContent = await fs.readFile(toolPath, "utf-8");
// Add identity comment to existing tools
const identityComment = `// Enhanced with XMCP-I identity features\n`;
toolContent = identityComment + toolContent;
await fs.writeFile(toolPath, toolContent);
}
}
else {
// Create identity-aware hello tool if no tools exist
await createIdentityHelloTool(toolsDir);
}
}
else {
// Create tools directory and identity-aware tools
await fs.ensureDir(toolsDir);
await createIdentityHelloTool(toolsDir);
}
// Update package.json with identity dependencies and scripts
await updatePackageJsonForIdentity(projectPath, projectName, transports);
// Create .mcp-i directory for identity files
const mcpiDir = path.join(projectPath, ".mcp-i");
await fs.ensureDir(mcpiDir);
// Create .gitignore with identity-specific entries
await updateGitignore(projectPath);
console.log(chalk.green("✅ Identity preset applied"));
}
async function createIdentityHelloTool(toolsDir) {
// Create tools/hello.ts with identity features
const helloToolContent = `// Enhanced with XMCP-I identity features
import { z } from "zod";
export const hello = {
name: "hello",
description: "Say hello with identity verification",
inputSchema: z.object({
name: z.string().describe("Name to greet"),
}),
handler: async ({ name }: { name: string }) => {
return {
content: [
{
type: "text" as const,
text: \`Hello, \${name}! This message is cryptographically signed.\`,
},
],
};
},
};
`;
await fs.writeFile(path.join(toolsDir, "hello.ts"), helloToolContent);
// Create or update tools/index.ts
const indexPath = path.join(toolsDir, "index.ts");
let indexContent = "";
if (await fs.pathExists(indexPath)) {
indexContent = await fs.readFile(indexPath, "utf-8");
}
// Add hello export if not already present
if (!indexContent.includes("hello")) {
indexContent += `\nexport * from "./hello.js";`;
await fs.writeFile(indexPath, indexContent);
}
}
async function updatePackageJsonForIdentity(projectPath, projectName, transports) {
const packageJsonPath = path.join(projectPath, "package.json");
const packageJson = await fs.readJson(packageJsonPath);
// Add identity dependencies
// Use workspace dependencies for local testing, published versions for production
const useWorkspace = process.env.MCPI_USE_WORKSPACE === "true";
packageJson.dependencies = {
...packageJson.dependencies,
"@kya-os/mcp-i": useWorkspace ? "workspace:*" : "^1.2.0",
"@kya-os/cli": useWorkspace ? "workspace:*" : "^1.2.2",
};
// Add exactly 8 scripts as required by spec
packageJson.scripts = {
dev: "mcpi dev",
build: "mcpi build",
start: "mcpi start",
init: "mcpi init",
register: "mcpi register",
"keys:rotate": "mcpi keys rotate",
"identity:clean": "mcpi identity clean",
status: "mcpi status",
};
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
}
async function updateGitignore(projectPath) {
const gitignorePath = path.join(projectPath, ".gitignore");
let gitignoreContent = "";
if (await fs.pathExists(gitignorePath)) {
gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
}
// Add identity-specific gitignore entries if not already present
const identityEntries = [
"# MCP-I Identity",
".mcp-i/",
".mcpi/", // Legacy support
".xmcpi/", // Legacy support for old naming
".env.local",
"generated-identity.json",
".mcp-identity.json",
];
for (const entry of identityEntries) {
if (!gitignoreContent.includes(entry)) {
gitignoreContent += `\n${entry}`;
}
}
await fs.writeFile(gitignorePath, gitignoreContent);
}
//# sourceMappingURL=apply-identity-preset.js.map