trelly 0.2.1 → 0.3.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/package.json +1 -1
- package/skills/trelly/SKILL.md +2 -0
- package/skills/trelly-mcp/SKILL.md +22 -0
- package/src/mcp/handlers.ts +39 -1
- package/src/mcp/tools/boards.ts +34 -8
- package/src/mcp/tools/cards.ts +13 -3
- package/src/mcp/tools/lists.ts +18 -4
- package/src/mcp/tools/search.ts +13 -1
package/package.json
CHANGED
package/skills/trelly/SKILL.md
CHANGED
|
@@ -40,6 +40,8 @@ Credentials: `~/.config/trelly/config.json` (migrates from `~/.config/trello-cli
|
|
|
40
40
|
| Pretty JSON | `--json --pretty` |
|
|
41
41
|
|
|
42
42
|
- **Scripts:** always `--json` if parsing stdout.
|
|
43
|
+
- **Token cost:** raw `--json` returns full Trello objects (a board list ≈ 10k tokens) — trim with `jq` (e.g. `| jq '.data[] | {id,name}'`) or request fewer fields (`--fields`, `--query "fields=id,name"`).
|
|
44
|
+
- **Showing cards to a human?** Render one markdown line per card: `[name](shortUrl)` + non-zero badges `💬 n · 📎 n · ✓ x/y · ⏰ due` + labels as colored dots (red 🔴 orange 🟠 yellow 🟡 green 🟢 blue 🔵 purple 🟣). Slim the JSON first: `jq '.data[] | {name, shortUrl, labels: [.labels[] | {name, color}], badges: {comments: .badges.comments, attachments: .badges.attachments, checkItems: .badges.checkItems, checkItemsChecked: .badges.checkItemsChecked}, due, dueComplete}'`.
|
|
43
45
|
- **`--pretty` alone** does not emit JSON; it only indents `--json` output.
|
|
44
46
|
- Errors: red `✗` in human mode; exit code `1`.
|
|
45
47
|
|
|
@@ -12,6 +12,28 @@ MCP server for Trello (npm package **trelly**, bin **`trelly-mcp`**). Returns JS
|
|
|
12
12
|
envelope on every tool: `{ ok, profile, data }` /
|
|
13
13
|
`{ ok: false, error, status?, details? }`. Never uses CLI human/Ink output.
|
|
14
14
|
|
|
15
|
+
List/get tools default to lean `fields` (id, name, url, due, …) to keep responses
|
|
16
|
+
token-cheap; pass `fields: "all"` or a comma list when you need more.
|
|
17
|
+
`trello_list_cards` and `trello_card_get` also include slim `badges`
|
|
18
|
+
(comments/attachments/checkItems counts) and `labels` for rich rendering.
|
|
19
|
+
|
|
20
|
+
## Rendering card lists for humans
|
|
21
|
+
|
|
22
|
+
When the user asks to **see** cards (not automation), render one markdown line per
|
|
23
|
+
card — title linked to the card, then only the badges that are non-zero:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
1. [Publish module behavior](https://trello.com/c/1jH2UTAu) 🔴 `Priority: Highest` · 💬 4 · 📎 2 · ✓ 2/5 · ⏰ Jul 5
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- Title → `[name](shortUrl)`. Counts from `badges`: 💬 comments · 📎 attachments · ✓ checkItemsChecked/checkItems.
|
|
30
|
+
- Due: `⏰ Jul 5`, add `(overdue)` if past and not `dueComplete`; `✓` if complete.
|
|
31
|
+
- Labels: colored dot + name in backticks. Trello color → emoji: red 🔴 · orange 🟠 · yellow 🟡 · green/lime 🟢 · blue/sky 🔵 · purple/pink 🟣 · black ⚫ · none ⚪.
|
|
32
|
+
- Whole board: add `fields: "…,badges,labels"` to `trello_board_cards` (lean by default).
|
|
33
|
+
- Custom-field chips (e.g. `Priority: Highest`): fetch defs once via `trello_api` GET
|
|
34
|
+
`/boards/{boardId}/customFields`, request `customFieldItems` on cards, match
|
|
35
|
+
`idValue` → option text/color. Skip unless the user wants them — it's an extra call.
|
|
36
|
+
|
|
15
37
|
## Setup
|
|
16
38
|
|
|
17
39
|
### 1. Auth (CLI, once)
|
package/src/mcp/handlers.ts
CHANGED
|
@@ -19,11 +19,49 @@ export const profileField = z.string().optional().describe("Auth profile name");
|
|
|
19
19
|
export function toolResult(envelope: ToolEnvelope): CallToolResult {
|
|
20
20
|
return {
|
|
21
21
|
structuredContent: envelope,
|
|
22
|
-
content: [{ type: "text", text: JSON.stringify(envelope
|
|
22
|
+
content: [{ type: "text", text: JSON.stringify(envelope) }],
|
|
23
23
|
isError: envelope.ok === false,
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// Trello's badges object is ~400 chars of mostly unused keys; keep the 4 that matter.
|
|
28
|
+
const BADGE_KEYS = ["comments", "attachments", "checkItems", "checkItemsChecked"];
|
|
29
|
+
|
|
30
|
+
function slimCard(card: Record<string, unknown>): Record<string, unknown> {
|
|
31
|
+
const out = { ...card };
|
|
32
|
+
if (out.badges && typeof out.badges === "object" && !Array.isArray(out.badges)) {
|
|
33
|
+
const badges = out.badges as Record<string, unknown>;
|
|
34
|
+
out.badges = Object.fromEntries(
|
|
35
|
+
BADGE_KEYS.filter((key) => badges[key] !== undefined).map((key) => [
|
|
36
|
+
key,
|
|
37
|
+
badges[key],
|
|
38
|
+
]),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(out.labels)) {
|
|
42
|
+
out.labels = out.labels.map((label) => {
|
|
43
|
+
const { id, name, color } = label as Record<string, unknown>;
|
|
44
|
+
return { id, name, color };
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Slim badges/labels on a card or card array returned by Trello. */
|
|
51
|
+
export function slimCards(data: unknown): unknown {
|
|
52
|
+
if (Array.isArray(data)) {
|
|
53
|
+
return data.map((item) =>
|
|
54
|
+
item && typeof item === "object"
|
|
55
|
+
? slimCard(item as Record<string, unknown>)
|
|
56
|
+
: item,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (data && typeof data === "object") {
|
|
60
|
+
return slimCard(data as Record<string, unknown>);
|
|
61
|
+
}
|
|
62
|
+
return data;
|
|
63
|
+
}
|
|
64
|
+
|
|
27
65
|
export async function withClient<T>(
|
|
28
66
|
profile: string | undefined,
|
|
29
67
|
fn: (client: TrelloClient, profileName: string) => Promise<T>,
|
package/src/mcp/tools/boards.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
profileField,
|
|
5
|
+
slimCards,
|
|
6
|
+
toolEnvelopeSchema,
|
|
7
|
+
withClient,
|
|
8
|
+
} from "../handlers.ts";
|
|
4
9
|
|
|
5
10
|
export function registerBoardTools(server: McpServer): void {
|
|
6
11
|
const outputSchema = toolEnvelopeSchema;
|
|
@@ -25,7 +30,10 @@ export function registerBoardTools(server: McpServer): void {
|
|
|
25
30
|
])
|
|
26
31
|
.optional()
|
|
27
32
|
.default("open"),
|
|
28
|
-
fields: z
|
|
33
|
+
fields: z
|
|
34
|
+
.string()
|
|
35
|
+
.default("id,name,shortUrl,closed")
|
|
36
|
+
.describe('comma-separated fields, "all" for everything'),
|
|
29
37
|
},
|
|
30
38
|
annotations: { readOnlyHint: true },
|
|
31
39
|
outputSchema,
|
|
@@ -41,7 +49,10 @@ export function registerBoardTools(server: McpServer): void {
|
|
|
41
49
|
inputSchema: {
|
|
42
50
|
profile: profileField,
|
|
43
51
|
boardId: z.string().min(1),
|
|
44
|
-
fields: z
|
|
52
|
+
fields: z
|
|
53
|
+
.string()
|
|
54
|
+
.default("id,name,desc,shortUrl,closed")
|
|
55
|
+
.describe('comma-separated fields, "all" for everything'),
|
|
45
56
|
},
|
|
46
57
|
annotations: { readOnlyHint: true },
|
|
47
58
|
outputSchema,
|
|
@@ -94,23 +105,38 @@ export function registerBoardTools(server: McpServer): void {
|
|
|
94
105
|
profile: profileField,
|
|
95
106
|
boardId: z.string().min(1),
|
|
96
107
|
filter: z.string().optional().default("open"),
|
|
108
|
+
fields: z
|
|
109
|
+
.string()
|
|
110
|
+
.default("id,name,closed,pos")
|
|
111
|
+
.describe('comma-separated fields, "all" for everything'),
|
|
97
112
|
},
|
|
98
113
|
annotations: { readOnlyHint: true },
|
|
99
114
|
outputSchema,
|
|
100
115
|
},
|
|
101
|
-
async ({ profile, boardId, filter }) =>
|
|
102
|
-
withClient(profile, (client) => client.boardLists(boardId, { filter })),
|
|
116
|
+
async ({ profile, boardId, filter, fields }) =>
|
|
117
|
+
withClient(profile, (client) => client.boardLists(boardId, { filter, fields })),
|
|
103
118
|
);
|
|
104
119
|
|
|
105
120
|
server.registerTool(
|
|
106
121
|
"trello_board_cards",
|
|
107
122
|
{
|
|
108
123
|
description: "List all cards on a board.",
|
|
109
|
-
inputSchema: {
|
|
124
|
+
inputSchema: {
|
|
125
|
+
profile: profileField,
|
|
126
|
+
boardId: z.string().min(1),
|
|
127
|
+
fields: z
|
|
128
|
+
.string()
|
|
129
|
+
.default("id,name,idList,due,dueComplete,shortUrl,closed")
|
|
130
|
+
.describe(
|
|
131
|
+
'comma-separated fields, "all" for everything; add "badges,labels" for rich lists',
|
|
132
|
+
),
|
|
133
|
+
},
|
|
110
134
|
annotations: { readOnlyHint: true },
|
|
111
135
|
outputSchema,
|
|
112
136
|
},
|
|
113
|
-
async ({ profile, boardId }) =>
|
|
114
|
-
withClient(profile, (client) =>
|
|
137
|
+
async ({ profile, boardId, fields }) =>
|
|
138
|
+
withClient(profile, async (client) =>
|
|
139
|
+
slimCards(await client.boardCards(boardId, { fields })),
|
|
140
|
+
),
|
|
115
141
|
);
|
|
116
142
|
}
|
package/src/mcp/tools/cards.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
profileField,
|
|
5
|
+
slimCards,
|
|
6
|
+
toolEnvelopeSchema,
|
|
7
|
+
withClient,
|
|
8
|
+
} from "../handlers.ts";
|
|
4
9
|
|
|
5
10
|
export function registerCardTools(server: McpServer): void {
|
|
6
11
|
const outputSchema = toolEnvelopeSchema;
|
|
@@ -12,13 +17,18 @@ export function registerCardTools(server: McpServer): void {
|
|
|
12
17
|
inputSchema: {
|
|
13
18
|
profile: profileField,
|
|
14
19
|
cardId: z.string().min(1),
|
|
15
|
-
fields: z
|
|
20
|
+
fields: z
|
|
21
|
+
.string()
|
|
22
|
+
.default("id,name,desc,due,dueComplete,idList,shortUrl,labels,badges")
|
|
23
|
+
.describe('comma-separated fields, "all" for everything'),
|
|
16
24
|
},
|
|
17
25
|
annotations: { readOnlyHint: true },
|
|
18
26
|
outputSchema,
|
|
19
27
|
},
|
|
20
28
|
async ({ profile, cardId, fields }) =>
|
|
21
|
-
withClient(profile, (client) =>
|
|
29
|
+
withClient(profile, async (client) =>
|
|
30
|
+
slimCards(await client.cardGet(cardId, { fields })),
|
|
31
|
+
),
|
|
22
32
|
);
|
|
23
33
|
|
|
24
34
|
server.registerTool(
|
package/src/mcp/tools/lists.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
profileField,
|
|
5
|
+
slimCards,
|
|
6
|
+
toolEnvelopeSchema,
|
|
7
|
+
withClient,
|
|
8
|
+
} from "../handlers.ts";
|
|
4
9
|
|
|
5
10
|
export function registerListTools(server: McpServer): void {
|
|
6
11
|
const outputSchema = toolEnvelopeSchema;
|
|
@@ -27,11 +32,20 @@ export function registerListTools(server: McpServer): void {
|
|
|
27
32
|
"trello_list_cards",
|
|
28
33
|
{
|
|
29
34
|
description: "List cards in a list.",
|
|
30
|
-
inputSchema: {
|
|
35
|
+
inputSchema: {
|
|
36
|
+
profile: profileField,
|
|
37
|
+
listId: z.string().min(1),
|
|
38
|
+
fields: z
|
|
39
|
+
.string()
|
|
40
|
+
.default("id,name,idList,due,dueComplete,shortUrl,closed,badges,labels")
|
|
41
|
+
.describe('comma-separated fields, "all" for everything'),
|
|
42
|
+
},
|
|
31
43
|
annotations: { readOnlyHint: true },
|
|
32
44
|
outputSchema,
|
|
33
45
|
},
|
|
34
|
-
async ({ profile, listId }) =>
|
|
35
|
-
withClient(profile, (client) =>
|
|
46
|
+
async ({ profile, listId, fields }) =>
|
|
47
|
+
withClient(profile, async (client) =>
|
|
48
|
+
slimCards(await client.listCards(listId, { fields })),
|
|
49
|
+
),
|
|
36
50
|
);
|
|
37
51
|
}
|
package/src/mcp/tools/search.ts
CHANGED
|
@@ -15,16 +15,28 @@ export function registerSearchTools(server: McpServer): void {
|
|
|
15
15
|
modelTypes: z.string().optional(),
|
|
16
16
|
cardsLimit: z.number().int().positive().optional(),
|
|
17
17
|
boardsLimit: z.number().int().positive().optional(),
|
|
18
|
+
cardFields: z.string().default("id,name,idList,shortUrl"),
|
|
19
|
+
boardFields: z.string().default("id,name,shortUrl"),
|
|
18
20
|
},
|
|
19
21
|
annotations: { readOnlyHint: true },
|
|
20
22
|
outputSchema,
|
|
21
23
|
},
|
|
22
|
-
async ({
|
|
24
|
+
async ({
|
|
25
|
+
profile,
|
|
26
|
+
query,
|
|
27
|
+
modelTypes,
|
|
28
|
+
cardsLimit,
|
|
29
|
+
boardsLimit,
|
|
30
|
+
cardFields,
|
|
31
|
+
boardFields,
|
|
32
|
+
}) =>
|
|
23
33
|
withClient(profile, (client) =>
|
|
24
34
|
client.search(query, {
|
|
25
35
|
modelTypes,
|
|
26
36
|
cards_limit: cardsLimit,
|
|
27
37
|
boards_limit: boardsLimit,
|
|
38
|
+
card_fields: cardFields,
|
|
39
|
+
board_fields: boardFields,
|
|
28
40
|
}),
|
|
29
41
|
),
|
|
30
42
|
);
|