+157
-31
tools/bluesky/update_bluesky_profile.py
+157
-31
tools/bluesky/update_bluesky_profile.py
···
2
2
3
3
import os
4
4
from typing import Optional
5
+
from atproto import Client, models
6
+
5
7
6
8
def update_bluesky_profile(display_name: Optional[str] = None, bio: Optional[str] = None) -> dict:
7
9
"""
8
-
Safely update the user's Bluesky profile display name and/or bio
9
-
without losing other fields (avatar, banner, etc.).
10
+
Update the authenticated user's Bluesky profile display name and/or bio.
11
+
12
+
This tool safely updates profile information while automatically preserving all other
13
+
profile data (avatar, banner, pinned post, etc.). You can confidently update just the
14
+
display name, just the bio, or both together - the tool handles preserving unchanged
15
+
fields for you.
16
+
17
+
The tool uses a safe fetch-merge-write pattern: it retrieves the current profile,
18
+
merges your updates with existing data, then saves the complete record. This ensures
19
+
no data loss when updating individual fields.
20
+
21
+
Use this tool when you need to update the user's display name, bio, or both. Updates
22
+
are immediate and visible on the user's profile.
10
23
11
24
Args:
12
-
display_name: The new display name for the profile (max 64 characters). Optional
13
-
bio: The new bio/description for the profile (max 256 characters). Optional
25
+
display_name (Optional[str]): The new display name for the profile (max 64 characters). This is the prominent name shown on posts and the profile page. Can include Unicode characters, emoji, and special characters. Use empty string to remove the display name. If None, the current display name is preserved unchanged. Defaults to None.
26
+
bio (Optional[str]): The new bio/description for the profile (max 256 characters). This appears below the display name and profile picture. Can include line breaks, emoji, and special characters. Supports plain text only (no markdown). Use empty string to remove the bio. If None, the current bio is preserved unchanged. Defaults to None. At least one of display_name or bio must be provided (not both None).
14
27
15
28
Returns:
16
-
A dictionary containing the status, success message, updated fields, and profile URL
29
+
dict: A dictionary containing the operation result with the following keys:
30
+
31
+
On success:
32
+
- status (str): "success"
33
+
- message (str): Confirmation message
34
+
- updated_fields (dict): The fields that were changed with their new values:
35
+
- displayName (str): New display name if updated
36
+
- description (str): New bio if updated
37
+
- profile_url (str): URL to view the updated profile on bsky.app
38
+
39
+
On error:
40
+
- status (str): "error"
41
+
- message (str): Human-readable error description with resolution guidance
42
+
43
+
Examples:
44
+
# Update only the display name
45
+
update_bluesky_profile(display_name="Alice Johnson")
46
+
47
+
# Update only the bio
48
+
update_bluesky_profile(bio="Software engineer interested in AI")
49
+
50
+
# Update both display name and bio
51
+
update_bluesky_profile(
52
+
display_name="Dr. Alice Johnson",
53
+
bio="Researcher in distributed systems and AI"
54
+
)
55
+
56
+
# Remove display name (use empty string)
57
+
update_bluesky_profile(display_name="")
58
+
59
+
# Update bio with multiple lines and emoji
60
+
update_bluesky_profile(
61
+
bio="🚀 Building the decentralized web\\nPreviously @BigTech\\n📍 San Francisco"
62
+
)
17
63
"""
18
64
try:
19
-
from atproto import Client, models
20
-
21
65
# Validate input
22
66
if display_name is None and bio is None:
23
-
raise ValueError("At least one of display_name or bio must be provided.")
67
+
return {
68
+
"status": "error",
69
+
"message": "Error: No fields provided for update. To resolve this, provide at least one field to update: "
70
+
"either display_name, bio, or both. You cannot call this tool with both parameters as None. "
71
+
"This is a common mistake and can be fixed by specifying which field(s) you want to update."
72
+
}
73
+
24
74
if display_name is not None and len(display_name) > 64:
25
-
raise ValueError("Display name exceeds 64 characters.")
75
+
return {
76
+
"status": "error",
77
+
"message": f"Error: The display_name is too long (current length: {len(display_name)} characters). "
78
+
f"To resolve this, shorten it to 64 characters or less (you need to remove "
79
+
f"{len(display_name) - 64} characters). This is a Bluesky platform limitation that applies "
80
+
f"to all users. Consider making the name more concise."
81
+
}
82
+
26
83
if bio is not None and len(bio) > 256:
27
-
raise ValueError("Bio exceeds 256 characters.")
84
+
return {
85
+
"status": "error",
86
+
"message": f"Error: The bio is too long (current length: {len(bio)} characters). To resolve this, "
87
+
f"shorten it to 256 characters or less (you need to remove {len(bio) - 256} characters). "
88
+
f"This is a Bluesky platform limitation that applies to all users. Consider making the "
89
+
f"bio more concise or breaking it into shorter sentences."
90
+
}
28
91
29
92
username = os.environ.get("BSKY_USERNAME")
30
93
password = os.environ.get("BSKY_APP_PASSWORD")
31
94
if not username or not password:
32
-
raise EnvironmentError("BSKY_USERNAME and BSKY_APP_PASSWORD environment variables must be set.")
95
+
return {
96
+
"status": "error",
97
+
"message": "Error: Missing Bluesky authentication credentials. The BSKY_USERNAME and BSKY_APP_PASSWORD "
98
+
"environment variables are not set. To resolve this, ask the user to configure these environment "
99
+
"variables with valid Bluesky credentials. This is a configuration issue that the user needs to "
100
+
"address before you can update their profile."
101
+
}
33
102
34
103
# Initialize client and login
35
104
client = Client()
36
105
client.login(username, password)
37
106
38
-
# ✅ Use the proper parameter model for get_record
39
-
params = models.ComAtprotoRepoGetRecord.Params(
40
-
repo=client.me.did,
41
-
collection="app.bsky.actor.profile",
42
-
rkey="self"
43
-
)
44
-
record = client.com.atproto.repo.get_record(params)
45
-
current_record = record.value
107
+
# STEP 1A: Fetch current profile via get_profile (reliable for display name and bio)
108
+
try:
109
+
current_profile = client.get_profile(actor=client.me.did)
110
+
except Exception as e:
111
+
return {
112
+
"status": "error",
113
+
"message": f"Error: Failed to fetch current profile via AppView. To resolve this, verify you are authenticated "
114
+
f"and try again. This error can occur due to authentication issues or temporary API problems. "
115
+
f"API details: {str(e)}"
116
+
}
117
+
118
+
# Extract current display name and bio from profile
119
+
current_display_name = getattr(current_profile, 'display_name', None) or ""
120
+
current_bio = getattr(current_profile, 'description', None) or ""
121
+
122
+
# STEP 1B: Fetch the complete raw profile record (for avatar, banner, etc.)
123
+
# This gets ALL fields from the repository that we need to preserve
124
+
try:
125
+
params = models.ComAtprotoRepoGetRecord.Params(
126
+
repo=client.me.did,
127
+
collection="app.bsky.actor.profile",
128
+
rkey="self"
129
+
)
130
+
record = client.com.atproto.repo.get_record(params)
131
+
current_record = record.value
132
+
except Exception as e:
133
+
return {
134
+
"status": "error",
135
+
"message": f"Error: Failed to fetch current profile record. To resolve this, verify you are authenticated "
136
+
f"and try again. This error can occur due to authentication issues or temporary API problems. "
137
+
f"API details: {str(e)}"
138
+
}
46
139
47
140
# Convert model to dict if needed
48
141
if not isinstance(current_record, dict):
···
51
144
except Exception:
52
145
current_record = current_record.__dict__
53
146
54
-
# Merge updates
147
+
# STEP 2: Merge the updates into the existing record
148
+
# Use values from get_profile for fields not being updated (most reliable source)
149
+
# Start with the raw record to preserve avatar, banner, etc.
55
150
updated_record = dict(current_record)
151
+
152
+
# For display name: use provided value, or fallback to current from get_profile
56
153
if display_name is not None:
57
154
updated_record["displayName"] = display_name
155
+
else:
156
+
# Ensure we keep the current display name from get_profile
157
+
updated_record["displayName"] = current_display_name
158
+
159
+
# For bio: use provided value, or fallback to current from get_profile
58
160
if bio is not None:
59
161
updated_record["description"] = bio
162
+
else:
163
+
# Ensure we keep the current bio from get_profile
164
+
updated_record["description"] = current_bio
60
165
61
-
# Ensure type field exists
166
+
# Ensure the record type field exists (required by AT Protocol)
62
167
updated_record["$type"] = "app.bsky.actor.profile"
63
168
64
-
# ✅ Put updated record back using correct model
65
-
put_data = models.ComAtprotoRepoPutRecord.Data(
66
-
repo=client.me.did,
67
-
collection="app.bsky.actor.profile",
68
-
rkey="self",
69
-
record=updated_record
70
-
)
71
-
client.com.atproto.repo.put_record(put_data)
169
+
# STEP 3: Write the complete updated record back to Bluesky
170
+
# AT Protocol requires sending the entire record, not just changed fields
171
+
try:
172
+
put_data = models.ComAtprotoRepoPutRecord.Data(
173
+
repo=client.me.did,
174
+
collection="app.bsky.actor.profile",
175
+
rkey="self",
176
+
record=updated_record
177
+
)
178
+
client.com.atproto.repo.put_record(put_data)
179
+
except Exception as e:
180
+
return {
181
+
"status": "error",
182
+
"message": f"Error: Failed to update profile on Bluesky. To resolve this, verify your credentials are valid "
183
+
f"and try again. This error can occur due to authentication issues, network problems, or temporary "
184
+
f"API issues, and usually succeeds on retry. API details: {str(e)}"
185
+
}
72
186
73
187
# Build structured success response
74
188
updates = {}
···
85
199
}
86
200
87
201
except ImportError:
88
-
raise ImportError("atproto package not installed. Install it with: pip install atproto")
202
+
return {
203
+
"status": "error",
204
+
"message": "Error: The atproto Python package is not installed in the execution environment. "
205
+
"To resolve this, the system administrator needs to install it using 'pip install atproto'. "
206
+
"This is a dependency issue that prevents the tool from connecting to Bluesky. Once the "
207
+
"package is installed, this tool will work normally."
208
+
}
89
209
except Exception as e:
90
-
raise RuntimeError(f"Error updating Bluesky profile: {e}")
210
+
return {
211
+
"status": "error",
212
+
"message": f"Error: An unexpected issue occurred while updating the profile: {str(e)}. To resolve this, "
213
+
f"verify your credentials are correct, ensure the display_name and bio meet length requirements, "
214
+
f"and try again. This type of error is uncommon but can usually be resolved by retrying or "
215
+
f"checking that your parameters are valid."
216
+
}