Skip to main content
Glama
projects.ts3.98 kB
// src/tools/projects.ts import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { bcRequestWithHeaders, parseNextPage } from "../lib/basecamp.js"; export function registerProjectsTools(server: McpServer) { server.registerTool( "list_projects", { title: "List Basecamp projects (rich)", description: "Returns id, name, status, timestamps, purpose, clients_enabled, bookmarked, and enabled dock tools. Supports pagination and JSON output.", inputSchema: { limit: z.number().int().positive().max(200).optional(), page: z.number().int().positive().optional(), include_archived: z.boolean().optional(), include_dock: z.boolean().optional(), dock_detail: z.boolean().optional(), // NEW: include raw dock format: z.enum(["table", "json"]).optional(), }, }, async (args) => { const { limit, page, include_archived, include_dock, dock_detail, format, } = args; const wantDock = include_dock ?? true; const fmt = format ?? "table"; const { data, headers } = await bcRequestWithHeaders<any[]>( "GET", "/projects.json", undefined, page ? { page } : undefined ); let projects = Array.isArray(data) ? data : []; if (!include_archived) projects = projects.filter((p) => p.status === "active"); if (limit) projects = projects.slice(0, limit); const rows = projects.map((p) => { const item: any = { id: p.id, name: p.name, status: p.status, created_at: p.created_at, updated_at: p.updated_at, description: p.description ?? "", purpose: p.purpose ?? "", clients_enabled: Boolean(p.clients_enabled), bookmarked: Boolean(p.bookmarked), }; if (wantDock && Array.isArray(p.dock)) { item.dock_tools = p.dock .filter((d: any) => d?.enabled) .map((d: any) => d.name); if (dock_detail) item.dock = p.dock; // raw dock objects (includes todoset URL) } return item; }); const nextPage = parseNextPage(headers.get("Link")); if (fmt === "json") { return { content: [ { type: "text", text: JSON.stringify( { page: page ?? 1, nextPage, count: rows.length, projects: rows, }, null, 2 ), }, ], }; } const trunc = (s: string, n: number) => s && s.length > n ? s.slice(0, n - 1) + "…" : s || ""; const header = [ "ID".padEnd(12), "STATUS".padEnd(9), "CLIENTS".padEnd(8), "BOOKMK".padEnd(6), "CREATED".padEnd(10), "UPDATED".padEnd(10), "NAME".padEnd(28), "PURPOSE".padEnd(8), wantDock ? "DOCK" : "", ] .filter(Boolean) .join(" "); const lines = [ `Page: ${page ?? 1}${nextPage ? ` | Next page: ${nextPage}` : ""}`, "", header, "-".repeat(120), ...rows.map((r: any) => [ String(r.id).padEnd(12), String(r.status ?? "").padEnd(9), String(r.clients_enabled).padEnd(8), String(r.bookmarked).padEnd(6), (r.created_at ?? "").slice(0, 10).padEnd(10), (r.updated_at ?? "").slice(0, 10).padEnd(10), trunc(r.name ?? "", 28).padEnd(28), trunc(r.purpose ?? "", 8).padEnd(8), wantDock ? trunc((r.dock_tools ?? []).join(","), 28) : "", ] .filter(Boolean) .join(" ") ), ].join("\n"); return { content: [{ type: "text", text: lines }] }; } ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/craigashields/basecamp-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server