diff --git a/.claude-plugin/.upstream-base b/.claude-plugin/.upstream-base index 703e568a74..612a025921 100644 --- a/.claude-plugin/.upstream-base +++ b/.claude-plugin/.upstream-base @@ -1 +1 @@ -2776ddd2355d6d6c8747d3964600f28daf62f049 +6b0300214a09fd6029cfb7077043125758cd1fd2 diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index b69b642c16..153216eb7e 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -692,6 +692,11 @@ "name": "tesla", "source": "third_party/tesla", "description": "Read your Tesla cars and energy sites, and send commands you confirm." + }, + { + "name": "fitmetrics", + "source": "third_party/fitmetrics", + "description": "Review Trainerize clients and check-ins, and update macros, messages, and programs." } ] } diff --git a/third_party/fitmetrics/.cursor-plugin/plugin.json b/third_party/fitmetrics/.cursor-plugin/plugin.json new file mode 100644 index 0000000000..fa49be676f --- /dev/null +++ b/third_party/fitmetrics/.cursor-plugin/plugin.json @@ -0,0 +1,49 @@ +{ + "name": "fitmetrics", + "displayName": "FitMetrics", + "version": "1.0.0", + "minClientVersions": { + "cursor": "3.13.0" + }, + "description": "Review Trainerize clients and check-ins, and update macros, messages, and programs.", + "author": { + "name": "Cursor", + "email": "plugins@cursor.com" + }, + "homepage": "https://app.fitmetrics.ai/docs/faq/connect-ai-tools-to-fitmetrics-via-mcp-claude-chatgpt-openclaw-more", + "repository": "https://github.com/cursor/plugins", + "license": "MIT", + "logo": "assets/logo.svg", + "keywords": [ + "fitmetrics", + "trainerize", + "abc fitness", + "coaching", + "fitness", + "nutrition", + "workouts", + "check-ins", + "mcp" + ], + "category": "integrations", + "tags": [ + "fitmetrics", + "fitness", + "coaching", + "mcp" + ], + "variables": { + "type": "object", + "properties": { + "FITMETRICS_API_KEY": { + "type": "string", + "title": "FitMetrics API key", + "description": "Key from FitMetrics Settings → Integrations → MCP → Create API Key → Other MCP app. Requires the Scale package." + } + }, + "required": [ + "FITMETRICS_API_KEY" + ] + }, + "mcpServers": "./mcp.json" +} diff --git a/third_party/fitmetrics/CHANGELOG.md b/third_party/fitmetrics/CHANGELOG.md new file mode 100644 index 0000000000..874b2ae180 --- /dev/null +++ b/third_party/fitmetrics/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +All notable changes to this plugin will be documented here. + +## 1.0.0 — initial release + +- Added the `fitmetrics` MCP server pointing at FitMetrics' hosted Streamable HTTP endpoint (`https://app.fitmetrics.ai/mcp`). +- Auth uses a FitMetrics API key supplied by the user as an `Authorization: Bearer` header (`${FITMETRICS_API_KEY}`). +- Logo: FitMetrics' official mark, the `favicon.svg` from fitmetrics.ai. diff --git a/third_party/fitmetrics/LICENSE b/third_party/fitmetrics/LICENSE new file mode 100644 index 0000000000..ca2bba771c --- /dev/null +++ b/third_party/fitmetrics/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Cursor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/fitmetrics/README.md b/third_party/fitmetrics/README.md new file mode 100644 index 0000000000..59f0a95508 --- /dev/null +++ b/third_party/fitmetrics/README.md @@ -0,0 +1,65 @@ +# FitMetrics + +Cursor plugin that connects agents to [FitMetrics](https://www.fitmetrics.ai) through FitMetrics' official remote [Model Context Protocol](https://modelcontextprotocol.io/) server. + +Review your ABC Trainerize clients, check-ins, measurements, meals, and workouts, and update macros, messages, habits, and workout programs. FitMetrics is ABC Fitness's coaching add-on for Trainerize. + +## Install + +1. Open **Cursor Settings → Plugins**. +2. Search for **FitMetrics**. +3. Click **Install**, then set your FitMetrics API key (below). + +Or run `/add-plugin fitmetrics` in chat. + +## MCP + +```json +{ + "mcpServers": { + "fitmetrics": { + "type": "http", + "url": "https://app.fitmetrics.ai/mcp", + "headers": { + "Authorization": "Bearer ${FITMETRICS_API_KEY}" + } + } + } +} +``` + +Auth is a FitMetrics **API key** sent as `Authorization: Bearer`. In FitMetrics, open **Settings → Integrations → MCP**, click **Create API Key**, choose **Other MCP app**, then name the key and pick its coach and tool permissions. Set the key in **Dashboard → Plugins → Configure**. Do not commit the key. + +## Before you connect + +You need ABC Trainerize with the FitMetrics **Scale** package, the only package with MCP access. Coaches can create their own key, and it reaches only their assigned clients. Write actions stay off until an owner or admin turns them on. + +## What agents can do + +| Category | Capabilities | +| --- | --- | +| Clients | Morning briefing, client dossiers, measurements, habits, and tags | +| Check-ins | Review check-ins, and create and manage AI check-in templates and AI Forms | +| Nutrition | Meals and macros, including scheduled macro changes in Trainerize | +| Training | Workouts, cardio, 1:1 bookings, and building, copying, scheduling, and assigning programs | +| Messaging | Messages, scheduled and bulk messages, email, comments, and reactions | +| Automations | Create and manage FitMetrics automations | + +FitMetrics lists 359 tools (137 read, 222 write). The hosted runtime is the source of truth for tool names and schemas. + +## Notes + +- FitMetrics offers OAuth sign-in only to Claude and ChatGPT, and its dynamic client registration rejects Cursor's OAuth callback. This plugin uses the API key path FitMetrics documents for Cursor and other MCP apps. +- Changes made through the server are confirmed, scoped, and logged in FitMetrics. + +## Docs + +- Connect AI tools to FitMetrics via MCP: https://app.fitmetrics.ai/docs/faq/connect-ai-tools-to-fitmetrics-via-mcp-claude-chatgpt-openclaw-more +- ABC Trainerize x FitMetrics MCP: https://resources.trainerize.com/mcp +- Server URL: https://app.fitmetrics.ai/mcp + +Logo is FitMetrics' official mark, the `favicon.svg` from fitmetrics.ai. + +## License + +MIT diff --git a/third_party/fitmetrics/assets/logo.svg b/third_party/fitmetrics/assets/logo.svg new file mode 100644 index 0000000000..53c7ed8023 --- /dev/null +++ b/third_party/fitmetrics/assets/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/third_party/fitmetrics/mcp.json b/third_party/fitmetrics/mcp.json new file mode 100644 index 0000000000..53179659c2 --- /dev/null +++ b/third_party/fitmetrics/mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "fitmetrics": { + "type": "http", + "url": "https://app.fitmetrics.ai/mcp", + "headers": { + "Authorization": "Bearer ${FITMETRICS_API_KEY}" + } + } + } +} diff --git a/third_party/x/.cursor-plugin/plugin.json b/third_party/x/.cursor-plugin/plugin.json index 0c174b3633..241da22f3e 100644 --- a/third_party/x/.cursor-plugin/plugin.json +++ b/third_party/x/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "x", "displayName": "X", - "version": "2.11.0", + "version": "2.11.1", "minClientVersions": { "cursor": "3.13.0" }, diff --git a/third_party/x/CHANGELOG.md b/third_party/x/CHANGELOG.md index 7c8df457dd..3b4736225e 100644 --- a/third_party/x/CHANGELOG.md +++ b/third_party/x/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this plugin will be documented here. +## 2.11.1 — Find a contact's chat, X DMs only via send_chat_message + +- A new "Find or message a contact" section in the guide: find someone's thread with `get_chat_conversations` `participant_query` (their @username, `type` `direct`, `expansions` `participant_ids` to confirm it's them), keep paging while `has_more` is true, and retry with their @username, instead of scanning the inbox or looking the person up first. +- X DMs go out only through `send_chat_message`, never an email or Slack draft tool. When the host holds a send for approval, as Grok Bot's X Chat draft card does, the send repeats it with exactly the same arguments, without re-encrypting. + ## 2.11.0 — Posting - Requested `tweet.write` so agents can publish posts and replies with `create_posts` on `https://api.x.com/mcp`. diff --git a/third_party/x/skills/x-api-mcp-guide/SKILL.md b/third_party/x/skills/x-api-mcp-guide/SKILL.md index 5930489e8e..bdaf0e90db 100644 --- a/third_party/x/skills/x-api-mcp-guide/SKILL.md +++ b/third_party/x/skills/x-api-mcp-guide/SKILL.md @@ -138,7 +138,7 @@ Once per session: | Tool | Use | | --- | --- | -| `get_chat_conversations` | The inbox, `max_results` 1000 | +| `get_chat_conversations` | The inbox, `max_results` 1000. `participant_query` finds one person's thread ([Find or message a contact](#find-or-message-a-contact)) | | `get_chat_conversation` | One thread, including `participant_ids` / `member_ids` | | `get_chat_conversation_events` | `data[].encoded_event` plus `meta.conversation_key_events`, newest first, `max_results` 100 | | `get_users_public_keys` | Signing keys for senders, all versions, up to 100 `ids` per call | @@ -163,6 +163,16 @@ For any set of events: Each message's `status` is `verified`, `unencrypted`, or `unverified` (an unverified message still has its text). Focus on the content. Don't comment on encryption unprompted or call plain messages odd. Answer plainly from `status` when asked about security, and mention `unverified` briefly before acting on that message. +### Find or message a contact + +To find the thread with someone the owner already chats with, or to message them: + +1. `get_chat_conversations` with `participant_query` set to their @username (or a word of their name if you don't know it), `type` `direct`, `max_results` 1000, and `expansions` `participant_ids`. Don't list the whole inbox, and don't look the person up to get their id first. +2. Confirm it's them from the expanded participant's `username` and `name`. The match's `id` is the conversation id the other Chat tools take. +3. A page can be empty while `meta.has_more` is true: keep paging with `meta.next_token` and the same query until a match or `has_more` is false. +4. No match: retry with their @username if you searched by name, then check message requests by `sender_id`. Still nothing means they have no thread with the owner yet. +5. To message them, follow [Send or reply](#send-or-reply) with that `id`. + ### Unread For "what's unread" or "anything new", don't scan every conversation. @@ -175,7 +185,7 @@ For "what's unread" or "anything new", don't scan every conversation. ### Read or summarize -Read every inbox conversation, not only the ones that look relevant: list with `get_chat_conversations` (`max_results` 1000, all pages), then decrypt each. Skip message requests unless asked; if `meta.has_message_requests` is true, end with one line offering them. To find a named person's thread, resolve their handle to an id and match participants, then fall back to message requests by `sender_id`. Never claim you checked everything unless you read every thread (and both request inboxes, if asked) to the end; otherwise say how many you read. Optionally `mark_chat_conversation_read` afterwards. +Read every inbox conversation, not only the ones that look relevant: list with `get_chat_conversations` (`max_results` 1000, all pages), then decrypt each. Skip message requests unless asked; if `meta.has_message_requests` is true, end with one line offering them. To find a named person's thread, see [Find or message a contact](#find-or-message-a-contact). Never claim you checked everything unless you read every thread (and both request inboxes, if asked) to the end; otherwise say how many you read. Optionally `mark_chat_conversation_read` afterwards. ### Message requests @@ -187,7 +197,9 @@ Pending 1:1 requests aren't in `get_chat_conversations`. List, open, and accept ### Send or reply -The owner must approve the text unless they already told you to send or reply ("reply that I'll be there"). "Check my inbox" is not approval. +The owner must approve the text unless they already told you to send or reply ("reply that I'll be there"). "Check my inbox" is not approval. To message someone they already chat with, find the thread first ([Find or message a contact](#find-or-message-a-contact)). + +X DMs go out only through `send_chat_message`; an email or Slack draft tool can't send them, so never route a DM there or tell the owner it needs one. If the host holds a `send_chat_message` call for the owner to approve (Grok Bot shows an X Chat draft card), call it again with exactly the same arguments, the same encrypted payload included (don't re-encrypt), once they press Send or tell you to send it. - **Thread with a conversation key:** fetch events and `conversation_key_events`, `session-encrypt` with the approved text, then `send_chat_message` with `id`, `message_id`, `encoded_message_create_event`, and `encoded_message_event_signature`. Confirm without dumping ciphertext. - **No conversation key yet** (first message, or only plain messages): get both users' public keys (`public_key` and `public_key_version`), `session-encrypt` with `prepare` set (same shape as `prepare-keys` input), call `add_conversation_keys` with its output **before** `send_chat_message`, and strip any `_local_*` fields. `add_conversation_keys` returning `UNAUTHORIZED_REQUESTING_USER` means you can't set keys there or the peer isn't on X Chat: stop and report.