@markmnl/fmsg-mcp 1.5.1 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Tool annotations follow OpenAI's plugin guidelines ("false only for additive writes without
6
+ destructive or irreversible effects"), after a directory review flagged them: `react`, `indicate`
7
+ and `mark_read` are `destructiveHint: true` (a reaction or status reaches the other participants at
8
+ once and supersedes the previous one; marking read can't be undone here), so a host may ask before
9
+ each. `get_attachment_download_url` is no longer `readOnlyHint` (its presigned link is created on the
10
+ server); it still needs only `fmsg:read`.
5
11
  - `send_message`, `reply` and `create_draft` take `files`: files the user shared in the chat, given by
6
12
  reference (ChatGPT's `openai/fileParams`, declared in each tool's `_meta`). The server downloads each
7
13
  one, from public HTTPS addresses only, hashes it, attaches the exact bytes and reports its sha256 in
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.5.1",
3
- "commit": "78f92e1272532d7b08b98659f792b357aeea703e",
4
- "describe": "v1.5.1"
2
+ "version": "1.5.2",
3
+ "commit": "03f0a9e5bef6d1d05765030d81c90c60487a6351",
4
+ "describe": "v1.5.2"
5
5
  }
@@ -6,7 +6,7 @@ import { toolError } from "../errors.js";
6
6
  import { localFileName, shellQuote } from "../links.js";
7
7
  import { attachmentType, isoTime, messageData } from "../render.js";
8
8
  import { FILENAME } from "./attachments.js";
9
- import { SENDS, attachmentMissingFrom, idSchema, openEnum, outputObject, READ_ONLY, UNTRUSTED, untrustedNotice, withCaller } from "./common.js";
9
+ import { SENDS, attachmentMissingFrom, idSchema, openEnum, outputObject, UNTRUSTED, untrustedNotice, withCaller } from "./common.js";
10
10
  const FALLBACK = "If you cannot fetch URLs with this connection's authorization, use download_attachment instead.";
11
11
  const PRESIGNED_FALLBACK = "If you cannot fetch URLs with this connection's authorization, pass presigned: true for a single-use link that needs none.";
12
12
  const presignedOutput = outputObject({
@@ -46,7 +46,10 @@ export const registerDownloadTool = (server, deps) => {
46
46
  presigned: presignedOutput.partial().optional().describe("present when presigned was true"),
47
47
  ...untrustedNotice,
48
48
  }),
49
- annotations: READ_ONLY,
49
+ // Not read-only: with presigned it creates a short-lived single-use
50
+ // download link. Not destructive: it changes no messages. Closed-world:
51
+ // only the caller's own attachments. Reading needs only fmsg:read.
52
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
50
53
  }, async ({ id, filename, presigned }, ctx) => withCaller(deps, ctx, async (caller, signal) => {
51
54
  const mid = normalizeMessageId(id);
52
55
  attachmentFilename(filename);
@@ -166,7 +166,10 @@ export const registerReadTools = (server, deps) => {
166
166
  marked: z.array(outputObject({ id: z.string(), time_read: z.string().nullable() })),
167
167
  failed: z.array(outputObject({ id: z.string(), error: z.string() })),
168
168
  }),
169
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
169
+ // Destructive (OpenAI's plugin guidelines): marking read changes the
170
+ // messages' read state, which this server cannot undo. Closed-world:
171
+ // only the caller's own mailbox.
172
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
170
173
  }, async ({ ids }, ctx) => withCaller(deps, ctx, async (caller, signal) => {
171
174
  deps.indicators.handled(caller, ids);
172
175
  const marked = [];
@@ -254,10 +254,11 @@ export const registerSendTools = (server, deps) => {
254
254
  emoji: z.string().max(32).nullable().describe("a single emoji; null or empty clears your reaction"),
255
255
  }),
256
256
  outputSchema: outputObject({ id: z.string(), reaction_id: z.string().nullable(), time: z.string().nullable(), cleared: z.boolean() }),
257
- // Not destructive: a reaction is one emoji that its sender changes or
258
- // clears with another reaction, and it alters no one's messages, so hosts
259
- // needn't ask before each one (maintainer decision).
260
- annotations: { ...SENDS, destructiveHint: false, idempotentHint: true },
257
+ // Destructive (OpenAI's plugin guidelines): a reaction is a send the
258
+ // other participants receive at once and cannot be recalled, and it
259
+ // supersedes the sender's previous reaction. Earlier reaction messages
260
+ // stay; setting the same emoji again is a no-op.
261
+ annotations: { ...SENDS, idempotentHint: true },
261
262
  }, async ({ id, emoji }, ctx) => withCaller(deps, ctx, async (caller, signal) => {
262
263
  const value = emoji && emoji.trim() ? emoji.trim() : null;
263
264
  const result = await caller.client.react(id, value, signal);
@@ -281,7 +282,9 @@ export const registerSendTools = (server, deps) => {
281
282
  ttl: z.number().describe("seconds; 0 when cleared"),
282
283
  cleared: z.boolean(),
283
284
  }),
284
- annotations: { ...SENDS, destructiveHint: false, idempotentHint: true },
285
+ // Destructive (OpenAI's plugin guidelines): the status is shown to the
286
+ // other participants at once and replaces the caller's previous one.
287
+ annotations: { ...SENDS, idempotentHint: true },
285
288
  }, async ({ id, emoji, ttl }, ctx) => withCaller(deps, ctx, async (caller, signal) => {
286
289
  const value = emoji.trim();
287
290
  // The thread root lets a reply anywhere in the thread clear it; a failed lookup only means it is not known.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@markmnl/fmsg-mcp",
3
3
  "mcpName": "io.github.markmnl/fmsg-mcp",
4
- "version": "1.5.1",
4
+ "version": "1.5.2",
5
5
  "description": "MCP server for fmsg: send and receive federated messages from any AI agent via a deployed fmsg Web API",
6
6
  "type": "module",
7
7
  "license": "MIT",
package/server.json CHANGED
@@ -8,13 +8,13 @@
8
8
  "url": "https://github.com/markmnl/fmsg-mcp",
9
9
  "source": "github"
10
10
  },
11
- "version": "1.5.1",
11
+ "version": "1.5.2",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "registryBaseUrl": "https://registry.npmjs.org",
16
16
  "identifier": "@markmnl/fmsg-mcp",
17
- "version": "1.5.1",
17
+ "version": "1.5.2",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  },