import { ProjectService } from "@/server/features/projects/services/ProjectService"; import { mcpResponse } from "@/server/mcp/formatters"; import { requireMcpToolAuthContext, type ToolExtra, } from "@/server/mcp/context"; import { optionalMetaOutputSchema } from "@/server/mcp/output-schemas"; import { buildDashboardUrl } from "@/server/mcp/urls"; import { z } from "zod"; export const listProjectsTool = { name: "list_projects", config: { title: "List projects", description: "Lists all projects in the user's organization. Free — does not call DataForSEO. Use this whenever you need a `projectId` for another OpenSEO tool. Returns an array of {id, name, domain}; pass the `id` value as `projectId`.", inputSchema: {} as Record, outputSchema: { projects: z.array( z .object({ id: z.string(), name: z.string(), domain: z.string().nullable().optional(), url: z.string(), }) .passthrough(), ), ...optionalMetaOutputSchema, }, annotations: { readOnlyHint: true, openWorldHint: false, destructiveHint: false, }, }, handler: async (_args: Record, extra: ToolExtra) => { const { baseUrl, ...auth } = requireMcpToolAuthContext(extra); const projects = await ProjectService.listProjects(auth.organizationId); const lines = projects.length === 0 ? ["No projects yet. Create one in the dashboard."] : projects.map( (p) => `- ${p.id} ${p.name}${p.domain ? ` (${p.domain})` : ""}`, ); return mcpResponse({ text: `Projects (${projects.length}):\n${lines.join("\n")}`, meta: { organizationId: auth.organizationId, url: buildDashboardUrl(baseUrl, "/"), }, structuredContent: { projects: projects.map((p) => ({ id: p.id, name: p.name, domain: p.domain, url: buildDashboardUrl(baseUrl, `/p/${p.id}`), })), }, }); }, };