trelly 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -0
- package/bin/run-ts +31 -0
- package/bin/trelly +2 -0
- package/bin/trelly-mcp +2 -0
- package/package.json +64 -0
- package/src/api/client.ts +332 -0
- package/src/api/http.ts +86 -0
- package/src/auth/browser-flow.ts +217 -0
- package/src/auth/profiles.ts +163 -0
- package/src/cli/commands/actions.ts +17 -0
- package/src/cli/commands/api.ts +46 -0
- package/src/cli/commands/auth.ts +248 -0
- package/src/cli/commands/boards.ts +152 -0
- package/src/cli/commands/cards.ts +194 -0
- package/src/cli/commands/checklists.ts +79 -0
- package/src/cli/commands/custom-fields.ts +75 -0
- package/src/cli/commands/labels.ts +47 -0
- package/src/cli/commands/lists.ts +54 -0
- package/src/cli/commands/members.ts +14 -0
- package/src/cli/commands/orgs.ts +23 -0
- package/src/cli/commands/run.ts +32 -0
- package/src/cli/commands/search.ts +23 -0
- package/src/cli/commands/ui.ts +48 -0
- package/src/cli/commands/webhooks.ts +44 -0
- package/src/cli/context.ts +70 -0
- package/src/cli/index.ts +47 -0
- package/src/cli/ui/app.tsx +753 -0
- package/src/cli/ui/custom-fields.ts +75 -0
- package/src/cli/ui/palette.ts +69 -0
- package/src/cli/ui/shapes.ts +68 -0
- package/src/cli/ui/static.tsx +382 -0
- package/src/index.test.ts +117 -0
- package/src/mcp/handlers.ts +61 -0
- package/src/mcp/server.ts +17 -0
- package/src/mcp/tools/api.ts +27 -0
- package/src/mcp/tools/boards.ts +116 -0
- package/src/mcp/tools/cards.ts +138 -0
- package/src/mcp/tools/checklists.ts +40 -0
- package/src/mcp/tools/index.ts +22 -0
- package/src/mcp/tools/labels.ts +40 -0
- package/src/mcp/tools/lists.ts +37 -0
- package/src/mcp/tools/profiles.ts +40 -0
- package/src/mcp/tools/search.ts +31 -0
- package/src/mcp/tools/webhooks.ts +52 -0
- package/src/util/runtime.ts +39 -0
- package/src/version.ts +15 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { parseTrelloResponse, trelloErrorMessage } from "./api/http.ts";
|
|
4
|
+
import { authLoginUrl } from "./auth/profiles.ts";
|
|
5
|
+
import { parseKvPairs } from "./cli/context.ts";
|
|
6
|
+
import { customFieldChips } from "./cli/ui/custom-fields.ts";
|
|
7
|
+
import { dueStatus, labelHex, listAccentHex } from "./cli/ui/palette.ts";
|
|
8
|
+
import { isBoard, isCard, isLabel, isList } from "./cli/ui/shapes.ts";
|
|
9
|
+
|
|
10
|
+
describe("parseKvPairs", () => {
|
|
11
|
+
it("parses key=value pairs", () => {
|
|
12
|
+
assert.deepEqual(parseKvPairs(["name=foo", "closed=true"]), {
|
|
13
|
+
name: "foo",
|
|
14
|
+
closed: "true",
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("rejects invalid pairs", () => {
|
|
19
|
+
assert.throws(() => parseKvPairs(["invalid"]), /Invalid key=value/);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("authLoginUrl", () => {
|
|
24
|
+
it("defaults to read,write and 30days", () => {
|
|
25
|
+
const url = new URL(authLoginUrl("test-key"));
|
|
26
|
+
assert.equal(url.searchParams.get("key"), "test-key");
|
|
27
|
+
assert.equal(url.searchParams.get("scope"), "read,write");
|
|
28
|
+
assert.equal(url.searchParams.get("expiration"), "30days");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("supports callback return_url", () => {
|
|
32
|
+
const url = new URL(
|
|
33
|
+
authLoginUrl("test-key", {
|
|
34
|
+
returnUrl: "http://127.0.0.1:14189/callback",
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
assert.equal(url.searchParams.get("callback_method"), "fragment");
|
|
38
|
+
assert.equal(url.searchParams.get("return_url"), "http://127.0.0.1:14189/callback");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("trello HTTP helpers", () => {
|
|
43
|
+
it("parses JSON responses", () => {
|
|
44
|
+
assert.deepEqual(parseTrelloResponse('{"id":"1"}'), { id: "1" });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("extracts Trello error messages", () => {
|
|
48
|
+
assert.equal(
|
|
49
|
+
trelloErrorMessage({ message: "invalid token" }, 401),
|
|
50
|
+
"invalid token",
|
|
51
|
+
);
|
|
52
|
+
assert.equal(trelloErrorMessage(null, 500), "Trello API 500");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("ui palette", () => {
|
|
57
|
+
it("maps Trello label colors including shades", () => {
|
|
58
|
+
assert.equal(labelHex("green"), "#61bd4f");
|
|
59
|
+
assert.equal(labelHex("green_dark"), "#61bd4f");
|
|
60
|
+
assert.equal(labelHex(null), "#6b778c");
|
|
61
|
+
assert.equal(labelHex("mauve"), "#6b778c");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("maps list accents, falling back to Trello blue", () => {
|
|
65
|
+
assert.equal(listAccentHex(null), "#0079bf");
|
|
66
|
+
assert.equal(listAccentHex(undefined), "#0079bf");
|
|
67
|
+
assert.equal(listAccentHex("teal"), "#6cc3e0");
|
|
68
|
+
assert.equal(listAccentHex("purple"), "#0079bf");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("classifies due status", () => {
|
|
72
|
+
const now = new Date("2026-07-03T12:00:00Z");
|
|
73
|
+
assert.equal(dueStatus(null, false, now), "none");
|
|
74
|
+
assert.equal(dueStatus("2026-07-01T00:00:00Z", true, now), "complete");
|
|
75
|
+
assert.equal(dueStatus("2026-07-01T00:00:00Z", false, now), "overdue");
|
|
76
|
+
assert.equal(dueStatus("2026-07-03T18:00:00Z", false, now), "soon");
|
|
77
|
+
assert.equal(dueStatus("2026-08-01T00:00:00Z", false, now), "later");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("custom field chips", () => {
|
|
82
|
+
const defs = [
|
|
83
|
+
{
|
|
84
|
+
id: "f1",
|
|
85
|
+
name: "Priority",
|
|
86
|
+
type: "list",
|
|
87
|
+
cardFront: true,
|
|
88
|
+
options: [{ id: "o1", text: "Highest", color: "red" }],
|
|
89
|
+
},
|
|
90
|
+
{ id: "f2", name: "Points", type: "number", cardFront: true, options: [] },
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
it("resolves list options and raw values, skipping unknown fields", () => {
|
|
94
|
+
const chips = customFieldChips(defs, [
|
|
95
|
+
{ idCustomField: "f1", idValue: "o1" },
|
|
96
|
+
{ idCustomField: "f2", value: { number: "5" } },
|
|
97
|
+
{ idCustomField: "missing", idValue: "x" },
|
|
98
|
+
]);
|
|
99
|
+
assert.deepEqual(chips, [
|
|
100
|
+
{ id: "f1", label: "Priority: Highest", color: "red" },
|
|
101
|
+
{ id: "f2", label: "Points: 5", color: null },
|
|
102
|
+
]);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("ui shapes", () => {
|
|
107
|
+
it("distinguishes cards, lists, labels, and boards", () => {
|
|
108
|
+
const card = { id: "c", name: "Card", idList: "l", idBoard: "b", pos: 1 };
|
|
109
|
+
const list = { id: "l", name: "List", idBoard: "b", pos: 2, closed: false };
|
|
110
|
+
const label = { id: "lb", name: "bug", color: "red", idBoard: "b" };
|
|
111
|
+
const board = { id: "b", name: "Board", prefs: {}, idOrganization: "o" };
|
|
112
|
+
assert.ok(isCard(card) && !isCard(list) && !isCard(board));
|
|
113
|
+
assert.ok(isList(list) && !isList(card) && !isList(label));
|
|
114
|
+
assert.ok(isLabel(label) && !isLabel(list) && !isLabel(card));
|
|
115
|
+
assert.ok(isBoard(board) && !isBoard(card) && !isBoard(list));
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { type JsonValue, type TrelloClient, TrelloError } from "../api/client.ts";
|
|
4
|
+
import { getClient } from "../cli/context.ts";
|
|
5
|
+
|
|
6
|
+
export const toolEnvelopeSchema = z.object({
|
|
7
|
+
ok: z.boolean(),
|
|
8
|
+
profile: z.string().optional(),
|
|
9
|
+
data: z.unknown().optional(),
|
|
10
|
+
error: z.string().optional(),
|
|
11
|
+
status: z.number().optional(),
|
|
12
|
+
details: z.unknown().optional(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export type ToolEnvelope = z.infer<typeof toolEnvelopeSchema>;
|
|
16
|
+
|
|
17
|
+
export const profileField = z.string().optional().describe("Auth profile name");
|
|
18
|
+
|
|
19
|
+
export function toolResult(envelope: ToolEnvelope): CallToolResult {
|
|
20
|
+
return {
|
|
21
|
+
structuredContent: envelope,
|
|
22
|
+
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }],
|
|
23
|
+
isError: envelope.ok === false,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function withClient<T>(
|
|
28
|
+
profile: string | undefined,
|
|
29
|
+
fn: (client: TrelloClient, profileName: string) => Promise<T>,
|
|
30
|
+
): Promise<CallToolResult> {
|
|
31
|
+
try {
|
|
32
|
+
const { profileName, client } = getClient(profile);
|
|
33
|
+
const data = await fn(client, profileName);
|
|
34
|
+
return toolResult({ ok: true, profile: profileName, data });
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err instanceof TrelloError) {
|
|
37
|
+
return toolResult({
|
|
38
|
+
ok: false,
|
|
39
|
+
error: err.message,
|
|
40
|
+
status: err.status,
|
|
41
|
+
details: err.body,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return toolResult({
|
|
45
|
+
ok: false,
|
|
46
|
+
error: err instanceof Error ? err.message : String(err),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function runApi(
|
|
52
|
+
profile: string | undefined,
|
|
53
|
+
method: string,
|
|
54
|
+
path: string,
|
|
55
|
+
query: Record<string, string> | undefined,
|
|
56
|
+
body: JsonValue | undefined,
|
|
57
|
+
): Promise<CallToolResult> {
|
|
58
|
+
return withClient(profile, (client) =>
|
|
59
|
+
client.request(method.toUpperCase(), path, query ?? {}, body),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { packageVersion } from "../version.ts";
|
|
4
|
+
import { registerTrelloTools } from "./tools/index.ts";
|
|
5
|
+
|
|
6
|
+
const server = new McpServer({ name: "trelly", version: packageVersion() });
|
|
7
|
+
registerTrelloTools(server);
|
|
8
|
+
|
|
9
|
+
async function main(): Promise<void> {
|
|
10
|
+
const transport = new StdioServerTransport();
|
|
11
|
+
await server.connect(transport);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
main().catch((err) => {
|
|
15
|
+
console.error(err);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { JsonValue } from "../../api/client.ts";
|
|
4
|
+
import { profileField, runApi, toolEnvelopeSchema } from "../handlers.ts";
|
|
5
|
+
|
|
6
|
+
export function registerApiTools(server: McpServer): void {
|
|
7
|
+
const outputSchema = toolEnvelopeSchema;
|
|
8
|
+
|
|
9
|
+
server.registerTool(
|
|
10
|
+
"trello_api",
|
|
11
|
+
{
|
|
12
|
+
description:
|
|
13
|
+
"Raw Trello REST escape hatch. path like /boards/{id}. Prefer specific tools when possible.",
|
|
14
|
+
inputSchema: {
|
|
15
|
+
profile: profileField,
|
|
16
|
+
method: z.enum(["GET", "POST", "PUT", "DELETE"]).default("GET"),
|
|
17
|
+
path: z.string().min(1),
|
|
18
|
+
query: z.record(z.string(), z.string()).optional(),
|
|
19
|
+
body: z.unknown().optional(),
|
|
20
|
+
},
|
|
21
|
+
annotations: { destructiveHint: true },
|
|
22
|
+
outputSchema,
|
|
23
|
+
},
|
|
24
|
+
async ({ profile, method, path, query, body }) =>
|
|
25
|
+
runApi(profile, method, path, query, body as JsonValue | undefined),
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { profileField, toolEnvelopeSchema, withClient } from "../handlers.ts";
|
|
4
|
+
|
|
5
|
+
export function registerBoardTools(server: McpServer): void {
|
|
6
|
+
const outputSchema = toolEnvelopeSchema;
|
|
7
|
+
|
|
8
|
+
server.registerTool(
|
|
9
|
+
"trello_boards_list",
|
|
10
|
+
{
|
|
11
|
+
description: "List boards visible to the authenticated member.",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
profile: profileField,
|
|
14
|
+
filter: z
|
|
15
|
+
.enum([
|
|
16
|
+
"all",
|
|
17
|
+
"closed",
|
|
18
|
+
"memberships",
|
|
19
|
+
"open",
|
|
20
|
+
"organization",
|
|
21
|
+
"pinned",
|
|
22
|
+
"public",
|
|
23
|
+
"starred",
|
|
24
|
+
"unpinned",
|
|
25
|
+
])
|
|
26
|
+
.optional()
|
|
27
|
+
.default("open"),
|
|
28
|
+
fields: z.string().optional(),
|
|
29
|
+
},
|
|
30
|
+
annotations: { readOnlyHint: true },
|
|
31
|
+
outputSchema,
|
|
32
|
+
},
|
|
33
|
+
async ({ profile, filter, fields }) =>
|
|
34
|
+
withClient(profile, (client) => client.memberBoards("me", { filter, fields })),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
server.registerTool(
|
|
38
|
+
"trello_board_get",
|
|
39
|
+
{
|
|
40
|
+
description: "Get a board by id.",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
profile: profileField,
|
|
43
|
+
boardId: z.string().min(1),
|
|
44
|
+
fields: z.string().optional(),
|
|
45
|
+
},
|
|
46
|
+
annotations: { readOnlyHint: true },
|
|
47
|
+
outputSchema,
|
|
48
|
+
},
|
|
49
|
+
async ({ profile, boardId, fields }) =>
|
|
50
|
+
withClient(profile, (client) => client.boardGet(boardId, { fields })),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
server.registerTool(
|
|
54
|
+
"trello_board_create",
|
|
55
|
+
{
|
|
56
|
+
description: "Create a board.",
|
|
57
|
+
inputSchema: {
|
|
58
|
+
profile: profileField,
|
|
59
|
+
name: z.string().min(1),
|
|
60
|
+
description: z.string().optional(),
|
|
61
|
+
organizationId: z.string().optional(),
|
|
62
|
+
defaultLists: z.boolean().optional(),
|
|
63
|
+
},
|
|
64
|
+
outputSchema,
|
|
65
|
+
},
|
|
66
|
+
async ({ profile, name, description, organizationId, defaultLists }) =>
|
|
67
|
+
withClient(profile, (client) =>
|
|
68
|
+
client.boardCreate({
|
|
69
|
+
name,
|
|
70
|
+
desc: description,
|
|
71
|
+
idOrganization: organizationId,
|
|
72
|
+
defaultLists,
|
|
73
|
+
}),
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
server.registerTool(
|
|
78
|
+
"trello_board_archive",
|
|
79
|
+
{
|
|
80
|
+
description: "Close (archive) a board. Reversible in the Trello UI.",
|
|
81
|
+
inputSchema: { profile: profileField, boardId: z.string().min(1) },
|
|
82
|
+
annotations: { destructiveHint: true },
|
|
83
|
+
outputSchema,
|
|
84
|
+
},
|
|
85
|
+
async ({ profile, boardId }) =>
|
|
86
|
+
withClient(profile, (client) => client.boardArchive(boardId)),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
server.registerTool(
|
|
90
|
+
"trello_board_lists",
|
|
91
|
+
{
|
|
92
|
+
description: "List lists on a board.",
|
|
93
|
+
inputSchema: {
|
|
94
|
+
profile: profileField,
|
|
95
|
+
boardId: z.string().min(1),
|
|
96
|
+
filter: z.string().optional().default("open"),
|
|
97
|
+
},
|
|
98
|
+
annotations: { readOnlyHint: true },
|
|
99
|
+
outputSchema,
|
|
100
|
+
},
|
|
101
|
+
async ({ profile, boardId, filter }) =>
|
|
102
|
+
withClient(profile, (client) => client.boardLists(boardId, { filter })),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
server.registerTool(
|
|
106
|
+
"trello_board_cards",
|
|
107
|
+
{
|
|
108
|
+
description: "List all cards on a board.",
|
|
109
|
+
inputSchema: { profile: profileField, boardId: z.string().min(1) },
|
|
110
|
+
annotations: { readOnlyHint: true },
|
|
111
|
+
outputSchema,
|
|
112
|
+
},
|
|
113
|
+
async ({ profile, boardId }) =>
|
|
114
|
+
withClient(profile, (client) => client.boardCards(boardId)),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { profileField, toolEnvelopeSchema, withClient } from "../handlers.ts";
|
|
4
|
+
|
|
5
|
+
export function registerCardTools(server: McpServer): void {
|
|
6
|
+
const outputSchema = toolEnvelopeSchema;
|
|
7
|
+
|
|
8
|
+
server.registerTool(
|
|
9
|
+
"trello_card_get",
|
|
10
|
+
{
|
|
11
|
+
description: "Get a card by id or short link.",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
profile: profileField,
|
|
14
|
+
cardId: z.string().min(1),
|
|
15
|
+
fields: z.string().optional(),
|
|
16
|
+
},
|
|
17
|
+
annotations: { readOnlyHint: true },
|
|
18
|
+
outputSchema,
|
|
19
|
+
},
|
|
20
|
+
async ({ profile, cardId, fields }) =>
|
|
21
|
+
withClient(profile, (client) => client.cardGet(cardId, { fields })),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
server.registerTool(
|
|
25
|
+
"trello_card_create",
|
|
26
|
+
{
|
|
27
|
+
description: "Create a card on a list.",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
profile: profileField,
|
|
30
|
+
listId: z.string().min(1),
|
|
31
|
+
name: z.string().min(1),
|
|
32
|
+
description: z.string().optional(),
|
|
33
|
+
due: z.string().optional(),
|
|
34
|
+
position: z.string().optional(),
|
|
35
|
+
},
|
|
36
|
+
outputSchema,
|
|
37
|
+
},
|
|
38
|
+
async ({ profile, listId, name, description, due, position }) =>
|
|
39
|
+
withClient(profile, (client) =>
|
|
40
|
+
client.cardCreate({
|
|
41
|
+
idList: listId,
|
|
42
|
+
name,
|
|
43
|
+
desc: description,
|
|
44
|
+
due,
|
|
45
|
+
pos: position,
|
|
46
|
+
}),
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
server.registerTool(
|
|
51
|
+
"trello_card_update",
|
|
52
|
+
{
|
|
53
|
+
description: "Update card fields (name, desc, due, closed, idList, etc.).",
|
|
54
|
+
inputSchema: {
|
|
55
|
+
profile: profileField,
|
|
56
|
+
cardId: z.string().min(1),
|
|
57
|
+
fields: z.record(z.string(), z.string()),
|
|
58
|
+
},
|
|
59
|
+
outputSchema,
|
|
60
|
+
},
|
|
61
|
+
async ({ profile, cardId, fields }) =>
|
|
62
|
+
withClient(profile, (client) => client.cardUpdate(cardId, fields)),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
server.registerTool(
|
|
66
|
+
"trello_card_move",
|
|
67
|
+
{
|
|
68
|
+
description: "Move a card to another list.",
|
|
69
|
+
inputSchema: {
|
|
70
|
+
profile: profileField,
|
|
71
|
+
cardId: z.string().min(1),
|
|
72
|
+
listId: z.string().min(1),
|
|
73
|
+
position: z.string().optional(),
|
|
74
|
+
},
|
|
75
|
+
outputSchema,
|
|
76
|
+
},
|
|
77
|
+
async ({ profile, cardId, listId, position }) =>
|
|
78
|
+
withClient(profile, (client) =>
|
|
79
|
+
client.cardUpdate(cardId, { idList: listId, pos: position }),
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
server.registerTool(
|
|
84
|
+
"trello_card_comments",
|
|
85
|
+
{
|
|
86
|
+
description: "List comments on a card.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
profile: profileField,
|
|
89
|
+
cardId: z.string().min(1),
|
|
90
|
+
limit: z.number().int().positive().optional(),
|
|
91
|
+
},
|
|
92
|
+
annotations: { readOnlyHint: true },
|
|
93
|
+
outputSchema,
|
|
94
|
+
},
|
|
95
|
+
async ({ profile, cardId, limit }) =>
|
|
96
|
+
withClient(profile, (client) => client.cardComments(cardId, { limit })),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
server.registerTool(
|
|
100
|
+
"trello_card_comment",
|
|
101
|
+
{
|
|
102
|
+
description: "Add a comment to a card.",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
profile: profileField,
|
|
105
|
+
cardId: z.string().min(1),
|
|
106
|
+
text: z.string().min(1),
|
|
107
|
+
},
|
|
108
|
+
outputSchema,
|
|
109
|
+
},
|
|
110
|
+
async ({ profile, cardId, text }) =>
|
|
111
|
+
withClient(profile, (client) => client.cardComment(cardId, text)),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
server.registerTool(
|
|
115
|
+
"trello_card_archive",
|
|
116
|
+
{
|
|
117
|
+
description: "Close (archive) a card. Reversible in the Trello UI.",
|
|
118
|
+
inputSchema: { profile: profileField, cardId: z.string().min(1) },
|
|
119
|
+
annotations: { destructiveHint: true },
|
|
120
|
+
outputSchema,
|
|
121
|
+
},
|
|
122
|
+
async ({ profile, cardId }) =>
|
|
123
|
+
withClient(profile, (client) => client.cardArchive(cardId)),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
server.registerTool(
|
|
127
|
+
"trello_card_delete",
|
|
128
|
+
{
|
|
129
|
+
description:
|
|
130
|
+
"Permanently delete a card. Irreversible — prefer trello_card_archive to close.",
|
|
131
|
+
inputSchema: { profile: profileField, cardId: z.string().min(1) },
|
|
132
|
+
annotations: { destructiveHint: true },
|
|
133
|
+
outputSchema,
|
|
134
|
+
},
|
|
135
|
+
async ({ profile, cardId }) =>
|
|
136
|
+
withClient(profile, (client) => client.cardDelete(cardId)),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { profileField, toolEnvelopeSchema, withClient } from "../handlers.ts";
|
|
4
|
+
|
|
5
|
+
export function registerChecklistTools(server: McpServer): void {
|
|
6
|
+
const outputSchema = toolEnvelopeSchema;
|
|
7
|
+
|
|
8
|
+
server.registerTool(
|
|
9
|
+
"trello_checklist_create",
|
|
10
|
+
{
|
|
11
|
+
description: "Create a checklist on a card.",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
profile: profileField,
|
|
14
|
+
cardId: z.string().min(1),
|
|
15
|
+
name: z.string().min(1),
|
|
16
|
+
},
|
|
17
|
+
outputSchema,
|
|
18
|
+
},
|
|
19
|
+
async ({ profile, cardId, name }) =>
|
|
20
|
+
withClient(profile, (client) => client.checklistCreate({ idCard: cardId, name })),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
server.registerTool(
|
|
24
|
+
"trello_checklist_add_item",
|
|
25
|
+
{
|
|
26
|
+
description: "Add an item to a checklist.",
|
|
27
|
+
inputSchema: {
|
|
28
|
+
profile: profileField,
|
|
29
|
+
checklistId: z.string().min(1),
|
|
30
|
+
name: z.string().min(1),
|
|
31
|
+
checked: z.boolean().optional(),
|
|
32
|
+
},
|
|
33
|
+
outputSchema,
|
|
34
|
+
},
|
|
35
|
+
async ({ profile, checklistId, name, checked }) =>
|
|
36
|
+
withClient(profile, (client) =>
|
|
37
|
+
client.checklistAddItem(checklistId, { name, checked }),
|
|
38
|
+
),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { registerApiTools } from "./api.ts";
|
|
3
|
+
import { registerBoardTools } from "./boards.ts";
|
|
4
|
+
import { registerCardTools } from "./cards.ts";
|
|
5
|
+
import { registerChecklistTools } from "./checklists.ts";
|
|
6
|
+
import { registerLabelTools } from "./labels.ts";
|
|
7
|
+
import { registerListTools } from "./lists.ts";
|
|
8
|
+
import { registerProfileTools } from "./profiles.ts";
|
|
9
|
+
import { registerSearchTools } from "./search.ts";
|
|
10
|
+
import { registerWebhookTools } from "./webhooks.ts";
|
|
11
|
+
|
|
12
|
+
export function registerTrelloTools(server: McpServer): void {
|
|
13
|
+
registerProfileTools(server);
|
|
14
|
+
registerBoardTools(server);
|
|
15
|
+
registerListTools(server);
|
|
16
|
+
registerCardTools(server);
|
|
17
|
+
registerChecklistTools(server);
|
|
18
|
+
registerLabelTools(server);
|
|
19
|
+
registerSearchTools(server);
|
|
20
|
+
registerWebhookTools(server);
|
|
21
|
+
registerApiTools(server);
|
|
22
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { profileField, toolEnvelopeSchema, withClient } from "../handlers.ts";
|
|
4
|
+
|
|
5
|
+
export function registerLabelTools(server: McpServer): void {
|
|
6
|
+
const outputSchema = toolEnvelopeSchema;
|
|
7
|
+
|
|
8
|
+
server.registerTool(
|
|
9
|
+
"trello_label_create",
|
|
10
|
+
{
|
|
11
|
+
description: "Create a board label.",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
profile: profileField,
|
|
14
|
+
boardId: z.string().min(1),
|
|
15
|
+
name: z.string().min(1),
|
|
16
|
+
color: z.string().min(1),
|
|
17
|
+
},
|
|
18
|
+
outputSchema,
|
|
19
|
+
},
|
|
20
|
+
async ({ profile, boardId, name, color }) =>
|
|
21
|
+
withClient(profile, (client) =>
|
|
22
|
+
client.labelCreate({ idBoard: boardId, name, color }),
|
|
23
|
+
),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
server.registerTool(
|
|
27
|
+
"trello_card_add_label",
|
|
28
|
+
{
|
|
29
|
+
description: "Add a label to a card.",
|
|
30
|
+
inputSchema: {
|
|
31
|
+
profile: profileField,
|
|
32
|
+
cardId: z.string().min(1),
|
|
33
|
+
labelId: z.string().min(1),
|
|
34
|
+
},
|
|
35
|
+
outputSchema,
|
|
36
|
+
},
|
|
37
|
+
async ({ profile, cardId, labelId }) =>
|
|
38
|
+
withClient(profile, (client) => client.cardAddLabel(cardId, labelId)),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { profileField, toolEnvelopeSchema, withClient } from "../handlers.ts";
|
|
4
|
+
|
|
5
|
+
export function registerListTools(server: McpServer): void {
|
|
6
|
+
const outputSchema = toolEnvelopeSchema;
|
|
7
|
+
|
|
8
|
+
server.registerTool(
|
|
9
|
+
"trello_list_create",
|
|
10
|
+
{
|
|
11
|
+
description: "Create a list on a board.",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
profile: profileField,
|
|
14
|
+
boardId: z.string().min(1),
|
|
15
|
+
name: z.string().min(1),
|
|
16
|
+
position: z.string().optional(),
|
|
17
|
+
},
|
|
18
|
+
outputSchema,
|
|
19
|
+
},
|
|
20
|
+
async ({ profile, boardId, name, position }) =>
|
|
21
|
+
withClient(profile, (client) =>
|
|
22
|
+
client.listCreate({ idBoard: boardId, name, pos: position }),
|
|
23
|
+
),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
server.registerTool(
|
|
27
|
+
"trello_list_cards",
|
|
28
|
+
{
|
|
29
|
+
description: "List cards in a list.",
|
|
30
|
+
inputSchema: { profile: profileField, listId: z.string().min(1) },
|
|
31
|
+
annotations: { readOnlyHint: true },
|
|
32
|
+
outputSchema,
|
|
33
|
+
},
|
|
34
|
+
async ({ profile, listId }) =>
|
|
35
|
+
withClient(profile, (client) => client.listCards(listId)),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { profileField, toolEnvelopeSchema, withClient } from "../handlers.ts";
|
|
3
|
+
|
|
4
|
+
export function registerProfileTools(server: McpServer): void {
|
|
5
|
+
const outputSchema = toolEnvelopeSchema;
|
|
6
|
+
|
|
7
|
+
server.registerTool(
|
|
8
|
+
"trello_profiles_list",
|
|
9
|
+
{
|
|
10
|
+
description: "List saved Trello auth profiles and the active default.",
|
|
11
|
+
annotations: { readOnlyHint: true },
|
|
12
|
+
outputSchema,
|
|
13
|
+
},
|
|
14
|
+
async () =>
|
|
15
|
+
withClient(undefined, async (_client, profileName) => {
|
|
16
|
+
const { loadConfig } = await import("../../auth/profiles.ts");
|
|
17
|
+
const config = loadConfig();
|
|
18
|
+
return {
|
|
19
|
+
activeProfile: profileName,
|
|
20
|
+
defaultProfile: config.defaultProfile,
|
|
21
|
+
profiles: Object.entries(config.profiles).map(([name, p]) => ({
|
|
22
|
+
name,
|
|
23
|
+
label: p.label,
|
|
24
|
+
isDefault: name === config.defaultProfile,
|
|
25
|
+
})),
|
|
26
|
+
};
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
server.registerTool(
|
|
31
|
+
"trello_member_me",
|
|
32
|
+
{
|
|
33
|
+
description: "Get the authenticated Trello member.",
|
|
34
|
+
inputSchema: { profile: profileField },
|
|
35
|
+
annotations: { readOnlyHint: true },
|
|
36
|
+
outputSchema,
|
|
37
|
+
},
|
|
38
|
+
async ({ profile }) => withClient(profile, (client) => client.memberMe()),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { profileField, toolEnvelopeSchema, withClient } from "../handlers.ts";
|
|
4
|
+
|
|
5
|
+
export function registerSearchTools(server: McpServer): void {
|
|
6
|
+
const outputSchema = toolEnvelopeSchema;
|
|
7
|
+
|
|
8
|
+
server.registerTool(
|
|
9
|
+
"trello_search",
|
|
10
|
+
{
|
|
11
|
+
description: "Search Trello for cards, boards, members, and actions.",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
profile: profileField,
|
|
14
|
+
query: z.string().min(1),
|
|
15
|
+
modelTypes: z.string().optional(),
|
|
16
|
+
cardsLimit: z.number().int().positive().optional(),
|
|
17
|
+
boardsLimit: z.number().int().positive().optional(),
|
|
18
|
+
},
|
|
19
|
+
annotations: { readOnlyHint: true },
|
|
20
|
+
outputSchema,
|
|
21
|
+
},
|
|
22
|
+
async ({ profile, query, modelTypes, cardsLimit, boardsLimit }) =>
|
|
23
|
+
withClient(profile, (client) =>
|
|
24
|
+
client.search(query, {
|
|
25
|
+
modelTypes,
|
|
26
|
+
cards_limit: cardsLimit,
|
|
27
|
+
boards_limit: boardsLimit,
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
}
|