"""Work package relation tools for OpenProject MCP server."""
from typing import Any, Literal
from ..client import OpenProjectClient
from ..utils.hal import build_link
RelationType = Literal[
"relates",
"duplicates",
"duplicated",
"blocks",
"blocked",
"precedes",
"follows",
"includes",
"partof",
"requires",
"required",
]
async def list_work_package_relations(work_package_id: int) -> dict[str, Any]:
"""Get all relations for a work package.
Args:
work_package_id: Work package ID
Returns:
Collection of relations (parent, blocks, relates, etc.)
"""
client = OpenProjectClient()
try:
result = await client.get(f"work_packages/{work_package_id}/relations")
return result
finally:
await client.close()
async def create_relation(
from_id: int,
to_id: int,
relation_type: RelationType,
lag: int | None = None,
) -> dict[str, Any]:
"""Create a relation between two work packages.
Args:
from_id: Source work package ID
to_id: Target work package ID
relation_type: Type of relation. Options:
- relates: General relation
- duplicates: Source duplicates target
- duplicated: Source is duplicated by target
- blocks: Source blocks target
- blocked: Source is blocked by target
- precedes: Source precedes target (with optional lag in days)
- follows: Source follows target (with optional lag in days)
- includes: Source includes target (parent-child)
- partof: Source is part of target (child-parent)
- requires: Source requires target
- required: Source is required by target
lag: Optional lag in days for precedes/follows relations
Returns:
Created relation object
"""
client = OpenProjectClient()
try:
payload: dict[str, Any] = {
"_links": {
"from": build_link(f"/api/v3/work_packages/{from_id}"),
"to": build_link(f"/api/v3/work_packages/{to_id}"),
},
"type": relation_type,
}
if lag is not None:
payload["lag"] = lag
result = await client.post("relations", data=payload)
return result
finally:
await client.close()
async def get_relation(relation_id: int) -> dict[str, Any]:
"""Get details of a specific relation.
Args:
relation_id: Relation ID
Returns:
Relation object with details
"""
client = OpenProjectClient()
try:
result = await client.get(f"relations/{relation_id}")
return result
finally:
await client.close()
async def delete_relation(relation_id: int) -> dict[str, Any]:
"""Remove a relation between work packages.
Args:
relation_id: Relation ID to delete
Returns:
Success confirmation
"""
client = OpenProjectClient()
try:
result = await client.delete(f"relations/{relation_id}")
return result
finally:
await client.close()