#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Trello API設定
const TRELLO_API_BASE = "https://api.trello.com/1";
// 環境変数から認証情報を取得
const TRELLO_API_KEY = process.env.TRELLO_API_KEY;
const TRELLO_TOKEN = process.env.TRELLO_TOKEN;
// MCPサーバーの作成
const server = new McpServer({
name: "trello-mcp",
version: "1.0.0",
});
// Trello APIへのリクエストヘルパー
async function trelloRequest(
endpoint: string,
method: string = "GET",
body?: Record<string, unknown>
): Promise<unknown> {
if (!TRELLO_API_KEY || !TRELLO_TOKEN) {
throw new Error(
"TRELLO_API_KEY and TRELLO_TOKEN environment variables are required"
);
}
const url = new URL(`${TRELLO_API_BASE}${endpoint}`);
url.searchParams.append("key", TRELLO_API_KEY);
url.searchParams.append("token", TRELLO_TOKEN);
const options: RequestInit = {
method,
headers: {
"Content-Type": "application/json",
},
};
if (body && method !== "GET") {
options.body = JSON.stringify(body);
}
const response = await fetch(url.toString(), options);
if (!response.ok) {
throw new Error(`Trello API error: ${response.status} ${response.statusText}`);
}
return response.json();
}
// ツール: ボード一覧を取得
server.tool(
"tre__get_boards",
"Get all boards for the authenticated user",
{},
async () => {
const boards = await trelloRequest("/members/me/boards");
return {
content: [
{
type: "text",
text: JSON.stringify(boards, null, 2),
},
],
};
}
);
// ツール: ボードのリスト一覧を取得
server.tool(
"tre__get_lists",
"Get all lists in a board",
{
boardId: z.string().describe("The ID of the board"),
},
async ({ boardId }) => {
const lists = await trelloRequest(`/boards/${boardId}/lists`);
return {
content: [
{
type: "text",
text: JSON.stringify(lists, null, 2),
},
],
};
}
);
// ツール: リストのカード一覧を取得
server.tool(
"tre__get_cards",
"Get all cards in a list",
{
listId: z.string().describe("The ID of the list"),
},
async ({ listId }) => {
const cards = await trelloRequest(`/lists/${listId}/cards`);
return {
content: [
{
type: "text",
text: JSON.stringify(cards, null, 2),
},
],
};
}
);
// ツール: カードを作成
server.tool(
"tre__create_card",
"Create a new card in a list",
{
listId: z.string().describe("The ID of the list to add the card to"),
name: z.string().describe("The name of the card"),
desc: z.string().optional().describe("The description of the card"),
},
async ({ listId, name, desc }) => {
const url = new URL(`${TRELLO_API_BASE}/cards`);
url.searchParams.append("key", TRELLO_API_KEY!);
url.searchParams.append("token", TRELLO_TOKEN!);
url.searchParams.append("idList", listId);
url.searchParams.append("name", name);
if (desc) {
url.searchParams.append("desc", desc);
}
const response = await fetch(url.toString(), { method: "POST" });
const card = await response.json();
return {
content: [
{
type: "text",
text: JSON.stringify(card, null, 2),
},
],
};
}
);
// ツール: カードを更新
server.tool(
"tre__update_card",
"Update an existing card",
{
cardId: z.string().describe("The ID of the card to update"),
name: z.string().optional().describe("The new name of the card"),
desc: z.string().optional().describe("The new description of the card"),
idList: z.string().optional().describe("The ID of the list to move the card to"),
},
async ({ cardId, name, desc, idList }) => {
const url = new URL(`${TRELLO_API_BASE}/cards/${cardId}`);
url.searchParams.append("key", TRELLO_API_KEY!);
url.searchParams.append("token", TRELLO_TOKEN!);
if (name) url.searchParams.append("name", name);
if (desc) url.searchParams.append("desc", desc);
if (idList) url.searchParams.append("idList", idList);
const response = await fetch(url.toString(), { method: "PUT" });
const card = await response.json();
return {
content: [
{
type: "text",
text: JSON.stringify(card, null, 2),
},
],
};
}
);
// ツール: カードを削除
server.tool(
"tre__delete_card",
"Delete a card",
{
cardId: z.string().describe("The ID of the card to delete"),
},
async ({ cardId }) => {
await trelloRequest(`/cards/${cardId}`, "DELETE");
return {
content: [
{
type: "text",
text: `Card ${cardId} deleted successfully`,
},
],
};
}
);
// ツール: ボードにメンバーを招待
server.tool(
"tre__invite_to_board",
"Invite a member to a board by email",
{
boardId: z.string().describe("The ID of the board"),
email: z.string().describe("The email address of the user to invite"),
type: z.enum(["admin", "normal", "observer"]).optional().describe("The type of membership (default: normal)"),
},
async ({ boardId, email, type = "normal" }) => {
const url = new URL(`${TRELLO_API_BASE}/boards/${boardId}/members`);
url.searchParams.append("key", TRELLO_API_KEY!);
url.searchParams.append("token", TRELLO_TOKEN!);
url.searchParams.append("email", email);
url.searchParams.append("type", type);
const response = await fetch(url.toString(), { method: "PUT" });
if (!response.ok) {
const error = await response.text();
throw new Error(`Failed to invite member: ${response.status} ${error}`);
}
const result = await response.json();
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
);
// サーバーの起動
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Trello MCP server running on stdio");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});