Skip to content

Commit 01f8f76

Browse files
committed
feat: dooray!의 업무 상위 설정/해제 및 업무 이동 관련 도구 추가
1 parent 2f879f1 commit 01f8f76

1 file changed

Lines changed: 92 additions & 19 deletions

File tree

mcp_app/dooray.py

Lines changed: 92 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,21 @@ def _csv(value: str | Iterable[str] | None) -> str | None:
2727
return ",".join(str(v) for v in value)
2828

2929

30-
async def _proxy(method: str, dooray_path: str, *, params: dict | None = None, data: dict | None = None) -> dict:
30+
async def _proxy(
31+
method: str,
32+
dooray_path: str,
33+
*,
34+
params: dict | None = None,
35+
data: dict | None = None,
36+
keep_data_nulls: Iterable[str] = (),
37+
) -> dict:
38+
keep = set(keep_data_nulls)
39+
body = {k: v for k, v in (data or {}).items() if v is not None or k in keep}
3140
resp = await _client.request(
3241
method,
3342
f"/v1/admin-api/proxy/dooray/{dooray_path.lstrip('/')}",
3443
params={k: v for k, v in (params or {}).items() if v is not None},
35-
json={k: v for k, v in (data or {}).items() if v is not None} or None,
44+
json=body or None,
3645
headers={"Authorization": f"Bearer {(await get_context().get_state(AUTH_KEY)).jwt}"},
3746
)
3847
if message := _PROXY_ERRORS.get(resp.status_code):
@@ -49,6 +58,15 @@ def _members(ids: Iterable[str] | None) -> list[dict] | None:
4958
return [{"type": "member", "member": {"organizationMemberId": m}} for m in ids] if ids else None
5059

5160

61+
def _users(to_member_ids: Iterable[str] | None, cc_member_ids: Iterable[str] | None) -> dict | None:
62+
users = {}
63+
if to := _members(to_member_ids):
64+
users["to"] = to
65+
if cc := _members(cc_member_ids):
66+
users["cc"] = cc
67+
return users or None
68+
69+
5270
async def _with_member_names(rows: list[dict]) -> list[dict]:
5371
sem = Semaphore(8)
5472

@@ -175,7 +193,8 @@ async def dooray_post_comments(project_id: str, post_id: str) -> dict:
175193
description=(
176194
"⚠️ Dooray 에 즉시 반영. 새 업무(Post)를 생성한다. 본문은 마크다운. "
177195
"담당자/참조자 id 는 `dooray_project_choices`(프로젝트 멤버)·`auth_status`의 dooray.member_id(본인)에서 확보. "
178-
"priority=highest|high|normal|low|lowest|none."
196+
"priority=highest|high|normal|low|lowest|none. "
197+
"workflow_id 지정 시 생성 직후 해당 상태로 이동(미지정이면 프로젝트 기본 상태=보통 보류/등록)."
179198
),
180199
tags=tag,
181200
)
@@ -190,27 +209,29 @@ async def dooray_create_post(
190209
tag_ids: list[str] | None = None,
191210
priority: str | None = None,
192211
parent_post_id: str | None = None,
212+
workflow_id: str | None = None,
193213
) -> dict:
194-
users = {}
195-
if to_member_ids:
196-
users["to"] = _members(to_member_ids)
197-
if cc_member_ids:
198-
users["cc"] = _members(cc_member_ids)
199-
200-
return await _proxy(
214+
created = await _proxy(
201215
method="POST",
202216
dooray_path=f"/project/v1/projects/{project_id}/posts",
203217
data={
204218
"subject": subject,
205219
"body": {"mimeType": "text/x-markdown", "content": body_markdown},
206-
"users": users or None,
220+
"users": _users(to_member_ids, cc_member_ids),
207221
"dueDate": due_date,
208222
"milestoneId": milestone_id,
209223
"tagIds": tag_ids,
210224
"priority": priority,
211225
"parentPostId": parent_post_id,
212226
},
213227
)
228+
if workflow_id and (post_id := (created.get("result") or {}).get("id")):
229+
await _proxy(
230+
method="POST",
231+
dooray_path=f"/project/v1/projects/{project_id}/posts/{post_id}/set-workflow",
232+
data={"workflowId": workflow_id},
233+
)
234+
return created
214235

215236
@mcp.tool(
216237
title="Dooray 업무 수정",
@@ -229,12 +250,6 @@ async def dooray_update_post(
229250
tag_ids: list[str] | None = None,
230251
priority: str | None = None,
231252
) -> dict:
232-
users = {}
233-
if to_member_ids:
234-
users["to"] = _members(to_member_ids)
235-
if cc_member_ids:
236-
users["cc"] = _members(cc_member_ids)
237-
238253
return await _proxy(
239254
method="PUT",
240255
dooray_path=f"/project/v1/projects/{project_id}/posts/{post_id}",
@@ -243,7 +258,7 @@ async def dooray_update_post(
243258
"body": (
244259
{"mimeType": "text/x-markdown", "content": body_markdown} if body_markdown is not None else None
245260
),
246-
"users": users or None,
261+
"users": _users(to_member_ids, cc_member_ids),
247262
"dueDate": due_date,
248263
"milestoneId": milestone_id,
249264
"tagIds": tag_ids,
@@ -265,7 +280,11 @@ async def dooray_set_post_workflow(project_id: str, post_id: str, workflow_id: s
265280

266281
@mcp.tool(
267282
title="Dooray 업무 완료 처리",
268-
description="⚠️ Dooray 에 즉시 반영. 업무를 완료(closed) 상태로 변경.",
283+
description=(
284+
"⚠️ Dooray 에 즉시 반영. 업무를 완료(closed) 상태로 변경. "
285+
"‼️ 업무 삭제(휴지통)는 이 도구(및 Dooray API)로 불가 — 완료 처리가 최대치다. "
286+
"정말 지우려면 Dooray 웹 UI에서 수동으로만 가능."
287+
),
269288
tags=tag,
270289
)
271290
async def dooray_set_post_done(project_id: str, post_id: str) -> dict:
@@ -274,6 +293,60 @@ async def dooray_set_post_done(project_id: str, post_id: str) -> dict:
274293
dooray_path=f"/project/v1/projects/{project_id}/posts/{post_id}/set-done",
275294
)
276295

296+
@mcp.tool(
297+
title="Dooray 업무 상위(에픽) 설정/해제",
298+
description=(
299+
"⚠️ Dooray 에 즉시 반영. 기존 업무의 상위 업무(에픽 등)를 설정하거나 해제한다. "
300+
"parent_post_id 지정=그 업무의 하위로 묶기, 미지정(None)=상위 해제(최상위로). "
301+
"제약: 이미 하위 업무를 가진 업무는 다른 업무의 하위로 설정할 수 없다(2단계 계층 불가). "
302+
"상위-하위 관계는 같은 프로젝트 안에서만 가능."
303+
),
304+
tags=tag,
305+
)
306+
async def dooray_set_parent_post(project_id: str, post_id: str, parent_post_id: str | None = None) -> dict:
307+
return await _proxy(
308+
method="POST",
309+
dooray_path=f"/project/v1/projects/{project_id}/posts/{post_id}/set-parent-post",
310+
data={"parentPostId": parent_post_id},
311+
keep_data_nulls=("parentPostId",),
312+
)
313+
314+
@mcp.tool(
315+
title="Dooray 업무 이동",
316+
description=(
317+
"⚠️ Dooray 에 즉시 반영·비가역. 업무를 다른 프로젝트로 이동한다. "
318+
"주의: 이동하면 그 업무의 워크플로(단계)·태그 정보는 사라진다. "
319+
"include_sub_posts=True 면 하위 업무도 함께 이동. target_project_id 는 `dooray_projects` 로 확보."
320+
),
321+
tags=tag,
322+
)
323+
async def dooray_move_post(
324+
project_id: str, post_id: str, target_project_id: str, include_sub_posts: bool = True
325+
) -> dict:
326+
return await _proxy(
327+
method="POST",
328+
dooray_path=f"/project/v1/projects/{project_id}/posts/{post_id}/move",
329+
data={"targetProjectId": target_project_id, "includeSubPosts": include_sub_posts},
330+
)
331+
332+
@mcp.tool(
333+
title="Dooray 태그 정의 생성",
334+
description=(
335+
"⚠️ Dooray 에 즉시 반영. 프로젝트에 새 태그(정의)를 생성한다. "
336+
"name 이 `그룹명:태그명` 형식이면 그룹 태그, 그냥 `태그명`이면 개별 태그. color 는 6자리 hex(예: ffffff). "
337+
"생성된 태그 id 는 이후 업무의 tag_ids 로 사용. "
338+
"‼️ 주의: 한 번 만든 태그 정의는 이 도구(및 Dooray API)로는 수정·삭제할 수 없다 — "
339+
"오타/불필요 태그 정리는 오직 Dooray 웹 UI에서 수동으로만 가능하니, 생성 전 name·color 를 신중히 확정할 것."
340+
),
341+
tags=tag,
342+
)
343+
async def dooray_add_tag(project_id: str, name: str, color: str = "ffffff") -> dict:
344+
return await _proxy(
345+
method="POST",
346+
dooray_path=f"/project/v1/projects/{project_id}/tags",
347+
data={"name": name, "color": color},
348+
)
349+
277350
@mcp.tool(
278351
title="Dooray 업무 댓글 작성",
279352
description="⚠️ Dooray 에 즉시 반영(담당자에게 알림). 업무에 댓글(마크다운)을 작성.",

0 commit comments

Comments
 (0)