cc-jandi 1.0.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.
@@ -0,0 +1,57 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ import { TeamIncomingWebhookService } from "../../services/TeamIncomingWebhookService.js";
4
+ import { resolveTeamToken } from "../../utils/resolveTeamToken.js";
5
+ class SendTeamMessageTool extends MCPTool {
6
+ name = "send_team_message";
7
+ description = "Send a text message to specific team member(s) via Jandi Team Incoming Webhook. Messages are sent as personal messages to the specified email addresses.";
8
+ schema = {
9
+ email: {
10
+ type: z.string(),
11
+ description: "Comma-separated email addresses of recipients (max 100). e.g., 'user1@example.com,user2@example.com'",
12
+ },
13
+ message: {
14
+ type: z.string(),
15
+ description: "The message text to send",
16
+ },
17
+ teamId: {
18
+ type: z.string().optional(),
19
+ description: "Jandi team ID. Required if not using tokenAlias",
20
+ },
21
+ token: {
22
+ type: z.string().optional(),
23
+ description: "Jandi team webhook token. Required if not using tokenAlias",
24
+ },
25
+ tokenAlias: {
26
+ type: z.string().optional(),
27
+ description: "Team token alias from configuration (e.g., 'sales'). Maps to JANDI_TEAM_ID_{alias} and JANDI_TEAM_TOKEN_{alias} env vars",
28
+ },
29
+ };
30
+ async execute(input) {
31
+ try {
32
+ const resolved = resolveTeamToken(input);
33
+ if (!resolved.success) {
34
+ return { success: false, error: resolved.error };
35
+ }
36
+ const message = TeamIncomingWebhookService.createBasicMessage(input.message, input.email);
37
+ const result = await TeamIncomingWebhookService.sendMessage(resolved.config, message);
38
+ if (result.success) {
39
+ return {
40
+ success: true,
41
+ data: {
42
+ message: "Team message sent successfully",
43
+ tokenUsed: resolved.config.alias || 'direct',
44
+ recipients: input.email
45
+ }
46
+ };
47
+ }
48
+ else {
49
+ return { success: false, error: result.error };
50
+ }
51
+ }
52
+ catch (error) {
53
+ return { success: false, error: `Unexpected error: ${error}` };
54
+ }
55
+ }
56
+ }
57
+ export default SendTeamMessageTool;
@@ -0,0 +1,83 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ import { TeamIncomingWebhookService } from "../../services/TeamIncomingWebhookService.js";
4
+ import { resolveTeamToken } from "../../utils/resolveTeamToken.js";
5
+ import { validateHexColor } from "../../utils/validateColor.js";
6
+ import { JandiColors } from "../../types/common.js";
7
+ class SendTeamRichMessageTool extends MCPTool {
8
+ name = "send_team_rich_message";
9
+ description = "Send a rich message with color and attachments to specific team member(s) via Jandi Team Incoming Webhook";
10
+ schema = {
11
+ email: {
12
+ type: z.string(),
13
+ description: "Comma-separated email addresses of recipients (max 100)",
14
+ },
15
+ message: {
16
+ type: z.string(),
17
+ description: "The main message text to send",
18
+ },
19
+ color: {
20
+ type: z.string().optional(),
21
+ description: "Hex color code for the message (e.g., '#FF0000'). Default is Jandi's default color",
22
+ },
23
+ connectInfo: {
24
+ type: z.array(z.object({
25
+ title: z.string().optional(),
26
+ description: z.string().optional(),
27
+ imageUrl: z.string().optional(),
28
+ })).optional(),
29
+ description: "Array of additional information sections with optional title, description, and image URL",
30
+ },
31
+ teamId: {
32
+ type: z.string().optional(),
33
+ description: "Jandi team ID. Required if not using tokenAlias",
34
+ },
35
+ token: {
36
+ type: z.string().optional(),
37
+ description: "Jandi team webhook token. Required if not using tokenAlias",
38
+ },
39
+ tokenAlias: {
40
+ type: z.string().optional(),
41
+ description: "Team token alias from configuration (e.g., 'sales')",
42
+ },
43
+ };
44
+ async execute(input) {
45
+ try {
46
+ const resolved = resolveTeamToken(input);
47
+ if (!resolved.success) {
48
+ return { success: false, error: resolved.error };
49
+ }
50
+ let normalizedColor = input.color;
51
+ if (input.color) {
52
+ const colorResult = validateHexColor(input.color);
53
+ if (!colorResult.valid) {
54
+ return { success: false, error: colorResult.error };
55
+ }
56
+ normalizedColor = colorResult.normalized;
57
+ }
58
+ const message = TeamIncomingWebhookService.createRichMessage(input.message, input.email, normalizedColor, input.connectInfo);
59
+ const result = await TeamIncomingWebhookService.sendMessage(resolved.config, message);
60
+ if (result.success) {
61
+ return {
62
+ success: true,
63
+ data: {
64
+ message: "Team rich message sent successfully",
65
+ tokenUsed: resolved.config.alias || 'direct',
66
+ recipients: input.email,
67
+ messageDetails: {
68
+ color: input.color || JandiColors.DEFAULT,
69
+ attachments: input.connectInfo?.length || 0
70
+ }
71
+ }
72
+ };
73
+ }
74
+ else {
75
+ return { success: false, error: result.error };
76
+ }
77
+ }
78
+ catch (error) {
79
+ return { success: false, error: `Unexpected error: ${error}` };
80
+ }
81
+ }
82
+ }
83
+ export default SendTeamRichMessageTool;
@@ -0,0 +1,56 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ import { TeamIncomingWebhookService } from "../../services/TeamIncomingWebhookService.js";
4
+ import { resolveTeamToken } from "../../utils/resolveTeamToken.js";
5
+ class ValidateTeamTokenTool extends MCPTool {
6
+ name = "validate_team_token";
7
+ description = "Validate a Jandi Team Incoming Webhook token by testing the endpoint";
8
+ schema = {
9
+ teamId: {
10
+ type: z.string().optional(),
11
+ description: "Jandi team ID. Required if not using tokenAlias",
12
+ },
13
+ token: {
14
+ type: z.string().optional(),
15
+ description: "Jandi team webhook token. Required if not using tokenAlias",
16
+ },
17
+ tokenAlias: {
18
+ type: z.string().optional(),
19
+ description: "Team token alias from configuration (e.g., 'sales'). Maps to JANDI_TEAM_ID_{alias} and JANDI_TEAM_TOKEN_{alias} env vars",
20
+ },
21
+ };
22
+ async execute(input) {
23
+ try {
24
+ const resolved = resolveTeamToken(input);
25
+ if (!resolved.success) {
26
+ return { success: false, error: resolved.error };
27
+ }
28
+ const result = await TeamIncomingWebhookService.validateToken(resolved.config);
29
+ if (result.success) {
30
+ return {
31
+ success: true,
32
+ data: {
33
+ message: "Team webhook token is valid",
34
+ tokenAlias: resolved.config.alias || 'direct'
35
+ }
36
+ };
37
+ }
38
+ else {
39
+ return {
40
+ success: false,
41
+ error: result.error,
42
+ data: {
43
+ tokenAlias: resolved.config.alias || 'direct'
44
+ }
45
+ };
46
+ }
47
+ }
48
+ catch (error) {
49
+ return {
50
+ success: false,
51
+ error: `Unexpected error during team token validation: ${error}`
52
+ };
53
+ }
54
+ }
55
+ }
56
+ export default ValidateTeamTokenTool;
@@ -0,0 +1,19 @@
1
+ export var JandiColors;
2
+ (function (JandiColors) {
3
+ JandiColors["RED"] = "#FF0000";
4
+ JandiColors["GREEN"] = "#00FF00";
5
+ JandiColors["BLUE"] = "#0000FF";
6
+ JandiColors["YELLOW"] = "#FFFF00";
7
+ JandiColors["ORANGE"] = "#FFA500";
8
+ JandiColors["PURPLE"] = "#800080";
9
+ JandiColors["PINK"] = "#FFC0CB";
10
+ JandiColors["GRAY"] = "#808080";
11
+ JandiColors["DEFAULT"] = "#FAC11B";
12
+ })(JandiColors || (JandiColors = {}));
13
+ export var MessageType;
14
+ (function (MessageType) {
15
+ MessageType["INFO"] = "info";
16
+ MessageType["SUCCESS"] = "success";
17
+ MessageType["WARNING"] = "warning";
18
+ MessageType["ERROR"] = "error";
19
+ })(MessageType || (MessageType = {}));
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ // Common types
2
+ export { JandiColors, MessageType, } from './common.js';
@@ -0,0 +1,3 @@
1
+ // Backward compatibility facade
2
+ // New code should import from './index.js' or specific type files
3
+ export { JandiColors, MessageType, } from './common.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export { resolveIncomingToken } from './resolveToken.js';
2
+ export { resolveTeamToken } from './resolveTeamToken.js';
3
+ export { validateHexColor } from './validateColor.js';
@@ -0,0 +1,23 @@
1
+ import { ConfigService } from '../services/configService.js';
2
+ export function resolveTeamToken(input) {
3
+ if (input.teamId && input.token) {
4
+ return {
5
+ success: true,
6
+ config: { teamId: input.teamId, token: input.token }
7
+ };
8
+ }
9
+ if (input.tokenAlias) {
10
+ const config = ConfigService.getTeamToken(input.tokenAlias);
11
+ if (!config) {
12
+ return {
13
+ success: false,
14
+ error: `Team token alias '${input.tokenAlias}' not found. Available aliases: ${ConfigService.listTeamTokenAliases().join(', ')}`
15
+ };
16
+ }
17
+ return { success: true, config };
18
+ }
19
+ return {
20
+ success: false,
21
+ error: "Please provide teamId+token or a tokenAlias for team webhook. Set JANDI_TEAM_ID_{alias} and JANDI_TEAM_TOKEN_{alias} environment variables"
22
+ };
23
+ }
@@ -0,0 +1,30 @@
1
+ import { ConfigService } from '../services/configService.js';
2
+ export function resolveIncomingToken(input) {
3
+ if (input.token) {
4
+ if (!ConfigService.validateTokenFormat(input.token)) {
5
+ return {
6
+ success: false,
7
+ error: "Invalid token format. Token should be a 32-character hexadecimal string"
8
+ };
9
+ }
10
+ return { success: true, config: { token: input.token } };
11
+ }
12
+ if (input.tokenAlias) {
13
+ const config = ConfigService.getToken(input.tokenAlias);
14
+ if (!config) {
15
+ return {
16
+ success: false,
17
+ error: `Token alias '${input.tokenAlias}' not found. Available aliases: ${ConfigService.listTokenAliases().join(', ')}`
18
+ };
19
+ }
20
+ return { success: true, config };
21
+ }
22
+ const config = ConfigService.getToken('default');
23
+ if (!config) {
24
+ return {
25
+ success: false,
26
+ error: "No default token configured. Please provide a token or tokenAlias, or set JANDI_TOKEN environment variable"
27
+ };
28
+ }
29
+ return { success: true, config };
30
+ }
@@ -0,0 +1,9 @@
1
+ export function validateHexColor(color) {
2
+ if (!color.match(/^#[0-9A-Fa-f]{6}$/)) {
3
+ return {
4
+ valid: false,
5
+ error: "Invalid color format. Color should be a hex color code (e.g., '#FF0000')"
6
+ };
7
+ }
8
+ return { valid: true, normalized: color.toUpperCase() };
9
+ }
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "cc-jandi",
3
+ "version": "1.0.0",
4
+ "description": "잔디(Jandi) 팀 협업 도구용 MCP 서버 & Claude Code 플러그인 - 웹훅 메시지 전송, 알림 자동화, 스크립트 생성",
5
+ "type": "module",
6
+ "author": "kwag93",
7
+ "license": "MIT",
8
+ "homepage": "https://github.com/kwag93/cc-jandi#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/kwag93/cc-jandi.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/kwag93/cc-jandi/issues"
15
+ },
16
+ "keywords": [
17
+ "jandi",
18
+ "mcp",
19
+ "webhook",
20
+ "korean",
21
+ "collaboration",
22
+ "team",
23
+ "messaging",
24
+ "automation",
25
+ "claude",
26
+ "plugin",
27
+ "claude-code"
28
+ ],
29
+ "bin": {
30
+ "cc-jandi": "./dist/index.js"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsc && mcp-build",
39
+ "watch": "tsc --watch",
40
+ "start": "node dist/index.js",
41
+ "prepublishOnly": "npm run build",
42
+ "version:patch": "npm version patch",
43
+ "version:minor": "npm version minor",
44
+ "version:major": "npm version major",
45
+ "release:patch": "npm run version:patch && npm publish && git push --follow-tags",
46
+ "release:minor": "npm run version:minor && npm publish && git push --follow-tags",
47
+ "release:major": "npm run version:major && npm publish && git push --follow-tags"
48
+ },
49
+ "dependencies": {
50
+ "axios": "^1.10.0",
51
+ "dotenv": "^17.1.0",
52
+ "mcp-framework": "^0.2.2"
53
+ },
54
+ "devDependencies": {
55
+ "@types/node": "^20.11.24",
56
+ "typescript": "^5.3.3"
57
+ },
58
+ "engines": {
59
+ "node": ">=18.19.0"
60
+ }
61
+ }