Collection of agent skills for SLICC and Tessl-compatible runtimes — productivity, creative, document, and integration skills.
73
92%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Risky
Do not use without reviewing
Per-endpoint documentation for the teams skill. All calls use an OAuth 2
bearer token extracted from the user's Teams browser session.
The skill extracts the delegated access token from the MSAL cache in the
Teams web client's localStorage (Teams v2, teams.microsoft.com/v2/).
Older Teams versions that use sessionStorage are covered by a fallback.
MSAL v2 stores tokens under composite keys of the form:
<homeAccountId>-<environment>-accesstoken-<clientId>-<realm>-<scopes>Each value is JSON along the lines of:
{
"credential_type": "AccessToken",
"secret": "<the actual bearer token>",
"home_account_id": "...",
"environment": "login.microsoftonline.com",
"client_id": "...",
"target": "openid profile User.Read ...",
"realm": "<tenant-id>",
"token_type": "Bearer",
"expires_on": "1713200000",
"extended_expires_on": "1713203600"
}The page-context fetch searches for keys containing both accesstoken and
graph.microsoft.com, picks the entry with the highest expiresOn /
expires_on, and uses the secret field as the bearer token. Substrate
Search calls use the same lookup against substrate.office.com. All of this
runs inside the Teams tab via playwright-cli eval; the token value never
leaves the browser context.
The Teams web app requests broad scopes. This skill uses:
| Scope | Used by |
|---|---|
User.Read | activity (resolve /me), user |
User.ReadBasic.All | user (lookup by name / UPN) |
Team.ReadBasic.All | teams, name → ID resolution |
Channel.ReadBasic.All | channels, info, channel name → ID resolution |
ChannelMessage.Read.Group / ChannelMessage.Read.All (beta) | history, unanswered, digest, thread |
ChannelMessage.Send | post |
Chat.Read | search, activity (via Search API) |
If the token lacks a scope, Graph returns 403. The user may need to
consent via the Azure portal or re-open Teams after a tenant policy change.
Tokens typically expire after 60–90 minutes. The Teams web app silently refreshes them. When a token expires:
teams commands fail with 401 Unauthorized.GRAPH_BASE = https://graph.microsoft.com/v1.0GRAPH_BETA = https://graph.microsoft.com/betaChannel message reads and POSTs go through beta; team/channel/user metadata uses v1.0.
GET https://graph.microsoft.com/v1.0/meReturns displayName, mail, userPrincipalName, id. Used by
teams activity to resolve the current user for mention matching.
GET https://graph.microsoft.com/v1.0/me/joinedTeamsReturns an array (under value) with id, displayName, description.
GET https://graph.microsoft.com/v1.0/teams/{team-id}/channelsEach channel has id, displayName, description, membershipType
(standard / private / shared).
GET https://graph.microsoft.com/v1.0/teams/{team-id}/channels/{channel-id}Returns id, displayName, description, membershipType, webUrl.
GET https://graph.microsoft.com/beta/teams/{team-id}/channels/{channel-id}/messagesReturns top-level messages (not replies). Key query parameters:
| Parameter | Example | Notes |
|---|---|---|
$top | $top=50 | Max 50 per page |
$expand | $expand=replies($top=1) | Inline a cheap reply probe (used by unanswered) |
$filter | $filter=lastModifiedDateTime gt 2024-01-01T00:00:00Z | Time filter (limited support on beta) |
$orderby | $orderby=createdDateTime desc | Sort order |
Pagination: responses include @odata.nextLink (a full URL) when more
results exist. Follow it until it is absent.
Each message is shaped like:
{
"id": "...",
"messageType": "message",
"createdDateTime": "2024-03-15T10:30:00Z",
"from": { "user": { "displayName": "Jane Doe", "id": "..." } },
"body": { "contentType": "html", "content": "<p>Hello</p>" },
"importance": "normal",
"mentions": [
{ "id": 0, "mentionText": "John", "mentioned": { "user": { "id": "...", "displayName": "John" } } }
],
"reactions": [
{ "reactionType": "like", "user": { "displayName": "Bob" } }
],
"attachments": [],
"replies": []
}POST https://graph.microsoft.com/beta/teams/{team-id}/channels/{channel-id}/messages
Authorization: Bearer {token}
Content-Type: application/json
{
"body": { "contentType": "text", "content": "Hello!" }
}Returns the created message resource (same shape as the list-messages
response). Use contentType: "html" for formatted content (mentions,
bold, links, etc.).
POST https://graph.microsoft.com/beta/teams/{team-id}/channels/{channel-id}/messages/{message-id}/replies
Authorization: Bearer {token}
Content-Type: application/json
{
"body": { "contentType": "text", "content": "Got it!" }
}{message-id} is the id of the top-level parent message. The response is
a reply message resource — structurally identical to a channel message.
GET https://graph.microsoft.com/beta/teams/{team-id}/channels/{channel-id}/messages/{message-id}/repliesSupports $top (max 50) and @odata.nextLink pagination. Same response
shape as channel messages.
GET https://graph.microsoft.com/v1.0/users/{user-id-or-upn}
GET https://graph.microsoft.com/v1.0/users?$filter=startswith(displayName,'Name')&$top=5&$select=id,displayName,mail,userPrincipalName,jobTitle,department,officeLocationThe skill picks the direct lookup when the input is a GUID or contains @;
otherwise it uses $filter=startswith(displayName,'...'). Response fields:
id, displayName, mail, userPrincipalName, jobTitle, department,
officeLocation.
POST https://graph.microsoft.com/beta/search/query
Content-Type: application/json
{
"requests": [
{
"entityTypes": ["chatMessage"],
"query": { "queryString": "deployment issue" },
"from": 0,
"size": 25
}
]
}Response contains value[0].hitsContainers[0].hits[]. Each hit has a
resource (the chatMessage) and summary (highlighted snippet). Supports
KQL-style operators in the query string. Used by teams search and the
primary path of teams activity.
GET https://graph.microsoft.com/v1.0/teams/{team-id}/channels/getAllMessagesRequires application permissions (ChannelMessage.Read.All) — not
available with delegated tokens from the browser session. Mentioned here
only for reference.
Microsoft Graph applies per-app and per-tenant throttling. When throttled:
429 Too Many RequestsRetry-After header indicates seconds to waitThe skill paginates conservatively (maxPages 2–5 per call) to stay within
limits. If you hit throttling, wait per the Retry-After header and retry.
| Status | Meaning | Resolution |
|---|---|---|
| 401 | Token expired or invalid | Refresh the Teams tab in the browser; next command auto-picks up the new token |
| 403 | Insufficient permissions | Token lacks a required scope. Confirm you're on the beta endpoint for message reads. |
| 404 | Resource not found | Team / channel / message ID is wrong |
| 429 | Throttled | Wait per Retry-After header |
| 503 | Service unavailable | Transient — retry after a few seconds |
skills
aem
ai-writing-detector
references
apple-music
references
bluebubbles
concur
fluffyjaws
github
gmail
icloud
references
mixtape
references
monday
oryx
outlook
pm-prd
pptx
pptx2pdf
presentations
review
save-the-cat
servicenow
references
strudel-music
swarm
references
teams
references
wavespeed
xai-grok