diff --git a/github/app.json b/github/app.json index 738d52c8..17962913 100644 --- a/github/app.json +++ b/github/app.json @@ -1,6 +1,6 @@ { "scopeName": "deco", - "name": "github-mcp", + "name": "mcp-github", "friendlyName": "GitHub", "connection": { "type": "HTTP", diff --git a/github/server/lib/event-bus.ts b/github/server/lib/event-bus.ts new file mode 100644 index 00000000..e3ee633a --- /dev/null +++ b/github/server/lib/event-bus.ts @@ -0,0 +1,38 @@ +/** + * Event Bus reference cache. + * + * Captured during onChange (when the runtime has full state), + * so the webhook handler can publish events without going through + * the authenticated runtime path. + */ + +type EventPublishFn = (event: { + type: string; + subject: string; + data?: Record; +}) => Promise; + +let cachedPublish: EventPublishFn | null = null; + +export function setEventPublisher(publish: EventPublishFn): void { + cachedPublish = publish; +} + +export function clearEventPublisher(): void { + cachedPublish = null; +} + +export async function publishEvent(event: { + type: string; + subject: string; + data?: Record; +}): Promise { + if (!cachedPublish) { + console.warn( + "[EventBus] No publisher available — EVENT_BUS not yet configured", + ); + return; + } + + await cachedPublish(event); +} diff --git a/github/server/main.ts b/github/server/main.ts index de81fd2a..caf193fe 100644 --- a/github/server/main.ts +++ b/github/server/main.ts @@ -9,6 +9,7 @@ import type { Registry } from "@decocms/mcps-shared/registry"; import { serve } from "@decocms/mcps-shared/serve"; import { withRuntime } from "@decocms/runtime"; import { exchangeCodeForToken } from "./lib/github-client.ts"; +import { setEventPublisher } from "./lib/event-bus.ts"; import { captureInstallationMappings } from "./lib/installation-map.ts"; import { handleProxiedRequest, @@ -72,12 +73,19 @@ const runtime = withRuntime({ onChange: async (env) => { invalidateUpstreamCache(); + // Capture Event Bus reference for webhook handler + const eventBus = env.MESH_REQUEST_CONTEXT?.state?.EVENT_BUS; + if (eventBus) { + setEventPublisher((event) => eventBus.EVENT_PUBLISH(event)); + } + const token = env.MESH_REQUEST_CONTEXT?.authorization; const connectionId = env.MESH_REQUEST_CONTEXT?.connectionId; if (token && connectionId) { await captureInstallationMappings(token, connectionId); } }, + scopes: ["EVENT_BUS::*"], state: StateSchema, }, diff --git a/github/server/types/env.ts b/github/server/types/env.ts index 9f379728..07bc080c 100644 --- a/github/server/types/env.ts +++ b/github/server/types/env.ts @@ -3,14 +3,12 @@ */ import type { Registry } from "@decocms/mcps-shared/registry"; -import { type DefaultEnv } from "@decocms/runtime"; +import { BindingOf, type DefaultEnv } from "@decocms/runtime"; import { z } from "zod"; -/** - * State schema — no user-configurable options needed. - * The upstream URL is always https://api.githubcopilot.com/mcp/. - */ -export const StateSchema = z.object({}); +export const StateSchema = z.object({ + EVENT_BUS: BindingOf("@deco/event-bus"), +}); /** * Environment type combining Deco bindings with shared Registry diff --git a/github/server/webhook.ts b/github/server/webhook.ts index 43c93cd6..d41e3205 100644 --- a/github/server/webhook.ts +++ b/github/server/webhook.ts @@ -2,12 +2,13 @@ * GitHub Webhook HTTP Handler * * Receives GitHub App webhook events, verifies signatures, - * and routes them to the correct connection. + * and publishes them to the Event Bus. */ +import { publishEvent } from "./lib/event-bus.ts"; import { getConnectionForInstallation } from "./lib/installation-map.ts"; -import { verifyGitHubWebhook } from "./lib/webhook.ts"; import { hasMatchingTrigger } from "./lib/trigger-store.ts"; +import { verifyGitHubWebhook } from "./lib/webhook.ts"; const GITHUB_WEBHOOK_SECRET = process.env.GITHUB_WEBHOOK_SECRET || ""; @@ -49,6 +50,18 @@ export async function handleGitHubWebhook(req: Request): Promise { `[Webhook] ${fullEventType} | subject=${subject} | connection=${connectionId} | sender=${payload.sender?.login}`, ); + // Publish to Event Bus + try { + await publishEvent({ + type: fullEventType, + subject, + data: payload as Record, + }); + } catch (error) { + console.error("[Webhook] Failed to publish to Event Bus:", error); + return Response.json({ error: "Failed to publish event" }, { status: 502 }); + } + if ( hasMatchingTrigger( fullEventType,