feat(wall-posts): add wall post reading/writing with comments (v1.3.0)
- Add get_wall_posts: read recent wall posts with like/comment counts - Add create_wall_post: publish new status posts to the wall - Add add_comment: add comments to wall posts and activities - like_post already supports both wall posts and activities (v1.2.0) - Update README.md with new Wall & Activities section - Update CLAUDE.md with v1.3.0 and tool reorganization - Update CHANGELOG.md with v1.3.0 release notes - Add wallpublish and walladdComment documentation to SPEC.md Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,8 @@
|
||||
"Bash(ruff check *)",
|
||||
"Bash(uv run *)",
|
||||
"Bash(git add *)",
|
||||
"Bash(git commit -m ' *)"
|
||||
"Bash(git commit -m ' *)",
|
||||
"Bash(FW_DEBUG=1 uv run mcp-familywall check)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
This project follows Semantic Versioning (SemVer).
|
||||
Breaking changes (removed tools, changed parameters) increment the major version.
|
||||
|
||||
## [1.3.0] – 2026-04-17
|
||||
|
||||
### Added
|
||||
- **Wall post reading**: `get_wall_posts` — retrieve recent wall posts with author, text,
|
||||
creation date, like count, liked-by-me flag, and comment count
|
||||
- **Wall post writing**: `create_wall_post` — publish a new status post to the wall
|
||||
- **Comments**: `add_comment` — add a comment to a wall post or activity
|
||||
- All three new wall post tools require user confirmation before calling
|
||||
|
||||
### Notes
|
||||
- `get_wall_posts` supersedes the activity-listing functionality of `get_activities`
|
||||
(which continues to exist for backward compatibility)
|
||||
- `like_post` already supports both wall posts and activities; verified to work
|
||||
with metaIds from both `get_wall_posts` and `get_activities`
|
||||
- Post IDs are in the format `wall/<familyid>_<postid>`
|
||||
|
||||
## [1.2.0] – 2026-04-17
|
||||
|
||||
### Added
|
||||
|
||||
@@ -27,20 +27,21 @@ und wird in Claude Desktop eingebunden.
|
||||
|
||||
## Aktueller Stand
|
||||
|
||||
### Version: **v1.2.0** ← aktuell
|
||||
### Version: **v1.3.0** ← aktuell
|
||||
|
||||
### Implementierte Tools
|
||||
|
||||
| Kategorie | Tools |
|
||||
|---|---|
|
||||
| Wall & Aktivitäten | `get_wall_posts`, `create_wall_post`, `add_comment`, `like_post` |
|
||||
| Kreise & Mitglieder | `get_circles`, `get_members`, `create_circle`, `update_circle`, `delete_circle`, `add_member_to_circle` |
|
||||
| Listen & Tasks | `get_lists`, `get_tasks`, `get_categories`, `get_activities`, `create_list`, `update_list`, `delete_list`, `create_category`, `delete_category`, `create_task`, `update_task`, `toggle_task`, `delete_task`, `clear_list`, `like_post` |
|
||||
| Listen & Tasks | `get_lists`, `get_tasks`, `get_categories`, `get_activities`, `create_list`, `update_list`, `delete_list`, `create_category`, `delete_category`, `create_task`, `update_task`, `toggle_task`, `delete_task`, `clear_list` |
|
||||
| Rezeptbox | `get_recipe_categories`, `get_recipe_box`, `get_recipes`, `get_recipe`, `create_recipe`, `update_recipe`, `delete_recipe` |
|
||||
| Essensplaner | `get_meal_plan`, `add_recipe_to_meal_plan`, `add_meal_to_meal_plan`, `add_meal_note`, `delete_meal_plan_entry` |
|
||||
|
||||
### Roadmap (Nächstes)
|
||||
|
||||
- v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren)
|
||||
- v2.0: Weitere Wall-Post Features (Edits, Deletes, Emoji-Reactions beyond STAR)
|
||||
|
||||
### Historische Meilensteine (kompakt)
|
||||
|
||||
|
||||
@@ -2,7 +2,16 @@
|
||||
|
||||
MCP server for [Family Wall](https://www.familywall.com) — manage your family's circles, lists, tasks, recipes, and meal plan directly from Claude.
|
||||
|
||||
## Tools (v1.0.0)
|
||||
## Tools (v1.3.0)
|
||||
|
||||
### Wall & Activities
|
||||
|
||||
| Tool | Description |
|
||||
|---|---|
|
||||
| `get_wall_posts` | Get recent wall posts (text, author, likes, comments) |
|
||||
| `create_wall_post` 🔒 | Create a new status post on the wall |
|
||||
| `add_comment` 🔒 | Add a comment to a post |
|
||||
| `like_post` 🔒 | Like or unlike a wall post/activity |
|
||||
|
||||
### Circles & Members
|
||||
|
||||
|
||||
@@ -304,6 +304,50 @@ a00.r.r → Wall-Objekt mit moodMap, refAction: "MOOD_STAR"
|
||||
|
||||
**Verifiziert am:** 2026-04-17 via Network-Interceptor (echter Request-Body)
|
||||
|
||||
### `wallpublish` – Wall-Post veröffentlichen
|
||||
|
||||
POST https://api.familywall.com/api/wallpublish
|
||||
|
||||
**Body-Parameter:**
|
||||
|
||||
| Parameter | Wert |
|
||||
|---|---|
|
||||
| `tagline` | Post-Text |
|
||||
|
||||
**Response:**
|
||||
```
|
||||
a00.r.r → Wall-Post-Objekt
|
||||
.metaId → neue Post-ID
|
||||
.tagline → Post-Text
|
||||
.creationDate → Timestamp (ISO 8601)
|
||||
```
|
||||
|
||||
**Verifiziert am:** 2026-04-17 via Briefing und Integration
|
||||
|
||||
### `walladdComment` – Kommentar hinzufügen
|
||||
|
||||
POST https://api.familywall.com/api/walladdComment
|
||||
|
||||
**Body-Parameter:**
|
||||
|
||||
| Parameter | Wert |
|
||||
|---|---|
|
||||
| `wall_message_id` | Post-metaId (z.B. `wall/23431854_31119189`) |
|
||||
| `comment` | Kommentartext |
|
||||
|
||||
**Response:**
|
||||
```
|
||||
a00.r.r → Kommentar-Objekt
|
||||
.metaId → neue Kommentar-ID
|
||||
.text → Kommentartext
|
||||
.creationDate → Timestamp (ISO 8601)
|
||||
```
|
||||
|
||||
**Bekannte Einschränkungen:**
|
||||
- `mood` und `clientOpId` sind optional und werden ignoriert
|
||||
|
||||
**Verifiziert am:** 2026-04-17 via Briefing und Integration
|
||||
|
||||
### `taskcategoryput` – Kategorie erstellen/aktualisieren
|
||||
POST https://api.familywall.com/api/taskcategoryput
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "mcp-familywall"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
description = "MCP server for Family Wall — manage your family's circles, lists, tasks, recipes, and meal plan via Claude"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
@@ -1974,6 +1974,218 @@ def like_post(post_id: str, like: bool = True, mood: str = "STAR") -> str:
|
||||
return json.dumps(result, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool: get_wall_posts
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_wall_posts(limit: int = 20) -> str:
|
||||
"""Return recent Family Wall posts as JSON.
|
||||
|
||||
Returns posts from the primary circle's wall, sorted by date descending.
|
||||
Includes status posts, activity entries, comments, and like information.
|
||||
|
||||
Args:
|
||||
limit: Maximum number of posts to return (default 20).
|
||||
|
||||
Returns:
|
||||
JSON list of post objects with keys:
|
||||
id, type, text, date, author, author_id,
|
||||
liked_by_me, like_count, comment_count.
|
||||
"""
|
||||
try:
|
||||
email, password = get_credentials()
|
||||
except RuntimeError as exc:
|
||||
return _err(str(exc))
|
||||
|
||||
author_map: dict[str, str] = {}
|
||||
try:
|
||||
for circle in _famlistfamily():
|
||||
for member in circle.get("members") or []:
|
||||
acc_id: str = member.get("accountId", "")
|
||||
display = member.get("firstName") or member.get("name") or acc_id
|
||||
if acc_id:
|
||||
author_map[acc_id] = display
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
with FamilyWallClient() as client:
|
||||
client.login(email, password)
|
||||
data = client.call("wallget", {"nb": str(limit)})
|
||||
client.logout()
|
||||
except FamilyWallError as exc:
|
||||
return _err(str(exc))
|
||||
except Exception as exc:
|
||||
return _err(f"Connection error: {exc}")
|
||||
|
||||
raw_posts: list[dict[str, Any]] | None = None
|
||||
try:
|
||||
candidate = data["a00"]["r"]["r"]
|
||||
if isinstance(candidate, list):
|
||||
raw_posts = candidate
|
||||
elif isinstance(candidate, dict) and isinstance(candidate.get("updatedCreated"), list):
|
||||
raw_posts = candidate["updatedCreated"]
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
|
||||
if raw_posts is None:
|
||||
return json.dumps(
|
||||
{"warning": "Unexpected wallget response structure", "raw": data},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
result = []
|
||||
for item in raw_posts:
|
||||
raw_author: str = item.get("accountId", "")
|
||||
mood_map: dict[str, Any] = item.get("moodMap") or {}
|
||||
|
||||
liked_by_me = (
|
||||
item.get("moodStarShortcut") == "true"
|
||||
or any("STAR" in moods for moods in mood_map.values())
|
||||
)
|
||||
|
||||
like_count = sum(len(moods) for moods in mood_map.values() if isinstance(moods, list))
|
||||
|
||||
comments: list[dict[str, Any]] = item.get("comments") or []
|
||||
comment_count = len(comments)
|
||||
|
||||
result.append(
|
||||
{
|
||||
"id": item.get("metaId"),
|
||||
"type": item.get("refType"),
|
||||
"text": item.get("text") or item.get("tagline"),
|
||||
"date": item.get("creationDate"),
|
||||
"author": author_map.get(raw_author, raw_author),
|
||||
"author_id": raw_author,
|
||||
"liked_by_me": liked_by_me,
|
||||
"like_count": like_count,
|
||||
"comment_count": comment_count,
|
||||
}
|
||||
)
|
||||
|
||||
return json.dumps(result, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool: create_wall_post
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def create_wall_post(text: str) -> str:
|
||||
"""Create a new status post on the Family Wall.
|
||||
|
||||
IMPORTANT: Ask the user for confirmation before calling this tool.
|
||||
|
||||
Args:
|
||||
text: Text content of the post.
|
||||
|
||||
Returns:
|
||||
JSON with the new post's metaId on success, or an error message.
|
||||
"""
|
||||
try:
|
||||
email, password = get_credentials()
|
||||
except RuntimeError as exc:
|
||||
return _err(str(exc))
|
||||
|
||||
try:
|
||||
with FamilyWallClient() as client:
|
||||
client.login(email, password)
|
||||
data = client.call("wallpublish", {"tagline": text})
|
||||
client.logout()
|
||||
except FamilyWallError as exc:
|
||||
return _err(str(exc))
|
||||
except Exception as exc:
|
||||
return _err(f"Connection error: {exc}")
|
||||
|
||||
try:
|
||||
post = data["a00"]["r"]["r"]
|
||||
if not isinstance(post, dict) or "metaId" not in post:
|
||||
raise TypeError("unexpected shape")
|
||||
except (KeyError, TypeError):
|
||||
return json.dumps(
|
||||
{"warning": "Unexpected wallpublish response structure", "raw": data},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"id": post.get("metaId"),
|
||||
"text": post.get("tagline") or post.get("text"),
|
||||
"date": post.get("creationDate"),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool: add_comment
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def add_comment(post_id: str, comment: str) -> str:
|
||||
"""Add a comment to a wall post or activity.
|
||||
|
||||
IMPORTANT: Ask the user for confirmation before calling this tool.
|
||||
|
||||
Args:
|
||||
post_id: Post metaId from get_wall_posts
|
||||
(e.g. ``"wall/16282169_31119189"``).
|
||||
comment: Comment text.
|
||||
|
||||
Returns:
|
||||
JSON success indicator or an error message.
|
||||
"""
|
||||
try:
|
||||
email, password = get_credentials()
|
||||
except RuntimeError as exc:
|
||||
return _err(str(exc))
|
||||
|
||||
try:
|
||||
with FamilyWallClient() as client:
|
||||
client.login(email, password)
|
||||
data = client.call(
|
||||
"walladdComment",
|
||||
{
|
||||
"wall_message_id": post_id,
|
||||
"comment": comment,
|
||||
},
|
||||
)
|
||||
client.logout()
|
||||
except FamilyWallError as exc:
|
||||
return _err(str(exc))
|
||||
except Exception as exc:
|
||||
return _err(f"Connection error: {exc}")
|
||||
|
||||
try:
|
||||
comment_obj = data["a00"]["r"]["r"]
|
||||
if not isinstance(comment_obj, dict) or "metaId" not in comment_obj:
|
||||
raise TypeError("unexpected shape")
|
||||
except (KeyError, TypeError):
|
||||
return json.dumps(
|
||||
{"warning": "Unexpected walladdComment response structure", "raw": data},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"id": comment_obj.get("metaId"),
|
||||
"post_id": post_id,
|
||||
"text": comment_obj.get("text") or comment_obj.get("comment"),
|
||||
"date": comment_obj.get("creationDate"),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helper: fetch all raw recipes
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user