From 8cd0a5e39b20cb9b1f4a604243c400e2e0e742d1 Mon Sep 17 00:00:00 2001 From: aline-pereira Date: Thu, 8 Jan 2026 09:37:48 -0300 Subject: [PATCH 01/12] added initial demo seed --- README.md | 108 ++++ apps/mesh/migrations/seeds/demo.ts | 11 + apps/mesh/migrations/seeds/demo/README.md | 205 +++++++ apps/mesh/migrations/seeds/demo/config.ts | 49 ++ .../mesh/migrations/seeds/demo/connections.ts | 113 ++++ apps/mesh/migrations/seeds/demo/factories.ts | 230 +++++++ apps/mesh/migrations/seeds/demo/gateways.ts | 26 + apps/mesh/migrations/seeds/demo/index.ts | 280 +++++++++ .../migrations/seeds/demo/monitoring-logs.ts | 571 ++++++++++++++++++ apps/mesh/migrations/seeds/demo/types.ts | 77 +++ apps/mesh/migrations/seeds/index.ts | 2 + apps/mesh/src/database/migrate.ts | 7 +- 12 files changed, 1678 insertions(+), 1 deletion(-) create mode 100644 apps/mesh/migrations/seeds/demo.ts create mode 100644 apps/mesh/migrations/seeds/demo/README.md create mode 100644 apps/mesh/migrations/seeds/demo/config.ts create mode 100644 apps/mesh/migrations/seeds/demo/connections.ts create mode 100644 apps/mesh/migrations/seeds/demo/factories.ts create mode 100644 apps/mesh/migrations/seeds/demo/gateways.ts create mode 100644 apps/mesh/migrations/seeds/demo/index.ts create mode 100644 apps/mesh/migrations/seeds/demo/monitoring-logs.ts create mode 100644 apps/mesh/migrations/seeds/demo/types.ts diff --git a/README.md b/README.md index fb3d61663b..051122883e 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,114 @@ Or use `npx @decocms/mesh` to instantly get a mesh running. --- +## 🎭 Demo Environment + +Get started instantly with a pre-configured demo account. Perfect for testing, demos, and exploring MCP Mesh features. + +### Quick Start + +```bash +# Clone and install +git clone https://github.com/decocms/mesh.git +cd mesh +bun install + +# Start with demo seed +SEED=demo bun run dev + +# Access at http://localhost:3000 +# Login with: admin@demo.local / demo123 +``` + +The demo seed creates: +- ✅ Pre-configured organization ("Demo Company") +- ✅ 5 users with different RBAC roles (admin, developer, analyst, billing, viewer) +- ✅ 7 real MCP connections from deco-store (validated against registry) +- ✅ 2 gateways: OpenRouter Gateway (dedicated), Default Gateway (passthrough) +- ✅ Sample monitoring data with realistic tool calls + +### Demo Users + +| User | Email | Password | Role | Permissions | +|------|-------|----------|------|-------------| +| Admin | admin@demo.local | demo123 | owner | Full access (read/write) | +| Developer | developer@demo.local | demo123 | member | Can create/edit connections | +| Analyst | analyst@demo.local | demo123 | member | Read-only access to monitoring | +| Billing | billing@demo.local | demo123 | member | Access to cost/billing data | +| Viewer | viewer@demo.local | demo123 | member | Read-only access | + +### Demo Connections + +The demo seed includes 7 real MCP connections with mixed states: + +| MCP | Status | Description | +|-----|--------|-------------| +| **Notion** (official) | needs_auth | Requires OAuth - click "Connect" to authenticate with your Notion account | +| **GitHub** (deco-hosted) | active | Pre-connected with demo token (functional for testing) | +| **openrouter** (deco) | needs_auth | OpenRouter App Connection for LLM uses (GPT, Claude, Gemini, etc) | +| **Nano Banana** (deco) | active | Use Nano Banana Integration to create images using AI | +| **Google Veo 3.1** (deco) | needs_auth | Generate high-quality videos with audio using Google Gemini Veo 3 and Veo 3.1 models | +| **OpenAI Sora 2** (deco) | active | Use OpenAI Sora 2 to generate professional videos using AI | +| **Grain mcp** (official) | active | Grain Official MCP - Meeting recordings, transcriptions, and insights | + +#### How Proxy URLs Work + +When you use MCP connections through the Mesh, you connect to **proxy URLs**: + +``` +http://localhost:3000/{org-slug}/mcps/{connection-id} +``` + +For example: `http://localhost:3000/demo-company/mcps/conn_abc123` + +The Mesh acts as a secure proxy that: +- ✅ Adds authentication (OAuth tokens, API keys) +- ✅ Logs all requests for observability +- ✅ Enforces rate limits and policies +- ✅ Provides unified error handling + +The real MCP URL (e.g., `https://mcp.notion.com/mcp`) is used internally by the Mesh proxy. This architecture allows centralized management of all MCP connections. + +### Persistent Demo Environment + +Add to your `.env.local` to always run with demo data: + +```bash +echo "SEED=demo" >> .env.local +bun run dev +``` + +### Reset Demo Environment + +```bash +# Delete database and restart with fresh seed +rm apps/mesh/data/mesh.db +SEED=demo bun run dev +``` + +### Docker & CI/CD + +```dockerfile +# Dockerfile +ENV SEED=demo +CMD ["bun", "run", "dev"] +``` + +```yaml +# docker-compose.yml +environment: + - SEED=demo +``` + +**Why Demo Seed?** +- **Open Source**: Seed code is in `apps/mesh/migrations/seeds/demo.ts` +- **Consistent**: Same env var = same environment everywhere +- **Fast**: Automatic seeding during migration +- **Reproducible**: Perfect for containers, CI/CD, and ephemeral environments +- **12-Factor**: Configuration via environment variables + +--- + ## Runtime strategies as gateways As tool surfaces grow, “send every tool definition to the model on every call” gets expensive and slow. diff --git a/apps/mesh/migrations/seeds/demo.ts b/apps/mesh/migrations/seeds/demo.ts new file mode 100644 index 0000000000..793309371e --- /dev/null +++ b/apps/mesh/migrations/seeds/demo.ts @@ -0,0 +1,11 @@ +/** + * Demo Seed + * + * Re-export from the refactored demo seed structure. + * All implementation has been moved to the demo/ folder for better organization. + * + * @deprecated Import from './demo/index' directly + */ + +export { seed } from "./demo/index"; +export type { DemoSeedResult } from "./demo/types"; diff --git a/apps/mesh/migrations/seeds/demo/README.md b/apps/mesh/migrations/seeds/demo/README.md new file mode 100644 index 0000000000..e5e170e6c1 --- /dev/null +++ b/apps/mesh/migrations/seeds/demo/README.md @@ -0,0 +1,205 @@ +# Demo Seed + +A comprehensive demo seed for MCP Mesh, creating a complete environment for demonstrations and testing. + +## Structure + +``` +demo/ +├── index.ts # Main seed orchestration (283 lines) +├── config.ts # Configuration and constants (45 lines) +├── connections.ts # MCP connection definitions (142 lines) +├── gateways.ts # Gateway configurations (38 lines) +├── monitoring-logs.ts # Demo monitoring data (597 lines) +├── factories.ts # Factory functions for records (205 lines) +├── types.ts # TypeScript types (59 lines) +└── README.md # This file +``` + +**Total: ~1,369 lines** (down from 1,513 lines in the monolithic version) + +## What it Creates + +### Organization +- **Name**: Demo Company +- **Slug**: demo-company + +### Users (5) +All users have password: `demo123` + +1. **Admin** (`admin@demo.local`) - Owner role +2. **Developer** (`developer@demo.local`) - Member role +3. **Analyst** (`analyst@demo.local`) - Member role (read-only focus) +4. **Billing** (`billing@demo.local`) - Member role +5. **Viewer** (`viewer@demo.local`) - Member role (guest-like) + +### API Keys (2) +- Admin API key +- Developer API key + +### Connections (7) +1. **Notion** - Official MCP, requires OAuth +2. **GitHub** - Deco-hosted, demo token +3. **OpenRouter** - LLM routing, requires API key +4. **Nano Banana** - AI image generation, demo token +5. **Google Veo 3.1** - Video generation, requires API key +6. **OpenAI Sora 2** - Video generation, demo token +7. **Grain** - Meeting transcription, demo token + +### Gateways (3) +1. **OpenRouter Gateway** - Dedicated LLM gateway (passthrough) +2. **All Tools Gateway** - Default gateway with all tools (passthrough) +3. **Smart Gateway** - Intelligent tool selection (code_execution) + +### Monitoring Logs (27) +Rich demo data spanning 7 days with: +- Successful operations +- Authentication errors +- Rate limits +- Various tools and services +- Different users and gateways +- Realistic durations and metadata + +## Usage + +```typescript +import { seed } from "./seeds/demo"; +import type { DemoSeedResult } from "./seeds/demo/types"; + +const result = await seed(db); +console.log("Created org:", result.organizationName); +console.log("Admin email:", result.users.adminEmail); +``` + +Or from the old location (re-exported): + +```typescript +import { seed } from "./seeds/demo.ts"; +``` + +## Adding New Connections + +Edit `connections.ts`: + +```typescript +export const DEMO_CONNECTIONS: Record = { + // ... existing connections + myNewConnection: { + title: "My Service", + description: "Description of the service", + icon: "https://example.com/icon.png", + appName: "my-service", + connectionUrl: "https://api.example.com/mcp", + connectionToken: null, + configurationState: "needs_auth", + metadata: { + provider: "example", + requiresOAuth: true, + }, + }, +}; +``` + +Then add it to a gateway in `gateways.ts`: + +```typescript +allTools: { + // ... + connections: ["notion", "github", /* ... */ "myNewConnection"], +}, +``` + +## Adding New Monitoring Logs + +Edit `monitoring-logs.ts`: + +```typescript +export const DEMO_MONITORING_LOGS: DemoMonitoringLog[] = [ + // ... existing logs + { + connectionKey: "myNewConnection", + toolName: "my_tool", + input: { param: "value" }, + output: { result: "success" }, + isError: false, + durationMs: 123, + offsetMs: -10 * MINUTES, // 10 minutes ago + userRole: "developer", + userAgent: "mesh-demo-client/1.0", + gatewayKey: "allTools", + properties: { cache_hit: "false" }, + }, +]; +``` + +## Customizing Users + +Edit `config.ts`: + +```typescript +export const DEMO_USERS: Record = { + // ... existing users + myNewUser: { + role: "member", + name: "My User Name", + email: `myuser${DEMO_CONFIG.EMAIL_DOMAIN}`, + }, +}; + +// Also add to member roles: +export const DEMO_MEMBER_ROLES: Record = { + // ... existing roles + myNewUser: "member", +}; +``` + +## Factory Functions + +Factories in `factories.ts` ensure consistent record creation: + +- `generateId(prefix)` - Unique ID generation +- `createUserRecord(...)` - User records +- `createAccountRecord(...)` - Credential accounts +- `createMemberRecord(...)` - Organization membership +- `createApiKeyRecord(...)` - API keys +- `createConnectionRecord(...)` - MCP connections +- `createGatewayRecord(...)` - Gateways +- `createGatewayConnectionRecord(...)` - Gateway-connection links +- `createMonitoringLogRecord(...)` - Monitoring logs + +## Benefits of This Structure + +### Before (Monolithic `demo.ts`) +- ❌ 1,513 lines in a single file +- ❌ ~1,100 lines of hardcoded data +- ❌ Difficult to find and update specific data +- ❌ Lots of code duplication +- ❌ Poor maintainability + +### After (Modular `demo/`) +- ✅ ~280 lines in main orchestration file +- ✅ Data separated by concern +- ✅ Easy to find and update +- ✅ Reusable factory functions +- ✅ Excellent maintainability +- ✅ -70% reduction in main file size + +## Testing + +The seed is automatically tested during migrations. To test manually: + +```bash +# Run migrations (includes seed if configured) +bun run migrate + +# Or import and run directly in a test +import { seed } from "./migrations/seeds/demo"; +await seed(db); +``` + +## Related Files + +- `benchmark.ts` - Performance testing seed +- `../index.ts` - Migration index +- `../../src/storage/types.ts` - Database types + diff --git a/apps/mesh/migrations/seeds/demo/config.ts b/apps/mesh/migrations/seeds/demo/config.ts new file mode 100644 index 0000000000..8e84bf870a --- /dev/null +++ b/apps/mesh/migrations/seeds/demo/config.ts @@ -0,0 +1,49 @@ +/** + * Demo seed configuration and constants + */ + +import type { DemoUser } from "./types"; + +export const DEMO_CONFIG = { + PASSWORD: "demo123", + EMAIL_DOMAIN: "@demo.local", + ORG_NAME: "Demo Company", + ORG_SLUG: "demo-company", + USER_AGENT_DEFAULT: "mesh-demo-client/1.0", +} as const; + +export const DEMO_USERS: Record = { + admin: { + role: "admin", + name: "Demo Admin", + email: `admin${DEMO_CONFIG.EMAIL_DOMAIN}`, + }, + developer: { + role: "member", + name: "Demo Developer", + email: `developer${DEMO_CONFIG.EMAIL_DOMAIN}`, + }, + analyst: { + role: "member", + name: "Demo Analyst", + email: `analyst${DEMO_CONFIG.EMAIL_DOMAIN}`, + }, + billing: { + role: "member", + name: "Demo Billing", + email: `billing${DEMO_CONFIG.EMAIL_DOMAIN}`, + }, + viewer: { + role: "member", + name: "Demo Viewer", + email: `viewer${DEMO_CONFIG.EMAIL_DOMAIN}`, + }, +} as const; + +export const DEMO_MEMBER_ROLES: Record = { + admin: "owner", + developer: "member", + analyst: "member", + billing: "member", + viewer: "member", +} as const; diff --git a/apps/mesh/migrations/seeds/demo/connections.ts b/apps/mesh/migrations/seeds/demo/connections.ts new file mode 100644 index 0000000000..dae8e2a395 --- /dev/null +++ b/apps/mesh/migrations/seeds/demo/connections.ts @@ -0,0 +1,113 @@ +/** + * Demo MCP Connections + * + * All connections use real URLs, icons, and descriptions from the deco registry. + */ + +import type { DemoConnection } from "./types"; + +export const DEMO_CONNECTIONS: Record = { + notion: { + title: "Notion", + description: "Manage pages and databases in Notion workspaces", + icon: "https://www.notion.so/images/logo-ios.png", + appName: "Notion", + connectionUrl: "https://mcp.notion.com/mcp", + connectionToken: null, + configurationState: "needs_auth", + metadata: { + provider: "notion", + requiresOAuth: true, + official: true, + }, + }, + github: { + title: "GitHub", + description: "GitHub issues, PRs, and repository management (deco-hosted)", + icon: "https://github.githubassets.com/favicons/favicon.svg", + appName: "GitHub", + connectionUrl: "https://api.decocms.com/apps/deco/github/mcp", + connectionToken: "ghp_demo_fake_token_DO_NOT_USE_IN_PRODUCTION", + configurationState: null, + metadata: { + provider: "github", + requiresOAuth: true, + decoHosted: true, + demoToken: true, + demoNote: + "Demo token for testing. Replace with real OAuth in production.", + }, + }, + openrouter: { + title: "openrouter", + description: "OpenRouter App Connection for LLM uses.", + icon: "https://assets.decocache.com/decocms/b2e2f64f-6025-45f7-9e8c-3b3ebdd073d8/openrouter_logojpg.jpg", + appName: "openrouter", + connectionUrl: "https://sites-openrouter.decocache.com/mcp", + connectionToken: null, + configurationState: "needs_auth", + metadata: { + provider: "deco", + requiresApiKey: true, + decoHosted: true, + }, + }, + nanoBanana: { + title: "Nano Banana", + description: "Use Nano Banana Integration to create images using AI.", + icon: "https://assets.decocache.com/starting/62401ea6-55e6-433d-b614-e43196890e05/nanobanana.png", + appName: "nanobanana", + connectionUrl: "https://api.decocms.com/apps/deco/nanobanana/mcp", + connectionToken: "nano_demo_token_fake", + configurationState: null, + metadata: { + provider: "deco", + decoHosted: true, + demoToken: true, + }, + }, + veo3: { + title: "Google Veo 3.1", + description: + "Generate high-quality videos with audio using Google Gemini Veo 3 and Veo 3.1 models. Supports text-to-video, image-to-video, and video extension.", + icon: "https://www.gstatic.com/lamda/images/gemini_sparkle_v002_d4735304ff6292a690345.svg", + appName: "veo3", + connectionUrl: "https://api.decocms.com/apps/deco/veo3/mcp", + connectionToken: null, + configurationState: "needs_auth", + metadata: { + provider: "deco", + decoHosted: true, + requiresApiKey: true, + }, + }, + sora: { + title: "OpenAI Sora 2", + description: "Use OpenAI Sora 2 to generate professional videos using AI.", + icon: "https://cdn.openai.com/nf2/nf2-lp/misc/dark-mode-icon.png?w=3840&q=100&fm=webp", + appName: "sora", + connectionUrl: "https://api.decocms.com/apps/deco/sora/mcp", + connectionToken: "sora_demo_token_fake", + configurationState: null, + metadata: { + provider: "deco", + decoHosted: true, + demoToken: true, + }, + }, + grain: { + title: "Grain mcp", + description: + "Grain Official MCP - Acesse e gerencie suas reuniões, transcrições e insights do Grain. Este é o MCP oficial da Grain para integração completa com a plataforma de gravação e análise de reuniões.", + icon: "https://assets.decocache.com/mcp/1bfc7176-e7be-487c-83e6-4b9e970a8e10/Grain.svg", + appName: "Grain mcp", + connectionUrl: "https://api.grain.com/_/mcp", + connectionToken: "grain_demo_token_fake", + configurationState: null, + metadata: { + provider: "grain", + official: true, + demoToken: true, + }, + }, +} as const; diff --git a/apps/mesh/migrations/seeds/demo/factories.ts b/apps/mesh/migrations/seeds/demo/factories.ts new file mode 100644 index 0000000000..6670d76e8b --- /dev/null +++ b/apps/mesh/migrations/seeds/demo/factories.ts @@ -0,0 +1,230 @@ +/** + * Factory functions for creating demo seed records + */ + +/** + * Generate a unique ID with prefix + */ +export function generateId(prefix: string): string { + return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; +} + +/** + * Create a user record + */ +export function createUserRecord( + userId: string, + email: string, + name: string, + role: string, + timestamp: string, +) { + return { + id: userId, + email, + emailVerified: 1, + name, + image: null, + role, + banned: null, + banReason: null, + banExpires: null, + createdAt: timestamp, + updatedAt: timestamp, + }; +} + +/** + * Create an account record for credential authentication + */ +export function createAccountRecord( + userId: string, + email: string, + passwordHash: string, + timestamp: string, +) { + return { + id: generateId("account"), + userId, + accountId: email, + providerId: "credential", + password: passwordHash, + accessToken: null, + refreshToken: null, + accessTokenExpiresAt: null, + refreshTokenExpiresAt: null, + scope: null, + idToken: null, + createdAt: timestamp, + updatedAt: timestamp, + }; +} + +/** + * Create a member record linking user to organization + */ +export function createMemberRecord( + organizationId: string, + userId: string, + role: "owner" | "member", + timestamp: string, +) { + return { + id: generateId("member"), + organizationId, + userId, + role, + createdAt: timestamp, + }; +} + +/** + * Create an API key record + */ +export function createApiKeyRecord( + userId: string, + name: string, + key: string, + timestamp: string, +) { + return { + id: generateId("apikey"), + name, + userId, + key, + createdAt: timestamp, + updatedAt: timestamp, + }; +} + +/** + * Create a connection record + */ +export function createConnectionRecord( + connectionId: string, + organizationId: string, + createdBy: string, + title: string, + description: string, + icon: string, + appName: string, + connectionUrl: string, + connectionToken: string | null, + configurationState: "needs_auth" | null, + metadata: Record, + timestamp: string, +) { + return { + id: connectionId, + organization_id: organizationId, + created_by: createdBy, + title, + description, + icon, + app_name: appName, + app_id: null, + connection_type: "HTTP" as const, + connection_url: connectionUrl, + connection_token: connectionToken, + connection_headers: null, + oauth_config: null, + configuration_state: configurationState, + configuration_scopes: null, + metadata: JSON.stringify(metadata), + tools: null, + bindings: null, + status: "active" as const, + created_at: timestamp, + updated_at: timestamp, + }; +} + +/** + * Create a gateway record + */ +export function createGatewayRecord( + gatewayId: string, + organizationId: string, + title: string, + description: string, + toolSelectionStrategy: "passthrough" | "code_execution", + toolSelectionMode: "inclusion" | "exclusion", + icon: string | null, + isDefault: boolean, + createdBy: string, + timestamp: string, +) { + return { + id: gatewayId, + organization_id: organizationId, + title, + description, + tool_selection_strategy: toolSelectionStrategy, + tool_selection_mode: toolSelectionMode, + icon, + status: "active" as const, + is_default: isDefault ? 1 : 0, + created_at: timestamp, + updated_at: timestamp, + created_by: createdBy, + updated_by: null, + }; +} + +/** + * Create a gateway-connection link record + */ +export function createGatewayConnectionRecord( + gatewayId: string, + connectionId: string, + timestamp: string, +) { + return { + id: generateId("gtw_conn"), + gateway_id: gatewayId, + connection_id: connectionId, + selected_tools: null, + selected_resources: null, + selected_prompts: null, + created_at: timestamp, + }; +} + +/** + * Create a monitoring log record + */ +export function createMonitoringLogRecord( + organizationId: string, + connectionId: string, + connectionTitle: string, + toolName: string, + input: unknown, + output: unknown, + isError: boolean, + errorMessage: string | null, + durationMs: number, + timestamp: string, + userId: string, + userAgent: string, + gatewayId: string | null, + properties: Record | null, +) { + return { + id: generateId("log"), + organization_id: organizationId, + connection_id: connectionId, + connection_title: connectionTitle, + tool_name: toolName, + input: JSON.stringify(input), + output: JSON.stringify(output), + is_error: isError ? 1 : 0, + error_message: errorMessage, + duration_ms: durationMs, + timestamp, + user_id: userId, + request_id: generateId("req"), + user_agent: userAgent, + gateway_id: gatewayId, + properties: properties ? JSON.stringify(properties) : null, + }; +} diff --git a/apps/mesh/migrations/seeds/demo/gateways.ts b/apps/mesh/migrations/seeds/demo/gateways.ts new file mode 100644 index 0000000000..e891db1609 --- /dev/null +++ b/apps/mesh/migrations/seeds/demo/gateways.ts @@ -0,0 +1,26 @@ +/** + * Demo Gateways Configuration + */ + +import type { DemoGateway } from "./types"; + +export const DEMO_GATEWAYS: Record = { + openrouter: { + title: "OpenRouter Gateway", + description: "Access hundreds of LLM models from a single API", + toolSelectionStrategy: "passthrough", + toolSelectionMode: "inclusion", + icon: "https://assets.decocache.com/decocms/b2e2f64f-6025-45f7-9e8c-3b3ebdd073d8/openrouter_logojpg.jpg", + isDefault: false, + connections: ["openrouter"], + }, + default: { + title: "Default Gateway", + description: "Auto-created gateway for organization", + toolSelectionStrategy: "passthrough", + toolSelectionMode: "exclusion", + icon: null, + isDefault: true, + connections: ["notion", "github", "nanoBanana", "veo3", "sora", "grain"], + }, +} as const; diff --git a/apps/mesh/migrations/seeds/demo/index.ts b/apps/mesh/migrations/seeds/demo/index.ts new file mode 100644 index 0000000000..6aaa0666d7 --- /dev/null +++ b/apps/mesh/migrations/seeds/demo/index.ts @@ -0,0 +1,280 @@ +/** + * Demo Seed + * + * Creates a complete demo environment with: + * - Fixed demo organization ("Demo Company") + * - 5 users with different RBAC roles (admin, developer, analyst, billing, viewer) + * - API keys for authentication + * - 7 real MCP connections from deco-store (validated against registry) + * - 3 gateways: OpenRouter Gateway (dedicated), All Tools Gateway (default), Smart Gateway (code_execution) + * - Sample monitoring logs with realistic tool calls + * - Organization settings + * + * This seed is open source and ensures consistency across all demo environments. + * All MCPs use real URLs, icons, and descriptions from the deco registry. + * + * Usage: + * await seed(db) + */ + +import type { Kysely } from "kysely"; +import type { Database } from "../../../src/storage/types"; +import { hashPassword } from "better-auth/crypto"; + +import type { DemoSeedResult } from "./types"; +import { DEMO_CONFIG, DEMO_USERS, DEMO_MEMBER_ROLES } from "./config"; +import { DEMO_CONNECTIONS } from "./connections"; +import { DEMO_GATEWAYS } from "./gateways"; +import { DEMO_MONITORING_LOGS } from "./monitoring-logs"; +import { + generateId, + createUserRecord, + createAccountRecord, + createMemberRecord, + createApiKeyRecord, + createConnectionRecord, + createGatewayRecord, + createGatewayConnectionRecord, + createMonitoringLogRecord, +} from "./factories"; + +/** + * Run the demo seed + */ +export async function seed(db: Kysely): Promise { + const now = new Date().toISOString(); + + // Generate IDs + const orgId = generateId("org"); + const userIds: Record = {}; + for (const key of Object.keys(DEMO_USERS)) { + userIds[key] = generateId("user"); + } + + // ============================================================================ + // 1. Create Organization + // ============================================================================ + + await db + .insertInto("organization") + .values({ + id: orgId, + slug: DEMO_CONFIG.ORG_SLUG, + name: DEMO_CONFIG.ORG_NAME, + createdAt: now, + }) + .execute(); + + // ============================================================================ + // 2. Create Users + // ============================================================================ + + const userRecords = Object.entries(DEMO_USERS).map(([key, user]) => + createUserRecord(userIds[key]!, user.email, user.name, user.role, now), + ); + + for (const userRecord of userRecords) { + await db + // @ts-ignore: Better Auth user table + .insertInto("user") + .values(userRecord) + .execute(); + } + + // ============================================================================ + // 3. Create Credential Accounts (for email/password login) + // ============================================================================ + + const passwordHash = await hashPassword(DEMO_CONFIG.PASSWORD); + + const accountRecords = Object.entries(DEMO_USERS).map(([key, user]) => + createAccountRecord(userIds[key]!, user.email, passwordHash, now), + ); + + await db + // @ts-ignore: Better Auth account table + .insertInto("account") + .values(accountRecords) + .execute(); + + // ============================================================================ + // 4. Link Users to Organization (Members) + // ============================================================================ + + const memberRecords = Object.entries(DEMO_MEMBER_ROLES).map(([key, role]) => + createMemberRecord(orgId, userIds[key]!, role, now), + ); + + await db.insertInto("member").values(memberRecords).execute(); + + // ============================================================================ + // 5. Create API Keys + // ============================================================================ + + const adminApiKeyHash = `demo_key_admin_${generateId("key")}`; + const memberApiKeyHash = `demo_key_member_${generateId("key")}`; + + await db + // @ts-ignore: Better Auth apikey table + .insertInto("apikey") + .values([ + createApiKeyRecord( + userIds.admin!, + "Demo Admin API Key", + adminApiKeyHash, + now, + ), + createApiKeyRecord( + userIds.developer!, + "Demo Developer API Key", + memberApiKeyHash, + now, + ), + ]) + .execute(); + + // ============================================================================ + // 6. Create MCP Connections (Real deco MCPs) + // ============================================================================ + + const connectionIds: Record = {}; + for (const key of Object.keys(DEMO_CONNECTIONS)) { + connectionIds[key] = generateId("conn"); + } + + const connectionRecords = Object.entries(DEMO_CONNECTIONS).map( + ([key, conn]) => + createConnectionRecord( + connectionIds[key]!, + orgId, + userIds.admin!, + conn.title, + conn.description, + conn.icon, + conn.appName, + conn.connectionUrl, + conn.connectionToken, + conn.configurationState, + conn.metadata, + now, + ), + ); + + await db.insertInto("connections").values(connectionRecords).execute(); + + // ============================================================================ + // 7. Create Gateways + // ============================================================================ + + const gatewayIds: Record = {}; + for (const key of Object.keys(DEMO_GATEWAYS)) { + gatewayIds[key] = generateId("gtw"); + } + + const gatewayRecords = Object.entries(DEMO_GATEWAYS).map(([key, gateway]) => + createGatewayRecord( + gatewayIds[key]!, + orgId, + gateway.title, + gateway.description, + gateway.toolSelectionStrategy, + gateway.toolSelectionMode, + gateway.icon, + gateway.isDefault, + userIds.admin!, + now, + ), + ); + + await db.insertInto("gateways").values(gatewayRecords).execute(); + + // ============================================================================ + // 8. Link Gateways to Connections + // ============================================================================ + + const gatewayConnectionRecords = Object.entries(DEMO_GATEWAYS).flatMap( + ([gatewayKey, gateway]) => + gateway.connections.map((connKey) => + createGatewayConnectionRecord( + gatewayIds[gatewayKey]!, + connectionIds[connKey]!, + now, + ), + ), + ); + + await db + .insertInto("gateway_connections") + .values(gatewayConnectionRecords) + .execute(); + + // ============================================================================ + // 9. Create Sample Monitoring Logs (Rich Demo Data - 7 days) + // ============================================================================ + + const monitoringLogRecords = DEMO_MONITORING_LOGS.map((log) => { + const timestamp = new Date(Date.now() + log.offsetMs).toISOString(); + const gatewayId = log.gatewayKey ? gatewayIds[log.gatewayKey]! : null; + + return createMonitoringLogRecord( + orgId, + connectionIds[log.connectionKey]!, + DEMO_CONNECTIONS[log.connectionKey]!.title, + log.toolName, + log.input, + log.output, + log.isError, + log.errorMessage ?? null, + log.durationMs, + timestamp, + userIds[log.userRole]!, + log.userAgent, + gatewayId, + log.properties ?? null, + ); + }); + + await db.insertInto("monitoring_logs").values(monitoringLogRecords).execute(); + + // ============================================================================ + // 10. Create Organization Settings + // ============================================================================ + + await db + .insertInto("organization_settings") + .values({ + organizationId: orgId, + sidebar_items: null, + createdAt: now, + updatedAt: now, + }) + .execute(); + + // ============================================================================ + // Return Result + // ============================================================================ + + return { + organizationId: orgId, + organizationName: DEMO_CONFIG.ORG_NAME, + organizationSlug: DEMO_CONFIG.ORG_SLUG, + users: { + adminId: userIds.admin!, + adminEmail: DEMO_USERS.admin!.email, + developerId: userIds.developer!, + developerEmail: DEMO_USERS.developer!.email, + analystId: userIds.analyst!, + analystEmail: DEMO_USERS.analyst!.email, + billingId: userIds.billing!, + billingEmail: DEMO_USERS.billing!.email, + viewerId: userIds.viewer!, + viewerEmail: DEMO_USERS.viewer!.email, + }, + apiKeys: { + admin: adminApiKeyHash, + member: memberApiKeyHash, + }, + connectionIds: Object.values(connectionIds), + gatewayIds: Object.values(gatewayIds), + }; +} diff --git a/apps/mesh/migrations/seeds/demo/monitoring-logs.ts b/apps/mesh/migrations/seeds/demo/monitoring-logs.ts new file mode 100644 index 0000000000..0b4b95e28f --- /dev/null +++ b/apps/mesh/migrations/seeds/demo/monitoring-logs.ts @@ -0,0 +1,571 @@ +/** + * Demo Monitoring Logs + * + * Rich demo data spanning 7 days with realistic tool calls, errors, and usage patterns. + * All logs are generated relative to the current time using offsetMs (in milliseconds). + */ + +import type { DemoMonitoringLog } from "./types"; + +const DAYS = 24 * 60 * 60 * 1000; +const HOURS = 60 * 60 * 1000; +const MINUTES = 60 * 1000; + +export const DEMO_MONITORING_LOGS: DemoMonitoringLog[] = [ + // === 7 days ago: Initial setup === + { + connectionKey: "github", + toolName: "list_repositories", + input: { org: "decocms", per_page: 30 }, + output: { + repositories: ["mesh", "runtime", "bindings", "ui", "cli"], + total_count: 5, + }, + isError: false, + durationMs: 243, + offsetMs: -7 * DAYS, + userRole: "admin", + userAgent: "mesh-demo-client/1.0", + gatewayKey: "allTools", + properties: { + cache_hit: "false", + region: "us-east-1", + }, + }, + + // === 6 days ago: Developer working === + { + connectionKey: "github", + toolName: "create_issue", + input: { + repo: "mesh", + title: "Add demo seed support", + body: "Implement comprehensive demo seed for bank presentations", + labels: ["enhancement", "demo"], + }, + output: { + issue_number: 156, + url: "https://github.com/decocms/mesh/issues/156", + state: "open", + }, + isError: false, + durationMs: 187, + offsetMs: -6 * DAYS, + userRole: "developer", + userAgent: "mesh-demo-client/1.0", + gatewayKey: "allTools", + properties: { cache_hit: "false" }, + }, + + // === 5 days ago: OpenRouter LLM usage === + { + connectionKey: "openrouter", + toolName: "chat_completion", + input: { + model: "anthropic/claude-3.5-sonnet", + messages: [ + { + role: "user", + content: "Explain the benefits of MCP for enterprise banking", + }, + ], + max_tokens: 500, + }, + output: { + response: + "MCP provides enterprise banking with standardized AI integration, secure tool access, audit trails, and centralized policy management...", + tokens: 412, + finish_reason: "stop", + }, + isError: false, + durationMs: 1847, + offsetMs: -5 * DAYS, + userRole: "analyst", + userAgent: "claude-desktop/1.2.0", + gatewayKey: "openrouter", + properties: { + tokens_prompt: "18", + tokens_completion: "412", + cost_usd: "0.0082", + model: "anthropic/claude-3.5-sonnet", + cache_hit: "false", + }, + }, + + // === 5 days ago: Notion auth error === + { + connectionKey: "notion", + toolName: "search_pages", + input: { query: "product roadmap", limit: 10 }, + output: { error: "Authentication required" }, + isError: true, + errorMessage: + "OAuth authentication required. Connect your Notion account to use this tool.", + durationMs: 67, + offsetMs: -5 * DAYS, + userRole: "analyst", + userAgent: "mesh-demo-client/1.0", + gatewayKey: "allTools", + properties: { auth_error: "true" }, + }, + + // === 4 days ago: Nano Banana image generation === + { + connectionKey: "nanoBanana", + toolName: "generate_image", + input: { + prompt: "Modern banking dashboard with AI assistant", + style: "professional", + size: "1024x1024", + }, + output: { + image_url: "https://assets.decocache.com/generated/abc123.png", + seed: 42, + model: "nano-banana-v2", + }, + isError: false, + durationMs: 3421, + offsetMs: -4 * DAYS, + userRole: "developer", + userAgent: "mesh-demo-client/1.0", + gatewayKey: "smart", + properties: { + tokens: "856", + cost_usd: "0.0342", + gpu_time_ms: "3200", + cache_hit: "false", + }, + }, + + // === 4 days ago: Grain meeting summary === + { + connectionKey: "grain", + toolName: "get_meeting_summary", + input: { + meeting_id: "meet_xyz789", + include_transcript: true, + }, + output: { + summary: + "Quarterly review: Discussed MCP integration roadmap, budget allocation for Q2, and demo requirements for Banco Itaú presentation.", + duration_minutes: 45, + participants: 8, + key_points: [ + "MCP demo approval", + "Budget increased by 15%", + "Timeline: 2 weeks", + ], + }, + isError: false, + durationMs: 892, + offsetMs: -4 * DAYS, + userRole: "analyst", + userAgent: "grain-desktop/2.1.0", + gatewayKey: "smart", + properties: { + transcript_length: "12547", + speakers: "8", + cache_hit: "false", + }, + }, + + // === 3 days ago: OpenRouter rate limit === + { + connectionKey: "openrouter", + toolName: "chat_completion", + input: { + model: "openai/gpt-4-turbo", + messages: [{ role: "user", content: "Generate quarterly report" }], + }, + output: { + error: "Rate limit exceeded. Please try again in 60 seconds.", + }, + isError: true, + errorMessage: "Rate limit exceeded (429)", + durationMs: 123, + offsetMs: -3 * DAYS, + userRole: "billing", + userAgent: "mesh-demo-client/1.0", + gatewayKey: "openrouter", + properties: { + rate_limit: "true", + retry_after: "60", + requests_remaining: "0", + }, + }, + + // === 3 days ago: GitHub PR review === + { + connectionKey: "github", + toolName: "create_pull_request_review", + input: { + repo: "mesh", + pr_number: 42, + event: "APPROVE", + body: "LGTM! Demo seed looks great.", + }, + output: { + review_id: 98765, + state: "APPROVED", + submitted_at: new Date(Date.now() - 3 * DAYS).toISOString(), + }, + isError: false, + durationMs: 234, + offsetMs: -3 * DAYS, + userRole: "admin", + userAgent: "github-cli/2.40.0", + gatewayKey: "allTools", + properties: { cache_hit: "false" }, + }, + + // === 2 days ago: Sora video generation === + { + connectionKey: "sora", + toolName: "generate_video", + input: { + prompt: + "Professional banking environment with AI assistants helping customers", + duration_seconds: 10, + resolution: "1080p", + }, + output: { + video_url: "https://cdn.openai.com/sora/video_def456.mp4", + duration: 10, + frames: 240, + model: "sora-2-turbo", + }, + isError: false, + durationMs: 12543, + offsetMs: -2 * DAYS, + userRole: "developer", + userAgent: "mesh-demo-client/1.0", + gatewayKey: "smart", + properties: { + tokens: "2134", + cost_usd: "0.567", + gpu_time_ms: "11800", + cache_hit: "false", + resolution: "1920x1080", + }, + }, + + // === 2 days ago: Veo3 video auth error === + { + connectionKey: "veo3", + toolName: "generate_video", + input: { + prompt: "Banking app demo walkthrough", + duration_seconds: 15, + }, + output: { error: "API key required" }, + isError: true, + errorMessage: "Authentication failed. Please add your Veo3 API key.", + durationMs: 89, + offsetMs: -2 * DAYS, + userRole: "viewer", + userAgent: "mesh-demo-client/1.0", + gatewayKey: "allTools", + properties: { auth_error: "true" }, + }, + + // === 1 day ago: Morning peak - Multiple operations === + { + connectionKey: "openrouter", + toolName: "chat_completion", + input: { + model: "anthropic/claude-3.5-sonnet", + messages: [ + { + role: "user", + content: "Summarize this week's development progress", + }, + ], + }, + output: { + response: + "This week: Completed demo seed, added 7 MCP connections, implemented 3 gateways, and created rich monitoring data.", + tokens: 234, + }, + isError: false, + durationMs: 1234, + offsetMs: -1 * DAYS, + userRole: "admin", + userAgent: "cursor-agent/0.42.0", + gatewayKey: "openrouter", + properties: { + tokens_prompt: "12", + tokens_completion: "234", + cost_usd: "0.0047", + cache_hit: "true", + }, + }, + { + connectionKey: "github", + toolName: "list_pull_requests", + input: { repo: "mesh", state: "open", per_page: 20 }, + output: { + pull_requests: [ + { number: 42, title: "Add demo seed", state: "open" }, + { number: 43, title: "Update docs", state: "open" }, + ], + total_count: 2, + }, + isError: false, + durationMs: 167, + offsetMs: -1 * DAYS, + userRole: "developer", + userAgent: "gh-cli/2.40.0", + gatewayKey: "allTools", + properties: { cache_hit: "true" }, + }, + { + connectionKey: "nanoBanana", + toolName: "upscale_image", + input: { + image_url: "https://assets.decocache.com/generated/abc123.png", + scale: 2, + }, + output: { + image_url: "https://assets.decocache.com/generated/abc123_2x.png", + resolution: "2048x2048", + }, + isError: false, + durationMs: 2156, + offsetMs: -1 * DAYS, + userRole: "developer", + userAgent: "mesh-demo-client/1.0", + gatewayKey: "smart", + properties: { + tokens: "423", + cost_usd: "0.0169", + cache_hit: "false", + }, + }, + + // === 12 hours ago: Grain meeting === + { + connectionKey: "grain", + toolName: "list_recent_meetings", + input: { limit: 10, days: 7 }, + output: { + meetings: [ + { + id: "meet_xyz789", + title: "Quarterly Review", + date: new Date(Date.now() - 4 * DAYS).toISOString(), + duration: 45, + }, + { + id: "meet_abc123", + title: "Demo Planning", + date: new Date(Date.now() - 2 * DAYS).toISOString(), + duration: 30, + }, + ], + total_count: 2, + }, + isError: false, + durationMs: 234, + offsetMs: -12 * HOURS, + userRole: "analyst", + userAgent: "grain-desktop/2.1.0", + gatewayKey: "smart", + properties: { cache_hit: "false" }, + }, + + // === 6 hours ago: Afternoon work === + { + connectionKey: "github", + toolName: "get_issue", + input: { repo: "mesh", issue_number: 156 }, + output: { + number: 156, + title: "Add demo seed support", + state: "closed", + closed_at: new Date(Date.now() - 3 * DAYS).toISOString(), + comments: 12, + }, + isError: false, + durationMs: 98, + offsetMs: -6 * HOURS, + userRole: "viewer", + userAgent: "mesh-demo-client/1.0", + gatewayKey: "allTools", + properties: { cache_hit: "true" }, + }, + { + connectionKey: "openrouter", + toolName: "list_models", + input: { provider: "anthropic" }, + output: { + models: [ + "anthropic/claude-3.5-sonnet", + "anthropic/claude-3-opus", + "anthropic/claude-3-haiku", + ], + total_count: 3, + }, + isError: false, + durationMs: 134, + offsetMs: -6 * HOURS, + userRole: "billing", + userAgent: "mesh-demo-client/1.0", + gatewayKey: "openrouter", + properties: { cache_hit: "true" }, + }, + + // === 2 hours ago: Recent activity === + { + connectionKey: "sora", + toolName: "check_generation_status", + input: { job_id: "job_def456" }, + output: { + status: "completed", + video_url: "https://cdn.openai.com/sora/video_def456.mp4", + progress: 100, + }, + isError: false, + durationMs: 67, + offsetMs: -2 * HOURS, + userRole: "developer", + userAgent: "mesh-demo-client/1.0", + gatewayKey: "smart", + properties: { cache_hit: "false" }, + }, + + // === 1 hour ago === + { + connectionKey: "github", + toolName: "merge_pull_request", + input: { + repo: "mesh", + pr_number: 42, + merge_method: "squash", + }, + output: { + merged: true, + sha: "a1b2c3d4e5f6", + message: "Merged PR #42: Add demo seed", + }, + isError: false, + durationMs: 432, + offsetMs: -1 * HOURS, + userRole: "admin", + userAgent: "gh-cli/2.40.0", + gatewayKey: "allTools", + properties: { cache_hit: "false" }, + }, + + // === 30 minutes ago === + { + connectionKey: "notion", + toolName: "create_page", + input: { + parent_id: "database-789", + title: "Banco Itaú Demo Notes", + content: "Preparation checklist for demo presentation", + }, + output: { error: "Authentication required" }, + isError: true, + errorMessage: + "OAuth authentication required. Connect your Notion account to use this tool.", + durationMs: 78, + offsetMs: -30 * MINUTES, + userRole: "analyst", + userAgent: "notion-desktop/3.5.0", + gatewayKey: "allTools", + properties: { auth_error: "true" }, + }, + + // === 10 minutes ago: High frequency usage === + { + connectionKey: "openrouter", + toolName: "chat_completion", + input: { + model: "openai/gpt-4o", + messages: [ + { + role: "user", + content: "Review this demo seed implementation for best practices", + }, + ], + max_tokens: 1000, + }, + output: { + response: + "The demo seed implementation looks excellent. It covers all key aspects: diverse users, comprehensive MCPs, realistic monitoring data, and proper error handling...", + tokens: 876, + }, + isError: false, + durationMs: 2341, + offsetMs: -10 * MINUTES, + userRole: "developer", + userAgent: "cursor-agent/0.42.0", + gatewayKey: "openrouter", + properties: { + tokens_prompt: "15", + tokens_completion: "876", + cost_usd: "0.0175", + model: "openai/gpt-4o", + cache_hit: "false", + }, + }, + + // === 5 minutes ago === + { + connectionKey: "nanoBanana", + toolName: "generate_variations", + input: { + base_image_url: "https://assets.decocache.com/generated/abc123.png", + variations: 3, + }, + output: { + variations: [ + "https://assets.decocache.com/generated/abc123_v1.png", + "https://assets.decocache.com/generated/abc123_v2.png", + "https://assets.decocache.com/generated/abc123_v3.png", + ], + }, + isError: false, + durationMs: 4567, + offsetMs: -5 * MINUTES, + userRole: "developer", + userAgent: "mesh-demo-client/1.0", + gatewayKey: "smart", + properties: { + tokens: "1245", + cost_usd: "0.0498", + cache_hit: "false", + }, + }, + + // === Just now: Real-time activity === + { + connectionKey: "grain", + toolName: "search_transcripts", + input: { + query: "demo presentation budget", + limit: 5, + }, + output: { + results: [ + { + meeting_id: "meet_xyz789", + snippet: "...budget allocation for Q2 demo increased by 15%...", + timestamp: "00:23:45", + }, + ], + total_count: 1, + }, + isError: false, + durationMs: 567, + offsetMs: 0, + userRole: "billing", + userAgent: "grain-desktop/2.1.0", + gatewayKey: "smart", + properties: { + search_index_size: "547823", + cache_hit: "false", + }, + }, +]; diff --git a/apps/mesh/migrations/seeds/demo/types.ts b/apps/mesh/migrations/seeds/demo/types.ts new file mode 100644 index 0000000000..cbdbb6d43c --- /dev/null +++ b/apps/mesh/migrations/seeds/demo/types.ts @@ -0,0 +1,77 @@ +/** + * Shared types for demo seed + */ + +export interface DemoSeedResult { + organizationId: string; + organizationName: string; + organizationSlug: string; + users: { + adminId: string; + adminEmail: string; + developerId: string; + developerEmail: string; + analystId: string; + analystEmail: string; + billingId: string; + billingEmail: string; + viewerId: string; + viewerEmail: string; + }; + apiKeys: { + admin: string; + member: string; + }; + connectionIds: string[]; + gatewayIds: string[]; +} + +export interface DemoUser { + role: "admin" | "member"; + name: string; + email: string; +} + +export interface DemoConnection { + title: string; + description: string; + icon: string; + appName: string; + connectionUrl: string; + connectionToken: string | null; + configurationState: "needs_auth" | null; + metadata: { + provider: string; + requiresOAuth?: boolean; + requiresApiKey?: boolean; + official?: boolean; + decoHosted?: boolean; + demoToken?: boolean; + demoNote?: string; + }; +} + +export interface DemoGateway { + title: string; + description: string; + toolSelectionStrategy: "passthrough" | "code_execution"; + toolSelectionMode: "inclusion" | "exclusion"; + icon: string | null; + isDefault: boolean; + connections: string[]; +} + +export interface DemoMonitoringLog { + connectionKey: string; + toolName: string; + input: Record; + output: Record; + isError: boolean; + errorMessage?: string; + durationMs: number; + offsetMs: number; // Time offset from now in milliseconds + userRole: "admin" | "developer" | "analyst" | "billing" | "viewer"; + userAgent: string; + gatewayKey: string | null; + properties?: Record; +} diff --git a/apps/mesh/migrations/seeds/index.ts b/apps/mesh/migrations/seeds/index.ts index 9d25c6b0b1..37a3d64de4 100644 --- a/apps/mesh/migrations/seeds/index.ts +++ b/apps/mesh/migrations/seeds/index.ts @@ -9,6 +9,7 @@ import type { Kysely } from "kysely"; import type { Database } from "../../src/storage/types"; export type { BenchmarkSeedResult } from "./benchmark"; +export type { DemoSeedResult } from "./demo"; /** * Seed function signature @@ -20,6 +21,7 @@ export type SeedFunction = (db: Kysely) => Promise; */ const seeds = { benchmark: () => import("./benchmark").then((m) => m.seed), + demo: () => import("./demo").then((m) => m.seed), } as const; export type SeedName = keyof typeof seeds; diff --git a/apps/mesh/src/database/migrate.ts b/apps/mesh/src/database/migrate.ts index 434de34adf..7b04fae11a 100644 --- a/apps/mesh/src/database/migrate.ts +++ b/apps/mesh/src/database/migrate.ts @@ -88,9 +88,12 @@ export async function migrateToLatest( keepOpen = false, database: customDb, skipBetterAuth = false, - seed, + seed: seedOption, } = options ?? {}; + // Check for seed from environment variable + const seed = seedOption || (process.env.SEED as SeedName | undefined); + // Run Better Auth migrations (unless skipped or using custom db) if (!skipBetterAuth && !customDb) { await migrateBetterAuth(); @@ -119,6 +122,8 @@ export async function migrateToLatest( // Run seed if specified let seedResult: T | undefined; if (seed) { + const source = seedOption ? "option" : "SEED environment variable"; + console.log(`🌱 Running seed "${seed}" (from ${source})...`); seedResult = await runSeed(database.db, seed); } From c2d1e905a3dd522cab9cf3f6dedfa5c0edfdbc74 Mon Sep 17 00:00:00 2001 From: aline-pereira Date: Thu, 8 Jan 2026 09:41:41 -0300 Subject: [PATCH 02/12] readme --- README.md | 108 ------------------------------------------------------ 1 file changed, 108 deletions(-) diff --git a/README.md b/README.md index 051122883e..fb3d61663b 100644 --- a/README.md +++ b/README.md @@ -65,114 +65,6 @@ Or use `npx @decocms/mesh` to instantly get a mesh running. --- -## 🎭 Demo Environment - -Get started instantly with a pre-configured demo account. Perfect for testing, demos, and exploring MCP Mesh features. - -### Quick Start - -```bash -# Clone and install -git clone https://github.com/decocms/mesh.git -cd mesh -bun install - -# Start with demo seed -SEED=demo bun run dev - -# Access at http://localhost:3000 -# Login with: admin@demo.local / demo123 -``` - -The demo seed creates: -- ✅ Pre-configured organization ("Demo Company") -- ✅ 5 users with different RBAC roles (admin, developer, analyst, billing, viewer) -- ✅ 7 real MCP connections from deco-store (validated against registry) -- ✅ 2 gateways: OpenRouter Gateway (dedicated), Default Gateway (passthrough) -- ✅ Sample monitoring data with realistic tool calls - -### Demo Users - -| User | Email | Password | Role | Permissions | -|------|-------|----------|------|-------------| -| Admin | admin@demo.local | demo123 | owner | Full access (read/write) | -| Developer | developer@demo.local | demo123 | member | Can create/edit connections | -| Analyst | analyst@demo.local | demo123 | member | Read-only access to monitoring | -| Billing | billing@demo.local | demo123 | member | Access to cost/billing data | -| Viewer | viewer@demo.local | demo123 | member | Read-only access | - -### Demo Connections - -The demo seed includes 7 real MCP connections with mixed states: - -| MCP | Status | Description | -|-----|--------|-------------| -| **Notion** (official) | needs_auth | Requires OAuth - click "Connect" to authenticate with your Notion account | -| **GitHub** (deco-hosted) | active | Pre-connected with demo token (functional for testing) | -| **openrouter** (deco) | needs_auth | OpenRouter App Connection for LLM uses (GPT, Claude, Gemini, etc) | -| **Nano Banana** (deco) | active | Use Nano Banana Integration to create images using AI | -| **Google Veo 3.1** (deco) | needs_auth | Generate high-quality videos with audio using Google Gemini Veo 3 and Veo 3.1 models | -| **OpenAI Sora 2** (deco) | active | Use OpenAI Sora 2 to generate professional videos using AI | -| **Grain mcp** (official) | active | Grain Official MCP - Meeting recordings, transcriptions, and insights | - -#### How Proxy URLs Work - -When you use MCP connections through the Mesh, you connect to **proxy URLs**: - -``` -http://localhost:3000/{org-slug}/mcps/{connection-id} -``` - -For example: `http://localhost:3000/demo-company/mcps/conn_abc123` - -The Mesh acts as a secure proxy that: -- ✅ Adds authentication (OAuth tokens, API keys) -- ✅ Logs all requests for observability -- ✅ Enforces rate limits and policies -- ✅ Provides unified error handling - -The real MCP URL (e.g., `https://mcp.notion.com/mcp`) is used internally by the Mesh proxy. This architecture allows centralized management of all MCP connections. - -### Persistent Demo Environment - -Add to your `.env.local` to always run with demo data: - -```bash -echo "SEED=demo" >> .env.local -bun run dev -``` - -### Reset Demo Environment - -```bash -# Delete database and restart with fresh seed -rm apps/mesh/data/mesh.db -SEED=demo bun run dev -``` - -### Docker & CI/CD - -```dockerfile -# Dockerfile -ENV SEED=demo -CMD ["bun", "run", "dev"] -``` - -```yaml -# docker-compose.yml -environment: - - SEED=demo -``` - -**Why Demo Seed?** -- **Open Source**: Seed code is in `apps/mesh/migrations/seeds/demo.ts` -- **Consistent**: Same env var = same environment everywhere -- **Fast**: Automatic seeding during migration -- **Reproducible**: Perfect for containers, CI/CD, and ephemeral environments -- **12-Factor**: Configuration via environment variables - ---- - ## Runtime strategies as gateways As tool surfaces grow, “send every tool definition to the model on every call” gets expensive and slow. From 78aa820c5a250f8a80f2278fe78f434ec28a9f3e Mon Sep 17 00:00:00 2001 From: aline-pereira Date: Thu, 8 Jan 2026 10:01:14 -0300 Subject: [PATCH 03/12] capitalization style --- .../mesh/migrations/seeds/demo/connections.ts | 26 +++++++------------ apps/mesh/migrations/seeds/demo/gateways.ts | 2 +- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/apps/mesh/migrations/seeds/demo/connections.ts b/apps/mesh/migrations/seeds/demo/connections.ts index dae8e2a395..7dad48a00f 100644 --- a/apps/mesh/migrations/seeds/demo/connections.ts +++ b/apps/mesh/migrations/seeds/demo/connections.ts @@ -27,22 +27,19 @@ export const DEMO_CONNECTIONS: Record = { icon: "https://github.githubassets.com/favicons/favicon.svg", appName: "GitHub", connectionUrl: "https://api.decocms.com/apps/deco/github/mcp", - connectionToken: "ghp_demo_fake_token_DO_NOT_USE_IN_PRODUCTION", - configurationState: null, + connectionToken: null, + configurationState: "needs_auth", metadata: { provider: "github", requiresOAuth: true, decoHosted: true, - demoToken: true, - demoNote: - "Demo token for testing. Replace with real OAuth in production.", }, }, openrouter: { - title: "openrouter", + title: "OpenRouter", description: "OpenRouter App Connection for LLM uses.", icon: "https://assets.decocache.com/decocms/b2e2f64f-6025-45f7-9e8c-3b3ebdd073d8/openrouter_logojpg.jpg", - appName: "openrouter", + appName: "OpenRouter", connectionUrl: "https://sites-openrouter.decocache.com/mcp", connectionToken: null, configurationState: "needs_auth", @@ -58,12 +55,11 @@ export const DEMO_CONNECTIONS: Record = { icon: "https://assets.decocache.com/starting/62401ea6-55e6-433d-b614-e43196890e05/nanobanana.png", appName: "nanobanana", connectionUrl: "https://api.decocms.com/apps/deco/nanobanana/mcp", - connectionToken: "nano_demo_token_fake", - configurationState: null, + connectionToken: null, + configurationState: "needs_auth", metadata: { provider: "deco", decoHosted: true, - demoToken: true, }, }, veo3: { @@ -87,12 +83,11 @@ export const DEMO_CONNECTIONS: Record = { icon: "https://cdn.openai.com/nf2/nf2-lp/misc/dark-mode-icon.png?w=3840&q=100&fm=webp", appName: "sora", connectionUrl: "https://api.decocms.com/apps/deco/sora/mcp", - connectionToken: "sora_demo_token_fake", - configurationState: null, + connectionToken: null, + configurationState: "needs_auth", metadata: { provider: "deco", decoHosted: true, - demoToken: true, }, }, grain: { @@ -102,12 +97,11 @@ export const DEMO_CONNECTIONS: Record = { icon: "https://assets.decocache.com/mcp/1bfc7176-e7be-487c-83e6-4b9e970a8e10/Grain.svg", appName: "Grain mcp", connectionUrl: "https://api.grain.com/_/mcp", - connectionToken: "grain_demo_token_fake", - configurationState: null, + connectionToken: null, + configurationState: "needs_auth", metadata: { provider: "grain", official: true, - demoToken: true, }, }, } as const; diff --git a/apps/mesh/migrations/seeds/demo/gateways.ts b/apps/mesh/migrations/seeds/demo/gateways.ts index e891db1609..a11bb9645e 100644 --- a/apps/mesh/migrations/seeds/demo/gateways.ts +++ b/apps/mesh/migrations/seeds/demo/gateways.ts @@ -18,7 +18,7 @@ export const DEMO_GATEWAYS: Record = { title: "Default Gateway", description: "Auto-created gateway for organization", toolSelectionStrategy: "passthrough", - toolSelectionMode: "exclusion", + toolSelectionMode: "inclusion", icon: null, isDefault: true, connections: ["notion", "github", "nanoBanana", "veo3", "sora", "grain"], From e273a4b4934975129433837f7f70a717c6ef2de7 Mon Sep 17 00:00:00 2001 From: aline-pereira Date: Thu, 8 Jan 2026 21:43:17 -0300 Subject: [PATCH 04/12] added more org and monitoring logs --- apps/mesh/migrations/seeds/demo.ts | 2 +- apps/mesh/migrations/seeds/demo/README.md | 205 ----- apps/mesh/migrations/seeds/demo/catalog.ts | 564 ++++++++++++ apps/mesh/migrations/seeds/demo/config.ts | 49 -- .../mesh/migrations/seeds/demo/connections.ts | 107 --- apps/mesh/migrations/seeds/demo/factories.ts | 230 ----- apps/mesh/migrations/seeds/demo/gateways.ts | 26 - apps/mesh/migrations/seeds/demo/index.ts | 330 ++----- .../migrations/seeds/demo/monitoring-logs.ts | 571 ------------ .../migrations/seeds/demo/orgs/deco-bank.ts | 823 ++++++++++++++++++ .../migrations/seeds/demo/orgs/onboarding.ts | 373 ++++++++ apps/mesh/migrations/seeds/demo/seeder.ts | 540 ++++++++++++ apps/mesh/migrations/seeds/demo/types.ts | 77 -- apps/mesh/migrations/seeds/index.ts | 2 +- 14 files changed, 2377 insertions(+), 1522 deletions(-) delete mode 100644 apps/mesh/migrations/seeds/demo/README.md create mode 100644 apps/mesh/migrations/seeds/demo/catalog.ts delete mode 100644 apps/mesh/migrations/seeds/demo/config.ts delete mode 100644 apps/mesh/migrations/seeds/demo/connections.ts delete mode 100644 apps/mesh/migrations/seeds/demo/factories.ts delete mode 100644 apps/mesh/migrations/seeds/demo/gateways.ts delete mode 100644 apps/mesh/migrations/seeds/demo/monitoring-logs.ts create mode 100644 apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts create mode 100644 apps/mesh/migrations/seeds/demo/orgs/onboarding.ts create mode 100644 apps/mesh/migrations/seeds/demo/seeder.ts delete mode 100644 apps/mesh/migrations/seeds/demo/types.ts diff --git a/apps/mesh/migrations/seeds/demo.ts b/apps/mesh/migrations/seeds/demo.ts index 793309371e..344bef7a89 100644 --- a/apps/mesh/migrations/seeds/demo.ts +++ b/apps/mesh/migrations/seeds/demo.ts @@ -8,4 +8,4 @@ */ export { seed } from "./demo/index"; -export type { DemoSeedResult } from "./demo/types"; +export type { DemoSeedResult } from "./demo/index"; diff --git a/apps/mesh/migrations/seeds/demo/README.md b/apps/mesh/migrations/seeds/demo/README.md deleted file mode 100644 index e5e170e6c1..0000000000 --- a/apps/mesh/migrations/seeds/demo/README.md +++ /dev/null @@ -1,205 +0,0 @@ -# Demo Seed - -A comprehensive demo seed for MCP Mesh, creating a complete environment for demonstrations and testing. - -## Structure - -``` -demo/ -├── index.ts # Main seed orchestration (283 lines) -├── config.ts # Configuration and constants (45 lines) -├── connections.ts # MCP connection definitions (142 lines) -├── gateways.ts # Gateway configurations (38 lines) -├── monitoring-logs.ts # Demo monitoring data (597 lines) -├── factories.ts # Factory functions for records (205 lines) -├── types.ts # TypeScript types (59 lines) -└── README.md # This file -``` - -**Total: ~1,369 lines** (down from 1,513 lines in the monolithic version) - -## What it Creates - -### Organization -- **Name**: Demo Company -- **Slug**: demo-company - -### Users (5) -All users have password: `demo123` - -1. **Admin** (`admin@demo.local`) - Owner role -2. **Developer** (`developer@demo.local`) - Member role -3. **Analyst** (`analyst@demo.local`) - Member role (read-only focus) -4. **Billing** (`billing@demo.local`) - Member role -5. **Viewer** (`viewer@demo.local`) - Member role (guest-like) - -### API Keys (2) -- Admin API key -- Developer API key - -### Connections (7) -1. **Notion** - Official MCP, requires OAuth -2. **GitHub** - Deco-hosted, demo token -3. **OpenRouter** - LLM routing, requires API key -4. **Nano Banana** - AI image generation, demo token -5. **Google Veo 3.1** - Video generation, requires API key -6. **OpenAI Sora 2** - Video generation, demo token -7. **Grain** - Meeting transcription, demo token - -### Gateways (3) -1. **OpenRouter Gateway** - Dedicated LLM gateway (passthrough) -2. **All Tools Gateway** - Default gateway with all tools (passthrough) -3. **Smart Gateway** - Intelligent tool selection (code_execution) - -### Monitoring Logs (27) -Rich demo data spanning 7 days with: -- Successful operations -- Authentication errors -- Rate limits -- Various tools and services -- Different users and gateways -- Realistic durations and metadata - -## Usage - -```typescript -import { seed } from "./seeds/demo"; -import type { DemoSeedResult } from "./seeds/demo/types"; - -const result = await seed(db); -console.log("Created org:", result.organizationName); -console.log("Admin email:", result.users.adminEmail); -``` - -Or from the old location (re-exported): - -```typescript -import { seed } from "./seeds/demo.ts"; -``` - -## Adding New Connections - -Edit `connections.ts`: - -```typescript -export const DEMO_CONNECTIONS: Record = { - // ... existing connections - myNewConnection: { - title: "My Service", - description: "Description of the service", - icon: "https://example.com/icon.png", - appName: "my-service", - connectionUrl: "https://api.example.com/mcp", - connectionToken: null, - configurationState: "needs_auth", - metadata: { - provider: "example", - requiresOAuth: true, - }, - }, -}; -``` - -Then add it to a gateway in `gateways.ts`: - -```typescript -allTools: { - // ... - connections: ["notion", "github", /* ... */ "myNewConnection"], -}, -``` - -## Adding New Monitoring Logs - -Edit `monitoring-logs.ts`: - -```typescript -export const DEMO_MONITORING_LOGS: DemoMonitoringLog[] = [ - // ... existing logs - { - connectionKey: "myNewConnection", - toolName: "my_tool", - input: { param: "value" }, - output: { result: "success" }, - isError: false, - durationMs: 123, - offsetMs: -10 * MINUTES, // 10 minutes ago - userRole: "developer", - userAgent: "mesh-demo-client/1.0", - gatewayKey: "allTools", - properties: { cache_hit: "false" }, - }, -]; -``` - -## Customizing Users - -Edit `config.ts`: - -```typescript -export const DEMO_USERS: Record = { - // ... existing users - myNewUser: { - role: "member", - name: "My User Name", - email: `myuser${DEMO_CONFIG.EMAIL_DOMAIN}`, - }, -}; - -// Also add to member roles: -export const DEMO_MEMBER_ROLES: Record = { - // ... existing roles - myNewUser: "member", -}; -``` - -## Factory Functions - -Factories in `factories.ts` ensure consistent record creation: - -- `generateId(prefix)` - Unique ID generation -- `createUserRecord(...)` - User records -- `createAccountRecord(...)` - Credential accounts -- `createMemberRecord(...)` - Organization membership -- `createApiKeyRecord(...)` - API keys -- `createConnectionRecord(...)` - MCP connections -- `createGatewayRecord(...)` - Gateways -- `createGatewayConnectionRecord(...)` - Gateway-connection links -- `createMonitoringLogRecord(...)` - Monitoring logs - -## Benefits of This Structure - -### Before (Monolithic `demo.ts`) -- ❌ 1,513 lines in a single file -- ❌ ~1,100 lines of hardcoded data -- ❌ Difficult to find and update specific data -- ❌ Lots of code duplication -- ❌ Poor maintainability - -### After (Modular `demo/`) -- ✅ ~280 lines in main orchestration file -- ✅ Data separated by concern -- ✅ Easy to find and update -- ✅ Reusable factory functions -- ✅ Excellent maintainability -- ✅ -70% reduction in main file size - -## Testing - -The seed is automatically tested during migrations. To test manually: - -```bash -# Run migrations (includes seed if configured) -bun run migrate - -# Or import and run directly in a test -import { seed } from "./migrations/seeds/demo"; -await seed(db); -``` - -## Related Files - -- `benchmark.ts` - Performance testing seed -- `../index.ts` - Migration index -- `../../src/storage/types.ts` - Database types - diff --git a/apps/mesh/migrations/seeds/demo/catalog.ts b/apps/mesh/migrations/seeds/demo/catalog.ts new file mode 100644 index 0000000000..d0b84b6e8a --- /dev/null +++ b/apps/mesh/migrations/seeds/demo/catalog.ts @@ -0,0 +1,564 @@ +/** + * Demo Catalog + * + * Consolidated catalog of all available: + * - MCP connections (verified and ready to use) + * - Gateway templates + * + * Data sourced from Deco Store API (https://api.decocms.com/mcp/registry) + */ + +import type { Connection, Gateway } from "./seeder"; + +// ============================================================================= +// Well-Known Connections (installed by default in production) +// ============================================================================= + +export const WELL_KNOWN_CONNECTIONS = { + meshMcp: { + title: "Mesh MCP", + description: "The MCP for the mesh API", + icon: "https://assets.decocache.com/mcp/09e44283-f47d-4046-955f-816d227c626f/app.png", + appName: "@deco/management-mcp", + connectionUrl: "https://mesh-admin.decocms.com/mcp", + connectionToken: null, + metadata: { provider: "deco", decoHosted: true }, + }, + + mcpRegistry: { + title: "MCP Registry", + description: "Community MCP registry with thousands of handy MCPs", + icon: "https://assets.decocache.com/decocms/cd7ca472-0f72-463a-b0de-6e44bdd0f9b4/mcp.png", + appName: "mcp-registry", + connectionUrl: "https://sites-registry.decocache.com/mcp", + connectionToken: null, + metadata: { provider: "deco", decoHosted: true }, + }, + + decoStore: { + title: "Deco Store", + description: "Official deco MCP registry with curated integrations", + icon: "https://assets.decocache.com/decocms/00ccf6c3-9e13-4517-83b0-75ab84554bb9/596364c63320075ca58483660156b6d9de9b526e.png", + appName: "deco-registry", + connectionUrl: "https://api.decocms.com/mcp/registry", + connectionToken: null, + metadata: { provider: "deco", decoHosted: true }, + }, +} as const satisfies Record; + +export type WellKnownConnectionKey = keyof typeof WELL_KNOWN_CONNECTIONS; + +// ============================================================================= +// Connection Catalog (data from Deco Store) +// ============================================================================= + +export const CONNECTIONS = { + // Development & Infrastructure + github: { + title: "GitHub", + description: + "GitHub MCP Server Official - Interact with GitHub platform through natural language. Manage repositories, issues, pull requests, analyze code, and automate workflows. This is the official MCP server from GitHub.", + icon: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png", + appName: "github/github-mcp-server", + connectionUrl: "https://api.githubcopilot.com/mcp/", + connectionToken: null, + metadata: { provider: "github", requiresOAuth: true, official: true }, + }, + + vercel: { + title: "Vercel", + description: "Frontend deployment and preview environments", + icon: "https://vercel.com/favicon.ico", + appName: "Vercel", + connectionUrl: "https://mcp.vercel.com", + connectionToken: null, + metadata: { provider: "vercel", requiresOAuth: true, official: true }, + }, + + supabase: { + title: "Supabase", + description: "Backend-as-a-service with real-time capabilities", + icon: "https://supabase.com/favicon.ico", + appName: "Supabase", + connectionUrl: "https://mcp.supabase.com/mcp", + connectionToken: null, + metadata: { provider: "supabase", requiresOAuth: true, official: true }, + }, + + prisma: { + title: "Prisma", + description: "ORM and database toolkit for schema management", + icon: "https://prismalens.vercel.app/header/logo-dark.svg", + appName: "Prisma", + connectionUrl: "https://mcp.prisma.io/sse", + connectionToken: null, + metadata: { provider: "prisma", requiresOAuth: true, official: true }, + }, + + cloudflare: { + title: "Cloudflare", + description: "Manage DNS, Workers, R2, and edge infrastructure", + icon: "https://www.cloudflare.com/favicon.ico", + appName: "Cloudflare", + connectionUrl: "https://mcp.cloudflare.com/sse", + connectionToken: null, + metadata: { provider: "cloudflare", requiresApiKey: true, official: true }, + }, + + aws: { + title: "AWS", + description: "Amazon Web Services cloud infrastructure management", + icon: "https://assets.decocache.com/mcp/ece686cd-c380-41e8-97c8-34616a3bf5ba/AWS.svg", + appName: "deco/aws", + connectionUrl: "https://api.decocms.com/apps/deco/aws/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresApiKey: true, decoHosted: true }, + }, + + // AI & LLM + openrouter: { + title: "OpenRouter", + description: + "Access 100+ LLM models from a single API with unified pricing", + icon: "https://assets.decocache.com/decocms/b2e2f64f-6025-45f7-9e8c-3b3ebdd073d8/openrouter_logojpg.jpg", + appName: "deco/openrouter", + connectionUrl: "https://sites-openrouter.decocache.com/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresApiKey: true, decoHosted: true }, + }, + + perplexity: { + title: "Perplexity", + description: "AI-powered search and research assistant", + icon: "https://assets.decocache.com/mcp/1b3b7880-e7a5-413b-8db2-601e84b22bcd/Perplexity.svg", + appName: "deco/mcp-perplexity", + connectionUrl: "https://api.decocms.com/apps/deco/mcp-perplexity/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresApiKey: true, decoHosted: true }, + }, + + elevenlabs: { + title: "ElevenLabs", + description: "AI voice generation and text-to-speech", + icon: "https://assets.decocache.com/mcp/d5b8b14e-7611-4cdd-8453-cad6a4c23703/ElevenLabs.svg", + appName: "deco/elevenlabs", + connectionUrl: "https://api.decocms.com/apps/deco/elevenlabs/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresApiKey: true, decoHosted: true }, + }, + + // Google Workspace + gmail: { + title: "Gmail", + description: "Send, read, and manage emails in Gmail", + icon: "https://assets.decocache.com/mcp/b4dbd04f-2d03-4e29-a881-f924f5946c4e/Gmail.svg", + appName: "deco/google-gmail", + connectionUrl: "https://api.decocms.com/apps/deco/google-gmail/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresOAuth: true, decoHosted: true }, + }, + + googleCalendar: { + title: "Google Calendar", + description: + "Integrate and manage your Google Calendar. Create, edit and delete events, check availability and sync your calendars.", + icon: "https://assets.decocache.com/mcp/b5fffe71-647a-461c-aa39-3da07b86cc96/Google-Meets.svg", + appName: "deco/google-calendar", + connectionUrl: "https://sites-google-calendar.decocache.com/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresOAuth: true, decoHosted: true }, + }, + + googleSheets: { + title: "Google Sheets", + description: "Create and edit spreadsheets, manage data", + icon: "https://assets.decocache.com/mcp/0b05c082-ce9d-4879-9258-1acbecf9bf68/Google-Sheets.svg", + appName: "deco/google-sheets", + connectionUrl: "https://api.decocms.com/apps/deco/google-sheets/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresOAuth: true, decoHosted: true }, + }, + + googleDocs: { + title: "Google Docs", + description: "Read, write, and edit content in Google Docs.", + icon: "https://assets.decocache.com/mcp/e0a00fae-ba76-487a-9f62-7b21bbb08d50/Google-Docs.svg", + appName: "deco/google-docs", + connectionUrl: "https://api.decocms.com/apps/deco/google-docs/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresOAuth: true, decoHosted: true }, + }, + + googleDrive: { + title: "Google Drive", + description: "Store, share, and manage files in the cloud", + icon: "https://assets.decocache.com/mcp/bc609f7d-e7c7-433d-b432-93639c5c84bf/Google-Drive.svg", + appName: "deco/google-drive", + connectionUrl: "https://api.decocms.com/apps/deco/google-drive/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresOAuth: true, decoHosted: true }, + }, + + googleTagManager: { + title: "Google Tag Manager", + description: "Manage marketing tags and tracking pixels", + icon: "https://img.icons8.com/color/1200/google-tag-manager.jpg", + appName: "deco/google-tag-manager", + connectionUrl: "https://sites-google-tag-manager.decocache.com/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresOAuth: true, decoHosted: true }, + }, + + youtube: { + title: "YouTube", + description: "Manage YouTube videos and channels", + icon: "https://assets.decocache.com/mcp/cac50532-150e-437d-a996-91fd9a0c115e/YouTube.svg", + appName: "deco/google-youtube", + connectionUrl: "https://api.decocms.com/apps/deco/google-youtube/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresOAuth: true, decoHosted: true }, + }, + + // Communication + discord: { + title: "Discord Bot", + description: + "Discord Bot integration for sending messages, managing channels, and server moderation", + icon: "https://support.discord.com/hc/user_images/PRywUXcqg0v5DD6s7C3LyQ.jpeg", + appName: "deco/discordbot", + connectionUrl: "https://api.decocms.com/apps/deco/discordbot/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresApiKey: true, decoHosted: true }, + }, + + discordWebhook: { + title: "Discord Webhook", + description: "Send rich, formatted messages to Discord channels.", + icon: "https://assets.decocache.com/mcp/a626d828-e641-4931-8557-850276e91702/DiscordWebhook.svg", + appName: "deco/discohook", + connectionUrl: "https://api.decocms.com/apps/deco/discohook/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresApiKey: true, decoHosted: true }, + }, + + slack: { + title: "Slack", + description: "Send messages and manage Slack workspaces", + icon: "https://assets.decocache.com/mcp/f7e005a9-1c53-48f7-989b-955baa422be1/Slack.svg", + appName: "deco/slack", + connectionUrl: "https://api.decocms.com/apps/deco/slack/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresOAuth: true, decoHosted: true }, + }, + + resend: { + title: "Resend", + description: "Send transactional emails with modern API", + icon: "https://assets.decocache.com/mcp/932e4c3a-6045-40af-9fd1-42894bdd138e/Resend.svg", + appName: "deco/resend", + connectionUrl: "https://api.decocms.com/apps/deco/resend/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresApiKey: true, decoHosted: true }, + }, + + // Productivity & Documentation + notion: { + title: "Notion", + description: "Manage pages and databases in Notion workspaces", + icon: "https://www.notion.so/images/logo-ios.png", + appName: "Notion", + connectionUrl: "https://mcp.notion.com/mcp", + connectionToken: null, + metadata: { provider: "notion", requiresOAuth: true, official: true }, + }, + + grain: { + title: "Grain", + description: "Meeting recording, transcription, and compliance archival", + icon: "https://assets.decocache.com/mcp/1bfc7176-e7be-487c-83e6-4b9e970a8e10/Grain.svg", + appName: "grain/grain-mcp", + connectionUrl: "https://api.grain.com/_/mcp", + connectionToken: null, + metadata: { provider: "grain", official: true }, + }, + + airtable: { + title: "Airtable", + description: "Manage databases, records, and collaborative workflows", + icon: "https://assets.decocache.com/mcp/e724f447-3b98-46c4-9194-6b79841305a2/Airtable.svg", + appName: "deco/airtable", + connectionUrl: "https://api.decocms.com/apps/deco/airtable/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresApiKey: true, decoHosted: true }, + }, + + jira: { + title: "Jira", + description: "Project management and issue tracking", + icon: "https://assets.decocache.com/mcp/7bae17a9-cfdb-4969-99ca-436b7a4dcf40/Jira.svg", + appName: "deco/jira", + connectionUrl: "https://api.decocms.com/apps/deco/jira/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresOAuth: true, decoHosted: true }, + }, + + hubspot: { + title: "HubSpot", + description: "CRM, marketing, and sales automation", + icon: "https://www.hubspot.com/hubfs/HubSpot_Logos/HubSpot-Inversed-Favicon.png", + appName: "deco/hubspot", + connectionUrl: "https://api.decocms.com/apps/deco/hubspot/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresOAuth: true, decoHosted: true }, + }, + + // Automation & Scraping + apify: { + title: "Apify", + description: "Web scraping and automation for data collection", + icon: "https://assets.decocache.com/mcp/4eda8c60-503f-4001-9edb-89de961ab7f0/Apify.svg", + appName: "deco/apify", + connectionUrl: "https://api.decocms.com/apps/deco/apify/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresApiKey: true, decoHosted: true }, + }, + + browserUse: { + title: "Browser Use", + description: "Browser automation for testing and scraping", + icon: "https://assets.decocache.com/mcp/1a7a2573-023c-43ed-82a2-95d77adca3db/Browser-Use.svg", + appName: "deco/browser-use", + connectionUrl: "https://api.decocms.com/apps/deco/browser-use/mcp", + connectionToken: null, + metadata: { provider: "deco", decoHosted: true }, + }, + + // Payments + stripe: { + title: "Stripe", + description: "Payment processing and subscription management", + icon: "https://stripe.com/favicon.ico", + appName: "Stripe", + connectionUrl: "https://mcp.stripe.com/", + connectionToken: null, + metadata: { provider: "stripe", requiresOAuth: true, official: true }, + }, + + // Brazilian APIs + brasilApi: { + title: "Brasil API", + description: "CEP, CNPJ, banks, holidays, and Brazilian public data", + icon: "https://assets.decocache.com/mcp/bd684c47-0525-4659-a298-97fa60ba24f1/BrasilAPI.svg", + appName: "deco/brasilapi", + connectionUrl: "https://api.decocms.com/apps/deco/brasilapi/mcp", + connectionToken: null, + metadata: { provider: "deco", decoHosted: true }, + }, + + queridoDiario: { + title: "Querido Diário", + description: "Access Brazilian official gazettes data", + icon: "https://assets.decocache.com/mcp/0bb451a6-db7c-4f9a-9720-8f87b8898da5/QueridoDirio.svg", + appName: "deco/querido-diario", + connectionUrl: "https://api.decocms.com/apps/deco/querido-diario/mcp", + connectionToken: null, + metadata: { provider: "deco", decoHosted: true }, + }, + + datajud: { + title: "Datajud", + description: "Brazilian judicial data from CNJ", + icon: "https://www.cnj.jus.br/wp-content/uploads/2023/09/logo-cnj-portal-20-09-1.svg", + appName: "deco/datajud", + connectionUrl: "https://api.decocms.com/apps/deco/datajud/mcp", + connectionToken: null, + metadata: { provider: "deco", decoHosted: true }, + }, + + // Design & Media + figma: { + title: "Figma", + description: "Design collaboration and prototyping", + icon: "https://assets.decocache.com/mcp/eb714f8a-404b-4b8e-bfc4-f3ce5bde6f51/Figma.svg", + appName: "deco/figma", + connectionUrl: "https://api.decocms.com/apps/deco/figma/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresOAuth: true, decoHosted: true }, + }, + + // E-commerce + shopify: { + title: "Shopify", + description: "E-commerce platform management", + icon: "https://assets.decocache.com/mcp/37122d09-6ceb-4d25-a641-11ce4af8a19b/Shopify.svg", + appName: "deco/shopify-mcp", + connectionUrl: "https://api.decocms.com/apps/deco/shopify-mcp/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresApiKey: true, decoHosted: true }, + }, + + vtex: { + title: "VTEX", + description: "E-commerce platform for Latin America", + icon: "https://assets.decocache.com/mcp/0d6e795b-cefd-4853-9a51-93b346c52c3f/VTEX.svg", + appName: "deco-team/mcp-vtex", + connectionUrl: "https://api.decocms.com/apps/deco-team/mcp-vtex/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresApiKey: true, decoHosted: true }, + }, + + // Logistics + superfrete: { + title: "SuperFrete", + description: "Brazilian shipping and logistics", + icon: "https://assets.decocache.com/mcp/2fdb628e-c10c-4fac-8985-b55e383a64b2/SuperFrete.svg", + appName: "deco/superfrete", + connectionUrl: "https://api.decocms.com/apps/deco/superfrete/mcp", + connectionToken: null, + metadata: { provider: "deco", requiresApiKey: true, decoHosted: true }, + }, +} as const satisfies Record; + +export type ConnectionKey = keyof typeof CONNECTIONS; + +// ============================================================================= +// Gateway Templates +// ============================================================================= + +export const GATEWAYS = { + // Default Hub - matches production behavior (exclusion mode = includes all connections) + defaultHub: { + title: "Default Hub", + description: "Auto-created Hub for organization", + toolSelectionStrategy: "passthrough", + toolSelectionMode: "exclusion", + icon: null, + isDefault: true, + connections: [], // Empty with exclusion mode = include all + }, + + simpleDefault: { + title: "My First Gateway", + description: "Default gateway for getting started with MCP tools", + toolSelectionStrategy: "passthrough", + toolSelectionMode: "inclusion", + icon: null, + isDefault: false, + connections: ["github", "openrouter", "notion"], + }, + + llm: { + title: "LLM Gateway", + description: + "AI model access with cost tracking (OpenRouter + GitHub Copilot)", + toolSelectionStrategy: "passthrough", + toolSelectionMode: "inclusion", + icon: "https://assets.decocache.com/decocms/b2e2f64f-6025-45f7-9e8c-3b3ebdd073d8/openrouter_logojpg.jpg", + isDefault: false, + connections: ["openrouter", "github", "perplexity", "elevenlabs"], + }, + + devGateway: { + title: "Development & Deployments", + description: "Complete development workflow: code, deployments, database", + toolSelectionStrategy: "passthrough", + toolSelectionMode: "inclusion", + icon: "https://assets.decocache.com/mcp/02e06fe6-a820-4c42-b960-bce022362702/GitHub.svg", + isDefault: false, + connections: ["github", "vercel", "supabase", "aws", "cloudflare"], + }, + + compliance: { + title: "Knowledge & Compliance", + description: "Documentation, meeting records, and audit trails", + toolSelectionStrategy: "passthrough", + toolSelectionMode: "inclusion", + icon: "https://assets.decocache.com/mcp/1bfc7176-e7be-487c-83e6-4b9e970a8e10/Grain.svg", + isDefault: false, + connections: ["notion", "grain", "airtable", "jira"], + }, + + dataGateway: { + title: "Data & Automation", + description: "Web scraping, data collection, and automation", + toolSelectionStrategy: "passthrough", + toolSelectionMode: "inclusion", + icon: "https://assets.decocache.com/mcp/4eda8c60-503f-4001-9edb-89de961ab7f0/Apify.svg", + isDefault: false, + connections: ["apify", "browserUse", "brasilApi"], + }, + + allAccess: { + title: "All Access Gateway", + description: "Full access to all connected tools (restricted to admins)", + toolSelectionStrategy: "passthrough", + toolSelectionMode: "inclusion", + icon: null, + isDefault: false, + connections: [ + "github", + "openrouter", + "perplexity", + "notion", + "grain", + "stripe", + "vercel", + "supabase", + "apify", + "gmail", + "googleCalendar", + "googleSheets", + "googleDocs", + "googleDrive", + "googleTagManager", + "discord", + "slack", + "airtable", + "brasilApi", + "cloudflare", + "jira", + "hubspot", + "figma", + "shopify", + ], + }, +} as const satisfies Record; + +export type GatewayKey = keyof typeof GATEWAYS; + +// ============================================================================= +// Picker Helpers +// ============================================================================= + +/** + * Get all well-known connections (Mesh MCP, MCP Registry, Deco Store) + * These are installed by default in production environments. + */ +export function getWellKnownConnections(): Record< + WellKnownConnectionKey, + Connection +> { + return { ...WELL_KNOWN_CONNECTIONS }; +} + +export function pickConnections( + keys: K[], +): Record { + const result = {} as Record; + for (const key of keys) { + const conn = CONNECTIONS[key]; + if (conn) result[key] = conn; + } + return result; +} + +export function pickGateways( + keys: K[], +): Record { + const result = {} as Record; + for (const key of keys) { + const gw = GATEWAYS[key]; + if (gw) result[key] = gw; + } + return result; +} diff --git a/apps/mesh/migrations/seeds/demo/config.ts b/apps/mesh/migrations/seeds/demo/config.ts deleted file mode 100644 index 8e84bf870a..0000000000 --- a/apps/mesh/migrations/seeds/demo/config.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Demo seed configuration and constants - */ - -import type { DemoUser } from "./types"; - -export const DEMO_CONFIG = { - PASSWORD: "demo123", - EMAIL_DOMAIN: "@demo.local", - ORG_NAME: "Demo Company", - ORG_SLUG: "demo-company", - USER_AGENT_DEFAULT: "mesh-demo-client/1.0", -} as const; - -export const DEMO_USERS: Record = { - admin: { - role: "admin", - name: "Demo Admin", - email: `admin${DEMO_CONFIG.EMAIL_DOMAIN}`, - }, - developer: { - role: "member", - name: "Demo Developer", - email: `developer${DEMO_CONFIG.EMAIL_DOMAIN}`, - }, - analyst: { - role: "member", - name: "Demo Analyst", - email: `analyst${DEMO_CONFIG.EMAIL_DOMAIN}`, - }, - billing: { - role: "member", - name: "Demo Billing", - email: `billing${DEMO_CONFIG.EMAIL_DOMAIN}`, - }, - viewer: { - role: "member", - name: "Demo Viewer", - email: `viewer${DEMO_CONFIG.EMAIL_DOMAIN}`, - }, -} as const; - -export const DEMO_MEMBER_ROLES: Record = { - admin: "owner", - developer: "member", - analyst: "member", - billing: "member", - viewer: "member", -} as const; diff --git a/apps/mesh/migrations/seeds/demo/connections.ts b/apps/mesh/migrations/seeds/demo/connections.ts deleted file mode 100644 index 7dad48a00f..0000000000 --- a/apps/mesh/migrations/seeds/demo/connections.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Demo MCP Connections - * - * All connections use real URLs, icons, and descriptions from the deco registry. - */ - -import type { DemoConnection } from "./types"; - -export const DEMO_CONNECTIONS: Record = { - notion: { - title: "Notion", - description: "Manage pages and databases in Notion workspaces", - icon: "https://www.notion.so/images/logo-ios.png", - appName: "Notion", - connectionUrl: "https://mcp.notion.com/mcp", - connectionToken: null, - configurationState: "needs_auth", - metadata: { - provider: "notion", - requiresOAuth: true, - official: true, - }, - }, - github: { - title: "GitHub", - description: "GitHub issues, PRs, and repository management (deco-hosted)", - icon: "https://github.githubassets.com/favicons/favicon.svg", - appName: "GitHub", - connectionUrl: "https://api.decocms.com/apps/deco/github/mcp", - connectionToken: null, - configurationState: "needs_auth", - metadata: { - provider: "github", - requiresOAuth: true, - decoHosted: true, - }, - }, - openrouter: { - title: "OpenRouter", - description: "OpenRouter App Connection for LLM uses.", - icon: "https://assets.decocache.com/decocms/b2e2f64f-6025-45f7-9e8c-3b3ebdd073d8/openrouter_logojpg.jpg", - appName: "OpenRouter", - connectionUrl: "https://sites-openrouter.decocache.com/mcp", - connectionToken: null, - configurationState: "needs_auth", - metadata: { - provider: "deco", - requiresApiKey: true, - decoHosted: true, - }, - }, - nanoBanana: { - title: "Nano Banana", - description: "Use Nano Banana Integration to create images using AI.", - icon: "https://assets.decocache.com/starting/62401ea6-55e6-433d-b614-e43196890e05/nanobanana.png", - appName: "nanobanana", - connectionUrl: "https://api.decocms.com/apps/deco/nanobanana/mcp", - connectionToken: null, - configurationState: "needs_auth", - metadata: { - provider: "deco", - decoHosted: true, - }, - }, - veo3: { - title: "Google Veo 3.1", - description: - "Generate high-quality videos with audio using Google Gemini Veo 3 and Veo 3.1 models. Supports text-to-video, image-to-video, and video extension.", - icon: "https://www.gstatic.com/lamda/images/gemini_sparkle_v002_d4735304ff6292a690345.svg", - appName: "veo3", - connectionUrl: "https://api.decocms.com/apps/deco/veo3/mcp", - connectionToken: null, - configurationState: "needs_auth", - metadata: { - provider: "deco", - decoHosted: true, - requiresApiKey: true, - }, - }, - sora: { - title: "OpenAI Sora 2", - description: "Use OpenAI Sora 2 to generate professional videos using AI.", - icon: "https://cdn.openai.com/nf2/nf2-lp/misc/dark-mode-icon.png?w=3840&q=100&fm=webp", - appName: "sora", - connectionUrl: "https://api.decocms.com/apps/deco/sora/mcp", - connectionToken: null, - configurationState: "needs_auth", - metadata: { - provider: "deco", - decoHosted: true, - }, - }, - grain: { - title: "Grain mcp", - description: - "Grain Official MCP - Acesse e gerencie suas reuniões, transcrições e insights do Grain. Este é o MCP oficial da Grain para integração completa com a plataforma de gravação e análise de reuniões.", - icon: "https://assets.decocache.com/mcp/1bfc7176-e7be-487c-83e6-4b9e970a8e10/Grain.svg", - appName: "Grain mcp", - connectionUrl: "https://api.grain.com/_/mcp", - connectionToken: null, - configurationState: "needs_auth", - metadata: { - provider: "grain", - official: true, - }, - }, -} as const; diff --git a/apps/mesh/migrations/seeds/demo/factories.ts b/apps/mesh/migrations/seeds/demo/factories.ts deleted file mode 100644 index 6670d76e8b..0000000000 --- a/apps/mesh/migrations/seeds/demo/factories.ts +++ /dev/null @@ -1,230 +0,0 @@ -/** - * Factory functions for creating demo seed records - */ - -/** - * Generate a unique ID with prefix - */ -export function generateId(prefix: string): string { - return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; -} - -/** - * Create a user record - */ -export function createUserRecord( - userId: string, - email: string, - name: string, - role: string, - timestamp: string, -) { - return { - id: userId, - email, - emailVerified: 1, - name, - image: null, - role, - banned: null, - banReason: null, - banExpires: null, - createdAt: timestamp, - updatedAt: timestamp, - }; -} - -/** - * Create an account record for credential authentication - */ -export function createAccountRecord( - userId: string, - email: string, - passwordHash: string, - timestamp: string, -) { - return { - id: generateId("account"), - userId, - accountId: email, - providerId: "credential", - password: passwordHash, - accessToken: null, - refreshToken: null, - accessTokenExpiresAt: null, - refreshTokenExpiresAt: null, - scope: null, - idToken: null, - createdAt: timestamp, - updatedAt: timestamp, - }; -} - -/** - * Create a member record linking user to organization - */ -export function createMemberRecord( - organizationId: string, - userId: string, - role: "owner" | "member", - timestamp: string, -) { - return { - id: generateId("member"), - organizationId, - userId, - role, - createdAt: timestamp, - }; -} - -/** - * Create an API key record - */ -export function createApiKeyRecord( - userId: string, - name: string, - key: string, - timestamp: string, -) { - return { - id: generateId("apikey"), - name, - userId, - key, - createdAt: timestamp, - updatedAt: timestamp, - }; -} - -/** - * Create a connection record - */ -export function createConnectionRecord( - connectionId: string, - organizationId: string, - createdBy: string, - title: string, - description: string, - icon: string, - appName: string, - connectionUrl: string, - connectionToken: string | null, - configurationState: "needs_auth" | null, - metadata: Record, - timestamp: string, -) { - return { - id: connectionId, - organization_id: organizationId, - created_by: createdBy, - title, - description, - icon, - app_name: appName, - app_id: null, - connection_type: "HTTP" as const, - connection_url: connectionUrl, - connection_token: connectionToken, - connection_headers: null, - oauth_config: null, - configuration_state: configurationState, - configuration_scopes: null, - metadata: JSON.stringify(metadata), - tools: null, - bindings: null, - status: "active" as const, - created_at: timestamp, - updated_at: timestamp, - }; -} - -/** - * Create a gateway record - */ -export function createGatewayRecord( - gatewayId: string, - organizationId: string, - title: string, - description: string, - toolSelectionStrategy: "passthrough" | "code_execution", - toolSelectionMode: "inclusion" | "exclusion", - icon: string | null, - isDefault: boolean, - createdBy: string, - timestamp: string, -) { - return { - id: gatewayId, - organization_id: organizationId, - title, - description, - tool_selection_strategy: toolSelectionStrategy, - tool_selection_mode: toolSelectionMode, - icon, - status: "active" as const, - is_default: isDefault ? 1 : 0, - created_at: timestamp, - updated_at: timestamp, - created_by: createdBy, - updated_by: null, - }; -} - -/** - * Create a gateway-connection link record - */ -export function createGatewayConnectionRecord( - gatewayId: string, - connectionId: string, - timestamp: string, -) { - return { - id: generateId("gtw_conn"), - gateway_id: gatewayId, - connection_id: connectionId, - selected_tools: null, - selected_resources: null, - selected_prompts: null, - created_at: timestamp, - }; -} - -/** - * Create a monitoring log record - */ -export function createMonitoringLogRecord( - organizationId: string, - connectionId: string, - connectionTitle: string, - toolName: string, - input: unknown, - output: unknown, - isError: boolean, - errorMessage: string | null, - durationMs: number, - timestamp: string, - userId: string, - userAgent: string, - gatewayId: string | null, - properties: Record | null, -) { - return { - id: generateId("log"), - organization_id: organizationId, - connection_id: connectionId, - connection_title: connectionTitle, - tool_name: toolName, - input: JSON.stringify(input), - output: JSON.stringify(output), - is_error: isError ? 1 : 0, - error_message: errorMessage, - duration_ms: durationMs, - timestamp, - user_id: userId, - request_id: generateId("req"), - user_agent: userAgent, - gateway_id: gatewayId, - properties: properties ? JSON.stringify(properties) : null, - }; -} diff --git a/apps/mesh/migrations/seeds/demo/gateways.ts b/apps/mesh/migrations/seeds/demo/gateways.ts deleted file mode 100644 index a11bb9645e..0000000000 --- a/apps/mesh/migrations/seeds/demo/gateways.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Demo Gateways Configuration - */ - -import type { DemoGateway } from "./types"; - -export const DEMO_GATEWAYS: Record = { - openrouter: { - title: "OpenRouter Gateway", - description: "Access hundreds of LLM models from a single API", - toolSelectionStrategy: "passthrough", - toolSelectionMode: "inclusion", - icon: "https://assets.decocache.com/decocms/b2e2f64f-6025-45f7-9e8c-3b3ebdd073d8/openrouter_logojpg.jpg", - isDefault: false, - connections: ["openrouter"], - }, - default: { - title: "Default Gateway", - description: "Auto-created gateway for organization", - toolSelectionStrategy: "passthrough", - toolSelectionMode: "inclusion", - icon: null, - isDefault: true, - connections: ["notion", "github", "nanoBanana", "veo3", "sora", "grain"], - }, -} as const; diff --git a/apps/mesh/migrations/seeds/demo/index.ts b/apps/mesh/migrations/seeds/demo/index.ts index 6aaa0666d7..43b30cf0cf 100644 --- a/apps/mesh/migrations/seeds/demo/index.ts +++ b/apps/mesh/migrations/seeds/demo/index.ts @@ -1,280 +1,100 @@ /** * Demo Seed * - * Creates a complete demo environment with: - * - Fixed demo organization ("Demo Company") - * - 5 users with different RBAC roles (admin, developer, analyst, billing, viewer) - * - API keys for authentication - * - 7 real MCP connections from deco-store (validated against registry) - * - 3 gateways: OpenRouter Gateway (dedicated), All Tools Gateway (default), Smart Gateway (code_execution) - * - Sample monitoring logs with realistic tool calls - * - Organization settings + * Creates two complete demo environments: * - * This seed is open source and ensures consistency across all demo environments. - * All MCPs use real URLs, icons, and descriptions from the deco registry. + * 1. **Onboarding** - Clean slate for demonstrating first steps + * - 2 users (admin + developer) + * - 3 connections (GitHub, OpenRouter, Notion) + * - 1 gateway + * - ~16 logs * - * Usage: - * await seed(db) + * 2. **Deco Bank** - Mature corporate banking environment + * - 12 users in realistic hierarchy + * - 10 real MCP connections (all verified) + * - 5 specialized gateways + * - ~2,500 logs over 90 days + * + * Demo Flow: + * 1. Login as carlos.mendes@decobank.com (password: demo123) + * 2. Start with "Onboarding" org -> show first steps + * 3. Switch to "Deco Bank" org -> show mature usage */ import type { Kysely } from "kysely"; import type { Database } from "../../../src/storage/types"; -import { hashPassword } from "better-auth/crypto"; - -import type { DemoSeedResult } from "./types"; -import { DEMO_CONFIG, DEMO_USERS, DEMO_MEMBER_ROLES } from "./config"; -import { DEMO_CONNECTIONS } from "./connections"; -import { DEMO_GATEWAYS } from "./gateways"; -import { DEMO_MONITORING_LOGS } from "./monitoring-logs"; -import { - generateId, - createUserRecord, - createAccountRecord, - createMemberRecord, - createApiKeyRecord, - createConnectionRecord, - createGatewayRecord, - createGatewayConnectionRecord, - createMonitoringLogRecord, -} from "./factories"; - -/** - * Run the demo seed - */ -export async function seed(db: Kysely): Promise { - const now = new Date().toISOString(); - - // Generate IDs - const orgId = generateId("org"); - const userIds: Record = {}; - for (const key of Object.keys(DEMO_USERS)) { - userIds[key] = generateId("user"); - } - - // ============================================================================ - // 1. Create Organization - // ============================================================================ - - await db - .insertInto("organization") - .values({ - id: orgId, - slug: DEMO_CONFIG.ORG_SLUG, - name: DEMO_CONFIG.ORG_NAME, - createdAt: now, - }) - .execute(); +import { createBetterAuthTables } from "../../../src/storage/test-helpers"; - // ============================================================================ - // 2. Create Users - // ============================================================================ +import type { OrgSeedResult } from "./seeder"; +import { cleanupOrgs, createMemberRecord } from "./seeder"; +import { seedOnboarding, ONBOARDING_SLUG } from "./orgs/onboarding"; +import { seedDecoBank, DECO_BANK_SLUG } from "./orgs/deco-bank"; - const userRecords = Object.entries(DEMO_USERS).map(([key, user]) => - createUserRecord(userIds[key]!, user.email, user.name, user.role, now), - ); +// ============================================================================= +// Types +// ============================================================================= - for (const userRecord of userRecords) { - await db - // @ts-ignore: Better Auth user table - .insertInto("user") - .values(userRecord) - .execute(); - } - - // ============================================================================ - // 3. Create Credential Accounts (for email/password login) - // ============================================================================ - - const passwordHash = await hashPassword(DEMO_CONFIG.PASSWORD); - - const accountRecords = Object.entries(DEMO_USERS).map(([key, user]) => - createAccountRecord(userIds[key]!, user.email, passwordHash, now), - ); +export interface DemoSeedResult { + onboarding: OrgSeedResult; + decoBank: OrgSeedResult; +} - await db - // @ts-ignore: Better Auth account table - .insertInto("account") - .values(accountRecords) - .execute(); +// ============================================================================= +// Main Seed Function +// ============================================================================= - // ============================================================================ - // 4. Link Users to Organization (Members) - // ============================================================================ +export async function seed(db: Kysely): Promise { + // Create Better Auth tables (not created by Kysely migrations) + await createBetterAuthTables(db); - const memberRecords = Object.entries(DEMO_MEMBER_ROLES).map(([key, role]) => - createMemberRecord(orgId, userIds[key]!, role, now), - ); + // Clean up any existing demo orgs (makes seed idempotent) + await cleanupOrgs(db, [ONBOARDING_SLUG, DECO_BANK_SLUG]); - await db.insertInto("member").values(memberRecords).execute(); + // Create Onboarding organization + console.log("🌱 Creating Onboarding organization..."); + const onboarding = await seedOnboarding(db); + console.log(` ✅ Onboarding: ${onboarding.logCount} logs`); - // ============================================================================ - // 5. Create API Keys - // ============================================================================ + // Create Deco Bank organization + console.log("🏦 Creating Deco Bank organization..."); + const decoBank = await seedDecoBank(db); + console.log(` ✅ Deco Bank: ${decoBank.logCount} logs`); - const adminApiKeyHash = `demo_key_admin_${generateId("key")}`; - const memberApiKeyHash = `demo_key_member_${generateId("key")}`; + // Cross-org access: Add Deco Bank CTO as member of Onboarding + console.log("🔗 Creating cross-org access..."); + const now = new Date().toISOString(); await db - // @ts-ignore: Better Auth apikey table - .insertInto("apikey") - .values([ - createApiKeyRecord( - userIds.admin!, - "Demo Admin API Key", - adminApiKeyHash, - now, - ), - createApiKeyRecord( - userIds.developer!, - "Demo Developer API Key", - memberApiKeyHash, - now, - ), - ]) - .execute(); - - // ============================================================================ - // 6. Create MCP Connections (Real deco MCPs) - // ============================================================================ - - const connectionIds: Record = {}; - for (const key of Object.keys(DEMO_CONNECTIONS)) { - connectionIds[key] = generateId("conn"); - } - - const connectionRecords = Object.entries(DEMO_CONNECTIONS).map( - ([key, conn]) => - createConnectionRecord( - connectionIds[key]!, - orgId, - userIds.admin!, - conn.title, - conn.description, - conn.icon, - conn.appName, - conn.connectionUrl, - conn.connectionToken, - conn.configurationState, - conn.metadata, + .insertInto("member") + .values( + createMemberRecord( + onboarding.organizationId, + decoBank.userIds.cto!, + "owner", now, ), - ); - - await db.insertInto("connections").values(connectionRecords).execute(); - - // ============================================================================ - // 7. Create Gateways - // ============================================================================ - - const gatewayIds: Record = {}; - for (const key of Object.keys(DEMO_GATEWAYS)) { - gatewayIds[key] = generateId("gtw"); - } - - const gatewayRecords = Object.entries(DEMO_GATEWAYS).map(([key, gateway]) => - createGatewayRecord( - gatewayIds[key]!, - orgId, - gateway.title, - gateway.description, - gateway.toolSelectionStrategy, - gateway.toolSelectionMode, - gateway.icon, - gateway.isDefault, - userIds.admin!, - now, - ), - ); - - await db.insertInto("gateways").values(gatewayRecords).execute(); - - // ============================================================================ - // 8. Link Gateways to Connections - // ============================================================================ - - const gatewayConnectionRecords = Object.entries(DEMO_GATEWAYS).flatMap( - ([gatewayKey, gateway]) => - gateway.connections.map((connKey) => - createGatewayConnectionRecord( - gatewayIds[gatewayKey]!, - connectionIds[connKey]!, - now, - ), - ), - ); - - await db - .insertInto("gateway_connections") - .values(gatewayConnectionRecords) - .execute(); - - // ============================================================================ - // 9. Create Sample Monitoring Logs (Rich Demo Data - 7 days) - // ============================================================================ - - const monitoringLogRecords = DEMO_MONITORING_LOGS.map((log) => { - const timestamp = new Date(Date.now() + log.offsetMs).toISOString(); - const gatewayId = log.gatewayKey ? gatewayIds[log.gatewayKey]! : null; - - return createMonitoringLogRecord( - orgId, - connectionIds[log.connectionKey]!, - DEMO_CONNECTIONS[log.connectionKey]!.title, - log.toolName, - log.input, - log.output, - log.isError, - log.errorMessage ?? null, - log.durationMs, - timestamp, - userIds[log.userRole]!, - log.userAgent, - gatewayId, - log.properties ?? null, - ); - }); - - await db.insertInto("monitoring_logs").values(monitoringLogRecords).execute(); - - // ============================================================================ - // 10. Create Organization Settings - // ============================================================================ - - await db - .insertInto("organization_settings") - .values({ - organizationId: orgId, - sidebar_items: null, - createdAt: now, - updatedAt: now, - }) + ) .execute(); - // ============================================================================ - // Return Result - // ============================================================================ - - return { - organizationId: orgId, - organizationName: DEMO_CONFIG.ORG_NAME, - organizationSlug: DEMO_CONFIG.ORG_SLUG, - users: { - adminId: userIds.admin!, - adminEmail: DEMO_USERS.admin!.email, - developerId: userIds.developer!, - developerEmail: DEMO_USERS.developer!.email, - analystId: userIds.analyst!, - analystEmail: DEMO_USERS.analyst!.email, - billingId: userIds.billing!, - billingEmail: DEMO_USERS.billing!.email, - viewerId: userIds.viewer!, - viewerEmail: DEMO_USERS.viewer!.email, - }, - apiKeys: { - admin: adminApiKeyHash, - member: memberApiKeyHash, - }, - connectionIds: Object.values(connectionIds), - gatewayIds: Object.values(gatewayIds), - }; + const ctoEmail = decoBank.userEmails.cto!; + console.log(` ✅ ${ctoEmail} now has access to both orgs`); + + // Display demo credentials + console.log("\n📋 Demo Credentials:"); + console.log(" Password for all users: demo123\n"); + console.log(" 🌟 RECOMMENDED FOR DEMO (access to both orgs):"); + console.log(` - ${ctoEmail}`); + console.log(""); + console.log(" Onboarding only:"); + console.log(` - ${onboarding.userEmails.admin} (owner)`); + console.log(` - ${onboarding.userEmails.developer} (member)`); + console.log(" Deco Bank only:"); + console.log(` - ${decoBank.userEmails.techLead} (admin)`); + console.log(` - ${decoBank.userEmails.seniorDev1} (member)`); + console.log(` - ... and more users`); + + return { onboarding, decoBank }; } + +// Re-export types +export type { OrgSeedResult } from "./seeder"; diff --git a/apps/mesh/migrations/seeds/demo/monitoring-logs.ts b/apps/mesh/migrations/seeds/demo/monitoring-logs.ts deleted file mode 100644 index 0b4b95e28f..0000000000 --- a/apps/mesh/migrations/seeds/demo/monitoring-logs.ts +++ /dev/null @@ -1,571 +0,0 @@ -/** - * Demo Monitoring Logs - * - * Rich demo data spanning 7 days with realistic tool calls, errors, and usage patterns. - * All logs are generated relative to the current time using offsetMs (in milliseconds). - */ - -import type { DemoMonitoringLog } from "./types"; - -const DAYS = 24 * 60 * 60 * 1000; -const HOURS = 60 * 60 * 1000; -const MINUTES = 60 * 1000; - -export const DEMO_MONITORING_LOGS: DemoMonitoringLog[] = [ - // === 7 days ago: Initial setup === - { - connectionKey: "github", - toolName: "list_repositories", - input: { org: "decocms", per_page: 30 }, - output: { - repositories: ["mesh", "runtime", "bindings", "ui", "cli"], - total_count: 5, - }, - isError: false, - durationMs: 243, - offsetMs: -7 * DAYS, - userRole: "admin", - userAgent: "mesh-demo-client/1.0", - gatewayKey: "allTools", - properties: { - cache_hit: "false", - region: "us-east-1", - }, - }, - - // === 6 days ago: Developer working === - { - connectionKey: "github", - toolName: "create_issue", - input: { - repo: "mesh", - title: "Add demo seed support", - body: "Implement comprehensive demo seed for bank presentations", - labels: ["enhancement", "demo"], - }, - output: { - issue_number: 156, - url: "https://github.com/decocms/mesh/issues/156", - state: "open", - }, - isError: false, - durationMs: 187, - offsetMs: -6 * DAYS, - userRole: "developer", - userAgent: "mesh-demo-client/1.0", - gatewayKey: "allTools", - properties: { cache_hit: "false" }, - }, - - // === 5 days ago: OpenRouter LLM usage === - { - connectionKey: "openrouter", - toolName: "chat_completion", - input: { - model: "anthropic/claude-3.5-sonnet", - messages: [ - { - role: "user", - content: "Explain the benefits of MCP for enterprise banking", - }, - ], - max_tokens: 500, - }, - output: { - response: - "MCP provides enterprise banking with standardized AI integration, secure tool access, audit trails, and centralized policy management...", - tokens: 412, - finish_reason: "stop", - }, - isError: false, - durationMs: 1847, - offsetMs: -5 * DAYS, - userRole: "analyst", - userAgent: "claude-desktop/1.2.0", - gatewayKey: "openrouter", - properties: { - tokens_prompt: "18", - tokens_completion: "412", - cost_usd: "0.0082", - model: "anthropic/claude-3.5-sonnet", - cache_hit: "false", - }, - }, - - // === 5 days ago: Notion auth error === - { - connectionKey: "notion", - toolName: "search_pages", - input: { query: "product roadmap", limit: 10 }, - output: { error: "Authentication required" }, - isError: true, - errorMessage: - "OAuth authentication required. Connect your Notion account to use this tool.", - durationMs: 67, - offsetMs: -5 * DAYS, - userRole: "analyst", - userAgent: "mesh-demo-client/1.0", - gatewayKey: "allTools", - properties: { auth_error: "true" }, - }, - - // === 4 days ago: Nano Banana image generation === - { - connectionKey: "nanoBanana", - toolName: "generate_image", - input: { - prompt: "Modern banking dashboard with AI assistant", - style: "professional", - size: "1024x1024", - }, - output: { - image_url: "https://assets.decocache.com/generated/abc123.png", - seed: 42, - model: "nano-banana-v2", - }, - isError: false, - durationMs: 3421, - offsetMs: -4 * DAYS, - userRole: "developer", - userAgent: "mesh-demo-client/1.0", - gatewayKey: "smart", - properties: { - tokens: "856", - cost_usd: "0.0342", - gpu_time_ms: "3200", - cache_hit: "false", - }, - }, - - // === 4 days ago: Grain meeting summary === - { - connectionKey: "grain", - toolName: "get_meeting_summary", - input: { - meeting_id: "meet_xyz789", - include_transcript: true, - }, - output: { - summary: - "Quarterly review: Discussed MCP integration roadmap, budget allocation for Q2, and demo requirements for Banco Itaú presentation.", - duration_minutes: 45, - participants: 8, - key_points: [ - "MCP demo approval", - "Budget increased by 15%", - "Timeline: 2 weeks", - ], - }, - isError: false, - durationMs: 892, - offsetMs: -4 * DAYS, - userRole: "analyst", - userAgent: "grain-desktop/2.1.0", - gatewayKey: "smart", - properties: { - transcript_length: "12547", - speakers: "8", - cache_hit: "false", - }, - }, - - // === 3 days ago: OpenRouter rate limit === - { - connectionKey: "openrouter", - toolName: "chat_completion", - input: { - model: "openai/gpt-4-turbo", - messages: [{ role: "user", content: "Generate quarterly report" }], - }, - output: { - error: "Rate limit exceeded. Please try again in 60 seconds.", - }, - isError: true, - errorMessage: "Rate limit exceeded (429)", - durationMs: 123, - offsetMs: -3 * DAYS, - userRole: "billing", - userAgent: "mesh-demo-client/1.0", - gatewayKey: "openrouter", - properties: { - rate_limit: "true", - retry_after: "60", - requests_remaining: "0", - }, - }, - - // === 3 days ago: GitHub PR review === - { - connectionKey: "github", - toolName: "create_pull_request_review", - input: { - repo: "mesh", - pr_number: 42, - event: "APPROVE", - body: "LGTM! Demo seed looks great.", - }, - output: { - review_id: 98765, - state: "APPROVED", - submitted_at: new Date(Date.now() - 3 * DAYS).toISOString(), - }, - isError: false, - durationMs: 234, - offsetMs: -3 * DAYS, - userRole: "admin", - userAgent: "github-cli/2.40.0", - gatewayKey: "allTools", - properties: { cache_hit: "false" }, - }, - - // === 2 days ago: Sora video generation === - { - connectionKey: "sora", - toolName: "generate_video", - input: { - prompt: - "Professional banking environment with AI assistants helping customers", - duration_seconds: 10, - resolution: "1080p", - }, - output: { - video_url: "https://cdn.openai.com/sora/video_def456.mp4", - duration: 10, - frames: 240, - model: "sora-2-turbo", - }, - isError: false, - durationMs: 12543, - offsetMs: -2 * DAYS, - userRole: "developer", - userAgent: "mesh-demo-client/1.0", - gatewayKey: "smart", - properties: { - tokens: "2134", - cost_usd: "0.567", - gpu_time_ms: "11800", - cache_hit: "false", - resolution: "1920x1080", - }, - }, - - // === 2 days ago: Veo3 video auth error === - { - connectionKey: "veo3", - toolName: "generate_video", - input: { - prompt: "Banking app demo walkthrough", - duration_seconds: 15, - }, - output: { error: "API key required" }, - isError: true, - errorMessage: "Authentication failed. Please add your Veo3 API key.", - durationMs: 89, - offsetMs: -2 * DAYS, - userRole: "viewer", - userAgent: "mesh-demo-client/1.0", - gatewayKey: "allTools", - properties: { auth_error: "true" }, - }, - - // === 1 day ago: Morning peak - Multiple operations === - { - connectionKey: "openrouter", - toolName: "chat_completion", - input: { - model: "anthropic/claude-3.5-sonnet", - messages: [ - { - role: "user", - content: "Summarize this week's development progress", - }, - ], - }, - output: { - response: - "This week: Completed demo seed, added 7 MCP connections, implemented 3 gateways, and created rich monitoring data.", - tokens: 234, - }, - isError: false, - durationMs: 1234, - offsetMs: -1 * DAYS, - userRole: "admin", - userAgent: "cursor-agent/0.42.0", - gatewayKey: "openrouter", - properties: { - tokens_prompt: "12", - tokens_completion: "234", - cost_usd: "0.0047", - cache_hit: "true", - }, - }, - { - connectionKey: "github", - toolName: "list_pull_requests", - input: { repo: "mesh", state: "open", per_page: 20 }, - output: { - pull_requests: [ - { number: 42, title: "Add demo seed", state: "open" }, - { number: 43, title: "Update docs", state: "open" }, - ], - total_count: 2, - }, - isError: false, - durationMs: 167, - offsetMs: -1 * DAYS, - userRole: "developer", - userAgent: "gh-cli/2.40.0", - gatewayKey: "allTools", - properties: { cache_hit: "true" }, - }, - { - connectionKey: "nanoBanana", - toolName: "upscale_image", - input: { - image_url: "https://assets.decocache.com/generated/abc123.png", - scale: 2, - }, - output: { - image_url: "https://assets.decocache.com/generated/abc123_2x.png", - resolution: "2048x2048", - }, - isError: false, - durationMs: 2156, - offsetMs: -1 * DAYS, - userRole: "developer", - userAgent: "mesh-demo-client/1.0", - gatewayKey: "smart", - properties: { - tokens: "423", - cost_usd: "0.0169", - cache_hit: "false", - }, - }, - - // === 12 hours ago: Grain meeting === - { - connectionKey: "grain", - toolName: "list_recent_meetings", - input: { limit: 10, days: 7 }, - output: { - meetings: [ - { - id: "meet_xyz789", - title: "Quarterly Review", - date: new Date(Date.now() - 4 * DAYS).toISOString(), - duration: 45, - }, - { - id: "meet_abc123", - title: "Demo Planning", - date: new Date(Date.now() - 2 * DAYS).toISOString(), - duration: 30, - }, - ], - total_count: 2, - }, - isError: false, - durationMs: 234, - offsetMs: -12 * HOURS, - userRole: "analyst", - userAgent: "grain-desktop/2.1.0", - gatewayKey: "smart", - properties: { cache_hit: "false" }, - }, - - // === 6 hours ago: Afternoon work === - { - connectionKey: "github", - toolName: "get_issue", - input: { repo: "mesh", issue_number: 156 }, - output: { - number: 156, - title: "Add demo seed support", - state: "closed", - closed_at: new Date(Date.now() - 3 * DAYS).toISOString(), - comments: 12, - }, - isError: false, - durationMs: 98, - offsetMs: -6 * HOURS, - userRole: "viewer", - userAgent: "mesh-demo-client/1.0", - gatewayKey: "allTools", - properties: { cache_hit: "true" }, - }, - { - connectionKey: "openrouter", - toolName: "list_models", - input: { provider: "anthropic" }, - output: { - models: [ - "anthropic/claude-3.5-sonnet", - "anthropic/claude-3-opus", - "anthropic/claude-3-haiku", - ], - total_count: 3, - }, - isError: false, - durationMs: 134, - offsetMs: -6 * HOURS, - userRole: "billing", - userAgent: "mesh-demo-client/1.0", - gatewayKey: "openrouter", - properties: { cache_hit: "true" }, - }, - - // === 2 hours ago: Recent activity === - { - connectionKey: "sora", - toolName: "check_generation_status", - input: { job_id: "job_def456" }, - output: { - status: "completed", - video_url: "https://cdn.openai.com/sora/video_def456.mp4", - progress: 100, - }, - isError: false, - durationMs: 67, - offsetMs: -2 * HOURS, - userRole: "developer", - userAgent: "mesh-demo-client/1.0", - gatewayKey: "smart", - properties: { cache_hit: "false" }, - }, - - // === 1 hour ago === - { - connectionKey: "github", - toolName: "merge_pull_request", - input: { - repo: "mesh", - pr_number: 42, - merge_method: "squash", - }, - output: { - merged: true, - sha: "a1b2c3d4e5f6", - message: "Merged PR #42: Add demo seed", - }, - isError: false, - durationMs: 432, - offsetMs: -1 * HOURS, - userRole: "admin", - userAgent: "gh-cli/2.40.0", - gatewayKey: "allTools", - properties: { cache_hit: "false" }, - }, - - // === 30 minutes ago === - { - connectionKey: "notion", - toolName: "create_page", - input: { - parent_id: "database-789", - title: "Banco Itaú Demo Notes", - content: "Preparation checklist for demo presentation", - }, - output: { error: "Authentication required" }, - isError: true, - errorMessage: - "OAuth authentication required. Connect your Notion account to use this tool.", - durationMs: 78, - offsetMs: -30 * MINUTES, - userRole: "analyst", - userAgent: "notion-desktop/3.5.0", - gatewayKey: "allTools", - properties: { auth_error: "true" }, - }, - - // === 10 minutes ago: High frequency usage === - { - connectionKey: "openrouter", - toolName: "chat_completion", - input: { - model: "openai/gpt-4o", - messages: [ - { - role: "user", - content: "Review this demo seed implementation for best practices", - }, - ], - max_tokens: 1000, - }, - output: { - response: - "The demo seed implementation looks excellent. It covers all key aspects: diverse users, comprehensive MCPs, realistic monitoring data, and proper error handling...", - tokens: 876, - }, - isError: false, - durationMs: 2341, - offsetMs: -10 * MINUTES, - userRole: "developer", - userAgent: "cursor-agent/0.42.0", - gatewayKey: "openrouter", - properties: { - tokens_prompt: "15", - tokens_completion: "876", - cost_usd: "0.0175", - model: "openai/gpt-4o", - cache_hit: "false", - }, - }, - - // === 5 minutes ago === - { - connectionKey: "nanoBanana", - toolName: "generate_variations", - input: { - base_image_url: "https://assets.decocache.com/generated/abc123.png", - variations: 3, - }, - output: { - variations: [ - "https://assets.decocache.com/generated/abc123_v1.png", - "https://assets.decocache.com/generated/abc123_v2.png", - "https://assets.decocache.com/generated/abc123_v3.png", - ], - }, - isError: false, - durationMs: 4567, - offsetMs: -5 * MINUTES, - userRole: "developer", - userAgent: "mesh-demo-client/1.0", - gatewayKey: "smart", - properties: { - tokens: "1245", - cost_usd: "0.0498", - cache_hit: "false", - }, - }, - - // === Just now: Real-time activity === - { - connectionKey: "grain", - toolName: "search_transcripts", - input: { - query: "demo presentation budget", - limit: 5, - }, - output: { - results: [ - { - meeting_id: "meet_xyz789", - snippet: "...budget allocation for Q2 demo increased by 15%...", - timestamp: "00:23:45", - }, - ], - total_count: 1, - }, - isError: false, - durationMs: 567, - offsetMs: 0, - userRole: "billing", - userAgent: "grain-desktop/2.1.0", - gatewayKey: "smart", - properties: { - search_index_size: "547823", - cache_hit: "false", - }, - }, -]; diff --git a/apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts b/apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts new file mode 100644 index 0000000000..7cfffce7d9 --- /dev/null +++ b/apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts @@ -0,0 +1,823 @@ +/** + * Deco Bank Organization + * + * Large corporate banking environment simulating 3 months of usage: + * - 12 users across multiple departments + * - 35+ connections (3 well-known + verified MCPs from Deco Store) + * - 6 gateways (Default Hub + 5 specialized) + * - ~2500 synthetic + static monitoring logs + */ + +import type { Kysely } from "kysely"; +import type { Database } from "../../../../src/storage/types"; +import type { + OrgConfig, + OrgSeedResult, + OrgUser, + MonitoringLog, +} from "../seeder"; +import { createOrg, TIME, USER_AGENTS } from "../seeder"; +import { + getWellKnownConnections, + pickConnections, + pickGateways, +} from "../catalog"; + +// ============================================================================= +// Configuration +// ============================================================================= + +const EMAIL_DOMAIN = "@decobank.com"; + +const USERS: Record = { + cto: { + role: "admin", + memberRole: "owner", + name: "Carlos Mendes", + email: `carlos.mendes${EMAIL_DOMAIN}`, + }, + techLead: { + role: "admin", + memberRole: "admin", + name: "Ana Silva", + email: `ana.silva${EMAIL_DOMAIN}`, + }, + seniorDev1: { + role: "member", + memberRole: "member", + name: "Pedro Costa", + email: `pedro.costa${EMAIL_DOMAIN}`, + }, + seniorDev2: { + role: "member", + memberRole: "member", + name: "Mariana Santos", + email: `mariana.santos${EMAIL_DOMAIN}`, + }, + midDev1: { + role: "member", + memberRole: "member", + name: "Rafael Oliveira", + email: `rafael.oliveira${EMAIL_DOMAIN}`, + }, + junior: { + role: "member", + memberRole: "member", + name: "Gabriel Lima", + email: `gabriel.lima${EMAIL_DOMAIN}`, + }, + analyst: { + role: "member", + memberRole: "member", + name: "Lucas Fernandes", + email: `lucas.fernandes${EMAIL_DOMAIN}`, + }, + dataEngineer: { + role: "member", + memberRole: "member", + name: "Beatriz Rodrigues", + email: `beatriz.rodrigues${EMAIL_DOMAIN}`, + }, + security: { + role: "admin", + memberRole: "admin", + name: "Roberto Alves", + email: `roberto.alves${EMAIL_DOMAIN}`, + }, + compliance: { + role: "member", + memberRole: "member", + name: "Julia Ferreira", + email: `julia.ferreira${EMAIL_DOMAIN}`, + }, + productManager: { + role: "member", + memberRole: "member", + name: "Fernanda Souza", + email: `fernanda.souza${EMAIL_DOMAIN}`, + }, + qa: { + role: "member", + memberRole: "member", + name: "Ricardo Martins", + email: `ricardo.martins${EMAIL_DOMAIN}`, + }, +}; + +const USER_ACTIVITY_WEIGHTS: Record = { + techLead: 0.18, + seniorDev1: 0.15, + seniorDev2: 0.14, + midDev1: 0.12, + analyst: 0.11, + dataEngineer: 0.1, + junior: 0.08, + security: 0.06, + productManager: 0.04, + qa: 0.01, + cto: 0.01, + compliance: 0.0, +}; + +// Include well-known connections (Mesh MCP, MCP Registry, Deco Store) + business connections +const CONNECTIONS = { + ...getWellKnownConnections(), + ...pickConnections([ + // Development & Infrastructure + "github", + "vercel", + "supabase", + "prisma", + "cloudflare", + "aws", + // AI & LLM + "openrouter", + "perplexity", + "elevenlabs", + // Automation & Scraping + "apify", + "browserUse", + // Google Workspace + "gmail", + "googleCalendar", + "googleSheets", + "googleDocs", + "googleDrive", + "googleTagManager", + "youtube", + // Productivity & Documentation + "notion", + "grain", + "airtable", + "jira", + "hubspot", + // Communication + "discord", + "discordWebhook", + "slack", + "resend", + // Payments + "stripe", + // Brazilian APIs + "brasilApi", + "queridoDiario", + "datajud", + // Design & E-commerce + "figma", + "shopify", + "vtex", + "superfrete", + ]), +}; + +// Default Hub (production-like) + specialized gateways +const GATEWAYS = pickGateways([ + "defaultHub", + "llm", + "devGateway", + "compliance", + "dataGateway", + "allAccess", +]); + +// ============================================================================= +// Static Logs - Hand-crafted story moments over 90 days +// ============================================================================= + +const STATIC_LOGS: MonitoringLog[] = [ + // 90 days ago: Q4 Planning + { + connectionKey: "notion", + toolName: "create_page", + input: { parent_id: "workspace_root", title: "Q1 2024 OKRs" }, + output: { page_id: "page_q1_okrs" }, + isError: false, + durationMs: 345, + offsetMs: -90 * TIME.DAY, + userKey: "cto", + userAgent: USER_AGENTS.notionDesktop, + gatewayKey: "compliance", + }, + { + connectionKey: "grain", + toolName: "get_transcript", + input: { meeting_id: "meet_q4_review" }, + output: { transcript: "Q4 Review...", duration_minutes: 120 }, + isError: false, + durationMs: 1847, + offsetMs: -90 * TIME.DAY, + userKey: "cto", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "compliance", + }, + + // 85 days ago: Security PR + { + connectionKey: "github", + toolName: "create_pull_request", + input: { repo: "payment-gateway", title: "SECURITY: SQL injection patch" }, + output: { number: 1892, state: "open" }, + isError: false, + durationMs: 456, + offsetMs: -85 * TIME.DAY, + userKey: "security", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "devGateway", + }, + + // 75 days ago: Payment refund + { + connectionKey: "stripe", + toolName: "create_refund", + input: { charge: "ch_123", amount: 125000 }, + output: { id: "re_123", status: "succeeded" }, + isError: false, + durationMs: 1234, + offsetMs: -75 * TIME.DAY, + userKey: "techLead", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "allAccess", + }, + + // 70 days ago: Deployment + { + connectionKey: "vercel", + toolName: "list_deployments", + input: { projectId: "prj_banking_mobile" }, + output: { deployments: [{ uid: "dpl_mobile_v2", state: "READY" }] }, + isError: false, + durationMs: 234, + offsetMs: -70 * TIME.DAY, + userKey: "seniorDev1", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "devGateway", + }, + + // 65 days ago: AI code review + { + connectionKey: "openrouter", + toolName: "chat_completion", + input: { + model: "anthropic/claude-3.5-sonnet", + messages: [{ role: "user", content: "Review this code..." }], + }, + output: { response: "Security concerns found...", tokens_used: 1847 }, + isError: false, + durationMs: 3456, + offsetMs: -65 * TIME.DAY, + userKey: "seniorDev2", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "llm", + properties: { cost_usd: "0.047" }, + }, + + // 60 days ago: Analytics + { + connectionKey: "supabase", + toolName: "run_sql", + input: { project_id: "proj_analytics", query: "SELECT..." }, + output: { rows: [{ transactions: 12847 }] }, + isError: false, + durationMs: 678, + offsetMs: -60 * TIME.DAY, + userKey: "analyst", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "allAccess", + }, + + // 55 days ago: Postmortem + { + connectionKey: "notion", + toolName: "create_page", + input: { title: "Postmortem: Payment Gateway Timeout" }, + output: { page_id: "page_postmortem" }, + isError: false, + durationMs: 432, + offsetMs: -55 * TIME.DAY, + userKey: "techLead", + userAgent: USER_AGENTS.notionDesktop, + gatewayKey: "compliance", + }, + + // 45 days ago: Payment reconciliation + { + connectionKey: "stripe", + toolName: "list_payments", + input: { limit: 1000 }, + output: { data: [{ id: "pi_1", amount: 25000 }], total_count: 8734 }, + isError: false, + durationMs: 1234, + offsetMs: -45 * TIME.DAY, + userKey: "analyst", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "allAccess", + }, + + // 30 days ago: LLM cost analysis + { + connectionKey: "openrouter", + toolName: "get_usage_stats", + input: { period: "last_30_days" }, + output: { total_requests: 45678, total_cost: 1234.56 }, + isError: false, + durationMs: 234, + offsetMs: -30 * TIME.DAY, + userKey: "cto", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "llm", + }, + + // 20 days ago: Bug investigation + { + connectionKey: "github", + toolName: "search_code", + input: { query: "processRefund", org: "decobank" }, + output: { + total_count: 8, + items: [{ path: "src/services/refund-processor.ts" }], + }, + isError: false, + durationMs: 567, + offsetMs: -20 * TIME.DAY, + userKey: "seniorDev2", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "devGateway", + }, + + // 15 days ago: Compliance audit + { + connectionKey: "grain", + toolName: "get_transcript", + input: { meeting_id: "meet_compliance_audit" }, + output: { transcript: "BACEN Compliance Audit...", duration_minutes: 87 }, + isError: false, + durationMs: 2134, + offsetMs: -15 * TIME.DAY, + userKey: "compliance", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "compliance", + }, + + // 7 days ago: Balance check + { + connectionKey: "stripe", + toolName: "get_balance", + input: {}, + output: { available: [{ amount: 12456789, currency: "brl" }] }, + isError: false, + durationMs: 156, + offsetMs: -7 * TIME.DAY, + userKey: "qa", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "allAccess", + }, + + // 3 days ago: AI code generation + { + connectionKey: "openrouter", + toolName: "chat_completion", + input: { + model: "openai/gpt-4-turbo", + messages: [{ role: "user", content: "Generate TypeScript types..." }], + }, + output: { response: "Here are the types...", tokens_used: 687 }, + isError: false, + durationMs: 2345, + offsetMs: -3 * TIME.DAY, + userKey: "junior", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "llm", + }, + + // 2 days ago: Repository maintenance + { + connectionKey: "github", + toolName: "list_repositories", + input: { org: "decobank", type: "private" }, + output: { repositories: [{ name: "payment-gateway" }], total: 523 }, + isError: false, + durationMs: 223, + offsetMs: -2 * TIME.DAY, + userKey: "techLead", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "devGateway", + }, + + // Today: Daily standup + { + connectionKey: "notion", + toolName: "create_page", + input: { parent_id: "db_standups", title: "Daily Standup" }, + output: { page_id: "page_standup_today" }, + isError: false, + durationMs: 267, + offsetMs: -2 * TIME.HOUR, + userKey: "techLead", + userAgent: USER_AGENTS.notionDesktop, + gatewayKey: "compliance", + }, +]; + +// ============================================================================= +// Synthetic Log Generator +// ============================================================================= + +interface ToolTemplate { + toolName: string; + connectionKey: string; + weight: number; + avgDurationMs: number; + durationVariance: number; + sampleInputs: object[]; + sampleOutputs: object[]; + properties?: Record; +} + +const TOOL_TEMPLATES: ToolTemplate[] = [ + // GitHub (25%) + { + toolName: "list_repositories", + connectionKey: "github", + weight: 0.08, + avgDurationMs: 210, + durationVariance: 90, + sampleInputs: [{ org: "decobank" }], + sampleOutputs: [{ repositories: [], total: 523 }], + }, + { + toolName: "create_pull_request", + connectionKey: "github", + weight: 0.05, + avgDurationMs: 340, + durationVariance: 120, + sampleInputs: [{ repo: "payment-gateway", title: "Feature" }], + sampleOutputs: [{ number: 1234, state: "open" }], + }, + { + toolName: "get_pr_status", + connectionKey: "github", + weight: 0.06, + avgDurationMs: 180, + durationVariance: 70, + sampleInputs: [{ repo: "payment-gateway", pr_number: 1234 }], + sampleOutputs: [{ state: "open", checks: { passed: 7 } }], + }, + { + toolName: "search_code", + connectionKey: "github", + weight: 0.03, + avgDurationMs: 420, + durationVariance: 180, + sampleInputs: [{ query: "processPayment" }], + sampleOutputs: [{ items: [], total: 127 }], + }, + + // OpenRouter (22%) + { + toolName: "chat_completion", + connectionKey: "openrouter", + weight: 0.15, + avgDurationMs: 1850, + durationVariance: 1200, + sampleInputs: [{ model: "anthropic/claude-3.5-sonnet" }], + sampleOutputs: [{ response: "...", tokens_used: 856 }], + properties: { cost_usd: "0.018" }, + }, + { + toolName: "list_models", + connectionKey: "openrouter", + weight: 0.04, + avgDurationMs: 180, + durationVariance: 60, + sampleInputs: [{}], + sampleOutputs: [{ models: [], total: 127 }], + }, + { + toolName: "get_usage_stats", + connectionKey: "openrouter", + weight: 0.03, + avgDurationMs: 220, + durationVariance: 80, + sampleInputs: [{ period: "last_7_days" }], + sampleOutputs: [{ total_requests: 12847, total_cost: 2456.78 }], + }, + + // Notion (18%) + { + toolName: "search_pages", + connectionKey: "notion", + weight: 0.07, + avgDurationMs: 320, + durationVariance: 140, + sampleInputs: [{ query: "API documentation" }], + sampleOutputs: [{ results: [], total: 234 }], + }, + { + toolName: "get_page", + connectionKey: "notion", + weight: 0.06, + avgDurationMs: 240, + durationVariance: 90, + sampleInputs: [{ page_id: "page_123" }], + sampleOutputs: [{ title: "Documentation", version: 34 }], + }, + { + toolName: "create_page", + connectionKey: "notion", + weight: 0.03, + avgDurationMs: 380, + durationVariance: 140, + sampleInputs: [{ title: "New Page" }], + sampleOutputs: [{ page_id: "page_new" }], + }, + + // Grain (12%) + { + toolName: "list_meetings", + connectionKey: "grain", + weight: 0.05, + avgDurationMs: 280, + durationVariance: 100, + sampleInputs: [{ date_from: "2024-01-01" }], + sampleOutputs: [{ meetings: [], total: 234 }], + }, + { + toolName: "get_transcript", + connectionKey: "grain", + weight: 0.04, + avgDurationMs: 420, + durationVariance: 180, + sampleInputs: [{ meeting_id: "meet_123" }], + sampleOutputs: [{ transcript: "...", duration_minutes: 87 }], + }, + { + toolName: "search_meetings", + connectionKey: "grain", + weight: 0.03, + avgDurationMs: 380, + durationVariance: 150, + sampleInputs: [{ query: "compliance" }], + sampleOutputs: [{ results: [], total: 67 }], + }, + + // Stripe (8%) + { + toolName: "list_payments", + connectionKey: "stripe", + weight: 0.03, + avgDurationMs: 240, + durationVariance: 90, + sampleInputs: [{ limit: 100 }], + sampleOutputs: [{ data: [], has_more: true }], + }, + { + toolName: "get_balance", + connectionKey: "stripe", + weight: 0.02, + avgDurationMs: 150, + durationVariance: 50, + sampleInputs: [{}], + sampleOutputs: [{ available: [{ amount: 1245678 }] }], + }, + { + toolName: "create_refund", + connectionKey: "stripe", + weight: 0.01, + avgDurationMs: 320, + durationVariance: 120, + sampleInputs: [{ charge: "ch_123" }], + sampleOutputs: [{ id: "re_456", status: "succeeded" }], + }, + + // Vercel (9%) + { + toolName: "list_deployments", + connectionKey: "vercel", + weight: 0.04, + avgDurationMs: 220, + durationVariance: 80, + sampleInputs: [{ projectId: "prj_banking" }], + sampleOutputs: [{ deployments: [] }], + }, + { + toolName: "get_deployment", + connectionKey: "vercel", + weight: 0.02, + avgDurationMs: 180, + durationVariance: 70, + sampleInputs: [{ deployment_id: "dpl_123" }], + sampleOutputs: [{ state: "READY" }], + }, + { + toolName: "list_projects", + connectionKey: "vercel", + weight: 0.02, + avgDurationMs: 190, + durationVariance: 60, + sampleInputs: [{}], + sampleOutputs: [{ projects: [] }], + }, + + // Supabase (6%) + { + toolName: "list_projects", + connectionKey: "supabase", + weight: 0.02, + avgDurationMs: 210, + durationVariance: 80, + sampleInputs: [{}], + sampleOutputs: [{ projects: [], total: 23 }], + }, + { + toolName: "get_project_health", + connectionKey: "supabase", + weight: 0.02, + avgDurationMs: 180, + durationVariance: 70, + sampleInputs: [{ project_id: "proj_123" }], + sampleOutputs: [{ status: "healthy" }], + }, + { + toolName: "run_sql", + connectionKey: "supabase", + weight: 0.01, + avgDurationMs: 420, + durationVariance: 180, + sampleInputs: [{ query: "SELECT..." }], + sampleOutputs: [{ rows: [] }], + }, + + // GitHub Copilot (8%) + { + toolName: "get_completions", + connectionKey: "github", + weight: 0.05, + avgDurationMs: 450, + durationVariance: 200, + sampleInputs: [{ prompt: "function..." }], + sampleOutputs: [{ completions: [] }], + }, + { + toolName: "explain_code", + connectionKey: "github", + weight: 0.02, + avgDurationMs: 1200, + durationVariance: 400, + sampleInputs: [{ code: "..." }], + sampleOutputs: [{ explanation: "..." }], + }, + + // Prisma (6%) + { + toolName: "generate_schema", + connectionKey: "prisma", + weight: 0.02, + avgDurationMs: 890, + durationVariance: 300, + sampleInputs: [{ introspect: true }], + sampleOutputs: [{ schema: "...", models_count: 23 }], + }, + { + toolName: "run_migration", + connectionKey: "prisma", + weight: 0.02, + avgDurationMs: 2340, + durationVariance: 800, + sampleInputs: [{ migration_name: "add_index" }], + sampleOutputs: [{ success: true }], + }, + + // Apify (5%) + { + toolName: "run_actor", + connectionKey: "apify", + weight: 0.02, + avgDurationMs: 8500, + durationVariance: 3000, + sampleInputs: [{ actor_id: "apify/web-scraper" }], + sampleOutputs: [{ run_id: "run_123", status: "SUCCEEDED" }], + }, + { + toolName: "get_dataset", + connectionKey: "apify", + weight: 0.015, + avgDurationMs: 450, + durationVariance: 180, + sampleInputs: [{ dataset_id: "dataset_rates" }], + sampleOutputs: [{ items: [], total: 1247 }], + }, +]; + +const CONNECTION_TO_GATEWAY: Record = { + github: "devGateway", + vercel: "devGateway", + prisma: "devGateway", + supabase: "devGateway", + openrouter: "llm", + githubCopilot: "llm", + notion: "compliance", + grain: "compliance", + apify: "dataGateway", + stripe: "allAccess", +}; + +function weightedRandom(items: T[]): T { + const total = items.reduce((sum, item) => sum + item.weight, 0); + let r = Math.random() * total; + for (const item of items) { + r -= item.weight; + if (r <= 0) return item; + } + return items[items.length - 1]!; +} + +function generateSyntheticLogs(targetCount: number): MonitoringLog[] { + const logs: MonitoringLog[] = []; + const userWeights = Object.entries(USER_ACTIVITY_WEIGHTS).map( + ([key, weight]) => ({ key, weight }), + ); + + for (let i = 0; i < targetCount; i++) { + const template = weightedRandom(TOOL_TEMPLATES); + const userEntry = weightedRandom(userWeights); + const isError = Math.random() < 0.08; + + // Generate timestamp with recency bias (40% last 24h, 35% last 7d, 20% last 30d, 5% last 90d) + const r = Math.random(); + let daysAgo = + r < 0.4 + ? Math.random() + : r < 0.75 + ? 1 + Math.random() * 6 + : r < 0.95 + ? 7 + Math.random() * 23 + : 30 + Math.random() * 60; + + const timestamp = new Date(Date.now() - daysAgo * TIME.DAY); + // Adjust to working hours (8-20) + const hour = timestamp.getHours(); + if (hour < 8 || hour > 20) + timestamp.setHours(8 + Math.floor(Math.random() * 12)); + + const duration = + template.avgDurationMs + + (Math.random() - 0.5) * 2 * template.durationVariance; + const input = template.sampleInputs[ + Math.floor(Math.random() * template.sampleInputs.length) + ] as Record; + const output = ( + isError + ? { error: "Internal error" } + : template.sampleOutputs[ + Math.floor(Math.random() * template.sampleOutputs.length) + ] + ) as Record; + + logs.push({ + connectionKey: template.connectionKey, + toolName: template.toolName, + input, + output, + isError, + durationMs: Math.max(50, Math.round(duration)), + offsetMs: timestamp.getTime() - Date.now(), + userKey: userEntry.key, + userAgent: "mesh-client/1.0", + gatewayKey: CONNECTION_TO_GATEWAY[template.connectionKey] || "allAccess", + properties: template.properties, + }); + } + + return logs.sort((a, b) => a.offsetMs - b.offsetMs); +} + +// ============================================================================= +// Seed Function +// ============================================================================= + +export const DECO_BANK_SLUG = "deco-bank"; + +export async function seedDecoBank( + db: Kysely, +): Promise { + // Generate ~2500 synthetic logs + static story logs + const syntheticLogs = generateSyntheticLogs(2500); + const allLogs = [...STATIC_LOGS, ...syntheticLogs]; + + const config: OrgConfig = { + orgName: "Deco Bank", + orgSlug: DECO_BANK_SLUG, + users: USERS, + apiKeys: [ + { userKey: "cto", name: "CTO API Key" }, + { userKey: "techLead", name: "Tech Lead API Key" }, + ], + connections: CONNECTIONS, + gateways: GATEWAYS, + gatewayConnections: [{ gatewayKey: "llm", connectionKey: "openrouter" }], + logs: allLogs, + ownerUserKey: "cto", + }; + + return createOrg(db, config); +} diff --git a/apps/mesh/migrations/seeds/demo/orgs/onboarding.ts b/apps/mesh/migrations/seeds/demo/orgs/onboarding.ts new file mode 100644 index 0000000000..f134687a27 --- /dev/null +++ b/apps/mesh/migrations/seeds/demo/orgs/onboarding.ts @@ -0,0 +1,373 @@ +/** + * Onboarding Organization + * + * Minimal setup for demonstrating first steps: + * - 2 users (admin + developer) + * - 6 connections (3 well-known + GitHub, OpenRouter, Notion) + * - 1 gateway (Default Hub) + * - ~16 logs showing early adoption + */ + +import type { Kysely } from "kysely"; +import type { Database } from "../../../../src/storage/types"; +import type { + OrgConfig, + OrgSeedResult, + OrgUser, + MonitoringLog, +} from "../seeder"; +import { createOrg, TIME, USER_AGENTS } from "../seeder"; +import { + getWellKnownConnections, + pickConnections, + pickGateways, +} from "../catalog"; + +// ============================================================================= +// Configuration +// ============================================================================= + +const EMAIL_DOMAIN = "@onboarding.local"; + +const USERS: Record = { + admin: { + role: "admin", + memberRole: "owner", + name: "Alice Admin", + email: `admin${EMAIL_DOMAIN}`, + }, + developer: { + role: "member", + memberRole: "member", + name: "Dev Developer", + email: `developer${EMAIL_DOMAIN}`, + }, +}; + +// Include well-known connections (Mesh MCP, MCP Registry, Deco Store) + additional ones +const CONNECTIONS = { + ...getWellKnownConnections(), + ...pickConnections(["github", "openrouter", "notion"]), +}; +const GATEWAYS = pickGateways(["defaultHub"]); + +// ============================================================================= +// Monitoring Logs - Early adoption pattern over 48 hours +// ============================================================================= + +const LOGS: MonitoringLog[] = [ + // 2 days ago: First connection test + { + connectionKey: "github", + toolName: "list_repositories", + input: { per_page: 10 }, + output: { + repositories: ["my-first-project", "learning-mcp"], + total_count: 2, + }, + isError: false, + durationMs: 234, + offsetMs: -2 * TIME.DAY, + userKey: "admin", + userAgent: USER_AGENTS.meshClient, + gatewayKey: null, + properties: { cache_hit: "false" }, + }, + + // 1.5 days ago: Trying OpenRouter without auth + { + connectionKey: "openrouter", + toolName: "chat_completion", + input: { + model: "anthropic/claude-3.5-sonnet", + messages: [{ role: "user", content: "Hello!" }], + }, + output: { error: "API key required" }, + isError: true, + errorMessage: + "Authentication required. Please configure your OpenRouter API key.", + durationMs: 45, + offsetMs: -36 * TIME.HOUR, + userKey: "admin", + userAgent: USER_AGENTS.cursorAgent, + gatewayKey: "defaultHub", + properties: { auth_error: "true" }, + }, + + // 1 day ago: Developer joins + { + connectionKey: "github", + toolName: "get_repository", + input: { owner: "onboarding-org", repo: "my-first-project" }, + output: { + name: "my-first-project", + description: "Learning MCP integration", + default_branch: "main", + }, + isError: false, + durationMs: 156, + offsetMs: -1 * TIME.DAY, + userKey: "developer", + userAgent: USER_AGENTS.vscode, + gatewayKey: "defaultHub", + }, + + // 1 day ago: Trying Notion without auth + { + connectionKey: "notion", + toolName: "search_pages", + input: { query: "getting started" }, + output: { error: "OAuth authentication required" }, + isError: true, + errorMessage: "Please connect your Notion account to use this tool.", + durationMs: 67, + offsetMs: -1 * TIME.DAY + 2 * TIME.HOUR, + userKey: "admin", + userAgent: USER_AGENTS.notionDesktop, + gatewayKey: "defaultHub", + properties: { auth_error: "true" }, + }, + + // 20 hours ago: GitHub exploration + { + connectionKey: "github", + toolName: "list_issues", + input: { repo: "my-first-project", state: "open" }, + output: { + issues: [ + { number: 1, title: "Set up CI/CD" }, + { number: 2, title: "Add README" }, + ], + total_count: 2, + }, + isError: false, + durationMs: 189, + offsetMs: -20 * TIME.HOUR, + userKey: "developer", + userAgent: USER_AGENTS.ghCli, + gatewayKey: "defaultHub", + }, + + // 18 hours ago: Creating first issue + { + connectionKey: "github", + toolName: "create_issue", + input: { + repo: "my-first-project", + title: "Integrate MCP tools", + labels: ["enhancement"], + }, + output: { + issue_number: 3, + url: "https://github.com/onboarding-org/my-first-project/issues/3", + }, + isError: false, + durationMs: 312, + offsetMs: -18 * TIME.HOUR, + userKey: "admin", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "defaultHub", + }, + + // 12 hours ago: Admin exploring + { + connectionKey: "github", + toolName: "list_branches", + input: { repo: "my-first-project" }, + output: { branches: ["main", "feature/mcp-setup"], total_count: 2 }, + isError: false, + durationMs: 145, + offsetMs: -12 * TIME.HOUR, + userKey: "admin", + userAgent: USER_AGENTS.vscode, + gatewayKey: "defaultHub", + properties: { cache_hit: "true" }, + }, + + // 10 hours ago: Developer working + { + connectionKey: "github", + toolName: "get_file_contents", + input: { repo: "my-first-project", path: "README.md" }, + output: { content: "# My First Project", encoding: "utf-8", size: 52 }, + isError: false, + durationMs: 98, + offsetMs: -10 * TIME.HOUR, + userKey: "developer", + userAgent: USER_AGENTS.cursorAgent, + gatewayKey: "defaultHub", + }, + + // 8 hours ago: Another OpenRouter attempt + { + connectionKey: "openrouter", + toolName: "list_models", + input: {}, + output: { error: "API key required" }, + isError: true, + errorMessage: + "Authentication required. Please configure your OpenRouter API key.", + durationMs: 34, + offsetMs: -8 * TIME.HOUR, + userKey: "developer", + userAgent: USER_AGENTS.cursorAgent, + gatewayKey: "defaultHub", + properties: { auth_error: "true" }, + }, + + // 6 hours ago: Creating PR + { + connectionKey: "github", + toolName: "create_pull_request", + input: { + repo: "my-first-project", + title: "Add MCP configuration", + head: "feature/mcp-setup", + base: "main", + }, + output: { + pr_number: 1, + url: "https://github.com/onboarding-org/my-first-project/pull/1", + }, + isError: false, + durationMs: 287, + offsetMs: -6 * TIME.HOUR, + userKey: "developer", + userAgent: USER_AGENTS.ghCli, + gatewayKey: "defaultHub", + }, + + // 4 hours ago: Admin reviewing PR + { + connectionKey: "github", + toolName: "get_pull_request", + input: { repo: "my-first-project", pr_number: 1 }, + output: { + number: 1, + title: "Add MCP configuration", + state: "open", + mergeable: true, + }, + isError: false, + durationMs: 167, + offsetMs: -4 * TIME.HOUR, + userKey: "admin", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "defaultHub", + }, + + // 3 hours ago: Listing PRs + { + connectionKey: "github", + toolName: "list_pull_requests", + input: { repo: "my-first-project", state: "open" }, + output: { + pull_requests: [{ number: 1, title: "Add MCP configuration" }], + total_count: 1, + }, + isError: false, + durationMs: 134, + offsetMs: -3 * TIME.HOUR, + userKey: "developer", + userAgent: USER_AGENTS.vscode, + gatewayKey: "defaultHub", + properties: { cache_hit: "true" }, + }, + + // 2 hours ago: Checking issue + { + connectionKey: "github", + toolName: "get_issue", + input: { repo: "my-first-project", issue_number: 3 }, + output: { + number: 3, + title: "Integrate MCP tools", + state: "open", + comments: 1, + }, + isError: false, + durationMs: 112, + offsetMs: -2 * TIME.HOUR, + userKey: "admin", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "defaultHub", + }, + + // 1 hour ago: Recent activity + { + connectionKey: "github", + toolName: "list_commits", + input: { + repo: "my-first-project", + branch: "feature/mcp-setup", + per_page: 5, + }, + output: { + commits: [{ sha: "abc123", message: "Add MCP config files" }], + total_count: 2, + }, + isError: false, + durationMs: 178, + offsetMs: -1 * TIME.HOUR, + userKey: "developer", + userAgent: USER_AGENTS.cursorAgent, + gatewayKey: "defaultHub", + }, + + // 30 minutes ago: Last Notion attempt + { + connectionKey: "notion", + toolName: "list_databases", + input: {}, + output: { error: "OAuth authentication required" }, + isError: true, + errorMessage: "Please connect your Notion account to use this tool.", + durationMs: 52, + offsetMs: -30 * TIME.MINUTE, + userKey: "admin", + userAgent: USER_AGENTS.notionDesktop, + gatewayKey: "defaultHub", + properties: { auth_error: "true" }, + }, + + // Just now: Current activity + { + connectionKey: "github", + toolName: "list_repositories", + input: { per_page: 20 }, + output: { + repositories: ["my-first-project", "learning-mcp", "mcp-experiments"], + total_count: 3, + }, + isError: false, + durationMs: 198, + offsetMs: 0, + userKey: "admin", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "defaultHub", + }, +]; + +// ============================================================================= +// Seed Function +// ============================================================================= + +export const ONBOARDING_SLUG = "onboarding"; + +export async function seedOnboarding( + db: Kysely, +): Promise { + const config: OrgConfig = { + orgName: "Onboarding", + orgSlug: ONBOARDING_SLUG, + users: USERS, + apiKeys: [{ userKey: "admin", name: "Onboarding Admin Key" }], + connections: CONNECTIONS, + gateways: GATEWAYS, + gatewayConnections: [], + logs: LOGS, + ownerUserKey: "admin", + }; + + return createOrg(db, config); +} diff --git a/apps/mesh/migrations/seeds/demo/seeder.ts b/apps/mesh/migrations/seeds/demo/seeder.ts new file mode 100644 index 0000000000..85046a797c --- /dev/null +++ b/apps/mesh/migrations/seeds/demo/seeder.ts @@ -0,0 +1,540 @@ +/** + * Demo Seeder + * + * Consolidated module containing: + * - Type definitions + * - Record factory functions + * - Generic organization seeder + * - Shared configuration + */ + +import type { Kysely } from "kysely"; +import type { Database } from "../../../src/storage/types"; +import { hashPassword } from "better-auth/crypto"; + +// ============================================================================= +// Shared Configuration +// ============================================================================= + +export const CONFIG = { + PASSWORD: "demo123", + USER_AGENT_DEFAULT: "mesh-demo-client/1.0", +} as const; + +export const USER_AGENTS = { + meshClient: "mesh-demo-client/1.0", + cursorAgent: "cursor-agent/0.42.0", + claudeDesktop: "claude-desktop/1.2.0", + vscode: "vscode-mcp/1.0.0", + ghCli: "gh-cli/2.40.0", + slackBot: "slack-mcp-bot/1.0", + notionDesktop: "notion-desktop/3.5.0", + grainDesktop: "grain-desktop/2.1.0", +} as const; + +export const TIME = { + MINUTE: 60 * 1000, + HOUR: 60 * 60 * 1000, + DAY: 24 * 60 * 60 * 1000, +} as const; + +// ============================================================================= +// Type Definitions +// ============================================================================= + +export type MemberRole = "owner" | "admin" | "member"; + +export interface OrgUser { + role: "admin" | "member"; + memberRole: MemberRole; + name: string; + email: string; +} + +export interface Connection { + title: string; + description: string; + icon: string; + appName: string; + connectionUrl: string; + connectionToken: string | null; + metadata: { + provider: string; + requiresOAuth?: boolean; + requiresApiKey?: boolean; + official?: boolean; + decoHosted?: boolean; + }; +} + +export interface Gateway { + title: string; + description: string; + toolSelectionStrategy: "passthrough" | "code_execution"; + toolSelectionMode: "inclusion" | "exclusion"; + icon: string | null; + isDefault: boolean; + connections: string[]; +} + +export interface MonitoringLog { + connectionKey: string; + toolName: string; + input: Record; + output: Record; + isError: boolean; + errorMessage?: string; + durationMs: number; + offsetMs: number; + userKey: string; + userAgent: string; + gatewayKey: string | null; + properties?: Record; +} + +export interface OrgConfig { + orgName: string; + orgSlug: string; + users: Record; + apiKeys?: { userKey: string; name: string }[]; + connections: Record; + gateways: Record; + gatewayConnections?: { gatewayKey: string; connectionKey: string }[]; + logs: MonitoringLog[]; + ownerUserKey: string; +} + +export interface OrgSeedResult { + organizationId: string; + organizationName: string; + organizationSlug: string; + userIds: Record; + userEmails: Record; + apiKeys: Record; + connectionIds: Record; + gatewayIds: Record; + logCount: number; +} + +// ============================================================================= +// ID Generator +// ============================================================================= + +export function generateId(prefix: string): string { + return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; +} + +// ============================================================================= +// Record Factories (internal) +// ============================================================================= + +function createUserRecord( + userId: string, + email: string, + name: string, + role: string, + timestamp: string, +) { + return { + id: userId, + email, + emailVerified: 1, + name, + image: null, + role, + banned: null, + banReason: null, + banExpires: null, + createdAt: timestamp, + updatedAt: timestamp, + }; +} + +function createAccountRecord( + userId: string, + email: string, + passwordHash: string, + timestamp: string, +) { + return { + id: generateId("account"), + userId, + accountId: email, + providerId: "credential", + password: passwordHash, + accessToken: null, + refreshToken: null, + accessTokenExpiresAt: null, + refreshTokenExpiresAt: null, + scope: null, + idToken: null, + createdAt: timestamp, + updatedAt: timestamp, + }; +} + +export function createMemberRecord( + organizationId: string, + userId: string, + role: MemberRole, + timestamp: string, +) { + return { + id: generateId("member"), + organizationId, + userId, + role, + createdAt: timestamp, + }; +} + +function createApiKeyRecord( + userId: string, + name: string, + key: string, + timestamp: string, +) { + return { + id: generateId("apikey"), + name, + userId, + key, + createdAt: timestamp, + updatedAt: timestamp, + }; +} + +function createConnectionRecord( + connectionId: string, + organizationId: string, + createdBy: string, + conn: Connection, + timestamp: string, +) { + return { + id: connectionId, + organization_id: organizationId, + created_by: createdBy, + title: conn.title, + description: conn.description, + icon: conn.icon, + app_name: conn.appName, + app_id: null, + connection_type: "HTTP" as const, + connection_url: conn.connectionUrl, + connection_token: conn.connectionToken, + connection_headers: null, + oauth_config: null, + configuration_state: null, + configuration_scopes: null, + metadata: JSON.stringify(conn.metadata), + tools: null, + bindings: null, + status: "active" as const, + created_at: timestamp, + updated_at: timestamp, + }; +} + +function createGatewayRecord( + gatewayId: string, + organizationId: string, + gateway: Gateway, + createdBy: string, + timestamp: string, +) { + return { + id: gatewayId, + organization_id: organizationId, + title: gateway.title, + description: gateway.description, + tool_selection_strategy: gateway.toolSelectionStrategy, + tool_selection_mode: gateway.toolSelectionMode, + icon: gateway.icon, + status: "active" as const, + is_default: gateway.isDefault ? 1 : 0, + created_at: timestamp, + updated_at: timestamp, + created_by: createdBy, + updated_by: null, + }; +} + +function createGatewayConnectionRecord( + gatewayId: string, + connectionId: string, + timestamp: string, +) { + return { + id: generateId("gtw_conn"), + gateway_id: gatewayId, + connection_id: connectionId, + selected_tools: null, + selected_resources: null, + selected_prompts: null, + created_at: timestamp, + }; +} + +function createMonitoringLogRecord( + organizationId: string, + connectionId: string, + connectionTitle: string, + log: MonitoringLog, + timestamp: string, + userId: string, + gatewayId: string | null, +) { + return { + id: generateId("log"), + organization_id: organizationId, + connection_id: connectionId, + connection_title: connectionTitle, + tool_name: log.toolName, + input: JSON.stringify(log.input), + output: JSON.stringify(log.output), + is_error: log.isError ? 1 : 0, + error_message: log.errorMessage ?? null, + duration_ms: log.durationMs, + timestamp, + user_id: userId, + request_id: generateId("req"), + user_agent: log.userAgent, + gateway_id: gatewayId, + properties: log.properties ? JSON.stringify(log.properties) : null, + }; +} + +// ============================================================================= +// Generic Organization Seeder +// ============================================================================= + +export async function createOrg( + db: Kysely, + config: OrgConfig, +): Promise { + const now = new Date().toISOString(); + const orgId = generateId("org"); + + // Generate user IDs + const userIds: Record = {}; + const userEmails: Record = {}; + for (const [key, user] of Object.entries(config.users)) { + userIds[key] = generateId("user"); + userEmails[key] = user.email; + } + + // 1. Create Organization + await db + .insertInto("organization") + .values({ + id: orgId, + slug: config.orgSlug, + name: config.orgName, + createdAt: now, + }) + .execute(); + + // 2. Create Users + const passwordHash = await hashPassword(CONFIG.PASSWORD); + for (const [key, user] of Object.entries(config.users)) { + await db + // @ts-ignore: Better Auth user table + .insertInto("user") + .values( + createUserRecord(userIds[key]!, user.email, user.name, user.role, now), + ) + .execute(); + } + + // 3. Create Credential Accounts + const accountRecords = Object.entries(config.users).map(([key, user]) => + createAccountRecord(userIds[key]!, user.email, passwordHash, now), + ); + // @ts-ignore: Better Auth account table + await db.insertInto("account").values(accountRecords).execute(); + + // 4. Link Users to Organization + const memberRecords = Object.entries(config.users).map(([key, user]) => + createMemberRecord(orgId, userIds[key]!, user.memberRole, now), + ); + await db.insertInto("member").values(memberRecords).execute(); + + // 5. Create API Keys + const apiKeyResults: Record = {}; + if (config.apiKeys?.length) { + const apiKeyRecords = config.apiKeys.map((apiKey) => { + const keyHash = `${config.orgSlug}_${apiKey.userKey}_${generateId("key")}`; + apiKeyResults[apiKey.userKey] = keyHash; + return createApiKeyRecord( + userIds[apiKey.userKey]!, + apiKey.name, + keyHash, + now, + ); + }); + // @ts-ignore: Better Auth apikey table + await db.insertInto("apikey").values(apiKeyRecords).execute(); + } + + // 6. Create Connections + const connectionIds: Record = {}; + for (const key of Object.keys(config.connections)) { + connectionIds[key] = generateId("conn"); + } + const ownerUserId = userIds[config.ownerUserKey]!; + const connectionRecords = Object.entries(config.connections).map( + ([key, conn]) => + createConnectionRecord( + connectionIds[key]!, + orgId, + ownerUserId, + conn, + now, + ), + ); + await db.insertInto("connections").values(connectionRecords).execute(); + + // 7. Create Gateways + const gatewayIds: Record = {}; + for (const key of Object.keys(config.gateways)) { + gatewayIds[key] = generateId("gtw"); + } + const gatewayRecords = Object.entries(config.gateways).map(([key, gateway]) => + createGatewayRecord(gatewayIds[key]!, orgId, gateway, ownerUserId, now), + ); + await db.insertInto("gateways").values(gatewayRecords).execute(); + + // 8. Link Gateways to Connections + if (config.gatewayConnections?.length) { + const gwConnRecords = config.gatewayConnections.map((link) => + createGatewayConnectionRecord( + gatewayIds[link.gatewayKey]!, + connectionIds[link.connectionKey]!, + now, + ), + ); + await db.insertInto("gateway_connections").values(gwConnRecords).execute(); + } + + // 9. Create Monitoring Logs + if (config.logs.length > 0) { + const logRecords = config.logs.map((log) => { + const timestamp = new Date(Date.now() + log.offsetMs).toISOString(); + const gatewayId = log.gatewayKey + ? (gatewayIds[log.gatewayKey] ?? null) + : null; + return createMonitoringLogRecord( + orgId, + connectionIds[log.connectionKey]!, + config.connections[log.connectionKey]!.title, + log, + timestamp, + userIds[log.userKey]!, + gatewayId, + ); + }); + await db.insertInto("monitoring_logs").values(logRecords).execute(); + } + + // 10. Create Organization Settings + await db + .insertInto("organization_settings") + .values({ + organizationId: orgId, + sidebar_items: null, + createdAt: now, + updatedAt: now, + }) + .execute(); + + return { + organizationId: orgId, + organizationName: config.orgName, + organizationSlug: config.orgSlug, + userIds, + userEmails, + apiKeys: apiKeyResults, + connectionIds, + gatewayIds, + logCount: config.logs.length, + }; +} + +// ============================================================================= +// Cleanup Helper +// ============================================================================= + +export async function cleanupOrgs( + db: Kysely, + slugs: string[], +): Promise { + const existingOrgs = await db + .selectFrom("organization") + .select(["id", "slug"]) + .where("slug", "in", slugs) + .execute(); + + if (existingOrgs.length === 0) return; + + console.log(`🧹 Cleaning up ${existingOrgs.length} existing demo org(s)...`); + const orgIds = existingOrgs.map((org) => org.id); + + await db + .deleteFrom("monitoring_logs") + .where("organization_id", "in", orgIds) + .execute(); + + const gatewayIds = await db + .selectFrom("gateways") + .select("id") + .where("organization_id", "in", orgIds) + .execute() + .then((rows) => rows.map((r) => r.id)); + + if (gatewayIds.length > 0) { + await db + .deleteFrom("gateway_connections") + .where("gateway_id", "in", gatewayIds) + .execute(); + } + + await db + .deleteFrom("gateways") + .where("organization_id", "in", orgIds) + .execute(); + await db + .deleteFrom("connections") + .where("organization_id", "in", orgIds) + .execute(); + await db + .deleteFrom("organization_settings") + .where("organizationId", "in", orgIds) + .execute(); + + const memberUserIds = await db + .selectFrom("member") + .select("userId") + .where("organizationId", "in", orgIds) + .execute() + .then((rows) => rows.map((r) => r.userId)); + + await db.deleteFrom("member").where("organizationId", "in", orgIds).execute(); + + if (memberUserIds.length > 0) { + // biome-ignore format: keep on single line for ts-ignore + // @ts-ignore: Better Auth tables not in Database type + await db.deleteFrom("apikey").where("userId", "in", memberUserIds).execute(); + + // biome-ignore format: keep on single line for ts-ignore + // @ts-ignore: Better Auth tables not in Database type + await db.deleteFrom("account").where("userId", "in", memberUserIds).execute(); + + await db.deleteFrom("user").where("id", "in", memberUserIds).execute(); + } + + await db.deleteFrom("organization").where("id", "in", orgIds).execute(); + console.log( + ` ✅ Cleaned up: ${existingOrgs.map((o) => o.slug).join(", ")}`, + ); +} diff --git a/apps/mesh/migrations/seeds/demo/types.ts b/apps/mesh/migrations/seeds/demo/types.ts deleted file mode 100644 index cbdbb6d43c..0000000000 --- a/apps/mesh/migrations/seeds/demo/types.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Shared types for demo seed - */ - -export interface DemoSeedResult { - organizationId: string; - organizationName: string; - organizationSlug: string; - users: { - adminId: string; - adminEmail: string; - developerId: string; - developerEmail: string; - analystId: string; - analystEmail: string; - billingId: string; - billingEmail: string; - viewerId: string; - viewerEmail: string; - }; - apiKeys: { - admin: string; - member: string; - }; - connectionIds: string[]; - gatewayIds: string[]; -} - -export interface DemoUser { - role: "admin" | "member"; - name: string; - email: string; -} - -export interface DemoConnection { - title: string; - description: string; - icon: string; - appName: string; - connectionUrl: string; - connectionToken: string | null; - configurationState: "needs_auth" | null; - metadata: { - provider: string; - requiresOAuth?: boolean; - requiresApiKey?: boolean; - official?: boolean; - decoHosted?: boolean; - demoToken?: boolean; - demoNote?: string; - }; -} - -export interface DemoGateway { - title: string; - description: string; - toolSelectionStrategy: "passthrough" | "code_execution"; - toolSelectionMode: "inclusion" | "exclusion"; - icon: string | null; - isDefault: boolean; - connections: string[]; -} - -export interface DemoMonitoringLog { - connectionKey: string; - toolName: string; - input: Record; - output: Record; - isError: boolean; - errorMessage?: string; - durationMs: number; - offsetMs: number; // Time offset from now in milliseconds - userRole: "admin" | "developer" | "analyst" | "billing" | "viewer"; - userAgent: string; - gatewayKey: string | null; - properties?: Record; -} diff --git a/apps/mesh/migrations/seeds/index.ts b/apps/mesh/migrations/seeds/index.ts index 37a3d64de4..aefd3b60c0 100644 --- a/apps/mesh/migrations/seeds/index.ts +++ b/apps/mesh/migrations/seeds/index.ts @@ -9,7 +9,7 @@ import type { Kysely } from "kysely"; import type { Database } from "../../src/storage/types"; export type { BenchmarkSeedResult } from "./benchmark"; -export type { DemoSeedResult } from "./demo"; +export type { DemoSeedResult } from "./demo/index"; /** * Seed function signature From c531d9f5d90f9369a3a8200dc2016263de936d94 Mon Sep 17 00:00:00 2001 From: aline-pereira Date: Thu, 8 Jan 2026 21:45:36 -0300 Subject: [PATCH 05/12] Merge branch 'main' of github.com:decocms/mesh into feat/demo-seed From 05344c725e1730bca21c3c7310d4a28d0a3a7b6d Mon Sep 17 00:00:00 2001 From: aline-pereira Date: Fri, 9 Jan 2026 08:49:40 -0300 Subject: [PATCH 06/12] added ci to seed --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5ad7643361..da24f7f3d4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,5 +34,8 @@ jobs: - name: Run tests run: bun test + - name: Test migrations with demo seed + run: cd apps/mesh && SEED=demo bun run migrate + - name: Run knip run: bun run knip From dc3111515983f7304486fff22d8a7dd4c40f9583 Mon Sep 17 00:00:00 2001 From: aline-pereira Date: Fri, 9 Jan 2026 11:35:08 -0300 Subject: [PATCH 07/12] registry installed --- apps/mesh/migrations/seeds/demo/catalog.ts | 37 ++ .../migrations/seeds/demo/orgs/deco-bank.ts | 457 +++++++++--------- .../migrations/seeds/demo/orgs/onboarding.ts | 25 +- apps/mesh/migrations/seeds/demo/seeder.ts | 77 ++- 4 files changed, 338 insertions(+), 258 deletions(-) diff --git a/apps/mesh/migrations/seeds/demo/catalog.ts b/apps/mesh/migrations/seeds/demo/catalog.ts index d0b84b6e8a..dab7697da2 100644 --- a/apps/mesh/migrations/seeds/demo/catalog.ts +++ b/apps/mesh/migrations/seeds/demo/catalog.ts @@ -14,6 +14,40 @@ import type { Connection, Gateway } from "./seeder"; // Well-Known Connections (installed by default in production) // ============================================================================= +// Registry tools (COLLECTION_REGISTRY_APP_* pattern) +const REGISTRY_TOOLS = [ + { + name: "COLLECTION_REGISTRY_APP_LIST", + description: "List all apps in the registry", + inputSchema: { + type: "object", + properties: { + limit: { type: "number" }, + cursor: { type: "string" }, + }, + }, + }, + { + name: "COLLECTION_REGISTRY_APP_GET", + description: "Get a specific app from the registry", + inputSchema: { + type: "object", + properties: { + id: { type: "string" }, + }, + required: ["id"], + }, + }, + { + name: "COLLECTION_REGISTRY_APP_FILTERS", + description: "Get available filters for registry apps", + inputSchema: { + type: "object", + properties: {}, + }, + }, +]; + export const WELL_KNOWN_CONNECTIONS = { meshMcp: { title: "Mesh MCP", @@ -23,6 +57,7 @@ export const WELL_KNOWN_CONNECTIONS = { connectionUrl: "https://mesh-admin.decocms.com/mcp", connectionToken: null, metadata: { provider: "deco", decoHosted: true }, + tools: [], // Mesh MCP tools are dynamic, populated at runtime }, mcpRegistry: { @@ -33,6 +68,7 @@ export const WELL_KNOWN_CONNECTIONS = { connectionUrl: "https://sites-registry.decocache.com/mcp", connectionToken: null, metadata: { provider: "deco", decoHosted: true }, + tools: REGISTRY_TOOLS, }, decoStore: { @@ -43,6 +79,7 @@ export const WELL_KNOWN_CONNECTIONS = { connectionUrl: "https://api.decocms.com/mcp/registry", connectionToken: null, metadata: { provider: "deco", decoHosted: true }, + tools: REGISTRY_TOOLS, }, } as const satisfies Record; diff --git a/apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts b/apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts index 7cfffce7d9..7fe102ec4b 100644 --- a/apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts +++ b/apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts @@ -1,11 +1,11 @@ /** * Deco Bank Organization * - * Large corporate banking environment simulating 3 months of usage: + * Large corporate banking environment simulating 30 days of high-volume usage: * - 12 users across multiple departments - * - 35+ connections (3 well-known + verified MCPs from Deco Store) + * - 24 connections (3 well-known + verified MCPs from Deco Store) * - 6 gateways (Default Hub + 5 specialized) - * - ~2500 synthetic + static monitoring logs + * - ~1M synthetic + static monitoring logs (200k in last 24h, 800k in last 30 days) */ import type { Kysely } from "kysely"; @@ -43,38 +43,38 @@ const USERS: Record = { email: `ana.silva${EMAIL_DOMAIN}`, }, seniorDev1: { - role: "member", - memberRole: "member", + role: "user", + memberRole: "user", name: "Pedro Costa", email: `pedro.costa${EMAIL_DOMAIN}`, }, seniorDev2: { - role: "member", - memberRole: "member", + role: "user", + memberRole: "user", name: "Mariana Santos", email: `mariana.santos${EMAIL_DOMAIN}`, }, midDev1: { - role: "member", - memberRole: "member", + role: "user", + memberRole: "user", name: "Rafael Oliveira", email: `rafael.oliveira${EMAIL_DOMAIN}`, }, junior: { - role: "member", - memberRole: "member", + role: "user", + memberRole: "user", name: "Gabriel Lima", email: `gabriel.lima${EMAIL_DOMAIN}`, }, analyst: { - role: "member", - memberRole: "member", + role: "user", + memberRole: "user", name: "Lucas Fernandes", email: `lucas.fernandes${EMAIL_DOMAIN}`, }, dataEngineer: { - role: "member", - memberRole: "member", + role: "user", + memberRole: "user", name: "Beatriz Rodrigues", email: `beatriz.rodrigues${EMAIL_DOMAIN}`, }, @@ -85,20 +85,20 @@ const USERS: Record = { email: `roberto.alves${EMAIL_DOMAIN}`, }, compliance: { - role: "member", - memberRole: "member", + role: "user", + memberRole: "user", name: "Julia Ferreira", email: `julia.ferreira${EMAIL_DOMAIN}`, }, productManager: { - role: "member", - memberRole: "member", + role: "user", + memberRole: "user", name: "Fernanda Souza", email: `fernanda.souza${EMAIL_DOMAIN}`, }, qa: { - role: "member", - memberRole: "member", + role: "user", + memberRole: "user", name: "Ricardo Martins", email: `ricardo.martins${EMAIL_DOMAIN}`, }, @@ -123,77 +123,65 @@ const USER_ACTIVITY_WEIGHTS: Record = { const CONNECTIONS = { ...getWellKnownConnections(), ...pickConnections([ - // Development & Infrastructure + "openrouter", "github", - "vercel", - "supabase", - "prisma", "cloudflare", "aws", - // AI & LLM - "openrouter", - "perplexity", - "elevenlabs", - // Automation & Scraping - "apify", - "browserUse", - // Google Workspace "gmail", "googleCalendar", - "googleSheets", "googleDocs", "googleDrive", + "googleSheets", "googleTagManager", - "youtube", - // Productivity & Documentation - "notion", - "grain", - "airtable", "jira", - "hubspot", - // Communication + "brasilApi", + "apify", + "airtable", + "slack", "discord", "discordWebhook", - "slack", - "resend", - // Payments - "stripe", - // Brazilian APIs - "brasilApi", - "queridoDiario", - "datajud", - // Design & E-commerce "figma", - "shopify", - "vtex", - "superfrete", + "grain", + "notion", + "perplexity", ]), }; // Default Hub (production-like) + specialized gateways -const GATEWAYS = pickGateways([ - "defaultHub", - "llm", - "devGateway", - "compliance", - "dataGateway", - "allAccess", -]); +const GATEWAYS = { + ...pickGateways([ + "llm", + "devGateway", + "compliance", + "dataGateway", + "allAccess", + ]), + // Override defaultHub to include only well-known connections (production behavior) + defaultHub: { + title: "Default Hub", + description: "Auto-created Hub for organization", + toolSelectionStrategy: "passthrough" as const, + toolSelectionMode: "inclusion" as const, + icon: null, + isDefault: true, + connections: ["meshMcp", "mcpRegistry", "decoStore"], + }, +}; // ============================================================================= -// Static Logs - Hand-crafted story moments over 90 days +// Static Logs - Hand-crafted story moments over 30 days // ============================================================================= const STATIC_LOGS: MonitoringLog[] = [ - // 90 days ago: Q4 Planning + // 30 days ago: Monthly planning { connectionKey: "notion", toolName: "create_page", - input: { parent_id: "workspace_root", title: "Q1 2024 OKRs" }, - output: { page_id: "page_q1_okrs" }, + input: { parent_id: "workspace_root", title: "Monthly OKRs" }, + output: { page_id: "page_monthly_okrs" }, isError: false, durationMs: 345, - offsetMs: -90 * TIME.DAY, + offsetMs: -30 * TIME.DAY, userKey: "cto", userAgent: USER_AGENTS.notionDesktop, gatewayKey: "compliance", @@ -201,17 +189,17 @@ const STATIC_LOGS: MonitoringLog[] = [ { connectionKey: "grain", toolName: "get_transcript", - input: { meeting_id: "meet_q4_review" }, - output: { transcript: "Q4 Review...", duration_minutes: 120 }, + input: { meeting_id: "meet_monthly_review" }, + output: { transcript: "Monthly Review...", duration_minutes: 120 }, isError: false, durationMs: 1847, - offsetMs: -90 * TIME.DAY, + offsetMs: -30 * TIME.DAY, userKey: "cto", userAgent: USER_AGENTS.meshClient, gatewayKey: "compliance", }, - // 85 days ago: Security PR + // 28 days ago: Security PR { connectionKey: "github", toolName: "create_pull_request", @@ -219,41 +207,41 @@ const STATIC_LOGS: MonitoringLog[] = [ output: { number: 1892, state: "open" }, isError: false, durationMs: 456, - offsetMs: -85 * TIME.DAY, + offsetMs: -28 * TIME.DAY, userKey: "security", userAgent: USER_AGENTS.meshClient, gatewayKey: "devGateway", }, - // 75 days ago: Payment refund + // 25 days ago: Infrastructure check { - connectionKey: "stripe", - toolName: "create_refund", - input: { charge: "ch_123", amount: 125000 }, - output: { id: "re_123", status: "succeeded" }, + connectionKey: "cloudflare", + toolName: "list_zones", + input: {}, + output: { zones: [{ name: "decobank.com", status: "active" }] }, isError: false, - durationMs: 1234, - offsetMs: -75 * TIME.DAY, + durationMs: 234, + offsetMs: -25 * TIME.DAY, userKey: "techLead", userAgent: USER_AGENTS.meshClient, - gatewayKey: "allAccess", + gatewayKey: "devGateway", }, - // 70 days ago: Deployment + // 23 days ago: AWS resources check { - connectionKey: "vercel", - toolName: "list_deployments", - input: { projectId: "prj_banking_mobile" }, - output: { deployments: [{ uid: "dpl_mobile_v2", state: "READY" }] }, + connectionKey: "aws", + toolName: "list_s3_buckets", + input: {}, + output: { buckets: [{ name: "decobank-prod-assets" }] }, isError: false, - durationMs: 234, - offsetMs: -70 * TIME.DAY, + durationMs: 345, + offsetMs: -23 * TIME.DAY, userKey: "seniorDev1", userAgent: USER_AGENTS.meshClient, gatewayKey: "devGateway", }, - // 65 days ago: AI code review + // 22 days ago: AI code review { connectionKey: "openrouter", toolName: "chat_completion", @@ -264,28 +252,28 @@ const STATIC_LOGS: MonitoringLog[] = [ output: { response: "Security concerns found...", tokens_used: 1847 }, isError: false, durationMs: 3456, - offsetMs: -65 * TIME.DAY, + offsetMs: -22 * TIME.DAY, userKey: "seniorDev2", userAgent: USER_AGENTS.meshClient, gatewayKey: "llm", properties: { cost_usd: "0.047" }, }, - // 60 days ago: Analytics + // 20 days ago: Brasil API integration { - connectionKey: "supabase", - toolName: "run_sql", - input: { project_id: "proj_analytics", query: "SELECT..." }, - output: { rows: [{ transactions: 12847 }] }, + connectionKey: "brasilApi", + toolName: "get_bank_info", + input: { code: "341" }, + output: { name: "Itaú", fullName: "Itaú Unibanco" }, isError: false, - durationMs: 678, - offsetMs: -60 * TIME.DAY, + durationMs: 456, + offsetMs: -20 * TIME.DAY, userKey: "analyst", userAgent: USER_AGENTS.meshClient, - gatewayKey: "allAccess", + gatewayKey: "dataGateway", }, - // 55 days ago: Postmortem + // 18 days ago: Postmortem { connectionKey: "notion", toolName: "create_page", @@ -293,27 +281,27 @@ const STATIC_LOGS: MonitoringLog[] = [ output: { page_id: "page_postmortem" }, isError: false, durationMs: 432, - offsetMs: -55 * TIME.DAY, + offsetMs: -18 * TIME.DAY, userKey: "techLead", userAgent: USER_AGENTS.notionDesktop, gatewayKey: "compliance", }, - // 45 days ago: Payment reconciliation + // 16 days ago: Gmail automation { - connectionKey: "stripe", - toolName: "list_payments", - input: { limit: 1000 }, - output: { data: [{ id: "pi_1", amount: 25000 }], total_count: 8734 }, + connectionKey: "gmail", + toolName: "send_email", + input: { to: "support@decobank.com", subject: "Weekly Report" }, + output: { id: "msg_123", threadId: "thread_456" }, isError: false, - durationMs: 1234, - offsetMs: -45 * TIME.DAY, + durationMs: 567, + offsetMs: -16 * TIME.DAY, userKey: "analyst", userAgent: USER_AGENTS.meshClient, gatewayKey: "allAccess", }, - // 30 days ago: LLM cost analysis + // 14 days ago: LLM cost analysis { connectionKey: "openrouter", toolName: "get_usage_stats", @@ -321,13 +309,13 @@ const STATIC_LOGS: MonitoringLog[] = [ output: { total_requests: 45678, total_cost: 1234.56 }, isError: false, durationMs: 234, - offsetMs: -30 * TIME.DAY, + offsetMs: -14 * TIME.DAY, userKey: "cto", userAgent: USER_AGENTS.meshClient, gatewayKey: "llm", }, - // 20 days ago: Bug investigation + // 12 days ago: Bug investigation { connectionKey: "github", toolName: "search_code", @@ -338,13 +326,13 @@ const STATIC_LOGS: MonitoringLog[] = [ }, isError: false, durationMs: 567, - offsetMs: -20 * TIME.DAY, + offsetMs: -12 * TIME.DAY, userKey: "seniorDev2", userAgent: USER_AGENTS.meshClient, gatewayKey: "devGateway", }, - // 15 days ago: Compliance audit + // 10 days ago: Compliance audit { connectionKey: "grain", toolName: "get_transcript", @@ -352,27 +340,27 @@ const STATIC_LOGS: MonitoringLog[] = [ output: { transcript: "BACEN Compliance Audit...", duration_minutes: 87 }, isError: false, durationMs: 2134, - offsetMs: -15 * TIME.DAY, + offsetMs: -10 * TIME.DAY, userKey: "compliance", userAgent: USER_AGENTS.meshClient, gatewayKey: "compliance", }, - // 7 days ago: Balance check + // 8 days ago: Slack notification { - connectionKey: "stripe", - toolName: "get_balance", - input: {}, - output: { available: [{ amount: 12456789, currency: "brl" }] }, + connectionKey: "slack", + toolName: "post_message", + input: { channel: "#engineering", text: "Deployment complete" }, + output: { ok: true, ts: "1234567890.123456" }, isError: false, - durationMs: 156, - offsetMs: -7 * TIME.DAY, + durationMs: 234, + offsetMs: -8 * TIME.DAY, userKey: "qa", userAgent: USER_AGENTS.meshClient, gatewayKey: "allAccess", }, - // 3 days ago: AI code generation + // 5 days ago: AI code generation { connectionKey: "openrouter", toolName: "chat_completion", @@ -383,13 +371,13 @@ const STATIC_LOGS: MonitoringLog[] = [ output: { response: "Here are the types...", tokens_used: 687 }, isError: false, durationMs: 2345, - offsetMs: -3 * TIME.DAY, + offsetMs: -5 * TIME.DAY, userKey: "junior", userAgent: USER_AGENTS.meshClient, gatewayKey: "llm", }, - // 2 days ago: Repository maintenance + // 3 days ago: Repository maintenance { connectionKey: "github", toolName: "list_repositories", @@ -560,131 +548,120 @@ const TOOL_TEMPLATES: ToolTemplate[] = [ sampleOutputs: [{ results: [], total: 67 }], }, - // Stripe (8%) + // Slack (8%) { - toolName: "list_payments", - connectionKey: "stripe", + toolName: "post_message", + connectionKey: "slack", weight: 0.03, avgDurationMs: 240, durationVariance: 90, - sampleInputs: [{ limit: 100 }], - sampleOutputs: [{ data: [], has_more: true }], + sampleInputs: [{ channel: "#engineering", text: "Update..." }], + sampleOutputs: [{ ok: true, ts: "1234567890.123456" }], }, { - toolName: "get_balance", - connectionKey: "stripe", + toolName: "list_channels", + connectionKey: "slack", weight: 0.02, - avgDurationMs: 150, - durationVariance: 50, + avgDurationMs: 180, + durationVariance: 60, sampleInputs: [{}], - sampleOutputs: [{ available: [{ amount: 1245678 }] }], + sampleOutputs: [{ channels: [], total: 42 }], }, { - toolName: "create_refund", - connectionKey: "stripe", - weight: 0.01, - avgDurationMs: 320, - durationVariance: 120, - sampleInputs: [{ charge: "ch_123" }], - sampleOutputs: [{ id: "re_456", status: "succeeded" }], + toolName: "get_channel_history", + connectionKey: "slack", + weight: 0.02, + avgDurationMs: 210, + durationVariance: 80, + sampleInputs: [{ channel: "C123456" }], + sampleOutputs: [{ messages: [], has_more: true }], }, - // Vercel (9%) + // Gmail (9%) { - toolName: "list_deployments", - connectionKey: "vercel", + toolName: "send_email", + connectionKey: "gmail", weight: 0.04, - avgDurationMs: 220, - durationVariance: 80, - sampleInputs: [{ projectId: "prj_banking" }], - sampleOutputs: [{ deployments: [] }], - }, - { - toolName: "get_deployment", - connectionKey: "vercel", - weight: 0.02, - avgDurationMs: 180, - durationVariance: 70, - sampleInputs: [{ deployment_id: "dpl_123" }], - sampleOutputs: [{ state: "READY" }], + avgDurationMs: 340, + durationVariance: 120, + sampleInputs: [{ to: "user@example.com", subject: "Report" }], + sampleOutputs: [{ id: "msg_123", threadId: "thread_456" }], }, { - toolName: "list_projects", - connectionKey: "vercel", - weight: 0.02, - avgDurationMs: 190, - durationVariance: 60, - sampleInputs: [{}], - sampleOutputs: [{ projects: [] }], + toolName: "list_emails", + connectionKey: "gmail", + weight: 0.03, + avgDurationMs: 280, + durationVariance: 100, + sampleInputs: [{ query: "is:unread" }], + sampleOutputs: [{ messages: [], resultSizeEstimate: 42 }], }, - - // Supabase (6%) { - toolName: "list_projects", - connectionKey: "supabase", + toolName: "get_email", + connectionKey: "gmail", weight: 0.02, avgDurationMs: 210, durationVariance: 80, - sampleInputs: [{}], - sampleOutputs: [{ projects: [], total: 23 }], + sampleInputs: [{ id: "msg_123" }], + sampleOutputs: [{ subject: "...", from: "..." }], }, + + // Brasil API (6%) { - toolName: "get_project_health", - connectionKey: "supabase", + toolName: "get_bank_info", + connectionKey: "brasilApi", weight: 0.02, avgDurationMs: 180, durationVariance: 70, - sampleInputs: [{ project_id: "proj_123" }], - sampleOutputs: [{ status: "healthy" }], + sampleInputs: [{ code: "341" }], + sampleOutputs: [{ name: "Itaú", fullName: "Itaú Unibanco" }], }, { - toolName: "run_sql", - connectionKey: "supabase", - weight: 0.01, - avgDurationMs: 420, - durationVariance: 180, - sampleInputs: [{ query: "SELECT..." }], - sampleOutputs: [{ rows: [] }], - }, - - // GitHub Copilot (8%) - { - toolName: "get_completions", - connectionKey: "github", - weight: 0.05, - avgDurationMs: 450, - durationVariance: 200, - sampleInputs: [{ prompt: "function..." }], - sampleOutputs: [{ completions: [] }], + toolName: "get_cep", + connectionKey: "brasilApi", + weight: 0.02, + avgDurationMs: 150, + durationVariance: 50, + sampleInputs: [{ cep: "01310100" }], + sampleOutputs: [{ street: "Av. Paulista", city: "São Paulo" }], }, { - toolName: "explain_code", - connectionKey: "github", + toolName: "get_cnpj", + connectionKey: "brasilApi", weight: 0.02, - avgDurationMs: 1200, - durationVariance: 400, - sampleInputs: [{ code: "..." }], - sampleOutputs: [{ explanation: "..." }], + avgDurationMs: 210, + durationVariance: 80, + sampleInputs: [{ cnpj: "00000000000191" }], + sampleOutputs: [{ razao_social: "Banco do Brasil" }], }, - // Prisma (6%) + // Jira (8%) { - toolName: "generate_schema", - connectionKey: "prisma", - weight: 0.02, - avgDurationMs: 890, - durationVariance: 300, - sampleInputs: [{ introspect: true }], - sampleOutputs: [{ schema: "...", models_count: 23 }], + toolName: "create_issue", + connectionKey: "jira", + weight: 0.03, + avgDurationMs: 340, + durationVariance: 120, + sampleInputs: [{ project: "DECO", summary: "Bug fix" }], + sampleOutputs: [{ id: "10001", key: "DECO-123" }], }, { - toolName: "run_migration", - connectionKey: "prisma", + toolName: "search_issues", + connectionKey: "jira", + weight: 0.03, + avgDurationMs: 280, + durationVariance: 100, + sampleInputs: [{ jql: "project = DECO" }], + sampleOutputs: [{ issues: [], total: 234 }], + }, + { + toolName: "get_issue", + connectionKey: "jira", weight: 0.02, - avgDurationMs: 2340, - durationVariance: 800, - sampleInputs: [{ migration_name: "add_index" }], - sampleOutputs: [{ success: true }], + avgDurationMs: 210, + durationVariance: 80, + sampleInputs: [{ key: "DECO-123" }], + sampleOutputs: [{ summary: "...", status: "In Progress" }], }, // Apify (5%) @@ -710,15 +687,17 @@ const TOOL_TEMPLATES: ToolTemplate[] = [ const CONNECTION_TO_GATEWAY: Record = { github: "devGateway", - vercel: "devGateway", - prisma: "devGateway", - supabase: "devGateway", + cloudflare: "devGateway", + aws: "devGateway", openrouter: "llm", - githubCopilot: "llm", + perplexity: "llm", notion: "compliance", grain: "compliance", apify: "dataGateway", - stripe: "allAccess", + brasilApi: "dataGateway", + gmail: "allAccess", + slack: "allAccess", + jira: "allAccess", }; function weightedRandom(items: T[]): T { @@ -742,22 +721,26 @@ function generateSyntheticLogs(targetCount: number): MonitoringLog[] { const userEntry = weightedRandom(userWeights); const isError = Math.random() < 0.08; - // Generate timestamp with recency bias (40% last 24h, 35% last 7d, 20% last 30d, 5% last 90d) + // Generate timestamp distributed over last 30 days (20% last 24h, 30% days 1-3, 25% days 3-7, 25% days 7-30) const r = Math.random(); - let daysAgo = - r < 0.4 - ? Math.random() - : r < 0.75 - ? 1 + Math.random() * 6 - : r < 0.95 - ? 7 + Math.random() * 23 - : 30 + Math.random() * 60; - - const timestamp = new Date(Date.now() - daysAgo * TIME.DAY); - // Adjust to working hours (8-20) - const hour = timestamp.getHours(); - if (hour < 8 || hour > 20) - timestamp.setHours(8 + Math.floor(Math.random() * 12)); + let randomOffset: number; + + if (r < 0.2) { + // Last 24h: 0 to 1 day ago + randomOffset = Math.random() * TIME.DAY; + } else if (r < 0.5) { + // Days 1-3: between 1 and 3 days ago + randomOffset = TIME.DAY + Math.random() * 2 * TIME.DAY; + } else if (r < 0.75) { + // Days 3-7: between 3 and 7 days ago + randomOffset = 3 * TIME.DAY + Math.random() * 4 * TIME.DAY; + } else { + // Days 7-30: between 7 and 30 days ago + randomOffset = 7 * TIME.DAY + Math.random() * 23 * TIME.DAY; + } + + // Calculate total offset as negative milliseconds from now + const totalOffsetMs = -randomOffset; const duration = template.avgDurationMs + @@ -780,7 +763,7 @@ function generateSyntheticLogs(targetCount: number): MonitoringLog[] { output, isError, durationMs: Math.max(50, Math.round(duration)), - offsetMs: timestamp.getTime() - Date.now(), + offsetMs: totalOffsetMs, userKey: userEntry.key, userAgent: "mesh-client/1.0", gatewayKey: CONNECTION_TO_GATEWAY[template.connectionKey] || "allAccess", @@ -800,8 +783,9 @@ export const DECO_BANK_SLUG = "deco-bank"; export async function seedDecoBank( db: Kysely, ): Promise { - // Generate ~2500 synthetic logs + static story logs - const syntheticLogs = generateSyntheticLogs(2500); + // Generate ~1M synthetic logs + static story logs + // This simulates high-volume production usage (200k/day peak, 1M over 30 days) + const syntheticLogs = generateSyntheticLogs(1_000_000); const allLogs = [...STATIC_LOGS, ...syntheticLogs]; const config: OrgConfig = { @@ -814,7 +798,14 @@ export async function seedDecoBank( ], connections: CONNECTIONS, gateways: GATEWAYS, - gatewayConnections: [{ gatewayKey: "llm", connectionKey: "openrouter" }], + gatewayConnections: [ + // Default Hub with well-known connections (production-like) + { gatewayKey: "defaultHub", connectionKey: "meshMcp" }, + { gatewayKey: "defaultHub", connectionKey: "mcpRegistry" }, + { gatewayKey: "defaultHub", connectionKey: "decoStore" }, + // LLM Gateway + { gatewayKey: "llm", connectionKey: "openrouter" }, + ], logs: allLogs, ownerUserKey: "cto", }; diff --git a/apps/mesh/migrations/seeds/demo/orgs/onboarding.ts b/apps/mesh/migrations/seeds/demo/orgs/onboarding.ts index f134687a27..eb410a5105 100644 --- a/apps/mesh/migrations/seeds/demo/orgs/onboarding.ts +++ b/apps/mesh/migrations/seeds/demo/orgs/onboarding.ts @@ -37,8 +37,8 @@ const USERS: Record = { email: `admin${EMAIL_DOMAIN}`, }, developer: { - role: "member", - memberRole: "member", + role: "user", + memberRole: "user", name: "Dev Developer", email: `developer${EMAIL_DOMAIN}`, }, @@ -49,7 +49,19 @@ const CONNECTIONS = { ...getWellKnownConnections(), ...pickConnections(["github", "openrouter", "notion"]), }; -const GATEWAYS = pickGateways(["defaultHub"]); + +// Override defaultHub to include only well-known connections (production behavior) +const GATEWAYS = { + defaultHub: { + title: "Default Hub", + description: "Auto-created Hub for organization", + toolSelectionStrategy: "passthrough" as const, + toolSelectionMode: "inclusion" as const, + icon: null, + isDefault: true, + connections: ["meshMcp", "mcpRegistry", "decoStore"], + }, +}; // ============================================================================= // Monitoring Logs - Early adoption pattern over 48 hours @@ -364,7 +376,12 @@ export async function seedOnboarding( apiKeys: [{ userKey: "admin", name: "Onboarding Admin Key" }], connections: CONNECTIONS, gateways: GATEWAYS, - gatewayConnections: [], + gatewayConnections: [ + // Default Hub with well-known connections (production-like) + { gatewayKey: "defaultHub", connectionKey: "meshMcp" }, + { gatewayKey: "defaultHub", connectionKey: "mcpRegistry" }, + { gatewayKey: "defaultHub", connectionKey: "decoStore" }, + ], logs: LOGS, ownerUserKey: "admin", }; diff --git a/apps/mesh/migrations/seeds/demo/seeder.ts b/apps/mesh/migrations/seeds/demo/seeder.ts index 85046a797c..a5ad64d322 100644 --- a/apps/mesh/migrations/seeds/demo/seeder.ts +++ b/apps/mesh/migrations/seeds/demo/seeder.ts @@ -42,10 +42,10 @@ export const TIME = { // Type Definitions // ============================================================================= -export type MemberRole = "owner" | "admin" | "member"; +export type MemberRole = "owner" | "admin" | "user"; export interface OrgUser { - role: "admin" | "member"; + role: "admin" | "user"; memberRole: MemberRole; name: string; email: string; @@ -65,6 +65,12 @@ export interface Connection { official?: boolean; decoHosted?: boolean; }; + tools?: Array<{ + name: string; + description?: string; + inputSchema?: Record; + outputSchema?: Record; + }>; } export interface Gateway { @@ -210,6 +216,7 @@ function createConnectionRecord( createdBy: string, conn: Connection, timestamp: string, + tools: unknown[] | null = null, ) { return { id: connectionId, @@ -228,7 +235,7 @@ function createConnectionRecord( configuration_state: null, configuration_scopes: null, metadata: JSON.stringify(conn.metadata), - tools: null, + tools: tools ? JSON.stringify(tools) : null, bindings: null, status: "active" as const, created_at: timestamp, @@ -284,9 +291,12 @@ function createMonitoringLogRecord( timestamp: string, userId: string, gatewayId: string | null, + logIndex: number, ) { + // Use index to ensure unique IDs when generating many logs quickly + const timestampMs = Date.now(); return { - id: generateId("log"), + id: `log_${timestampMs}_${logIndex.toString().padStart(7, "0")}`, organization_id: organizationId, connection_id: connectionId, connection_title: connectionTitle, @@ -298,7 +308,7 @@ function createMonitoringLogRecord( duration_ms: log.durationMs, timestamp, user_id: userId, - request_id: generateId("req"), + request_id: `req_${timestampMs}_${logIndex.toString().padStart(7, "0")}`, user_agent: log.userAgent, gateway_id: gatewayId, properties: log.properties ? JSON.stringify(log.properties) : null, @@ -383,6 +393,7 @@ export async function createOrg( connectionIds[key] = generateId("conn"); } const ownerUserId = userIds[config.ownerUserKey]!; + const connectionRecords = Object.entries(config.connections).map( ([key, conn]) => createConnectionRecord( @@ -391,6 +402,7 @@ export async function createOrg( ownerUserId, conn, now, + conn.tools ?? null, ), ); await db.insertInto("connections").values(connectionRecords).execute(); @@ -419,22 +431,45 @@ export async function createOrg( // 9. Create Monitoring Logs if (config.logs.length > 0) { - const logRecords = config.logs.map((log) => { - const timestamp = new Date(Date.now() + log.offsetMs).toISOString(); - const gatewayId = log.gatewayKey - ? (gatewayIds[log.gatewayKey] ?? null) - : null; - return createMonitoringLogRecord( - orgId, - connectionIds[log.connectionKey]!, - config.connections[log.connectionKey]!.title, - log, - timestamp, - userIds[log.userKey]!, - gatewayId, - ); - }); - await db.insertInto("monitoring_logs").values(logRecords).execute(); + const BATCH_SIZE = 500; // SQLite has a strict limit on SQL variables per query + const totalLogs = config.logs.length; + + console.log( + ` 📊 Inserting ${totalLogs.toLocaleString()} monitoring logs...`, + ); + + for (let i = 0; i < totalLogs; i += BATCH_SIZE) { + const batch = config.logs.slice(i, i + BATCH_SIZE); + const logRecords = batch.map((log, batchIndex) => { + const timestamp = new Date(Date.now() + log.offsetMs).toISOString(); + const gatewayId = log.gatewayKey + ? (gatewayIds[log.gatewayKey] ?? null) + : null; + // Use global index to ensure unique IDs across all batches + const logIndex = i + batchIndex; + return createMonitoringLogRecord( + orgId, + connectionIds[log.connectionKey]!, + config.connections[log.connectionKey]!.title, + log, + timestamp, + userIds[log.userKey]!, + gatewayId, + logIndex, + ); + }); + await db.insertInto("monitoring_logs").values(logRecords).execute(); + + if (totalLogs > BATCH_SIZE) { + const progress = Math.min(i + BATCH_SIZE, totalLogs); + const percentage = ((progress / totalLogs) * 100).toFixed(1); + console.log( + ` → ${progress.toLocaleString()}/${totalLogs.toLocaleString()} (${percentage}%)`, + ); + } + } + + console.log(` ✅ Inserted ${totalLogs.toLocaleString()} logs`); } // 10. Create Organization Settings From 5deef2d71e8ebc53bd5fc61659c9600627ca80c6 Mon Sep 17 00:00:00 2001 From: aline-pereira Date: Fri, 9 Jan 2026 12:10:18 -0300 Subject: [PATCH 08/12] changed monitoring log distribution --- .../migrations/seeds/demo/orgs/deco-bank.ts | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts b/apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts index 7fe102ec4b..b344925998 100644 --- a/apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts +++ b/apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts @@ -5,7 +5,8 @@ * - 12 users across multiple departments * - 24 connections (3 well-known + verified MCPs from Deco Store) * - 6 gateways (Default Hub + 5 specialized) - * - ~1M synthetic + static monitoring logs (200k in last 24h, 800k in last 30 days) + * - ~1M synthetic + static monitoring logs maximally distributed across 30 days + * with smooth 24/7 activity (60% business hours 7am-10pm, 40% off-hours) */ import type { Kysely } from "kysely"; @@ -721,25 +722,33 @@ function generateSyntheticLogs(targetCount: number): MonitoringLog[] { const userEntry = weightedRandom(userWeights); const isError = Math.random() < 0.08; - // Generate timestamp distributed over last 30 days (20% last 24h, 30% days 1-3, 25% days 3-7, 25% days 7-30) - const r = Math.random(); - let randomOffset: number; - - if (r < 0.2) { - // Last 24h: 0 to 1 day ago - randomOffset = Math.random() * TIME.DAY; - } else if (r < 0.5) { - // Days 1-3: between 1 and 3 days ago - randomOffset = TIME.DAY + Math.random() * 2 * TIME.DAY; - } else if (r < 0.75) { - // Days 3-7: between 3 and 7 days ago - randomOffset = 3 * TIME.DAY + Math.random() * 4 * TIME.DAY; + // Generate timestamp with maximum spread over 30 days + // Completely uniform distribution with smooth variations + const dayOffset = Math.random() * 30; // 0 to 30 days ago + + // Add hour variation across full 24h period + // Mix of concentrated business hours (60%) and off-hours (40%) + let hourOffset: number; + if (Math.random() < 0.6) { + // Business hours: 7am to 10pm (15 hours) + hourOffset = 7 + Math.random() * 15; } else { - // Days 7-30: between 7 and 30 days ago - randomOffset = 7 * TIME.DAY + Math.random() * 23 * TIME.DAY; + // Off hours: 10pm to 7am (9 hours) + const offHour = Math.random() * 9; + hourOffset = offHour < 5 ? 22 + offHour : offHour - 5; // 22-24 or 0-7 } - // Calculate total offset as negative milliseconds from now + // Add minute/second noise for maximum distribution + const minuteOffset = Math.random() * 60; + const secondOffset = Math.random() * 60; + + // Calculate total offset in milliseconds + const randomOffset = + dayOffset * TIME.DAY + + hourOffset * TIME.HOUR + + minuteOffset * TIME.MINUTE + + secondOffset * 1000; + const totalOffsetMs = -randomOffset; const duration = From 366505d413e20451f77e73ff19e5209984b8ed1a Mon Sep 17 00:00:00 2001 From: aline-pereira Date: Fri, 9 Jan 2026 14:11:07 -0300 Subject: [PATCH 09/12] fix store --- apps/mesh/migrations/seeds/demo/catalog.ts | 40 +-- apps/mesh/migrations/seeds/demo/index.ts | 11 +- .../migrations/seeds/demo/orgs/onboarding.ts | 338 +----------------- apps/mesh/migrations/seeds/demo/seeder.ts | 35 +- 4 files changed, 52 insertions(+), 372 deletions(-) diff --git a/apps/mesh/migrations/seeds/demo/catalog.ts b/apps/mesh/migrations/seeds/demo/catalog.ts index dab7697da2..9e8f06d03e 100644 --- a/apps/mesh/migrations/seeds/demo/catalog.ts +++ b/apps/mesh/migrations/seeds/demo/catalog.ts @@ -14,40 +14,8 @@ import type { Connection, Gateway } from "./seeder"; // Well-Known Connections (installed by default in production) // ============================================================================= -// Registry tools (COLLECTION_REGISTRY_APP_* pattern) -const REGISTRY_TOOLS = [ - { - name: "COLLECTION_REGISTRY_APP_LIST", - description: "List all apps in the registry", - inputSchema: { - type: "object", - properties: { - limit: { type: "number" }, - cursor: { type: "string" }, - }, - }, - }, - { - name: "COLLECTION_REGISTRY_APP_GET", - description: "Get a specific app from the registry", - inputSchema: { - type: "object", - properties: { - id: { type: "string" }, - }, - required: ["id"], - }, - }, - { - name: "COLLECTION_REGISTRY_APP_FILTERS", - description: "Get available filters for registry apps", - inputSchema: { - type: "object", - properties: {}, - }, - }, -]; - +// Note: Tools are fetched dynamically from the MCP servers (not mocked) +// This matches production behavior where fetchToolsFromMCP is called export const WELL_KNOWN_CONNECTIONS = { meshMcp: { title: "Mesh MCP", @@ -68,7 +36,7 @@ export const WELL_KNOWN_CONNECTIONS = { connectionUrl: "https://sites-registry.decocache.com/mcp", connectionToken: null, metadata: { provider: "deco", decoHosted: true }, - tools: REGISTRY_TOOLS, + // Tools will be fetched dynamically from the server }, decoStore: { @@ -79,7 +47,7 @@ export const WELL_KNOWN_CONNECTIONS = { connectionUrl: "https://api.decocms.com/mcp/registry", connectionToken: null, metadata: { provider: "deco", decoHosted: true }, - tools: REGISTRY_TOOLS, + // Tools will be fetched dynamically from the server }, } as const satisfies Record; diff --git a/apps/mesh/migrations/seeds/demo/index.ts b/apps/mesh/migrations/seeds/demo/index.ts index 43b30cf0cf..cc67095a31 100644 --- a/apps/mesh/migrations/seeds/demo/index.ts +++ b/apps/mesh/migrations/seeds/demo/index.ts @@ -3,11 +3,11 @@ * * Creates two complete demo environments: * - * 1. **Onboarding** - Clean slate for demonstrating first steps - * - 2 users (admin + developer) - * - 3 connections (GitHub, OpenRouter, Notion) - * - 1 gateway - * - ~16 logs + * 1. **Onboarding** - Clean slate matching production behavior + * - 1 owner user + * - 3 well-known connections (Mesh MCP, MCP Registry, Deco Store) + * - 1 default gateway + * - No monitoring logs (start from scratch) * * 2. **Deco Bank** - Mature corporate banking environment * - 12 users in realistic hierarchy @@ -87,7 +87,6 @@ export async function seed(db: Kysely): Promise { console.log(""); console.log(" Onboarding only:"); console.log(` - ${onboarding.userEmails.admin} (owner)`); - console.log(` - ${onboarding.userEmails.developer} (member)`); console.log(" Deco Bank only:"); console.log(` - ${decoBank.userEmails.techLead} (admin)`); console.log(` - ${decoBank.userEmails.seniorDev1} (member)`); diff --git a/apps/mesh/migrations/seeds/demo/orgs/onboarding.ts b/apps/mesh/migrations/seeds/demo/orgs/onboarding.ts index eb410a5105..7cf6e4d999 100644 --- a/apps/mesh/migrations/seeds/demo/orgs/onboarding.ts +++ b/apps/mesh/migrations/seeds/demo/orgs/onboarding.ts @@ -1,27 +1,18 @@ /** * Onboarding Organization * - * Minimal setup for demonstrating first steps: - * - 2 users (admin + developer) - * - 6 connections (3 well-known + GitHub, OpenRouter, Notion) - * - 1 gateway (Default Hub) - * - ~16 logs showing early adoption + * Clean slate organization for onboarding - matches production behavior: + * - 1 user (owner) + * - 3 well-known connections (Mesh MCP, MCP Registry, Deco Store) + * - 1 default gateway + * - No monitoring logs */ import type { Kysely } from "kysely"; import type { Database } from "../../../../src/storage/types"; -import type { - OrgConfig, - OrgSeedResult, - OrgUser, - MonitoringLog, -} from "../seeder"; -import { createOrg, TIME, USER_AGENTS } from "../seeder"; -import { - getWellKnownConnections, - pickConnections, - pickGateways, -} from "../catalog"; +import type { OrgConfig, OrgSeedResult, OrgUser } from "../seeder"; +import { createOrg } from "../seeder"; +import { getWellKnownConnections } from "../catalog"; // ============================================================================= // Configuration @@ -29,6 +20,7 @@ import { const EMAIL_DOMAIN = "@onboarding.local"; +// Single owner user - matches production organization creation const USERS: Record = { admin: { role: "admin", @@ -36,21 +28,14 @@ const USERS: Record = { name: "Alice Admin", email: `admin${EMAIL_DOMAIN}`, }, - developer: { - role: "user", - memberRole: "user", - name: "Dev Developer", - email: `developer${EMAIL_DOMAIN}`, - }, }; -// Include well-known connections (Mesh MCP, MCP Registry, Deco Store) + additional ones +// Only well-known connections - matches production seedOrgDb behavior const CONNECTIONS = { ...getWellKnownConnections(), - ...pickConnections(["github", "openrouter", "notion"]), }; -// Override defaultHub to include only well-known connections (production behavior) +// Default Hub with only well-known connections - matches production const GATEWAYS = { defaultHub: { title: "Default Hub", @@ -63,303 +48,6 @@ const GATEWAYS = { }, }; -// ============================================================================= -// Monitoring Logs - Early adoption pattern over 48 hours -// ============================================================================= - -const LOGS: MonitoringLog[] = [ - // 2 days ago: First connection test - { - connectionKey: "github", - toolName: "list_repositories", - input: { per_page: 10 }, - output: { - repositories: ["my-first-project", "learning-mcp"], - total_count: 2, - }, - isError: false, - durationMs: 234, - offsetMs: -2 * TIME.DAY, - userKey: "admin", - userAgent: USER_AGENTS.meshClient, - gatewayKey: null, - properties: { cache_hit: "false" }, - }, - - // 1.5 days ago: Trying OpenRouter without auth - { - connectionKey: "openrouter", - toolName: "chat_completion", - input: { - model: "anthropic/claude-3.5-sonnet", - messages: [{ role: "user", content: "Hello!" }], - }, - output: { error: "API key required" }, - isError: true, - errorMessage: - "Authentication required. Please configure your OpenRouter API key.", - durationMs: 45, - offsetMs: -36 * TIME.HOUR, - userKey: "admin", - userAgent: USER_AGENTS.cursorAgent, - gatewayKey: "defaultHub", - properties: { auth_error: "true" }, - }, - - // 1 day ago: Developer joins - { - connectionKey: "github", - toolName: "get_repository", - input: { owner: "onboarding-org", repo: "my-first-project" }, - output: { - name: "my-first-project", - description: "Learning MCP integration", - default_branch: "main", - }, - isError: false, - durationMs: 156, - offsetMs: -1 * TIME.DAY, - userKey: "developer", - userAgent: USER_AGENTS.vscode, - gatewayKey: "defaultHub", - }, - - // 1 day ago: Trying Notion without auth - { - connectionKey: "notion", - toolName: "search_pages", - input: { query: "getting started" }, - output: { error: "OAuth authentication required" }, - isError: true, - errorMessage: "Please connect your Notion account to use this tool.", - durationMs: 67, - offsetMs: -1 * TIME.DAY + 2 * TIME.HOUR, - userKey: "admin", - userAgent: USER_AGENTS.notionDesktop, - gatewayKey: "defaultHub", - properties: { auth_error: "true" }, - }, - - // 20 hours ago: GitHub exploration - { - connectionKey: "github", - toolName: "list_issues", - input: { repo: "my-first-project", state: "open" }, - output: { - issues: [ - { number: 1, title: "Set up CI/CD" }, - { number: 2, title: "Add README" }, - ], - total_count: 2, - }, - isError: false, - durationMs: 189, - offsetMs: -20 * TIME.HOUR, - userKey: "developer", - userAgent: USER_AGENTS.ghCli, - gatewayKey: "defaultHub", - }, - - // 18 hours ago: Creating first issue - { - connectionKey: "github", - toolName: "create_issue", - input: { - repo: "my-first-project", - title: "Integrate MCP tools", - labels: ["enhancement"], - }, - output: { - issue_number: 3, - url: "https://github.com/onboarding-org/my-first-project/issues/3", - }, - isError: false, - durationMs: 312, - offsetMs: -18 * TIME.HOUR, - userKey: "admin", - userAgent: USER_AGENTS.meshClient, - gatewayKey: "defaultHub", - }, - - // 12 hours ago: Admin exploring - { - connectionKey: "github", - toolName: "list_branches", - input: { repo: "my-first-project" }, - output: { branches: ["main", "feature/mcp-setup"], total_count: 2 }, - isError: false, - durationMs: 145, - offsetMs: -12 * TIME.HOUR, - userKey: "admin", - userAgent: USER_AGENTS.vscode, - gatewayKey: "defaultHub", - properties: { cache_hit: "true" }, - }, - - // 10 hours ago: Developer working - { - connectionKey: "github", - toolName: "get_file_contents", - input: { repo: "my-first-project", path: "README.md" }, - output: { content: "# My First Project", encoding: "utf-8", size: 52 }, - isError: false, - durationMs: 98, - offsetMs: -10 * TIME.HOUR, - userKey: "developer", - userAgent: USER_AGENTS.cursorAgent, - gatewayKey: "defaultHub", - }, - - // 8 hours ago: Another OpenRouter attempt - { - connectionKey: "openrouter", - toolName: "list_models", - input: {}, - output: { error: "API key required" }, - isError: true, - errorMessage: - "Authentication required. Please configure your OpenRouter API key.", - durationMs: 34, - offsetMs: -8 * TIME.HOUR, - userKey: "developer", - userAgent: USER_AGENTS.cursorAgent, - gatewayKey: "defaultHub", - properties: { auth_error: "true" }, - }, - - // 6 hours ago: Creating PR - { - connectionKey: "github", - toolName: "create_pull_request", - input: { - repo: "my-first-project", - title: "Add MCP configuration", - head: "feature/mcp-setup", - base: "main", - }, - output: { - pr_number: 1, - url: "https://github.com/onboarding-org/my-first-project/pull/1", - }, - isError: false, - durationMs: 287, - offsetMs: -6 * TIME.HOUR, - userKey: "developer", - userAgent: USER_AGENTS.ghCli, - gatewayKey: "defaultHub", - }, - - // 4 hours ago: Admin reviewing PR - { - connectionKey: "github", - toolName: "get_pull_request", - input: { repo: "my-first-project", pr_number: 1 }, - output: { - number: 1, - title: "Add MCP configuration", - state: "open", - mergeable: true, - }, - isError: false, - durationMs: 167, - offsetMs: -4 * TIME.HOUR, - userKey: "admin", - userAgent: USER_AGENTS.meshClient, - gatewayKey: "defaultHub", - }, - - // 3 hours ago: Listing PRs - { - connectionKey: "github", - toolName: "list_pull_requests", - input: { repo: "my-first-project", state: "open" }, - output: { - pull_requests: [{ number: 1, title: "Add MCP configuration" }], - total_count: 1, - }, - isError: false, - durationMs: 134, - offsetMs: -3 * TIME.HOUR, - userKey: "developer", - userAgent: USER_AGENTS.vscode, - gatewayKey: "defaultHub", - properties: { cache_hit: "true" }, - }, - - // 2 hours ago: Checking issue - { - connectionKey: "github", - toolName: "get_issue", - input: { repo: "my-first-project", issue_number: 3 }, - output: { - number: 3, - title: "Integrate MCP tools", - state: "open", - comments: 1, - }, - isError: false, - durationMs: 112, - offsetMs: -2 * TIME.HOUR, - userKey: "admin", - userAgent: USER_AGENTS.meshClient, - gatewayKey: "defaultHub", - }, - - // 1 hour ago: Recent activity - { - connectionKey: "github", - toolName: "list_commits", - input: { - repo: "my-first-project", - branch: "feature/mcp-setup", - per_page: 5, - }, - output: { - commits: [{ sha: "abc123", message: "Add MCP config files" }], - total_count: 2, - }, - isError: false, - durationMs: 178, - offsetMs: -1 * TIME.HOUR, - userKey: "developer", - userAgent: USER_AGENTS.cursorAgent, - gatewayKey: "defaultHub", - }, - - // 30 minutes ago: Last Notion attempt - { - connectionKey: "notion", - toolName: "list_databases", - input: {}, - output: { error: "OAuth authentication required" }, - isError: true, - errorMessage: "Please connect your Notion account to use this tool.", - durationMs: 52, - offsetMs: -30 * TIME.MINUTE, - userKey: "admin", - userAgent: USER_AGENTS.notionDesktop, - gatewayKey: "defaultHub", - properties: { auth_error: "true" }, - }, - - // Just now: Current activity - { - connectionKey: "github", - toolName: "list_repositories", - input: { per_page: 20 }, - output: { - repositories: ["my-first-project", "learning-mcp", "mcp-experiments"], - total_count: 3, - }, - isError: false, - durationMs: 198, - offsetMs: 0, - userKey: "admin", - userAgent: USER_AGENTS.meshClient, - gatewayKey: "defaultHub", - }, -]; - // ============================================================================= // Seed Function // ============================================================================= @@ -377,12 +65,12 @@ export async function seedOnboarding( connections: CONNECTIONS, gateways: GATEWAYS, gatewayConnections: [ - // Default Hub with well-known connections (production-like) + // Default Hub with well-known connections (production behavior) { gatewayKey: "defaultHub", connectionKey: "meshMcp" }, { gatewayKey: "defaultHub", connectionKey: "mcpRegistry" }, { gatewayKey: "defaultHub", connectionKey: "decoStore" }, ], - logs: LOGS, + logs: [], // Empty - matches production behavior ownerUserKey: "admin", }; diff --git a/apps/mesh/migrations/seeds/demo/seeder.ts b/apps/mesh/migrations/seeds/demo/seeder.ts index a5ad64d322..07a87c4512 100644 --- a/apps/mesh/migrations/seeds/demo/seeder.ts +++ b/apps/mesh/migrations/seeds/demo/seeder.ts @@ -11,6 +11,7 @@ import type { Kysely } from "kysely"; import type { Database } from "../../../src/storage/types"; import { hashPassword } from "better-auth/crypto"; +import { fetchToolsFromMCP } from "../../../src/tools/connection/fetch-tools"; // ============================================================================= // Shared Configuration @@ -394,16 +395,40 @@ export async function createOrg( } const ownerUserId = userIds[config.ownerUserKey]!; - const connectionRecords = Object.entries(config.connections).map( - ([key, conn]) => - createConnectionRecord( + // Fetch tools dynamically for connections that don't have them defined + const connectionRecords = await Promise.all( + Object.entries(config.connections).map(async ([key, conn]) => { + let tools = conn.tools ?? null; + + // If tools not provided, fetch them from the MCP server (production behavior) + if (!tools) { + const fetchedTools = await fetchToolsFromMCP({ + id: connectionIds[key]!, + title: conn.title, + connection_type: "HTTP", + connection_url: conn.connectionUrl, + connection_token: conn.connectionToken, + connection_headers: null, + }).catch((error) => { + console.warn( + `Failed to fetch tools for ${conn.title} (${conn.connectionUrl}):`, + error instanceof Error ? error.message : error, + ); + return null; + }); + + tools = fetchedTools; + } + + return createConnectionRecord( connectionIds[key]!, orgId, ownerUserId, conn, now, - conn.tools ?? null, - ), + tools, + ); + }), ); await db.insertInto("connections").values(connectionRecords).execute(); From 82e43b00989b5a328d4fa2385c7c639d3a19704f Mon Sep 17 00:00:00 2001 From: aline-pereira Date: Fri, 9 Jan 2026 14:22:13 -0300 Subject: [PATCH 10/12] logs --- .../migrations/seeds/demo/orgs/deco-bank.ts | 176 ++++++++++++------ 1 file changed, 116 insertions(+), 60 deletions(-) diff --git a/apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts b/apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts index b344925998..ba063f7c9a 100644 --- a/apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts +++ b/apps/mesh/migrations/seeds/demo/orgs/deco-bank.ts @@ -5,8 +5,11 @@ * - 12 users across multiple departments * - 24 connections (3 well-known + verified MCPs from Deco Store) * - 6 gateways (Default Hub + 5 specialized) - * - ~1M synthetic + static monitoring logs maximally distributed across 30 days - * with smooth 24/7 activity (60% business hours 7am-10pm, 40% off-hours) + * - ~1M synthetic + static monitoring logs with realistic activity patterns: + * - Peak hours: 9-11am (morning) and 2-4pm (afternoon) + * - Weekends with 80% reduced activity (visible valleys) + * - Special spike days on deployments (days 2, 7, 14, 21, 28) + * - Night/lunch hours with minimal activity (realistic lows) */ import type { Kysely } from "kysely"; @@ -717,67 +720,120 @@ function generateSyntheticLogs(targetCount: number): MonitoringLog[] { ([key, weight]) => ({ key, weight }), ); - for (let i = 0; i < targetCount; i++) { - const template = weightedRandom(TOOL_TEMPLATES); - const userEntry = weightedRandom(userWeights); - const isError = Math.random() < 0.08; - - // Generate timestamp with maximum spread over 30 days - // Completely uniform distribution with smooth variations - const dayOffset = Math.random() * 30; // 0 to 30 days ago - - // Add hour variation across full 24h period - // Mix of concentrated business hours (60%) and off-hours (40%) - let hourOffset: number; - if (Math.random() < 0.6) { - // Business hours: 7am to 10pm (15 hours) - hourOffset = 7 + Math.random() * 15; + // Pre-calculate logs per day with realistic distribution + const logsPerDay: number[] = []; + const totalDays = 30; + + for (let day = 0; day < totalDays; day++) { + const now = new Date(); + const targetDate = new Date(now.getTime() - day * TIME.DAY); + const dayOfWeek = targetDate.getDay(); + const isWeekend = dayOfWeek === 0 || dayOfWeek === 6; + + // Special spike days (deployments, incidents) + const isSpikeDay = [2, 7, 14, 21, 28].includes(day); + + // Calculate base activity for this day + let dayMultiplier = 1.0; + if (isWeekend) { + dayMultiplier = 0.3; // 30% activity on weekends (visible valley) + } else if (isSpikeDay) { + dayMultiplier = 2.5; // 250% activity on spike days (visible peak) } else { - // Off hours: 10pm to 7am (9 hours) - const offHour = Math.random() * 9; - hourOffset = offHour < 5 ? 22 + offHour : offHour - 5; // 22-24 or 0-7 + // Normal weekdays with slight variation + dayMultiplier = 0.85 + Math.random() * 0.3; // 85-115% of base } - // Add minute/second noise for maximum distribution - const minuteOffset = Math.random() * 60; - const secondOffset = Math.random() * 60; - - // Calculate total offset in milliseconds - const randomOffset = - dayOffset * TIME.DAY + - hourOffset * TIME.HOUR + - minuteOffset * TIME.MINUTE + - secondOffset * 1000; - - const totalOffsetMs = -randomOffset; - - const duration = - template.avgDurationMs + - (Math.random() - 0.5) * 2 * template.durationVariance; - const input = template.sampleInputs[ - Math.floor(Math.random() * template.sampleInputs.length) - ] as Record; - const output = ( - isError - ? { error: "Internal error" } - : template.sampleOutputs[ - Math.floor(Math.random() * template.sampleOutputs.length) - ] - ) as Record; - - logs.push({ - connectionKey: template.connectionKey, - toolName: template.toolName, - input, - output, - isError, - durationMs: Math.max(50, Math.round(duration)), - offsetMs: totalOffsetMs, - userKey: userEntry.key, - userAgent: "mesh-client/1.0", - gatewayKey: CONNECTION_TO_GATEWAY[template.connectionKey] || "allAccess", - properties: template.properties, - }); + logsPerDay.push(dayMultiplier); + } + + // Normalize to hit target count + const totalMultiplier = logsPerDay.reduce((sum, m) => sum + m, 0); + const logsPerDayFinal = logsPerDay.map((m) => + Math.floor((m / totalMultiplier) * targetCount), + ); + + // Generate logs for each day + for (let day = 0; day < totalDays; day++) { + const logsForThisDay = logsPerDayFinal[day] || 0; + + for (let logIdx = 0; logIdx < logsForThisDay; logIdx++) { + const template = weightedRandom(TOOL_TEMPLATES); + const userEntry = weightedRandom(userWeights); + const isError = Math.random() < 0.08; + + // Create realistic hourly patterns with clear peaks + // Peak hours: 9-11am (morning peak), 2-4pm (afternoon peak) + // Low activity: 12-1pm (lunch), 5pm-7am (evening/night) + let hourOffset: number; + const hourPattern = Math.random(); + + if (hourPattern < 0.25) { + // 25% - Morning peak (9-11am) - HIGHEST activity + hourOffset = 9 + Math.random() * 2; + } else if (hourPattern < 0.45) { + // 20% - Afternoon peak (2-4pm) - HIGH activity + hourOffset = 14 + Math.random() * 2; + } else if (hourPattern < 0.6) { + // 15% - Early morning ramp-up (7-9am) + hourOffset = 7 + Math.random() * 2; + } else if (hourPattern < 0.75) { + // 15% - Late afternoon (4-6pm) + hourOffset = 16 + Math.random() * 2; + } else if (hourPattern < 0.85) { + // 10% - Lunch dip (12-2pm) - LOWER activity + hourOffset = 12 + Math.random() * 2; + } else if (hourPattern < 0.93) { + // 8% - Evening (6-10pm) - LOW activity + hourOffset = 18 + Math.random() * 4; + } else { + // 7% - Night/early morning (10pm-7am) - MINIMAL activity + const nightHour = Math.random() * 9; + hourOffset = nightHour < 2 ? 22 + nightHour : nightHour - 2; + } + + // Add minute/second variation for spreading within the hour + const minuteOffset = Math.random() * 60; + const secondOffset = Math.random() * 60; + + // Calculate total offset in milliseconds + const randomOffset = + day * TIME.DAY + + hourOffset * TIME.HOUR + + minuteOffset * TIME.MINUTE + + secondOffset * 1000; + + const totalOffsetMs = -randomOffset; + + const duration = + template.avgDurationMs + + (Math.random() - 0.5) * 2 * template.durationVariance; + const input = template.sampleInputs[ + Math.floor(Math.random() * template.sampleInputs.length) + ] as Record; + const output = ( + isError + ? { error: "Internal error" } + : template.sampleOutputs[ + Math.floor(Math.random() * template.sampleOutputs.length) + ] + ) as Record; + + logs.push({ + connectionKey: template.connectionKey, + toolName: template.toolName, + input, + output, + isError, + durationMs: Math.max(50, Math.round(duration)), + offsetMs: totalOffsetMs, + userKey: userEntry.key, + userAgent: "mesh-client/1.0", + gatewayKey: + CONNECTION_TO_GATEWAY[template.connectionKey] || "allAccess", + properties: template.properties, + }); + } } return logs.sort((a, b) => a.offsetMs - b.offsetMs); From faca9cea46c3e3b2a240ddcc38687de58f0711b9 Mon Sep 17 00:00:00 2001 From: aline-pereira Date: Fri, 9 Jan 2026 14:23:10 -0300 Subject: [PATCH 11/12] Merge branch 'main' of github.com:decocms/mesh into feat/demo-seed From a38de64cdfaea1b58100d6986d0fcd86a204c725 Mon Sep 17 00:00:00 2001 From: aline-pereira Date: Fri, 9 Jan 2026 14:48:09 -0300 Subject: [PATCH 12/12] monitoring --- .../migrations/seeds/demo/orgs/onboarding.ts | 344 +++++++++++++++++- 1 file changed, 339 insertions(+), 5 deletions(-) diff --git a/apps/mesh/migrations/seeds/demo/orgs/onboarding.ts b/apps/mesh/migrations/seeds/demo/orgs/onboarding.ts index 7cf6e4d999..3f1fab183e 100644 --- a/apps/mesh/migrations/seeds/demo/orgs/onboarding.ts +++ b/apps/mesh/migrations/seeds/demo/orgs/onboarding.ts @@ -1,17 +1,22 @@ /** * Onboarding Organization * - * Clean slate organization for onboarding - matches production behavior: + * Clean slate organization for onboarding - shows early usage: * - 1 user (owner) * - 3 well-known connections (Mesh MCP, MCP Registry, Deco Store) * - 1 default gateway - * - No monitoring logs + * - ~300 monitoring logs over 3 days (light usage pattern) */ import type { Kysely } from "kysely"; import type { Database } from "../../../../src/storage/types"; -import type { OrgConfig, OrgSeedResult, OrgUser } from "../seeder"; -import { createOrg } from "../seeder"; +import type { + OrgConfig, + OrgSeedResult, + OrgUser, + MonitoringLog, +} from "../seeder"; +import { createOrg, TIME, USER_AGENTS } from "../seeder"; import { getWellKnownConnections } from "../catalog"; // ============================================================================= @@ -48,6 +53,330 @@ const GATEWAYS = { }, }; +// ============================================================================= +// Static Logs - Hand-crafted onboarding journey over 3 days +// ============================================================================= + +const STATIC_LOGS: MonitoringLog[] = [ + // 3 days ago: First connection exploration + { + connectionKey: "mcpRegistry", + toolName: "search_servers", + input: { query: "github" }, + output: { results: [], total: 45 }, + isError: false, + durationMs: 234, + offsetMs: -3 * TIME.DAY, + userKey: "admin", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "defaultHub", + }, + { + connectionKey: "decoStore", + toolName: "list_verified_mcps", + input: {}, + output: { mcps: [], total: 127 }, + isError: false, + durationMs: 189, + offsetMs: -3 * TIME.DAY, + userKey: "admin", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "defaultHub", + }, + + // 2 days ago: Exploring Mesh capabilities + { + connectionKey: "meshMcp", + toolName: "list_connections", + input: {}, + output: { connections: [] }, + isError: false, + durationMs: 145, + offsetMs: -2 * TIME.DAY, + userKey: "admin", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "defaultHub", + }, + { + connectionKey: "meshMcp", + toolName: "list_gateways", + input: {}, + output: { gateways: [] }, + isError: false, + durationMs: 123, + offsetMs: -2 * TIME.DAY, + userKey: "admin", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "defaultHub", + }, + { + connectionKey: "mcpRegistry", + toolName: "get_server_details", + input: { server: "github" }, + output: { name: "GitHub MCP", version: "1.0.0" }, + isError: false, + durationMs: 267, + offsetMs: -2 * TIME.DAY, + userKey: "admin", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "defaultHub", + }, + + // Yesterday: Active exploration + { + connectionKey: "decoStore", + toolName: "search_mcps", + input: { query: "notion" }, + output: { results: [], total: 12 }, + isError: false, + durationMs: 198, + offsetMs: -1 * TIME.DAY, + userKey: "admin", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "defaultHub", + }, + { + connectionKey: "meshMcp", + toolName: "get_organization_info", + input: {}, + output: { name: "Onboarding", members: 1 }, + isError: false, + durationMs: 112, + offsetMs: -1 * TIME.DAY, + userKey: "admin", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "defaultHub", + }, + + // Today: Getting started + { + connectionKey: "mcpRegistry", + toolName: "list_categories", + input: {}, + output: { categories: [] }, + isError: false, + durationMs: 156, + offsetMs: -3 * TIME.HOUR, + userKey: "admin", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "defaultHub", + }, +]; + +// ============================================================================= +// Synthetic Log Generator for Onboarding +// ============================================================================= + +interface ToolTemplate { + toolName: string; + connectionKey: string; + weight: number; + avgDurationMs: number; + durationVariance: number; + sampleInputs: object[]; + sampleOutputs: object[]; +} + +// Simple tools for onboarding exploration +const TOOL_TEMPLATES: ToolTemplate[] = [ + // Mesh MCP (40%) + { + toolName: "list_connections", + connectionKey: "meshMcp", + weight: 0.15, + avgDurationMs: 145, + durationVariance: 50, + sampleInputs: [{}], + sampleOutputs: [{ connections: [] }], + }, + { + toolName: "list_gateways", + connectionKey: "meshMcp", + weight: 0.12, + avgDurationMs: 123, + durationVariance: 40, + sampleInputs: [{}], + sampleOutputs: [{ gateways: [] }], + }, + { + toolName: "get_organization_info", + connectionKey: "meshMcp", + weight: 0.08, + avgDurationMs: 112, + durationVariance: 35, + sampleInputs: [{}], + sampleOutputs: [{ name: "Onboarding", members: 1 }], + }, + { + toolName: "get_monitoring_stats", + connectionKey: "meshMcp", + weight: 0.05, + avgDurationMs: 167, + durationVariance: 55, + sampleInputs: [{}], + sampleOutputs: [{ total_calls: 234 }], + }, + + // MCP Registry (35%) + { + toolName: "search_servers", + connectionKey: "mcpRegistry", + weight: 0.15, + avgDurationMs: 234, + durationVariance: 80, + sampleInputs: [ + { query: "github" }, + { query: "notion" }, + { query: "slack" }, + ], + sampleOutputs: [{ results: [], total: 45 }], + }, + { + toolName: "get_server_details", + connectionKey: "mcpRegistry", + weight: 0.1, + avgDurationMs: 267, + durationVariance: 90, + sampleInputs: [{ server: "github" }, { server: "notion" }], + sampleOutputs: [{ name: "GitHub MCP", version: "1.0.0" }], + }, + { + toolName: "list_categories", + connectionKey: "mcpRegistry", + weight: 0.1, + avgDurationMs: 156, + durationVariance: 50, + sampleInputs: [{}], + sampleOutputs: [{ categories: [] }], + }, + + // Deco Store (25%) + { + toolName: "list_verified_mcps", + connectionKey: "decoStore", + weight: 0.12, + avgDurationMs: 189, + durationVariance: 60, + sampleInputs: [{}], + sampleOutputs: [{ mcps: [], total: 127 }], + }, + { + toolName: "search_mcps", + connectionKey: "decoStore", + weight: 0.08, + avgDurationMs: 198, + durationVariance: 65, + sampleInputs: [ + { query: "github" }, + { query: "notion" }, + { query: "slack" }, + ], + sampleOutputs: [{ results: [], total: 12 }], + }, + { + toolName: "get_mcp_details", + connectionKey: "decoStore", + weight: 0.05, + avgDurationMs: 212, + durationVariance: 70, + sampleInputs: [{ id: "github-mcp" }], + sampleOutputs: [{ name: "GitHub MCP", verified: true }], + }, +]; + +function weightedRandom(items: T[]): T { + const total = items.reduce((sum, item) => sum + item.weight, 0); + let r = Math.random() * total; + for (const item of items) { + r -= item.weight; + if (r <= 0) return item; + } + return items[items.length - 1]!; +} + +function generateOnboardingLogs(targetCount: number): MonitoringLog[] { + const logs: MonitoringLog[] = []; + const totalDays = 3; + + // Pre-calculate logs per day - progressive increase (learning curve) + const logsPerDay = [ + Math.floor(targetCount * 0.25), // Day 3: 25% (just starting) + Math.floor(targetCount * 0.35), // Day 2: 35% (exploring more) + Math.floor(targetCount * 0.4), // Day 1: 40% (getting comfortable) + ]; + + // Generate logs for each day + for (let day = 0; day < totalDays; day++) { + const logsForThisDay = logsPerDay[day] || 0; + + for (let logIdx = 0; logIdx < logsForThisDay; logIdx++) { + const template = weightedRandom(TOOL_TEMPLATES); + const isError = Math.random() < 0.03; // Very few errors for onboarding + + // Distributed hourly pattern - more activity during business hours + let hourOffset: number; + const hourPattern = Math.random(); + + if (hourPattern < 0.3) { + // 30% - Morning (9am-12pm) + hourOffset = 9 + Math.random() * 3; + } else if (hourPattern < 0.6) { + // 30% - Afternoon (2pm-5pm) + hourOffset = 14 + Math.random() * 3; + } else if (hourPattern < 0.8) { + // 20% - Late afternoon (5pm-7pm) + hourOffset = 17 + Math.random() * 2; + } else if (hourPattern < 0.9) { + // 10% - Evening (7pm-10pm) + hourOffset = 19 + Math.random() * 3; + } else { + // 10% - Off hours + hourOffset = Math.random() * 24; + } + + const minuteOffset = Math.random() * 60; + const secondOffset = Math.random() * 60; + + const randomOffset = + day * TIME.DAY + + hourOffset * TIME.HOUR + + minuteOffset * TIME.MINUTE + + secondOffset * 1000; + + const totalOffsetMs = -randomOffset; + + const duration = + template.avgDurationMs + + (Math.random() - 0.5) * 2 * template.durationVariance; + const input = template.sampleInputs[ + Math.floor(Math.random() * template.sampleInputs.length) + ] as Record; + const output = ( + isError + ? { error: "Request failed" } + : template.sampleOutputs[ + Math.floor(Math.random() * template.sampleOutputs.length) + ] + ) as Record; + + logs.push({ + connectionKey: template.connectionKey, + toolName: template.toolName, + input, + output, + isError, + durationMs: Math.max(50, Math.round(duration)), + offsetMs: totalOffsetMs, + userKey: "admin", + userAgent: USER_AGENTS.meshClient, + gatewayKey: "defaultHub", + }); + } + } + + return logs.sort((a, b) => a.offsetMs - b.offsetMs); +} + // ============================================================================= // Seed Function // ============================================================================= @@ -57,6 +386,11 @@ export const ONBOARDING_SLUG = "onboarding"; export async function seedOnboarding( db: Kysely, ): Promise { + // Generate ~300 synthetic logs + static story logs + // This simulates 3 days of light onboarding usage (~100/day average) + const syntheticLogs = generateOnboardingLogs(300); + const allLogs = [...STATIC_LOGS, ...syntheticLogs]; + const config: OrgConfig = { orgName: "Onboarding", orgSlug: ONBOARDING_SLUG, @@ -70,7 +404,7 @@ export async function seedOnboarding( { gatewayKey: "defaultHub", connectionKey: "mcpRegistry" }, { gatewayKey: "defaultHub", connectionKey: "decoStore" }, ], - logs: [], // Empty - matches production behavior + logs: allLogs, ownerUserKey: "admin", };