Skip to content
41 changes: 41 additions & 0 deletions app/api/sites/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { NextRequest, NextResponse } from "next/server";
import { actionSchema } from "@/lib/sites/schema";
import { siteOperationHandler } from "@/lib/sites/siteOperationHandler";
import { getCorsHeaders } from "@/lib/networking/getCorsHeaders";
export const maxDuration = 300;
type Context = { params: Promise<{ id: string }> };
/**
* Read the authenticated site's current version.
*
* @param request - Request or route context.
* @param context - Request or route context.
* @returns HTTP response.
*/
export async function GET(request: NextRequest, context: Context) {
return siteOperationHandler(request, "get", await context.params);
}
/**
* Generate, publish or unpublish using an expected revision.
*
* @param request - Request or route context.
* @param context - Request or route context.
* @returns HTTP response.
*/
export async function PATCH(request: NextRequest, context: Context) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Custom agent: API Design Consistency and Maintainability

This handler uses PATCH for the generate, publish, and unpublish mutations, but the API design rule requires POST for actions or mutations. Expose this action endpoint through POST instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/sites/[id]/route.ts, line 24:

<comment>This handler uses `PATCH` for the `generate`, `publish`, and `unpublish` mutations, but the API design rule requires `POST` for actions or mutations. Expose this action endpoint through `POST` instead.</comment>

<file context>
@@ -0,0 +1,41 @@
+ * @param context - Request or route context.
+ * @returns HTTP response.
+ */
+export async function PATCH(request: NextRequest, context: Context) {
+  const parsed = actionSchema.safeParse(await request.json().catch(() => null));
+  if (!parsed.success)
</file context>
Suggested change
export async function PATCH(request: NextRequest, context: Context) {
export async function POST(request: NextRequest, context: Context) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Custom agent: API Design Consistency and Maintainability

The PATCH action can repeatedly trigger the expensive generateSite operation, but this route has no rate-limit check or rate-limited wrapper. Add throttling for site mutations, at least for generate, before delegating to siteOperationHandler to prevent authenticated callers from exhausting AI/backend resources.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/sites/[id]/route.ts, line 24:

<comment>The `PATCH` action can repeatedly trigger the expensive `generateSite` operation, but this route has no rate-limit check or rate-limited wrapper. Add throttling for site mutations, at least for `generate`, before delegating to `siteOperationHandler` to prevent authenticated callers from exhausting AI/backend resources.</comment>

<file context>
@@ -0,0 +1,41 @@
+ * @param context - Request or route context.
+ * @returns HTTP response.
+ */
+export async function PATCH(request: NextRequest, context: Context) {
+  const parsed = actionSchema.safeParse(await request.json().catch(() => null));
+  if (!parsed.success)
</file context>

const parsed = actionSchema.safeParse(await request.json().catch(() => null));
if (!parsed.success)
return NextResponse.json(
{ error: "Invalid site action" },
{ status: 400, headers: getCorsHeaders() },
);
const { action, ...input } = parsed.data;
return siteOperationHandler(request, action, { ...input, ...(await context.params) });
}
/**
* Browser preflight.
*
* @returns HTTP response.
*/
export async function OPTIONS() {
return new Response(null, { status: 204, headers: getCorsHeaders() });
}
22 changes: 22 additions & 0 deletions app/api/sites/[id]/signups/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NextRequest } from "next/server";
import { siteOperationHandler } from "@/lib/sites/siteOperationHandler";
import { getCorsHeaders } from "@/lib/networking/getCorsHeaders";
/**
* Read fan signups only after verifying workspace access.
*
* @param request - Request or route context.
* @param root0 - Request or route context.
* @param root0.params - Request or route context.
* @returns HTTP response.
*/
Comment on lines +5 to +11

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Custom agent: Flag AI Slop and Fabricated Changes

The GET handler's JSDoc documents a nonexistent root0 parameter and repeats the filler description Request or route context. Remove these generated parameter entries and keep only concise, accurate documentation for the actual destructured route context.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/sites/[id]/signups/route.ts, line 5:

<comment>The GET handler's JSDoc documents a nonexistent `root0` parameter and repeats the filler description `Request or route context`. Remove these generated parameter entries and keep only concise, accurate documentation for the actual destructured route context.</comment>

<file context>
@@ -0,0 +1,22 @@
+import { siteOperationHandler } from "@/lib/sites/siteOperationHandler";
+import { getCorsHeaders } from "@/lib/networking/getCorsHeaders";
+/**
+ * Read fan signups only after verifying workspace access.
+ *
+ * @param request - Request or route context.
</file context>
Suggested change
* Read fan signups only after verifying workspace access.
*
* @param request - Request or route context.
* @param root0 - Request or route context.
* @param root0.params - Request or route context.
* @returns HTTP response.
*/
/**
* Reads fan signups after verifying workspace access.
*/

export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
return siteOperationHandler(request, "signups", await params);
}
/**
* Browser preflight.
*
* @returns HTTP response.
*/
export async function OPTIONS() {
return new Response(null, { status: 204, headers: getCorsHeaders() });
}
36 changes: 36 additions & 0 deletions app/api/sites/assets/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { NextRequest, NextResponse } from "next/server";
import { validateAuthContext } from "@/lib/auth/validateAuthContext";
import { processSiteAsset } from "@/lib/sites/processSiteAsset";
import { siteResponseError } from "@/lib/sites/siteResponseError";
import { getCorsHeaders } from "@/lib/networking/getCorsHeaders";
/**
* Upload workspace-owned artwork or audio.
*
* @param request - Request or route context.
* @returns HTTP response.
*/
export async function POST(request: NextRequest) {
const auth = await validateAuthContext(request);
if (auth instanceof NextResponse) return auth;
try {
const data = await request.formData();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a caller sends JSON or malformed multipart data, request.formData() throws and siteResponseError returns 503. Return 400 for invalid upload encoding so clients do not treat their request as a transient service outage.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/sites/assets/route.ts, line 16:

<comment>When a caller sends JSON or malformed multipart data, `request.formData()` throws and `siteResponseError` returns 503. Return 400 for invalid upload encoding so clients do not treat their request as a transient service outage.</comment>

<file context>
@@ -0,0 +1,36 @@
+  const auth = await validateAuthContext(request);
+  if (auth instanceof NextResponse) return auth;
+  try {
+    const data = await request.formData();
+    return NextResponse.json(
+      await processSiteAsset(
</file context>
Suggested change
const data = await request.formData();
let data: FormData;
try {
data = await request.formData();
} catch {
return NextResponse.json(
{ error: "Invalid multipart/form-data upload" },
{ status: 400, headers: getCorsHeaders() },
);
}

return NextResponse.json(
await processSiteAsset(
auth.accountId,
request.nextUrl.searchParams.get("organizationId"),
data.get("file"),
),
{ headers: getCorsHeaders() },
);
} catch (error) {
return siteResponseError(error);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When Sharp or Supabase fails unexpectedly, this catch returns a generic 503 without logging error, so production loses the stack and context needed to diagnose upload failures. Log the exception before returning the sanitized response.

(Based on your team's feedback about logging unexpected API errors.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/sites/assets/route.ts, line 26:

<comment>When Sharp or Supabase fails unexpectedly, this catch returns a generic 503 without logging `error`, so production loses the stack and context needed to diagnose upload failures. Log the exception before returning the sanitized response.

(Based on your team's feedback about logging unexpected API errors.) </comment>

<file context>
@@ -0,0 +1,36 @@
+      { headers: getCorsHeaders() },
+    );
+  } catch (error) {
+    return siteResponseError(error);
+  }
+}
</file context>
Suggested change
return siteResponseError(error);
console.error("[ERROR] Site asset upload failed:", error);
return siteResponseError(error);

}
}
/**
* Browser preflight.
*
* @returns HTTP response.
*/
export async function OPTIONS() {
return new Response(null, { status: 204, headers: getCorsHeaders() });
}
21 changes: 21 additions & 0 deletions app/api/sites/public/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NextResponse } from "next/server";
import { processPublicSite } from "@/lib/sites/processPublicSite";
import { siteResponseError } from "@/lib/sites/siteResponseError";
export const dynamic = "force-dynamic";
/**
* Read a published snapshot without exposing the private draft.
*
* @param _request - Request or route context.
* @param root0 - Request or route context.
* @param root0.params - Request or route context.
* @returns HTTP response.
*/
export async function GET(_request: Request, { params }: { params: Promise<{ id: string }> }) {
try {
return NextResponse.json(await processPublicSite((await params).id), {
headers: { "Cache-Control": "no-store" },
});
} catch (error) {
return siteResponseError(error);
}
}
21 changes: 21 additions & 0 deletions app/api/sites/public/[id]/signup/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NextResponse } from "next/server";
import { processPublicSite } from "@/lib/sites/processPublicSite";
import { siteResponseError } from "@/lib/sites/siteResponseError";
/**
* Record explicit fan email consent on a currently published site.
*
* @param request - Request or route context.
* @param root0 - Request or route context.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Custom agent: Flag AI Slop and Fabricated Changes

The added JSDoc documents nonexistent root0 and root0.params parameters and repeats the same vague description for every tag. Remove these generated tags, or name and document the actual route-context parameter.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/sites/public/[id]/signup/route.ts, line 8:

<comment>The added JSDoc documents nonexistent `root0` and `root0.params` parameters and repeats the same vague description for every tag. Remove these generated tags, or name and document the actual route-context parameter.</comment>

<file context>
@@ -0,0 +1,21 @@
+ * Record explicit fan email consent on a currently published site.
+ *
+ * @param request - Request or route context.
+ * @param root0 - Request or route context.
+ * @param root0.params - Request or route context.
+ * @returns HTTP response.
</file context>

* @param root0.params - Request or route context.
* @returns HTTP response.
*/
export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Custom agent: API Design Consistency and Maintainability

Anonymous callers can repeatedly trigger signup inserts because this public POST route has no rate-limit guard. Add API-layer request validation and rate limiting before invoking processPublicSite.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/sites/public/[id]/signup/route.ts, line 12:

<comment>Anonymous callers can repeatedly trigger signup inserts because this public POST route has no rate-limit guard. Add API-layer request validation and rate limiting before invoking `processPublicSite`.</comment>

<file context>
@@ -0,0 +1,21 @@
+ * @param root0.params - Request or route context.
+ * @returns HTTP response.
+ */
+export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {
+  try {
+    return NextResponse.json(
</file context>

try {
return NextResponse.json(
await processPublicSite((await params).id, await request.json().catch(() => null)),
{ headers: { "Cache-Control": "no-store" } },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When the public page or fan signup form runs on a different origin than this API (chat/app.recoupable.dev vs api.recoupable.dev), browsers cannot read these success responses: the GET and POST success JSONs set only Cache-Control, while every other route in the repo spreads getCorsHeaders() on success (siteOperationHandler, assets route) and error responses here do include CORS via siteResponseError. The sibling public/authenticated routes also export an explicit OPTIONS preflight handler (assets route: "Browser preflight"), which the signup POST needs because Content-Type: application/json triggers a preflight. Add getCorsHeaders() to both success responses and export OPTIONS on these routes so fans can consent cross-origin.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/sites/public/[id]/signup/route.ts, line 16:

<comment>When the public page or fan signup form runs on a different origin than this API (chat/app.recoupable.dev vs api.recoupable.dev), browsers cannot read these success responses: the GET and POST success JSONs set only `Cache-Control`, while every other route in the repo spreads `getCorsHeaders()` on success (siteOperationHandler, assets route) and error responses here do include CORS via siteResponseError. The sibling public/authenticated routes also export an explicit `OPTIONS` preflight handler (assets route: "Browser preflight"), which the signup POST needs because `Content-Type: application/json` triggers a preflight. Add `getCorsHeaders()` to both success responses and export `OPTIONS` on these routes so fans can consent cross-origin.</comment>

<file context>
@@ -0,0 +1,21 @@
+  try {
+    return NextResponse.json(
+      await processPublicSite((await params).id, await request.json().catch(() => null)),
+      { headers: { "Cache-Control": "no-store" } },
+    );
+  } catch (error) {
</file context>

);
} catch (error) {
return siteResponseError(error);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When signup persistence fails, this catch returns a sanitized 503 but silently discards the exception, leaving no server-side diagnostic for the failure. Log error before returning siteResponseError(error).

(Based on your team's feedback about server-side logging for sanitized 500 responses.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/sites/public/[id]/signup/route.ts, line 19:

<comment>When signup persistence fails, this catch returns a sanitized 503 but silently discards the exception, leaving no server-side diagnostic for the failure. Log `error` before returning `siteResponseError(error)`.

(Based on your team's feedback about server-side logging for sanitized 500 responses.) </comment>

<file context>
@@ -0,0 +1,21 @@
+      { headers: { "Cache-Control": "no-store" } },
+    );
+  } catch (error) {
+    return siteResponseError(error);
+  }
+}
</file context>
Suggested change
return siteResponseError(error);
console.error("[sites/public/signup] failed:", error);
return siteResponseError(error);

}
}
30 changes: 30 additions & 0 deletions app/api/sites/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { NextRequest } from "next/server";
import { siteOperationHandler } from "@/lib/sites/siteOperationHandler";
import { getCorsHeaders } from "@/lib/networking/getCorsHeaders";
export const maxDuration = 300;
/**
* List sites in the authenticated workspace.
*
* @param request - Request or route context.
* @returns HTTP response.
*/
export async function GET(request: NextRequest) {
return siteOperationHandler(request, "list", Object.fromEntries(request.nextUrl.searchParams));
}
/**
* Create a private, durable draft.
*
* @param request - Request or route context.
* @returns HTTP response.
*/
export async function POST(request: NextRequest) {
return siteOperationHandler(request, "create", await request.json().catch(() => null));
}
/**
* Browser preflight.
*
* @returns HTTP response.
*/
export async function OPTIONS() {
return new Response(null, { status: 204, headers: getCorsHeaders() });
}
3 changes: 3 additions & 0 deletions app/mcp/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { registerAllTools } from "@/lib/mcp/tools";
import { createMcpHandler, withMcpAuth } from "mcp-handler";
import { verifyBearerToken } from "@/lib/mcp/verifyApiKey";

// Site generation can take several minutes, matching the HTTP generation route.
export const maxDuration = 300;

const baseHandler = createMcpHandler(
server => {
registerAllTools(server);
Expand Down
18 changes: 18 additions & 0 deletions app/workflows/sites/assetsStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { produceAssets } from "@/lib/sites/production/produceAssets";
import { FatalError } from "workflow";
export async function assetsStep(...args: Parameters<typeof produceAssets>) {
"use step";
try {
return await produceAssets(...args);
} catch (error) {
console.error(
"[sites:assetsStep]",
error instanceof Error
? { name: error.name, message: error.message.slice(0, 1200) }
: "Unknown failure",
);
throw new FatalError(
"Site production assetsStep failed. No automatic provider retry was attempted.",
);
}
}
18 changes: 18 additions & 0 deletions app/workflows/sites/buildStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { buildExperience } from "@/lib/sites/production/buildExperience";
import { FatalError } from "workflow";
export async function buildStep(...args: Parameters<typeof buildExperience>) {
"use step";
try {
return await buildExperience(...args);
} catch (error) {
console.error(
"[sites:buildStep]",
error instanceof Error
? { name: error.name, message: error.message.slice(0, 1200) }
: "Unknown failure",
);
throw new FatalError(
"Site production buildStep failed. No automatic provider retry was attempted.",
);
}
}
18 changes: 18 additions & 0 deletions app/workflows/sites/collectContextStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { collectReleaseContext } from "@/lib/sites/production/collectReleaseContext";
import { FatalError } from "workflow";
export async function collectContextStep(...args: Parameters<typeof collectReleaseContext>) {
"use step";
try {
return await collectReleaseContext(...args);
} catch (error) {
console.error(
"[sites:collectContextStep]",
error instanceof Error
? { name: error.name, message: error.message.slice(0, 1200) }
: "Unknown failure",
);
throw new FatalError(
"Site production collectContextStep failed. No automatic provider retry was attempted.",
);
}
}
18 changes: 18 additions & 0 deletions app/workflows/sites/directionStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { directExperience } from "@/lib/sites/production/directExperience";
import { FatalError } from "workflow";
export async function directionStep(...args: Parameters<typeof directExperience>) {
"use step";
try {
return await directExperience(...args);
} catch (error) {
console.error(
"[sites:directionStep]",
error instanceof Error
? { name: error.name, message: error.message.slice(0, 1200) }
: "Unknown failure",
);
throw new FatalError(
"Site production directionStep failed. No automatic provider retry was attempted.",
);
}
}
18 changes: 18 additions & 0 deletions app/workflows/sites/reviewStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { reviewExperience } from "@/lib/sites/production/reviewExperience";
import { FatalError } from "workflow";
export async function reviewStep(...args: Parameters<typeof reviewExperience>) {
"use step";
try {
return await reviewExperience(...args);
} catch (error) {
console.error(
"[sites:reviewStep]",
error instanceof Error
? { name: error.name, message: error.message.slice(0, 1200) }
: "Unknown failure",
);
throw new FatalError(
"Site production reviewStep failed. No automatic provider retry was attempted.",
);
}
}
16 changes: 16 additions & 0 deletions app/workflows/sites/reviseStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { reviseProduction } from "@/lib/sites/production/reviseProduction";
import { FatalError } from "workflow";
export async function reviseStep(...args: Parameters<typeof reviseProduction>) {
"use step";
try {
return await reviseProduction(...args);
} catch (error) {
console.error(
"[sites:reviseStep]",
error instanceof Error
? { name: error.name, message: error.message.slice(0, 1200) }
: "Unknown failure",
);
throw new FatalError("Site revision failed. No automatic provider retry was attempted.");
}
}
12 changes: 12 additions & 0 deletions app/workflows/sites/saveSiteStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { updateSite } from "@/lib/supabase/sites/updateSite";
import { authorizeSiteWorkspace } from "@/lib/sites/authorizeSiteWorkspace";
import type { Site, SiteSnapshot } from "@/lib/sites/schema";
import { FatalError } from "workflow";
export async function saveSiteStep(site: Site, draft: SiteSnapshot, accountId: string) {
"use step";
await authorizeSiteWorkspace(accountId, site.owner_id);
const updated = await updateSite(site.id, site.owner_id, site.revision, { draft });
if (!updated)
throw new FatalError("Site changed during generation. The newer draft was preserved.");
return { site: updated };
}
56 changes: 56 additions & 0 deletions app/workflows/sites/siteProductionWorkflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { reviseStep } from "./reviseStep";
import type { Site } from "@/lib/sites/schema";
import { collectContextStep } from "./collectContextStep";
import { directionStep } from "./directionStep";
import { assetsStep } from "./assetsStep";
import { buildStep } from "./buildStep";
import { reviewStep } from "./reviewStep";
import { saveSiteStep } from "./saveSiteStep";
/** Completed stages are durable; a browser disconnect does not discard production. */
export async function siteProductionWorkflow(site: Site, instruction: string, accountId: string) {
"use workflow";
try {
const context = await collectContextStep(site, accountId);
let direction = await directionStep(site, instruction, context, accountId);
let assets = await assetsStep(site, direction, accountId);
let snapshot = await buildStep(
site,
instruction,
{ release: context, direction },
assets,
accountId,
);
const reviews = [await reviewStep(snapshot, direction, accountId, site.id)];
if (reviews[0].verdict === "revise") {
({ snapshot, direction, assets } = await reviseStep(
site,
instruction,
context,
direction,
assets,
snapshot,
reviews[0],
accountId,
));
reviews.push(await reviewStep(snapshot, direction, accountId, site.id));
}
return await saveSiteStep(
site,
{
...snapshot,
production: {
version: 1,
context,
direction,
reviews,
status: reviews[reviews.length - 1].verdict === "pass" ? "reviewed" : "needs-review",
},
},
accountId,
);
} catch {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When saveSiteStep or orchestration code throws, this catch returns a generic failure without logging the exception. Log the server-side error before returning so operators can diagnose failures without exposing details to the client.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/workflows/sites/siteProductionWorkflow.ts, line 51:

<comment>When `saveSiteStep` or orchestration code throws, this catch returns a generic failure without logging the exception. Log the server-side error before returning so operators can diagnose failures without exposing details to the client.</comment>

<file context>
@@ -0,0 +1,56 @@
+      },
+      accountId,
+    );
+  } catch {
+    return {
+      error: "Production stopped before a draft could be saved. Your existing draft is unchanged.",
</file context>

return {
error: "Production stopped before a draft could be saved. Your existing draft is unchanged.",
};
}
}
2 changes: 2 additions & 0 deletions lib/mcp/tools/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { registerAllSitesTools } from "./sites";
import { registerContextTool } from "./context/registerContextTool";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerGetApiKeyTool } from "./registerGetApiKeyTool";
Expand Down Expand Up @@ -32,6 +33,7 @@ import { registerAllPulseTools } from "./pulse";
* @param server - The MCP server instance to register tools on.
*/
export const registerAllTools = (server: McpServer): void => {
registerAllSitesTools(server);
registerContextTool(server);
registerAllArtistTools(server);
registerAllArtistSocialsTools(server);
Expand Down
Loading
Loading