{
  "schemaVersion": "1.0",
  "item": {
    "slug": "interop-forge",
    "name": "Inter Operations & Integration Forge",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/guifav/interop-forge",
    "canonicalUrl": "https://clawhub.ai/guifav/interop-forge",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/interop-forge",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=interop-forge",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "SKILL.md",
      "claw.json"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "slug": "interop-forge",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-05-04T10:37:13.349Z",
      "expiresAt": "2026-05-11T10:37:13.349Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=interop-forge",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=interop-forge",
        "contentDisposition": "attachment; filename=\"interop-forge-0.1.1.zip\"",
        "redirectLocation": null,
        "bodySnippet": null,
        "slug": "interop-forge"
      },
      "scope": "item",
      "summary": "Item download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this item.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/interop-forge"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/interop-forge",
    "agentPageUrl": "https://openagent3.xyz/skills/interop-forge/agent",
    "manifestUrl": "https://openagent3.xyz/skills/interop-forge/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/interop-forge/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Interop Forge",
        "body": "You are a senior integration architect responsible for ensuring that multiple applications within a monorepo can interoperate seamlessly now and integrate fully in the future. You design shared contracts (types, schemas, validators), enforce API-first development with OpenAPI specifications, configure cross-app authentication, generate typed SDKs from specs, and scaffold full MCP servers so each app can be orchestrated by AI agents. This skill is stack-agnostic — it detects whether the project uses Vercel/Supabase, GCP, or another stack and adapts accordingly. This skill creates TypeScript packages, OpenAPI specs, MCP server files, and configuration files. It never reads or modifies .env, .env.local, or credential files directly.\n\nCredential scope: OPENROUTER_API_KEY is optionally used in generated MCP server code for apps that expose LLM-powered tools. SUPABASE_URL, SUPABASE_ANON_KEY, GCP_PROJECT_ID, and GOOGLE_APPLICATION_CREDENTIALS are referenced in generated inter-app SDK code and MCP server implementations when the target app uses those services. All env vars are accessed via process.env in generated code only — this skill never makes direct API calls itself."
      },
      {
        "title": "Planning Protocol (MANDATORY — execute before ANY action)",
        "body": "Before creating any shared package, spec, or MCP server, you MUST complete this planning phase:\n\nUnderstand the integration goal. Determine: (a) which apps need to interoperate, (b) what data or functionality is shared, (c) whether integration is real-time or async, (d) the direction of data flow (bidirectional, producer/consumer, hub/spoke).\n\n\nSurvey the monorepo. Check: (a) monorepo tool (turborepo, nx, pnpm workspaces, yarn workspaces), (b) existing shared packages in packages/, (c) existing OpenAPI specs, (d) auth strategy per app, (e) database topology (shared instance vs separate), (f) existing MCP servers. Read turbo.json, pnpm-workspace.yaml, nx.json, or package.json workspaces config.\n\n\nMap the app landscape. For each app, document: (a) name, (b) stack (Next.js, Nuxt, SvelteKit, etc.), (c) database (Supabase, Firestore, Cloud SQL, none), (d) auth provider (Firebase, Supabase Auth, Identity Platform), (e) existing API routes, (f) existing MCP server (if any).\n\n\nIdentify shared surfaces. Classify what should be shared: (a) types and interfaces, (b) validation schemas (Zod), (c) API contracts (OpenAPI), (d) auth tokens and user identity, (e) event schemas, (f) utility functions.\n\n\nDesign the integration architecture. Choose patterns: (a) shared contracts package, (b) API-first with generated SDK, (c) shared auth with JWT forwarding, (d) database sharing strategy, (e) MCP server topology.\n\n\nBuild the execution plan. List: (a) packages to create/modify, (b) specs to write, (c) SDKs to generate, (d) MCP servers to scaffold, (e) turbo pipeline changes.\n\n\nExecute incrementally. Create packages one at a time. Verify each builds before proceeding.\n\n\nSummarize. Report: packages created, specs generated, SDKs built, MCP servers scaffolded, and any manual steps remaining.\n\nDo NOT skip this protocol. Rushing integration architecture leads to circular dependencies, type mismatches, and auth holes between apps."
      },
      {
        "title": "Expected Directory Layout",
        "body": "my-monorepo/\n├── apps/\n│   ├── app-one/          # Next.js, Nuxt, SvelteKit, etc.\n│   ├── app-two/\n│   └── app-three/\n├── packages/\n│   ├── contracts/         # Shared types, Zod schemas, constants\n│   ├── api-specs/         # OpenAPI specifications per app\n│   ├── sdk/               # Auto-generated typed clients\n│   ├── auth/              # Shared auth utilities\n│   ├── mcp-core/          # Shared MCP server utilities\n│   └── eslint-config/     # Shared ESLint config (optional)\n├── mcp-servers/\n│   ├── app-one-mcp/       # MCP server exposing app-one's capabilities\n│   ├── app-two-mcp/\n│   └── app-three-mcp/\n├── turbo.json\n├── pnpm-workspace.yaml\n└── package.json"
      },
      {
        "title": "Monorepo Tool Detection and Setup",
        "body": "# Detect monorepo tool\nif [ -f \"turbo.json\" ]; then\n  MONOREPO=\"turborepo\"\nelif [ -f \"nx.json\" ]; then\n  MONOREPO=\"nx\"\nelif grep -q '\"workspaces\"' package.json 2>/dev/null; then\n  MONOREPO=\"pnpm-workspaces\"  # or yarn\nfi\n\nIf no monorepo tool exists, set up Turborepo (recommended default):\n\n# pnpm-workspace.yaml\npackages:\n  - \"apps/*\"\n  - \"packages/*\"\n  - \"mcp-servers/*\"\n\n// turbo.json\n{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"tasks\": {\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\"dist/**\", \".next/**\"]\n    },\n    \"dev\": {\n      \"cache\": false,\n      \"persistent\": true\n    },\n    \"lint\": {\n      \"dependsOn\": [\"^build\"]\n    },\n    \"typecheck\": {\n      \"dependsOn\": [\"^build\"]\n    },\n    \"generate:sdk\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\"packages/sdk/src/generated/**\"]\n    },\n    \"mcp:dev\": {\n      \"cache\": false,\n      \"persistent\": true,\n      \"dependsOn\": [\"^build\"]\n    }\n  }\n}"
      },
      {
        "title": "Part 2 — Shared Contracts Package",
        "body": "The contracts package is the single source of truth for all shared types, validation schemas, and constants across apps."
      },
      {
        "title": "Package Setup",
        "body": "// packages/contracts/package.json\n{\n  \"name\": \"@repo/contracts\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"main\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./dist/index.js\",\n      \"types\": \"./dist/index.d.ts\"\n    },\n    \"./schemas\": {\n      \"import\": \"./dist/schemas/index.js\",\n      \"types\": \"./dist/schemas/index.d.ts\"\n    },\n    \"./events\": {\n      \"import\": \"./dist/events/index.js\",\n      \"types\": \"./dist/events/index.d.ts\"\n    }\n  },\n  \"scripts\": {\n    \"build\": \"tsup src/index.ts src/schemas/index.ts src/events/index.ts --format esm --dts\",\n    \"typecheck\": \"tsc --noEmit\"\n  },\n  \"devDependencies\": {\n    \"tsup\": \"^8.0.0\",\n    \"typescript\": \"^5.4.0\"\n  },\n  \"dependencies\": {\n    \"zod\": \"^3.23.0\"\n  }\n}"
      },
      {
        "title": "Shared Types",
        "body": "// packages/contracts/src/index.ts\nexport * from \"./types\";\nexport * from \"./constants\";\nexport * from \"./schemas\";\nexport * from \"./events\";\n\n// packages/contracts/src/types/user.ts\nexport interface SharedUser {\n  id: string;\n  email: string;\n  displayName: string;\n  avatarUrl?: string;\n  provider: \"firebase\" | \"supabase\" | \"identity-platform\";\n  metadata: {\n    createdAt: string;\n    lastLoginAt: string;\n    source: string; // which app created this user\n  };\n}\n\nexport interface CrossAppToken {\n  sub: string;          // user ID\n  email: string;\n  iss: string;          // issuing app name\n  aud: string[];        // target app names\n  iat: number;\n  exp: number;\n  permissions: string[];\n}\n\n// packages/contracts/src/types/api.ts\nexport interface ApiResponse<T> {\n  data: T;\n  meta?: {\n    page?: number;\n    perPage?: number;\n    total?: number;\n  };\n}\n\nexport interface ApiError {\n  error: {\n    code: string;\n    message: string;\n    details?: Record<string, unknown>;\n  };\n}\n\nexport type ApiResult<T> = ApiResponse<T> | ApiError;\n\n// Standard pagination params\nexport interface PaginationParams {\n  page?: number;\n  perPage?: number;\n  sortBy?: string;\n  sortOrder?: \"asc\" | \"desc\";\n}\n\n// Standard filter params\nexport interface FilterParams {\n  search?: string;\n  startDate?: string;\n  endDate?: string;\n  status?: string;\n}"
      },
      {
        "title": "Shared Zod Schemas",
        "body": "// packages/contracts/src/schemas/user.schema.ts\nimport { z } from \"zod\";\n\nexport const SharedUserSchema = z.object({\n  id: z.string().uuid(),\n  email: z.string().email(),\n  displayName: z.string().min(1).max(100),\n  avatarUrl: z.string().url().optional(),\n  provider: z.enum([\"firebase\", \"supabase\", \"identity-platform\"]),\n  metadata: z.object({\n    createdAt: z.string().datetime(),\n    lastLoginAt: z.string().datetime(),\n    source: z.string(),\n  }),\n});\n\nexport type SharedUser = z.infer<typeof SharedUserSchema>;\n\n// packages/contracts/src/schemas/pagination.schema.ts\nimport { z } from \"zod\";\n\nexport const PaginationSchema = z.object({\n  page: z.coerce.number().int().positive().default(1),\n  perPage: z.coerce.number().int().positive().max(100).default(20),\n  sortBy: z.string().optional(),\n  sortOrder: z.enum([\"asc\", \"desc\"]).default(\"desc\"),\n});\n\nexport const FilterSchema = z.object({\n  search: z.string().optional(),\n  startDate: z.string().datetime().optional(),\n  endDate: z.string().datetime().optional(),\n  status: z.string().optional(),\n});"
      },
      {
        "title": "Shared Event Schemas (for inter-app communication)",
        "body": "// packages/contracts/src/events/index.ts\nimport { z } from \"zod\";\n\nexport const BaseEventSchema = z.object({\n  id: z.string().uuid(),\n  type: z.string(),\n  source: z.string(),       // app name that emitted the event\n  timestamp: z.string().datetime(),\n  version: z.literal(\"1.0\"),\n  payload: z.record(z.unknown()),\n});\n\nexport type BaseEvent = z.infer<typeof BaseEventSchema>;\n\n// Example domain events\nexport const UserCreatedEventSchema = BaseEventSchema.extend({\n  type: z.literal(\"user.created\"),\n  payload: z.object({\n    userId: z.string(),\n    email: z.string().email(),\n    provider: z.string(),\n  }),\n});\n\nexport const EntityUpdatedEventSchema = BaseEventSchema.extend({\n  type: z.literal(\"entity.updated\"),\n  payload: z.object({\n    entityId: z.string(),\n    changes: z.record(z.unknown()),\n    updatedBy: z.string(),\n  }),\n});\n\n// Event type registry — add new events here\nexport const EventSchemaRegistry = {\n  \"user.created\": UserCreatedEventSchema,\n  \"entity.updated\": EntityUpdatedEventSchema,\n} as const;\n\nexport type EventType = keyof typeof EventSchemaRegistry;"
      },
      {
        "title": "How Apps Consume Contracts",
        "body": "In any app's package.json:\n\n{\n  \"dependencies\": {\n    \"@repo/contracts\": \"workspace:*\"\n  }\n}\n\nUsage:\n\nimport { SharedUserSchema, type SharedUser } from \"@repo/contracts/schemas\";\nimport { PaginationSchema } from \"@repo/contracts/schemas\";\nimport type { ApiResponse, ApiError } from \"@repo/contracts\";"
      },
      {
        "title": "Part 3 — API-First Design with OpenAPI",
        "body": "Every app MUST define its public API as an OpenAPI 3.1 spec BEFORE implementing the endpoints. The spec lives in packages/api-specs/."
      },
      {
        "title": "Spec Structure",
        "body": "# packages/api-specs/app-one.openapi.yaml\nopenapi: \"3.1.0\"\ninfo:\n  title: App One API\n  version: \"1.0.0\"\n  description: Public API for App One\n  contact:\n    name: Team\nservers:\n  - url: https://app-one.vercel.app/api\n    description: Production\n  - url: http://localhost:3001/api\n    description: Local development\n\npaths:\n  /entities:\n    get:\n      operationId: listEntities\n      summary: List all entities for the authenticated user\n      tags: [Entities]\n      security:\n        - BearerAuth: []\n      parameters:\n        - $ref: \"#/components/parameters/Page\"\n        - $ref: \"#/components/parameters/PerPage\"\n        - $ref: \"#/components/parameters/Search\"\n      responses:\n        \"200\":\n          description: Paginated list of entities\n          content:\n            application/json:\n              schema:\n                $ref: \"#/components/schemas/EntityListResponse\"\n        \"401\":\n          $ref: \"#/components/responses/Unauthorized\"\n    post:\n      operationId: createEntity\n      summary: Create a new entity\n      tags: [Entities]\n      security:\n        - BearerAuth: []\n      requestBody:\n        required: true\n        content:\n          application/json:\n            schema:\n              $ref: \"#/components/schemas/CreateEntityInput\"\n      responses:\n        \"201\":\n          description: Entity created\n          content:\n            application/json:\n              schema:\n                $ref: \"#/components/schemas/EntityResponse\"\n        \"400\":\n          $ref: \"#/components/responses/ValidationError\"\n        \"401\":\n          $ref: \"#/components/responses/Unauthorized\"\n\n  /entities/{id}:\n    get:\n      operationId: getEntity\n      summary: Get a single entity\n      tags: [Entities]\n      security:\n        - BearerAuth: []\n      parameters:\n        - name: id\n          in: path\n          required: true\n          schema:\n            type: string\n            format: uuid\n      responses:\n        \"200\":\n          description: Entity details\n          content:\n            application/json:\n              schema:\n                $ref: \"#/components/schemas/EntityResponse\"\n        \"404\":\n          $ref: \"#/components/responses/NotFound\"\n\ncomponents:\n  securitySchemes:\n    BearerAuth:\n      type: http\n      scheme: bearer\n      bearerFormat: JWT\n\n  parameters:\n    Page:\n      name: page\n      in: query\n      schema:\n        type: integer\n        default: 1\n    PerPage:\n      name: perPage\n      in: query\n      schema:\n        type: integer\n        default: 20\n        maximum: 100\n    Search:\n      name: search\n      in: query\n      schema:\n        type: string\n\n  schemas:\n    Entity:\n      type: object\n      required: [id, name, createdAt]\n      properties:\n        id:\n          type: string\n          format: uuid\n        name:\n          type: string\n        description:\n          type: string\n        status:\n          type: string\n          enum: [active, archived]\n        createdAt:\n          type: string\n          format: date-time\n        updatedAt:\n          type: string\n          format: date-time\n\n    CreateEntityInput:\n      type: object\n      required: [name]\n      properties:\n        name:\n          type: string\n          minLength: 1\n          maxLength: 200\n        description:\n          type: string\n          maxLength: 2000\n\n    EntityResponse:\n      type: object\n      properties:\n        data:\n          $ref: \"#/components/schemas/Entity\"\n\n    EntityListResponse:\n      type: object\n      properties:\n        data:\n          type: array\n          items:\n            $ref: \"#/components/schemas/Entity\"\n        meta:\n          $ref: \"#/components/schemas/PaginationMeta\"\n\n    PaginationMeta:\n      type: object\n      properties:\n        page:\n          type: integer\n        perPage:\n          type: integer\n        total:\n          type: integer\n\n  responses:\n    Unauthorized:\n      description: Authentication required\n      content:\n        application/json:\n          schema:\n            type: object\n            properties:\n              error:\n                type: object\n                properties:\n                  code:\n                    type: string\n                    example: UNAUTHORIZED\n                  message:\n                    type: string\n                    example: Authentication required\n\n    NotFound:\n      description: Resource not found\n      content:\n        application/json:\n          schema:\n            type: object\n            properties:\n              error:\n                type: object\n                properties:\n                  code:\n                    type: string\n                    example: NOT_FOUND\n                  message:\n                    type: string\n\n    ValidationError:\n      description: Input validation failed\n      content:\n        application/json:\n          schema:\n            type: object\n            properties:\n              error:\n                type: object\n                properties:\n                  code:\n                    type: string\n                    example: VALIDATION_ERROR\n                  message:\n                    type: string\n                  details:\n                    type: object"
      },
      {
        "title": "Spec Validation",
        "body": "Before generating an SDK, always validate the spec:\n\nnpx @redocly/cli lint packages/api-specs/app-one.openapi.yaml"
      },
      {
        "title": "Part 4 — Auto-Generated Typed SDKs",
        "body": "Generate a typed TypeScript client from each OpenAPI spec so apps can call each other with full type safety."
      },
      {
        "title": "SDK Package Setup",
        "body": "// packages/sdk/package.json\n{\n  \"name\": \"@repo/sdk\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"exports\": {\n    \"./app-one\": {\n      \"import\": \"./src/generated/app-one/index.ts\",\n      \"types\": \"./src/generated/app-one/index.ts\"\n    },\n    \"./app-two\": {\n      \"import\": \"./src/generated/app-two/index.ts\",\n      \"types\": \"./src/generated/app-two/index.ts\"\n    }\n  },\n  \"scripts\": {\n    \"generate\": \"pnpm generate:app-one && pnpm generate:app-two\",\n    \"generate:app-one\": \"openapi-typescript ../api-specs/app-one.openapi.yaml -o src/generated/app-one/schema.d.ts\",\n    \"generate:app-two\": \"openapi-typescript ../api-specs/app-two.openapi.yaml -o src/generated/app-two/schema.d.ts\"\n  },\n  \"devDependencies\": {\n    \"openapi-typescript\": \"^7.0.0\",\n    \"openapi-fetch\": \"^0.10.0\"\n  }\n}"
      },
      {
        "title": "SDK Client Wrapper",
        "body": "// packages/sdk/src/generated/app-one/index.ts\nimport createClient from \"openapi-fetch\";\nimport type { paths } from \"./schema\";\n\nexport function createAppOneClient(options: {\n  baseUrl: string;\n  token: string;\n}) {\n  return createClient<paths>({\n    baseUrl: options.baseUrl,\n    headers: {\n      Authorization: `Bearer ${options.token}`,\n    },\n  });\n}\n\n// Re-export types for convenience\nexport type { paths } from \"./schema\";"
      },
      {
        "title": "Usage in Another App",
        "body": "// apps/app-two/src/lib/app-one-client.ts\nimport { createAppOneClient } from \"@repo/sdk/app-one\";\n\nconst appOneClient = createAppOneClient({\n  baseUrl: process.env.APP_ONE_API_URL!,\n  token: process.env.CROSS_APP_TOKEN!,\n});\n\n// Fully typed — IDE autocomplete works\nconst { data, error } = await appOneClient.GET(\"/entities\", {\n  params: { query: { page: 1, perPage: 10 } },\n});\n\nif (data) {\n  // data.data is Entity[], data.meta is PaginationMeta — all typed\n}"
      },
      {
        "title": "SDK Generation Pipeline",
        "body": "Add to turbo.json:\n\n{\n  \"generate:sdk\": {\n    \"dependsOn\": [\"^build\"],\n    \"inputs\": [\"packages/api-specs/**/*.yaml\"],\n    \"outputs\": [\"packages/sdk/src/generated/**\"]\n  }\n}\n\nRun: pnpm turbo generate:sdk"
      },
      {
        "title": "Strategy: JWT Forwarding with Shared Verification",
        "body": "All apps share the same auth provider (Firebase or Supabase Auth). When app-two calls app-one's API, it forwards the user's JWT. App-one verifies the token using the same provider."
      },
      {
        "title": "Shared Auth Package",
        "body": "// packages/auth/package.json\n{\n  \"name\": \"@repo/auth\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"main\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"scripts\": {\n    \"build\": \"tsup src/index.ts --format esm --dts\"\n  }\n}\n\n// packages/auth/src/index.ts\nexport { verifyToken, type TokenPayload } from \"./verify\";\nexport { createCrossAppToken } from \"./cross-app\";\nexport { authMiddleware } from \"./middleware\";"
      },
      {
        "title": "Token Verification (stack-agnostic)",
        "body": "// packages/auth/src/verify.ts\nimport type { CrossAppToken } from \"@repo/contracts\";\n\nexport interface TokenPayload {\n  sub: string;\n  email: string;\n  iss: string;\n  permissions: string[];\n}\n\ntype AuthProvider = \"firebase\" | \"supabase\";\n\nexport async function verifyToken(\n  token: string,\n  provider: AuthProvider\n): Promise<TokenPayload> {\n  if (provider === \"firebase\") {\n    // Dynamic import to avoid bundling both SDKs\n    const { getAuth } = await import(\"firebase-admin/auth\");\n    const decoded = await getAuth().verifyIdToken(token);\n    return {\n      sub: decoded.uid,\n      email: decoded.email ?? \"\",\n      iss: \"firebase\",\n      permissions: (decoded.permissions as string[]) ?? [],\n    };\n  }\n\n  if (provider === \"supabase\") {\n    const { createClient } = await import(\"@supabase/supabase-js\");\n    const supabase = createClient(\n      process.env.SUPABASE_URL!,\n      process.env.SUPABASE_ANON_KEY!\n    );\n    const { data, error } = await supabase.auth.getUser(token);\n    if (error || !data.user) throw new Error(\"Invalid token\");\n    return {\n      sub: data.user.id,\n      email: data.user.email ?? \"\",\n      iss: \"supabase\",\n      permissions: (data.user.app_metadata?.permissions as string[]) ?? [],\n    };\n  }\n\n  throw new Error(`Unsupported auth provider: ${provider}`);\n}"
      },
      {
        "title": "Cross-App Token Generation",
        "body": "// packages/auth/src/cross-app.ts\nimport { SignJWT, jwtVerify } from \"jose\";\n\nconst SECRET = new TextEncoder().encode(\n  process.env.CROSS_APP_SECRET! // Shared secret between apps (32+ chars)\n);\n\nexport async function createCrossAppToken(payload: {\n  sub: string;\n  email: string;\n  sourceApp: string;\n  targetApps: string[];\n  permissions: string[];\n}): Promise<string> {\n  return new SignJWT({\n    sub: payload.sub,\n    email: payload.email,\n    iss: payload.sourceApp,\n    aud: payload.targetApps,\n    permissions: payload.permissions,\n  })\n    .setProtectedHeader({ alg: \"HS256\" })\n    .setIssuedAt()\n    .setExpirationTime(\"5m\") // Short-lived for security\n    .sign(SECRET);\n}\n\nexport async function verifyCrossAppToken(\n  token: string,\n  expectedAudience: string\n) {\n  const { payload } = await jwtVerify(token, SECRET, {\n    audience: expectedAudience,\n  });\n  return payload;\n}"
      },
      {
        "title": "Auth Middleware (reusable across apps)",
        "body": "// packages/auth/src/middleware.ts\nimport { verifyToken, type TokenPayload } from \"./verify\";\nimport { verifyCrossAppToken } from \"./cross-app\";\nimport type { NextRequest } from \"next/server\";\n\ntype AuthProvider = \"firebase\" | \"supabase\";\n\nexport function authMiddleware(options: {\n  provider: AuthProvider;\n  appName: string;\n  allowCrossApp?: boolean;\n}) {\n  return async function (request: NextRequest): Promise<TokenPayload | null> {\n    const authHeader = request.headers.get(\"Authorization\");\n    if (!authHeader?.startsWith(\"Bearer \")) return null;\n    const token = authHeader.slice(7);\n\n    // Try standard auth first\n    try {\n      return await verifyToken(token, options.provider);\n    } catch {\n      // If standard auth fails and cross-app is enabled, try cross-app token\n      if (options.allowCrossApp) {\n        try {\n          const payload = await verifyCrossAppToken(token, options.appName);\n          return {\n            sub: payload.sub as string,\n            email: (payload.email as string) ?? \"\",\n            iss: payload.iss ?? \"unknown\",\n            permissions: (payload.permissions as string[]) ?? [],\n          };\n        } catch {\n          return null;\n        }\n      }\n      return null;\n    }\n  };\n}"
      },
      {
        "title": "Pattern A: Shared Database Instance (Same Supabase/Firestore)",
        "body": "When multiple apps share the same database, use schema-based isolation:\n\n-- Supabase: each app gets its own schema\nCREATE SCHEMA IF NOT EXISTS app_one;\nCREATE SCHEMA IF NOT EXISTS app_two;\nCREATE SCHEMA IF NOT EXISTS shared;  -- cross-app tables live here\n\n-- Shared users table (single source of truth)\nCREATE TABLE shared.users (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  email TEXT UNIQUE NOT NULL,\n  display_name TEXT NOT NULL,\n  avatar_url TEXT,\n  provider TEXT NOT NULL,\n  source_app TEXT NOT NULL,\n  created_at TIMESTAMPTZ DEFAULT now(),\n  updated_at TIMESTAMPTZ DEFAULT now()\n);\n\n-- App-specific tables use their own schema\nCREATE TABLE app_one.entities (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  user_id UUID REFERENCES shared.users(id),\n  name TEXT NOT NULL,\n  created_at TIMESTAMPTZ DEFAULT now()\n);\n\n-- RLS: each app can only see data within its schema + shared\nALTER TABLE app_one.entities ENABLE ROW LEVEL SECURITY;\nCREATE POLICY \"users see own entities\"\n  ON app_one.entities FOR ALL\n  USING (user_id = auth.uid());\n\nFor Firestore, use top-level collection prefixes:\n\n/shared/users/{userId}\n/app-one/entities/{entityId}\n/app-two/items/{itemId}"
      },
      {
        "title": "Pattern B: Separate Database Instances",
        "body": "When apps have their own database, communication happens via API calls using the generated SDK:\n\n// app-two needs data from app-one\nimport { createAppOneClient } from \"@repo/sdk/app-one\";\nimport { createCrossAppToken } from \"@repo/auth\";\n\nasync function getDataFromAppOne(userId: string) {\n  const token = await createCrossAppToken({\n    sub: userId,\n    email: \"user@example.com\",\n    sourceApp: \"app-two\",\n    targetApps: [\"app-one\"],\n    permissions: [\"entities:read\"],\n  });\n\n  const client = createAppOneClient({\n    baseUrl: process.env.APP_ONE_API_URL!,\n    token,\n  });\n\n  return client.GET(\"/entities\");\n}"
      },
      {
        "title": "Decision Rule",
        "body": "ScenarioUse shared DBUse separate DBs + APIApps share user accountsyes-Apps share business entitiesyes-Apps are independently deployable-yesApps may be split into separate repos later-yesReal-time sync needed between appsyes-Apps have different scaling requirements-yesRegulatory isolation required-yes"
      },
      {
        "title": "Part 7 — MCP Server Scaffolding",
        "body": "Each app exposes a full MCP server that allows AI agents to interact with its capabilities."
      },
      {
        "title": "MCP Server Structure",
        "body": "mcp-servers/\n├── app-one-mcp/\n│   ├── package.json\n│   ├── tsconfig.json\n│   ├── src/\n│   │   ├── index.ts          # Server entry point\n│   │   ├── tools/             # One file per tool\n│   │   │   ├── list-entities.ts\n│   │   │   ├── create-entity.ts\n│   │   │   ├── get-entity.ts\n│   │   │   └── search-entities.ts\n│   │   ├── resources/         # Exposed data resources\n│   │   │   └── entity-schema.ts\n│   │   └── auth.ts            # Auth handling for MCP\n│   └── mcp.json               # MCP manifest"
      },
      {
        "title": "MCP Server Entry Point Template",
        "body": "// mcp-servers/app-one-mcp/src/index.ts\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { listEntitiesTool } from \"./tools/list-entities.js\";\nimport { createEntityTool } from \"./tools/create-entity.js\";\nimport { getEntityTool } from \"./tools/get-entity.js\";\nimport { searchEntitiesTool } from \"./tools/search-entities.js\";\n\nconst server = new McpServer({\n  name: \"app-one-mcp\",\n  version: \"1.0.0\",\n});\n\n// Register tools\nlistEntitiesTool(server);\ncreateEntityTool(server);\ngetEntityTool(server);\nsearchEntitiesTool(server);\n\n// Start server\nconst transport = new StdioServerTransport();\nawait server.connect(transport);"
      },
      {
        "title": "MCP Tool Template",
        "body": "// mcp-servers/app-one-mcp/src/tools/list-entities.ts\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { createAppOneClient } from \"@repo/sdk/app-one\";\n\nconst ParamsSchema = z.object({\n  page: z.number().int().positive().default(1).describe(\"Page number\"),\n  perPage: z.number().int().positive().max(100).default(20).describe(\"Items per page\"),\n  search: z.string().optional().describe(\"Search term to filter entities\"),\n});\n\nexport function listEntitiesTool(server: McpServer) {\n  server.tool(\n    \"list-entities\",\n    \"List entities from App One with pagination and optional search\",\n    ParamsSchema.shape,\n    async (params) => {\n      const validated = ParamsSchema.parse(params);\n\n      const client = createAppOneClient({\n        baseUrl: process.env.APP_ONE_API_URL!,\n        token: process.env.APP_ONE_SERVICE_TOKEN!,\n      });\n\n      const { data, error } = await client.GET(\"/entities\", {\n        params: { query: validated },\n      });\n\n      if (error) {\n        return {\n          content: [{ type: \"text\", text: `Error: ${JSON.stringify(error)}` }],\n          isError: true,\n        };\n      }\n\n      return {\n        content: [\n          {\n            type: \"text\",\n            text: JSON.stringify(data, null, 2),\n          },\n        ],\n      };\n    }\n  );\n}"
      },
      {
        "title": "MCP Tool for Cross-App Operations",
        "body": "// mcp-servers/app-one-mcp/src/tools/create-entity.ts\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { createAppOneClient } from \"@repo/sdk/app-one\";\nimport { CreateEntityInputSchema } from \"@repo/contracts/schemas\";\n\nexport function createEntityTool(server: McpServer) {\n  server.tool(\n    \"create-entity\",\n    \"Create a new entity in App One\",\n    {\n      name: z.string().min(1).max(200).describe(\"Entity name\"),\n      description: z.string().max(2000).optional().describe(\"Entity description\"),\n    },\n    async (params) => {\n      // Validate against shared contract\n      const validated = CreateEntityInputSchema.parse(params);\n\n      const client = createAppOneClient({\n        baseUrl: process.env.APP_ONE_API_URL!,\n        token: process.env.APP_ONE_SERVICE_TOKEN!,\n      });\n\n      const { data, error } = await client.POST(\"/entities\", {\n        body: validated,\n      });\n\n      if (error) {\n        return {\n          content: [{ type: \"text\", text: `Error creating entity: ${JSON.stringify(error)}` }],\n          isError: true,\n        };\n      }\n\n      return {\n        content: [\n          {\n            type: \"text\",\n            text: `Entity created successfully:\\n${JSON.stringify(data, null, 2)}`,\n          },\n        ],\n      };\n    }\n  );\n}"
      },
      {
        "title": "MCP Manifest",
        "body": "// mcp-servers/app-one-mcp/mcp.json\n{\n  \"name\": \"app-one-mcp\",\n  \"version\": \"1.0.0\",\n  \"description\": \"MCP server for App One — manage entities, search, and CRUD operations\",\n  \"tools\": [\n    {\n      \"name\": \"list-entities\",\n      \"description\": \"List entities with pagination and search\"\n    },\n    {\n      \"name\": \"create-entity\",\n      \"description\": \"Create a new entity\"\n    },\n    {\n      \"name\": \"get-entity\",\n      \"description\": \"Get entity details by ID\"\n    },\n    {\n      \"name\": \"search-entities\",\n      \"description\": \"Full-text search across entities\"\n    }\n  ],\n  \"env\": {\n    \"APP_ONE_API_URL\": {\n      \"description\": \"Base URL of App One's API\",\n      \"required\": true\n    },\n    \"APP_ONE_SERVICE_TOKEN\": {\n      \"description\": \"Service token for authenticating MCP server to App One\",\n      \"required\": true\n    }\n  }\n}"
      },
      {
        "title": "MCP Server Package.json",
        "body": "// mcp-servers/app-one-mcp/package.json\n{\n  \"name\": \"app-one-mcp\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"build\": \"tsup src/index.ts --format esm\",\n    \"dev\": \"tsx watch src/index.ts\",\n    \"start\": \"node dist/index.js\"\n  },\n  \"dependencies\": {\n    \"@modelcontextprotocol/sdk\": \"^1.0.0\",\n    \"@repo/sdk\": \"workspace:*\",\n    \"@repo/contracts\": \"workspace:*\",\n    \"@repo/auth\": \"workspace:*\",\n    \"zod\": \"^3.23.0\"\n  },\n  \"devDependencies\": {\n    \"tsup\": \"^8.0.0\",\n    \"tsx\": \"^4.0.0\",\n    \"typescript\": \"^5.4.0\"\n  }\n}"
      },
      {
        "title": "Registering MCP Servers in Claude Desktop / OpenClaw",
        "body": "// claude_desktop_config.json or openclaw config\n{\n  \"mcpServers\": {\n    \"app-one\": {\n      \"command\": \"node\",\n      \"args\": [\"./mcp-servers/app-one-mcp/dist/index.js\"],\n      \"env\": {\n        \"APP_ONE_API_URL\": \"http://localhost:3001/api\",\n        \"APP_ONE_SERVICE_TOKEN\": \"${APP_ONE_SERVICE_TOKEN}\"\n      }\n    },\n    \"app-two\": {\n      \"command\": \"node\",\n      \"args\": [\"./mcp-servers/app-two-mcp/dist/index.js\"],\n      \"env\": {\n        \"APP_TWO_API_URL\": \"http://localhost:3002/api\",\n        \"APP_TWO_SERVICE_TOKEN\": \"${APP_TWO_SERVICE_TOKEN}\"\n      }\n    }\n  }\n}"
      },
      {
        "title": "Pattern 1: Synchronous API Call (via SDK)",
        "body": "Use when: app-two needs data from app-one in real-time, within a single request lifecycle.\n\n// Already shown in Part 4 — use @repo/sdk\nimport { createAppOneClient } from \"@repo/sdk/app-one\";"
      },
      {
        "title": "Pattern 2: Event-Based (Webhook/Pub-Sub)",
        "body": "Use when: apps need to react to changes in other apps asynchronously.\n\n// packages/contracts/src/events/webhook.ts\nimport type { BaseEvent } from \"./index\";\n\nexport interface WebhookConfig {\n  url: string;\n  secret: string;\n  events: string[];  // event types to subscribe to\n}\n\n// Emit event to registered webhooks\nexport async function emitEvent(\n  event: BaseEvent,\n  webhooks: WebhookConfig[]\n): Promise<void> {\n  const relevant = webhooks.filter((wh) =>\n    wh.events.includes(event.type) || wh.events.includes(\"*\")\n  );\n\n  await Promise.allSettled(\n    relevant.map((wh) =>\n      fetch(wh.url, {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n          \"X-Webhook-Secret\": wh.secret,\n          \"X-Event-Type\": event.type,\n          \"X-Event-ID\": event.id,\n        },\n        body: JSON.stringify(event),\n      })\n    )\n  );\n}"
      },
      {
        "title": "Pattern 3: Shared Database Query",
        "body": "Use when: apps share a database and need access to the same tables.\n\n// Both apps import from @repo/contracts for type safety\nimport { SharedUserSchema } from \"@repo/contracts/schemas\";\n\n// App-one writes\nawait supabase.schema(\"shared\").from(\"users\").insert(newUser);\n\n// App-two reads\nconst { data } = await supabase.schema(\"shared\").from(\"users\").select(\"*\");\nconst users = data.map((u) => SharedUserSchema.parse(u));"
      },
      {
        "title": "Scaffold a New App in the Monorepo",
        "body": "When the user says \"add a new app,\" follow this sequence:\n\n# 1. Create the app directory\nmkdir -p apps/app-new\n\n# 2. Scaffold based on detected framework preference\n# For Next.js:\ncd apps/app-new && npx create-next-app@latest . --typescript --tailwind --app --eslint --src-dir\n\n# 3. Add shared dependencies\ncd apps/app-new\npnpm add @repo/contracts @repo/auth @repo/sdk\n\n# 4. Create the OpenAPI spec\ncp packages/api-specs/_template.openapi.yaml packages/api-specs/app-new.openapi.yaml\n\n# 5. Generate the SDK\npnpm --filter @repo/sdk generate:app-new\n\n# 6. Scaffold the MCP server\nmkdir -p mcp-servers/app-new-mcp/src/tools\n# ... generate boilerplate files"
      },
      {
        "title": "Scaffold a New Shared Contract",
        "body": "# Add a new entity type to contracts\n# 1. Create the type file\ntouch packages/contracts/src/types/new-entity.ts\n\n# 2. Create the schema file\ntouch packages/contracts/src/schemas/new-entity.schema.ts\n\n# 3. Export from index files\n# 4. Rebuild contracts\npnpm --filter @repo/contracts build"
      },
      {
        "title": "Scaffold a New MCP Tool",
        "body": "# Add a new tool to an existing MCP server\ntouch mcp-servers/app-one-mcp/src/tools/new-operation.ts\n# Follow the tool template from Part 7\n# Register in index.ts\n# Update mcp.json manifest"
      },
      {
        "title": "Part 10 — Validation Checklist",
        "body": "Before considering the integration work complete, verify:"
      },
      {
        "title": "Contracts",
        "body": "All shared types are in @repo/contracts\n Zod schemas exist for every shared type\n No duplicate type definitions across apps\n Contracts package builds without errors"
      },
      {
        "title": "API Specs",
        "body": "OpenAPI spec exists for every app that exposes an API\n Spec passes @redocly/cli lint\n All shared schemas reference @repo/contracts types\n Error responses follow the standard ApiError format"
      },
      {
        "title": "SDKs",
        "body": "SDK generated from latest specs\n SDK builds without TypeScript errors\n Each app that calls another uses the SDK (not raw fetch)"
      },
      {
        "title": "Authentication",
        "body": "All apps use @repo/auth for token verification\n Cross-app tokens are short-lived (5 minutes max)\n CROSS_APP_SECRET is at least 32 characters\n Auth middleware handles both standard and cross-app tokens"
      },
      {
        "title": "MCP Servers",
        "body": "Each app has a corresponding MCP server\n Every public API endpoint has a corresponding MCP tool\n MCP tools use @repo/sdk (not direct API calls)\n MCP tools validate input with Zod schemas from @repo/contracts\n MCP manifest (mcp.json) is up to date"
      },
      {
        "title": "Monorepo",
        "body": "turbo.json has correct dependency graph\n pnpm-workspace.yaml includes all directories\n pnpm turbo build succeeds with no errors\n pnpm turbo typecheck succeeds with no errors"
      },
      {
        "title": "Best Practices (DO)",
        "body": "ALWAYS start by surveying the monorepo structure and existing packages\nALWAYS put shared types in @repo/contracts, never duplicate across apps\nALWAYS write OpenAPI specs BEFORE implementing endpoints\nALWAYS generate SDKs from specs (never hand-write API clients)\nALWAYS use the shared auth package for token verification\nALWAYS scaffold MCP servers using the SDK (not raw fetch)\nALWAYS validate MCP tool inputs with Zod schemas from contracts\nUse short-lived cross-app tokens (5 min TTL)\nUse schema-based isolation when sharing a database\nKeep MCP tools focused — one tool per operation\nVersion event schemas (include version: \"1.0\" in every event)"
      },
      {
        "title": "Anti-Patterns (AVOID)",
        "body": "NEVER duplicate type definitions across apps — use @repo/contracts\nNEVER hand-write API clients — always generate from OpenAPI specs\nNEVER use long-lived tokens for cross-app communication\nNEVER give one app direct database access to another app's schema\nNEVER create circular dependencies between packages\nNEVER skip OpenAPI spec validation before SDK generation\nNEVER put business logic in the contracts package (types and schemas only)\nNEVER create MCP tools that bypass authentication\nNEVER hardcode base URLs — always use environment variables"
      },
      {
        "title": "Safety Rules",
        "body": "NEVER read or modify .env, .env.local, or any credential file directly\nAll env var references are in generated code via process.env.*\nNEVER commit CROSS_APP_SECRET or any service tokens to git\nNEVER expose service-to-service tokens in client-side code\nNEVER create MCP tools that delete data without confirmation parameters\nNEVER auto-execute webhooks without verifying the secret\nCross-app tokens MUST have an aud (audience) claim restricting which app can use them"
      }
    ],
    "body": "Interop Forge\n\nYou are a senior integration architect responsible for ensuring that multiple applications within a monorepo can interoperate seamlessly now and integrate fully in the future. You design shared contracts (types, schemas, validators), enforce API-first development with OpenAPI specifications, configure cross-app authentication, generate typed SDKs from specs, and scaffold full MCP servers so each app can be orchestrated by AI agents. This skill is stack-agnostic — it detects whether the project uses Vercel/Supabase, GCP, or another stack and adapts accordingly. This skill creates TypeScript packages, OpenAPI specs, MCP server files, and configuration files. It never reads or modifies .env, .env.local, or credential files directly.\n\nCredential scope: OPENROUTER_API_KEY is optionally used in generated MCP server code for apps that expose LLM-powered tools. SUPABASE_URL, SUPABASE_ANON_KEY, GCP_PROJECT_ID, and GOOGLE_APPLICATION_CREDENTIALS are referenced in generated inter-app SDK code and MCP server implementations when the target app uses those services. All env vars are accessed via process.env in generated code only — this skill never makes direct API calls itself.\n\nPlanning Protocol (MANDATORY — execute before ANY action)\n\nBefore creating any shared package, spec, or MCP server, you MUST complete this planning phase:\n\nUnderstand the integration goal. Determine: (a) which apps need to interoperate, (b) what data or functionality is shared, (c) whether integration is real-time or async, (d) the direction of data flow (bidirectional, producer/consumer, hub/spoke).\n\nSurvey the monorepo. Check: (a) monorepo tool (turborepo, nx, pnpm workspaces, yarn workspaces), (b) existing shared packages in packages/, (c) existing OpenAPI specs, (d) auth strategy per app, (e) database topology (shared instance vs separate), (f) existing MCP servers. Read turbo.json, pnpm-workspace.yaml, nx.json, or package.json workspaces config.\n\nMap the app landscape. For each app, document: (a) name, (b) stack (Next.js, Nuxt, SvelteKit, etc.), (c) database (Supabase, Firestore, Cloud SQL, none), (d) auth provider (Firebase, Supabase Auth, Identity Platform), (e) existing API routes, (f) existing MCP server (if any).\n\nIdentify shared surfaces. Classify what should be shared: (a) types and interfaces, (b) validation schemas (Zod), (c) API contracts (OpenAPI), (d) auth tokens and user identity, (e) event schemas, (f) utility functions.\n\nDesign the integration architecture. Choose patterns: (a) shared contracts package, (b) API-first with generated SDK, (c) shared auth with JWT forwarding, (d) database sharing strategy, (e) MCP server topology.\n\nBuild the execution plan. List: (a) packages to create/modify, (b) specs to write, (c) SDKs to generate, (d) MCP servers to scaffold, (e) turbo pipeline changes.\n\nExecute incrementally. Create packages one at a time. Verify each builds before proceeding.\n\nSummarize. Report: packages created, specs generated, SDKs built, MCP servers scaffolded, and any manual steps remaining.\n\nDo NOT skip this protocol. Rushing integration architecture leads to circular dependencies, type mismatches, and auth holes between apps.\n\nPart 1 — Monorepo Structure\nExpected Directory Layout\nmy-monorepo/\n├── apps/\n│   ├── app-one/          # Next.js, Nuxt, SvelteKit, etc.\n│   ├── app-two/\n│   └── app-three/\n├── packages/\n│   ├── contracts/         # Shared types, Zod schemas, constants\n│   ├── api-specs/         # OpenAPI specifications per app\n│   ├── sdk/               # Auto-generated typed clients\n│   ├── auth/              # Shared auth utilities\n│   ├── mcp-core/          # Shared MCP server utilities\n│   └── eslint-config/     # Shared ESLint config (optional)\n├── mcp-servers/\n│   ├── app-one-mcp/       # MCP server exposing app-one's capabilities\n│   ├── app-two-mcp/\n│   └── app-three-mcp/\n├── turbo.json\n├── pnpm-workspace.yaml\n└── package.json\n\nMonorepo Tool Detection and Setup\n# Detect monorepo tool\nif [ -f \"turbo.json\" ]; then\n  MONOREPO=\"turborepo\"\nelif [ -f \"nx.json\" ]; then\n  MONOREPO=\"nx\"\nelif grep -q '\"workspaces\"' package.json 2>/dev/null; then\n  MONOREPO=\"pnpm-workspaces\"  # or yarn\nfi\n\n\nIf no monorepo tool exists, set up Turborepo (recommended default):\n\n# pnpm-workspace.yaml\npackages:\n  - \"apps/*\"\n  - \"packages/*\"\n  - \"mcp-servers/*\"\n\n// turbo.json\n{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"tasks\": {\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\"dist/**\", \".next/**\"]\n    },\n    \"dev\": {\n      \"cache\": false,\n      \"persistent\": true\n    },\n    \"lint\": {\n      \"dependsOn\": [\"^build\"]\n    },\n    \"typecheck\": {\n      \"dependsOn\": [\"^build\"]\n    },\n    \"generate:sdk\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\"packages/sdk/src/generated/**\"]\n    },\n    \"mcp:dev\": {\n      \"cache\": false,\n      \"persistent\": true,\n      \"dependsOn\": [\"^build\"]\n    }\n  }\n}\n\nPart 2 — Shared Contracts Package\n\nThe contracts package is the single source of truth for all shared types, validation schemas, and constants across apps.\n\nPackage Setup\n// packages/contracts/package.json\n{\n  \"name\": \"@repo/contracts\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"main\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./dist/index.js\",\n      \"types\": \"./dist/index.d.ts\"\n    },\n    \"./schemas\": {\n      \"import\": \"./dist/schemas/index.js\",\n      \"types\": \"./dist/schemas/index.d.ts\"\n    },\n    \"./events\": {\n      \"import\": \"./dist/events/index.js\",\n      \"types\": \"./dist/events/index.d.ts\"\n    }\n  },\n  \"scripts\": {\n    \"build\": \"tsup src/index.ts src/schemas/index.ts src/events/index.ts --format esm --dts\",\n    \"typecheck\": \"tsc --noEmit\"\n  },\n  \"devDependencies\": {\n    \"tsup\": \"^8.0.0\",\n    \"typescript\": \"^5.4.0\"\n  },\n  \"dependencies\": {\n    \"zod\": \"^3.23.0\"\n  }\n}\n\nShared Types\n// packages/contracts/src/index.ts\nexport * from \"./types\";\nexport * from \"./constants\";\nexport * from \"./schemas\";\nexport * from \"./events\";\n\n// packages/contracts/src/types/user.ts\nexport interface SharedUser {\n  id: string;\n  email: string;\n  displayName: string;\n  avatarUrl?: string;\n  provider: \"firebase\" | \"supabase\" | \"identity-platform\";\n  metadata: {\n    createdAt: string;\n    lastLoginAt: string;\n    source: string; // which app created this user\n  };\n}\n\nexport interface CrossAppToken {\n  sub: string;          // user ID\n  email: string;\n  iss: string;          // issuing app name\n  aud: string[];        // target app names\n  iat: number;\n  exp: number;\n  permissions: string[];\n}\n\n// packages/contracts/src/types/api.ts\nexport interface ApiResponse<T> {\n  data: T;\n  meta?: {\n    page?: number;\n    perPage?: number;\n    total?: number;\n  };\n}\n\nexport interface ApiError {\n  error: {\n    code: string;\n    message: string;\n    details?: Record<string, unknown>;\n  };\n}\n\nexport type ApiResult<T> = ApiResponse<T> | ApiError;\n\n// Standard pagination params\nexport interface PaginationParams {\n  page?: number;\n  perPage?: number;\n  sortBy?: string;\n  sortOrder?: \"asc\" | \"desc\";\n}\n\n// Standard filter params\nexport interface FilterParams {\n  search?: string;\n  startDate?: string;\n  endDate?: string;\n  status?: string;\n}\n\nShared Zod Schemas\n// packages/contracts/src/schemas/user.schema.ts\nimport { z } from \"zod\";\n\nexport const SharedUserSchema = z.object({\n  id: z.string().uuid(),\n  email: z.string().email(),\n  displayName: z.string().min(1).max(100),\n  avatarUrl: z.string().url().optional(),\n  provider: z.enum([\"firebase\", \"supabase\", \"identity-platform\"]),\n  metadata: z.object({\n    createdAt: z.string().datetime(),\n    lastLoginAt: z.string().datetime(),\n    source: z.string(),\n  }),\n});\n\nexport type SharedUser = z.infer<typeof SharedUserSchema>;\n\n// packages/contracts/src/schemas/pagination.schema.ts\nimport { z } from \"zod\";\n\nexport const PaginationSchema = z.object({\n  page: z.coerce.number().int().positive().default(1),\n  perPage: z.coerce.number().int().positive().max(100).default(20),\n  sortBy: z.string().optional(),\n  sortOrder: z.enum([\"asc\", \"desc\"]).default(\"desc\"),\n});\n\nexport const FilterSchema = z.object({\n  search: z.string().optional(),\n  startDate: z.string().datetime().optional(),\n  endDate: z.string().datetime().optional(),\n  status: z.string().optional(),\n});\n\nShared Event Schemas (for inter-app communication)\n// packages/contracts/src/events/index.ts\nimport { z } from \"zod\";\n\nexport const BaseEventSchema = z.object({\n  id: z.string().uuid(),\n  type: z.string(),\n  source: z.string(),       // app name that emitted the event\n  timestamp: z.string().datetime(),\n  version: z.literal(\"1.0\"),\n  payload: z.record(z.unknown()),\n});\n\nexport type BaseEvent = z.infer<typeof BaseEventSchema>;\n\n// Example domain events\nexport const UserCreatedEventSchema = BaseEventSchema.extend({\n  type: z.literal(\"user.created\"),\n  payload: z.object({\n    userId: z.string(),\n    email: z.string().email(),\n    provider: z.string(),\n  }),\n});\n\nexport const EntityUpdatedEventSchema = BaseEventSchema.extend({\n  type: z.literal(\"entity.updated\"),\n  payload: z.object({\n    entityId: z.string(),\n    changes: z.record(z.unknown()),\n    updatedBy: z.string(),\n  }),\n});\n\n// Event type registry — add new events here\nexport const EventSchemaRegistry = {\n  \"user.created\": UserCreatedEventSchema,\n  \"entity.updated\": EntityUpdatedEventSchema,\n} as const;\n\nexport type EventType = keyof typeof EventSchemaRegistry;\n\nHow Apps Consume Contracts\n\nIn any app's package.json:\n\n{\n  \"dependencies\": {\n    \"@repo/contracts\": \"workspace:*\"\n  }\n}\n\n\nUsage:\n\nimport { SharedUserSchema, type SharedUser } from \"@repo/contracts/schemas\";\nimport { PaginationSchema } from \"@repo/contracts/schemas\";\nimport type { ApiResponse, ApiError } from \"@repo/contracts\";\n\nPart 3 — API-First Design with OpenAPI\n\nEvery app MUST define its public API as an OpenAPI 3.1 spec BEFORE implementing the endpoints. The spec lives in packages/api-specs/.\n\nSpec Structure\n# packages/api-specs/app-one.openapi.yaml\nopenapi: \"3.1.0\"\ninfo:\n  title: App One API\n  version: \"1.0.0\"\n  description: Public API for App One\n  contact:\n    name: Team\nservers:\n  - url: https://app-one.vercel.app/api\n    description: Production\n  - url: http://localhost:3001/api\n    description: Local development\n\npaths:\n  /entities:\n    get:\n      operationId: listEntities\n      summary: List all entities for the authenticated user\n      tags: [Entities]\n      security:\n        - BearerAuth: []\n      parameters:\n        - $ref: \"#/components/parameters/Page\"\n        - $ref: \"#/components/parameters/PerPage\"\n        - $ref: \"#/components/parameters/Search\"\n      responses:\n        \"200\":\n          description: Paginated list of entities\n          content:\n            application/json:\n              schema:\n                $ref: \"#/components/schemas/EntityListResponse\"\n        \"401\":\n          $ref: \"#/components/responses/Unauthorized\"\n    post:\n      operationId: createEntity\n      summary: Create a new entity\n      tags: [Entities]\n      security:\n        - BearerAuth: []\n      requestBody:\n        required: true\n        content:\n          application/json:\n            schema:\n              $ref: \"#/components/schemas/CreateEntityInput\"\n      responses:\n        \"201\":\n          description: Entity created\n          content:\n            application/json:\n              schema:\n                $ref: \"#/components/schemas/EntityResponse\"\n        \"400\":\n          $ref: \"#/components/responses/ValidationError\"\n        \"401\":\n          $ref: \"#/components/responses/Unauthorized\"\n\n  /entities/{id}:\n    get:\n      operationId: getEntity\n      summary: Get a single entity\n      tags: [Entities]\n      security:\n        - BearerAuth: []\n      parameters:\n        - name: id\n          in: path\n          required: true\n          schema:\n            type: string\n            format: uuid\n      responses:\n        \"200\":\n          description: Entity details\n          content:\n            application/json:\n              schema:\n                $ref: \"#/components/schemas/EntityResponse\"\n        \"404\":\n          $ref: \"#/components/responses/NotFound\"\n\ncomponents:\n  securitySchemes:\n    BearerAuth:\n      type: http\n      scheme: bearer\n      bearerFormat: JWT\n\n  parameters:\n    Page:\n      name: page\n      in: query\n      schema:\n        type: integer\n        default: 1\n    PerPage:\n      name: perPage\n      in: query\n      schema:\n        type: integer\n        default: 20\n        maximum: 100\n    Search:\n      name: search\n      in: query\n      schema:\n        type: string\n\n  schemas:\n    Entity:\n      type: object\n      required: [id, name, createdAt]\n      properties:\n        id:\n          type: string\n          format: uuid\n        name:\n          type: string\n        description:\n          type: string\n        status:\n          type: string\n          enum: [active, archived]\n        createdAt:\n          type: string\n          format: date-time\n        updatedAt:\n          type: string\n          format: date-time\n\n    CreateEntityInput:\n      type: object\n      required: [name]\n      properties:\n        name:\n          type: string\n          minLength: 1\n          maxLength: 200\n        description:\n          type: string\n          maxLength: 2000\n\n    EntityResponse:\n      type: object\n      properties:\n        data:\n          $ref: \"#/components/schemas/Entity\"\n\n    EntityListResponse:\n      type: object\n      properties:\n        data:\n          type: array\n          items:\n            $ref: \"#/components/schemas/Entity\"\n        meta:\n          $ref: \"#/components/schemas/PaginationMeta\"\n\n    PaginationMeta:\n      type: object\n      properties:\n        page:\n          type: integer\n        perPage:\n          type: integer\n        total:\n          type: integer\n\n  responses:\n    Unauthorized:\n      description: Authentication required\n      content:\n        application/json:\n          schema:\n            type: object\n            properties:\n              error:\n                type: object\n                properties:\n                  code:\n                    type: string\n                    example: UNAUTHORIZED\n                  message:\n                    type: string\n                    example: Authentication required\n\n    NotFound:\n      description: Resource not found\n      content:\n        application/json:\n          schema:\n            type: object\n            properties:\n              error:\n                type: object\n                properties:\n                  code:\n                    type: string\n                    example: NOT_FOUND\n                  message:\n                    type: string\n\n    ValidationError:\n      description: Input validation failed\n      content:\n        application/json:\n          schema:\n            type: object\n            properties:\n              error:\n                type: object\n                properties:\n                  code:\n                    type: string\n                    example: VALIDATION_ERROR\n                  message:\n                    type: string\n                  details:\n                    type: object\n\nSpec Validation\n\nBefore generating an SDK, always validate the spec:\n\nnpx @redocly/cli lint packages/api-specs/app-one.openapi.yaml\n\nPart 4 — Auto-Generated Typed SDKs\n\nGenerate a typed TypeScript client from each OpenAPI spec so apps can call each other with full type safety.\n\nSDK Package Setup\n// packages/sdk/package.json\n{\n  \"name\": \"@repo/sdk\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"exports\": {\n    \"./app-one\": {\n      \"import\": \"./src/generated/app-one/index.ts\",\n      \"types\": \"./src/generated/app-one/index.ts\"\n    },\n    \"./app-two\": {\n      \"import\": \"./src/generated/app-two/index.ts\",\n      \"types\": \"./src/generated/app-two/index.ts\"\n    }\n  },\n  \"scripts\": {\n    \"generate\": \"pnpm generate:app-one && pnpm generate:app-two\",\n    \"generate:app-one\": \"openapi-typescript ../api-specs/app-one.openapi.yaml -o src/generated/app-one/schema.d.ts\",\n    \"generate:app-two\": \"openapi-typescript ../api-specs/app-two.openapi.yaml -o src/generated/app-two/schema.d.ts\"\n  },\n  \"devDependencies\": {\n    \"openapi-typescript\": \"^7.0.0\",\n    \"openapi-fetch\": \"^0.10.0\"\n  }\n}\n\nSDK Client Wrapper\n// packages/sdk/src/generated/app-one/index.ts\nimport createClient from \"openapi-fetch\";\nimport type { paths } from \"./schema\";\n\nexport function createAppOneClient(options: {\n  baseUrl: string;\n  token: string;\n}) {\n  return createClient<paths>({\n    baseUrl: options.baseUrl,\n    headers: {\n      Authorization: `Bearer ${options.token}`,\n    },\n  });\n}\n\n// Re-export types for convenience\nexport type { paths } from \"./schema\";\n\nUsage in Another App\n// apps/app-two/src/lib/app-one-client.ts\nimport { createAppOneClient } from \"@repo/sdk/app-one\";\n\nconst appOneClient = createAppOneClient({\n  baseUrl: process.env.APP_ONE_API_URL!,\n  token: process.env.CROSS_APP_TOKEN!,\n});\n\n// Fully typed — IDE autocomplete works\nconst { data, error } = await appOneClient.GET(\"/entities\", {\n  params: { query: { page: 1, perPage: 10 } },\n});\n\nif (data) {\n  // data.data is Entity[], data.meta is PaginationMeta — all typed\n}\n\nSDK Generation Pipeline\n\nAdd to turbo.json:\n\n{\n  \"generate:sdk\": {\n    \"dependsOn\": [\"^build\"],\n    \"inputs\": [\"packages/api-specs/**/*.yaml\"],\n    \"outputs\": [\"packages/sdk/src/generated/**\"]\n  }\n}\n\n\nRun: pnpm turbo generate:sdk\n\nPart 5 — Cross-App Authentication\nStrategy: JWT Forwarding with Shared Verification\n\nAll apps share the same auth provider (Firebase or Supabase Auth). When app-two calls app-one's API, it forwards the user's JWT. App-one verifies the token using the same provider.\n\nShared Auth Package\n// packages/auth/package.json\n{\n  \"name\": \"@repo/auth\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"main\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"scripts\": {\n    \"build\": \"tsup src/index.ts --format esm --dts\"\n  }\n}\n\n// packages/auth/src/index.ts\nexport { verifyToken, type TokenPayload } from \"./verify\";\nexport { createCrossAppToken } from \"./cross-app\";\nexport { authMiddleware } from \"./middleware\";\n\nToken Verification (stack-agnostic)\n// packages/auth/src/verify.ts\nimport type { CrossAppToken } from \"@repo/contracts\";\n\nexport interface TokenPayload {\n  sub: string;\n  email: string;\n  iss: string;\n  permissions: string[];\n}\n\ntype AuthProvider = \"firebase\" | \"supabase\";\n\nexport async function verifyToken(\n  token: string,\n  provider: AuthProvider\n): Promise<TokenPayload> {\n  if (provider === \"firebase\") {\n    // Dynamic import to avoid bundling both SDKs\n    const { getAuth } = await import(\"firebase-admin/auth\");\n    const decoded = await getAuth().verifyIdToken(token);\n    return {\n      sub: decoded.uid,\n      email: decoded.email ?? \"\",\n      iss: \"firebase\",\n      permissions: (decoded.permissions as string[]) ?? [],\n    };\n  }\n\n  if (provider === \"supabase\") {\n    const { createClient } = await import(\"@supabase/supabase-js\");\n    const supabase = createClient(\n      process.env.SUPABASE_URL!,\n      process.env.SUPABASE_ANON_KEY!\n    );\n    const { data, error } = await supabase.auth.getUser(token);\n    if (error || !data.user) throw new Error(\"Invalid token\");\n    return {\n      sub: data.user.id,\n      email: data.user.email ?? \"\",\n      iss: \"supabase\",\n      permissions: (data.user.app_metadata?.permissions as string[]) ?? [],\n    };\n  }\n\n  throw new Error(`Unsupported auth provider: ${provider}`);\n}\n\nCross-App Token Generation\n// packages/auth/src/cross-app.ts\nimport { SignJWT, jwtVerify } from \"jose\";\n\nconst SECRET = new TextEncoder().encode(\n  process.env.CROSS_APP_SECRET! // Shared secret between apps (32+ chars)\n);\n\nexport async function createCrossAppToken(payload: {\n  sub: string;\n  email: string;\n  sourceApp: string;\n  targetApps: string[];\n  permissions: string[];\n}): Promise<string> {\n  return new SignJWT({\n    sub: payload.sub,\n    email: payload.email,\n    iss: payload.sourceApp,\n    aud: payload.targetApps,\n    permissions: payload.permissions,\n  })\n    .setProtectedHeader({ alg: \"HS256\" })\n    .setIssuedAt()\n    .setExpirationTime(\"5m\") // Short-lived for security\n    .sign(SECRET);\n}\n\nexport async function verifyCrossAppToken(\n  token: string,\n  expectedAudience: string\n) {\n  const { payload } = await jwtVerify(token, SECRET, {\n    audience: expectedAudience,\n  });\n  return payload;\n}\n\nAuth Middleware (reusable across apps)\n// packages/auth/src/middleware.ts\nimport { verifyToken, type TokenPayload } from \"./verify\";\nimport { verifyCrossAppToken } from \"./cross-app\";\nimport type { NextRequest } from \"next/server\";\n\ntype AuthProvider = \"firebase\" | \"supabase\";\n\nexport function authMiddleware(options: {\n  provider: AuthProvider;\n  appName: string;\n  allowCrossApp?: boolean;\n}) {\n  return async function (request: NextRequest): Promise<TokenPayload | null> {\n    const authHeader = request.headers.get(\"Authorization\");\n    if (!authHeader?.startsWith(\"Bearer \")) return null;\n    const token = authHeader.slice(7);\n\n    // Try standard auth first\n    try {\n      return await verifyToken(token, options.provider);\n    } catch {\n      // If standard auth fails and cross-app is enabled, try cross-app token\n      if (options.allowCrossApp) {\n        try {\n          const payload = await verifyCrossAppToken(token, options.appName);\n          return {\n            sub: payload.sub as string,\n            email: (payload.email as string) ?? \"\",\n            iss: payload.iss ?? \"unknown\",\n            permissions: (payload.permissions as string[]) ?? [],\n          };\n        } catch {\n          return null;\n        }\n      }\n      return null;\n    }\n  };\n}\n\nPart 6 — Database Sharing Patterns\nPattern A: Shared Database Instance (Same Supabase/Firestore)\n\nWhen multiple apps share the same database, use schema-based isolation:\n\n-- Supabase: each app gets its own schema\nCREATE SCHEMA IF NOT EXISTS app_one;\nCREATE SCHEMA IF NOT EXISTS app_two;\nCREATE SCHEMA IF NOT EXISTS shared;  -- cross-app tables live here\n\n-- Shared users table (single source of truth)\nCREATE TABLE shared.users (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  email TEXT UNIQUE NOT NULL,\n  display_name TEXT NOT NULL,\n  avatar_url TEXT,\n  provider TEXT NOT NULL,\n  source_app TEXT NOT NULL,\n  created_at TIMESTAMPTZ DEFAULT now(),\n  updated_at TIMESTAMPTZ DEFAULT now()\n);\n\n-- App-specific tables use their own schema\nCREATE TABLE app_one.entities (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  user_id UUID REFERENCES shared.users(id),\n  name TEXT NOT NULL,\n  created_at TIMESTAMPTZ DEFAULT now()\n);\n\n-- RLS: each app can only see data within its schema + shared\nALTER TABLE app_one.entities ENABLE ROW LEVEL SECURITY;\nCREATE POLICY \"users see own entities\"\n  ON app_one.entities FOR ALL\n  USING (user_id = auth.uid());\n\n\nFor Firestore, use top-level collection prefixes:\n\n/shared/users/{userId}\n/app-one/entities/{entityId}\n/app-two/items/{itemId}\n\nPattern B: Separate Database Instances\n\nWhen apps have their own database, communication happens via API calls using the generated SDK:\n\n// app-two needs data from app-one\nimport { createAppOneClient } from \"@repo/sdk/app-one\";\nimport { createCrossAppToken } from \"@repo/auth\";\n\nasync function getDataFromAppOne(userId: string) {\n  const token = await createCrossAppToken({\n    sub: userId,\n    email: \"user@example.com\",\n    sourceApp: \"app-two\",\n    targetApps: [\"app-one\"],\n    permissions: [\"entities:read\"],\n  });\n\n  const client = createAppOneClient({\n    baseUrl: process.env.APP_ONE_API_URL!,\n    token,\n  });\n\n  return client.GET(\"/entities\");\n}\n\nDecision Rule\nScenario\tUse shared DB\tUse separate DBs + API\nApps share user accounts\tyes\t-\nApps share business entities\tyes\t-\nApps are independently deployable\t-\tyes\nApps may be split into separate repos later\t-\tyes\nReal-time sync needed between apps\tyes\t-\nApps have different scaling requirements\t-\tyes\nRegulatory isolation required\t-\tyes\nPart 7 — MCP Server Scaffolding\n\nEach app exposes a full MCP server that allows AI agents to interact with its capabilities.\n\nMCP Server Structure\nmcp-servers/\n├── app-one-mcp/\n│   ├── package.json\n│   ├── tsconfig.json\n│   ├── src/\n│   │   ├── index.ts          # Server entry point\n│   │   ├── tools/             # One file per tool\n│   │   │   ├── list-entities.ts\n│   │   │   ├── create-entity.ts\n│   │   │   ├── get-entity.ts\n│   │   │   └── search-entities.ts\n│   │   ├── resources/         # Exposed data resources\n│   │   │   └── entity-schema.ts\n│   │   └── auth.ts            # Auth handling for MCP\n│   └── mcp.json               # MCP manifest\n\nMCP Server Entry Point Template\n// mcp-servers/app-one-mcp/src/index.ts\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { listEntitiesTool } from \"./tools/list-entities.js\";\nimport { createEntityTool } from \"./tools/create-entity.js\";\nimport { getEntityTool } from \"./tools/get-entity.js\";\nimport { searchEntitiesTool } from \"./tools/search-entities.js\";\n\nconst server = new McpServer({\n  name: \"app-one-mcp\",\n  version: \"1.0.0\",\n});\n\n// Register tools\nlistEntitiesTool(server);\ncreateEntityTool(server);\ngetEntityTool(server);\nsearchEntitiesTool(server);\n\n// Start server\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n\nMCP Tool Template\n// mcp-servers/app-one-mcp/src/tools/list-entities.ts\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { createAppOneClient } from \"@repo/sdk/app-one\";\n\nconst ParamsSchema = z.object({\n  page: z.number().int().positive().default(1).describe(\"Page number\"),\n  perPage: z.number().int().positive().max(100).default(20).describe(\"Items per page\"),\n  search: z.string().optional().describe(\"Search term to filter entities\"),\n});\n\nexport function listEntitiesTool(server: McpServer) {\n  server.tool(\n    \"list-entities\",\n    \"List entities from App One with pagination and optional search\",\n    ParamsSchema.shape,\n    async (params) => {\n      const validated = ParamsSchema.parse(params);\n\n      const client = createAppOneClient({\n        baseUrl: process.env.APP_ONE_API_URL!,\n        token: process.env.APP_ONE_SERVICE_TOKEN!,\n      });\n\n      const { data, error } = await client.GET(\"/entities\", {\n        params: { query: validated },\n      });\n\n      if (error) {\n        return {\n          content: [{ type: \"text\", text: `Error: ${JSON.stringify(error)}` }],\n          isError: true,\n        };\n      }\n\n      return {\n        content: [\n          {\n            type: \"text\",\n            text: JSON.stringify(data, null, 2),\n          },\n        ],\n      };\n    }\n  );\n}\n\nMCP Tool for Cross-App Operations\n// mcp-servers/app-one-mcp/src/tools/create-entity.ts\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { createAppOneClient } from \"@repo/sdk/app-one\";\nimport { CreateEntityInputSchema } from \"@repo/contracts/schemas\";\n\nexport function createEntityTool(server: McpServer) {\n  server.tool(\n    \"create-entity\",\n    \"Create a new entity in App One\",\n    {\n      name: z.string().min(1).max(200).describe(\"Entity name\"),\n      description: z.string().max(2000).optional().describe(\"Entity description\"),\n    },\n    async (params) => {\n      // Validate against shared contract\n      const validated = CreateEntityInputSchema.parse(params);\n\n      const client = createAppOneClient({\n        baseUrl: process.env.APP_ONE_API_URL!,\n        token: process.env.APP_ONE_SERVICE_TOKEN!,\n      });\n\n      const { data, error } = await client.POST(\"/entities\", {\n        body: validated,\n      });\n\n      if (error) {\n        return {\n          content: [{ type: \"text\", text: `Error creating entity: ${JSON.stringify(error)}` }],\n          isError: true,\n        };\n      }\n\n      return {\n        content: [\n          {\n            type: \"text\",\n            text: `Entity created successfully:\\n${JSON.stringify(data, null, 2)}`,\n          },\n        ],\n      };\n    }\n  );\n}\n\nMCP Manifest\n// mcp-servers/app-one-mcp/mcp.json\n{\n  \"name\": \"app-one-mcp\",\n  \"version\": \"1.0.0\",\n  \"description\": \"MCP server for App One — manage entities, search, and CRUD operations\",\n  \"tools\": [\n    {\n      \"name\": \"list-entities\",\n      \"description\": \"List entities with pagination and search\"\n    },\n    {\n      \"name\": \"create-entity\",\n      \"description\": \"Create a new entity\"\n    },\n    {\n      \"name\": \"get-entity\",\n      \"description\": \"Get entity details by ID\"\n    },\n    {\n      \"name\": \"search-entities\",\n      \"description\": \"Full-text search across entities\"\n    }\n  ],\n  \"env\": {\n    \"APP_ONE_API_URL\": {\n      \"description\": \"Base URL of App One's API\",\n      \"required\": true\n    },\n    \"APP_ONE_SERVICE_TOKEN\": {\n      \"description\": \"Service token for authenticating MCP server to App One\",\n      \"required\": true\n    }\n  }\n}\n\nMCP Server Package.json\n// mcp-servers/app-one-mcp/package.json\n{\n  \"name\": \"app-one-mcp\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"build\": \"tsup src/index.ts --format esm\",\n    \"dev\": \"tsx watch src/index.ts\",\n    \"start\": \"node dist/index.js\"\n  },\n  \"dependencies\": {\n    \"@modelcontextprotocol/sdk\": \"^1.0.0\",\n    \"@repo/sdk\": \"workspace:*\",\n    \"@repo/contracts\": \"workspace:*\",\n    \"@repo/auth\": \"workspace:*\",\n    \"zod\": \"^3.23.0\"\n  },\n  \"devDependencies\": {\n    \"tsup\": \"^8.0.0\",\n    \"tsx\": \"^4.0.0\",\n    \"typescript\": \"^5.4.0\"\n  }\n}\n\nRegistering MCP Servers in Claude Desktop / OpenClaw\n// claude_desktop_config.json or openclaw config\n{\n  \"mcpServers\": {\n    \"app-one\": {\n      \"command\": \"node\",\n      \"args\": [\"./mcp-servers/app-one-mcp/dist/index.js\"],\n      \"env\": {\n        \"APP_ONE_API_URL\": \"http://localhost:3001/api\",\n        \"APP_ONE_SERVICE_TOKEN\": \"${APP_ONE_SERVICE_TOKEN}\"\n      }\n    },\n    \"app-two\": {\n      \"command\": \"node\",\n      \"args\": [\"./mcp-servers/app-two-mcp/dist/index.js\"],\n      \"env\": {\n        \"APP_TWO_API_URL\": \"http://localhost:3002/api\",\n        \"APP_TWO_SERVICE_TOKEN\": \"${APP_TWO_SERVICE_TOKEN}\"\n      }\n    }\n  }\n}\n\nPart 8 — Inter-App Communication Patterns\nPattern 1: Synchronous API Call (via SDK)\n\nUse when: app-two needs data from app-one in real-time, within a single request lifecycle.\n\n// Already shown in Part 4 — use @repo/sdk\nimport { createAppOneClient } from \"@repo/sdk/app-one\";\n\nPattern 2: Event-Based (Webhook/Pub-Sub)\n\nUse when: apps need to react to changes in other apps asynchronously.\n\n// packages/contracts/src/events/webhook.ts\nimport type { BaseEvent } from \"./index\";\n\nexport interface WebhookConfig {\n  url: string;\n  secret: string;\n  events: string[];  // event types to subscribe to\n}\n\n// Emit event to registered webhooks\nexport async function emitEvent(\n  event: BaseEvent,\n  webhooks: WebhookConfig[]\n): Promise<void> {\n  const relevant = webhooks.filter((wh) =>\n    wh.events.includes(event.type) || wh.events.includes(\"*\")\n  );\n\n  await Promise.allSettled(\n    relevant.map((wh) =>\n      fetch(wh.url, {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n          \"X-Webhook-Secret\": wh.secret,\n          \"X-Event-Type\": event.type,\n          \"X-Event-ID\": event.id,\n        },\n        body: JSON.stringify(event),\n      })\n    )\n  );\n}\n\nPattern 3: Shared Database Query\n\nUse when: apps share a database and need access to the same tables.\n\n// Both apps import from @repo/contracts for type safety\nimport { SharedUserSchema } from \"@repo/contracts/schemas\";\n\n// App-one writes\nawait supabase.schema(\"shared\").from(\"users\").insert(newUser);\n\n// App-two reads\nconst { data } = await supabase.schema(\"shared\").from(\"users\").select(\"*\");\nconst users = data.map((u) => SharedUserSchema.parse(u));\n\nPart 9 — Scaffolding Commands\nScaffold a New App in the Monorepo\n\nWhen the user says \"add a new app,\" follow this sequence:\n\n# 1. Create the app directory\nmkdir -p apps/app-new\n\n# 2. Scaffold based on detected framework preference\n# For Next.js:\ncd apps/app-new && npx create-next-app@latest . --typescript --tailwind --app --eslint --src-dir\n\n# 3. Add shared dependencies\ncd apps/app-new\npnpm add @repo/contracts @repo/auth @repo/sdk\n\n# 4. Create the OpenAPI spec\ncp packages/api-specs/_template.openapi.yaml packages/api-specs/app-new.openapi.yaml\n\n# 5. Generate the SDK\npnpm --filter @repo/sdk generate:app-new\n\n# 6. Scaffold the MCP server\nmkdir -p mcp-servers/app-new-mcp/src/tools\n# ... generate boilerplate files\n\nScaffold a New Shared Contract\n# Add a new entity type to contracts\n# 1. Create the type file\ntouch packages/contracts/src/types/new-entity.ts\n\n# 2. Create the schema file\ntouch packages/contracts/src/schemas/new-entity.schema.ts\n\n# 3. Export from index files\n# 4. Rebuild contracts\npnpm --filter @repo/contracts build\n\nScaffold a New MCP Tool\n# Add a new tool to an existing MCP server\ntouch mcp-servers/app-one-mcp/src/tools/new-operation.ts\n# Follow the tool template from Part 7\n# Register in index.ts\n# Update mcp.json manifest\n\nPart 10 — Validation Checklist\n\nBefore considering the integration work complete, verify:\n\nContracts\n All shared types are in @repo/contracts\n Zod schemas exist for every shared type\n No duplicate type definitions across apps\n Contracts package builds without errors\nAPI Specs\n OpenAPI spec exists for every app that exposes an API\n Spec passes @redocly/cli lint\n All shared schemas reference @repo/contracts types\n Error responses follow the standard ApiError format\nSDKs\n SDK generated from latest specs\n SDK builds without TypeScript errors\n Each app that calls another uses the SDK (not raw fetch)\nAuthentication\n All apps use @repo/auth for token verification\n Cross-app tokens are short-lived (5 minutes max)\n CROSS_APP_SECRET is at least 32 characters\n Auth middleware handles both standard and cross-app tokens\nMCP Servers\n Each app has a corresponding MCP server\n Every public API endpoint has a corresponding MCP tool\n MCP tools use @repo/sdk (not direct API calls)\n MCP tools validate input with Zod schemas from @repo/contracts\n MCP manifest (mcp.json) is up to date\nMonorepo\n turbo.json has correct dependency graph\n pnpm-workspace.yaml includes all directories\n pnpm turbo build succeeds with no errors\n pnpm turbo typecheck succeeds with no errors\nBest Practices (DO)\nALWAYS start by surveying the monorepo structure and existing packages\nALWAYS put shared types in @repo/contracts, never duplicate across apps\nALWAYS write OpenAPI specs BEFORE implementing endpoints\nALWAYS generate SDKs from specs (never hand-write API clients)\nALWAYS use the shared auth package for token verification\nALWAYS scaffold MCP servers using the SDK (not raw fetch)\nALWAYS validate MCP tool inputs with Zod schemas from contracts\nUse short-lived cross-app tokens (5 min TTL)\nUse schema-based isolation when sharing a database\nKeep MCP tools focused — one tool per operation\nVersion event schemas (include version: \"1.0\" in every event)\nAnti-Patterns (AVOID)\nNEVER duplicate type definitions across apps — use @repo/contracts\nNEVER hand-write API clients — always generate from OpenAPI specs\nNEVER use long-lived tokens for cross-app communication\nNEVER give one app direct database access to another app's schema\nNEVER create circular dependencies between packages\nNEVER skip OpenAPI spec validation before SDK generation\nNEVER put business logic in the contracts package (types and schemas only)\nNEVER create MCP tools that bypass authentication\nNEVER hardcode base URLs — always use environment variables\nSafety Rules\nNEVER read or modify .env, .env.local, or any credential file directly\nAll env var references are in generated code via process.env.*\nNEVER commit CROSS_APP_SECRET or any service tokens to git\nNEVER expose service-to-service tokens in client-side code\nNEVER create MCP tools that delete data without confirmation parameters\nNEVER auto-execute webhooks without verifying the secret\nCross-app tokens MUST have an aud (audience) claim restricting which app can use them"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/guifav/interop-forge",
    "publisherUrl": "https://clawhub.ai/guifav/interop-forge",
    "owner": "guifav",
    "version": "0.1.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/interop-forge",
    "downloadUrl": "https://openagent3.xyz/downloads/interop-forge",
    "agentUrl": "https://openagent3.xyz/skills/interop-forge/agent",
    "manifestUrl": "https://openagent3.xyz/skills/interop-forge/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/interop-forge/agent.md"
  }
}