metatron-open-seo/src/server/mcp/tools/list-projects.ts
2026-05-28 16:37:33 -04:00

63 lines
2.0 KiB
TypeScript

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<string, never>,
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<string, never>, 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}`),
})),
},
});
},
};