@markmnl/fmsg-mcp 0.2.5 → 0.2.6

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.
@@ -1,14 +1,18 @@
1
1
  import * as z from "zod/v4";
2
2
  import { resolveAddresses, sameAddress } from "../address.js";
3
3
  import { toolError } from "../errors.js";
4
- import { isoTime, participantsOf } from "../render.js";
5
- import { READ_ONLY, SENDS, idSchema, ok, resolverFor, withCaller } from "./common.js";
4
+ import { attachmentType, isoTime, participantsOf } from "../render.js";
5
+ import { SENDS, attachmentItem, idSchema, ok, resolverFor, withCaller } from "./common.js";
6
6
  const IMMUTABLE = "fmsg messages are immutable: once sent they cannot be edited or recalled. Send within the user's requested task or authorized automation.";
7
7
  const attachmentInput = z.object({
8
8
  filename: z.string().regex(/^[A-Za-z0-9._-]+$/u, "letters, digits, dot, underscore, hyphen only"),
9
9
  data_base64: z.string().min(1),
10
10
  content_type: z.string().optional(),
11
11
  });
12
+ /** Sent attachments with the type each was given, or the one its filename implies. */
13
+ function sentAttachments(sent, outbound) {
14
+ return sent.map((a, i) => ({ ...a, type: attachmentType(a.filename, outbound[i]?.contentType) }));
15
+ }
12
16
  function decodeAttachments(items) {
13
17
  return (items ?? []).map((a) => {
14
18
  const data = Buffer.from(a.data_base64, "base64");
@@ -24,7 +28,7 @@ const sentOutput = z.object({
24
28
  to: z.array(z.string()),
25
29
  topic: z.string(),
26
30
  parent_id: z.string().nullable(),
27
- attachments: z.array(z.object({ filename: z.string(), size: z.number() })),
31
+ attachments: z.array(attachmentItem),
28
32
  redactions: z.number().describe("secrets replaced with placeholders before sending"),
29
33
  warnings: z.array(z.string()),
30
34
  });
@@ -48,6 +52,7 @@ export const registerSendTools = (server, deps) => {
48
52
  annotations: SENDS,
49
53
  }, async ({ to, topic, body, type, important, no_reply, attachments }, ctx) => withCaller(deps, ctx, async (caller, signal) => {
50
54
  const recipients = resolveAddresses(to, resolverFor(deps, caller));
55
+ const outbound = decodeAttachments(attachments);
51
56
  const sent = await caller.client.send({
52
57
  to: recipients,
53
58
  topic,
@@ -55,7 +60,7 @@ export const registerSendTools = (server, deps) => {
55
60
  type,
56
61
  important,
57
62
  noReply: no_reply,
58
- attachments: decodeAttachments(attachments),
63
+ attachments: outbound,
59
64
  signal,
60
65
  });
61
66
  const structured = {
@@ -65,7 +70,7 @@ export const registerSendTools = (server, deps) => {
65
70
  to: recipients,
66
71
  topic: sent.topic,
67
72
  parent_id: null,
68
- attachments: sent.attachments,
73
+ attachments: sentAttachments(sent.attachments, outbound),
69
74
  redactions: sent.redactions,
70
75
  warnings: [],
71
76
  };
@@ -105,6 +110,7 @@ export const registerSendTools = (server, deps) => {
105
110
  : participantsOf(parent).filter((a) => !sameAddress(a, caller.address));
106
111
  if (to.length === 0)
107
112
  return toolError(`message ${id} has no other participants to reply to; pass recipients`);
113
+ const outbound = decodeAttachments(attachments);
108
114
  const sent = await caller.client.send({
109
115
  to,
110
116
  pid: parent.id,
@@ -112,7 +118,7 @@ export const registerSendTools = (server, deps) => {
112
118
  type,
113
119
  important,
114
120
  noReply: no_reply,
115
- attachments: decodeAttachments(attachments),
121
+ attachments: outbound,
116
122
  signal,
117
123
  });
118
124
  const structured = {
@@ -122,7 +128,7 @@ export const registerSendTools = (server, deps) => {
122
128
  to,
123
129
  topic: "",
124
130
  parent_id: parent.id,
125
- attachments: sent.attachments,
131
+ attachments: sentAttachments(sent.attachments, outbound),
126
132
  redactions: sent.redactions,
127
133
  warnings,
128
134
  };
@@ -135,16 +141,26 @@ export const registerSendTools = (server, deps) => {
135
141
  description: "Add recipients to a message that was already sent (one you sent or received as a primary recipient). " +
136
142
  "They receive the message and become participants of its thread. This cannot be undone. Fails on terminal messages.",
137
143
  inputSchema: z.object({
138
- id: idSchema,
139
- add_to: z.array(z.string()).min(1).describe("addresses or short names to add"),
144
+ id: idSchema.describe("the sent message to add recipients to"),
145
+ recipients: z.array(z.string()).min(1).optional().describe("addresses or short names to add"),
146
+ add_to: z.array(z.string()).min(1).optional().describe("deprecated alias of recipients; pass one or the other"),
147
+ }),
148
+ outputSchema: z.object({
149
+ id: z.string(),
150
+ added: z.number(),
151
+ recipients: z.array(z.string()),
152
+ add_to: z.array(z.string()).describe("deprecated copy of recipients"),
140
153
  }),
141
- outputSchema: z.object({ id: z.string(), added: z.number(), add_to: z.array(z.string()) }),
142
154
  annotations: { ...SENDS, idempotentHint: true },
143
- }, async ({ id, add_to }, ctx) => withCaller(deps, ctx, async (caller, signal) => {
144
- const addresses = resolveAddresses(add_to, resolverFor(deps, caller));
145
- const result = await caller.client.addRecipients(id, addresses, signal);
146
- return ok(`Added ${result.added} recipient(s) to message ${id}: ${addresses.join(", ")}`, { ...result, add_to: addresses });
147
- }));
155
+ }, async ({ id, recipients, add_to }, ctx) => {
156
+ if ((recipients === undefined) === (add_to === undefined))
157
+ return toolError("pass recipients: the addresses to add (add_to is its deprecated alias; not both)");
158
+ return withCaller(deps, ctx, async (caller, signal) => {
159
+ const addresses = resolveAddresses((recipients ?? add_to), resolverFor(deps, caller));
160
+ const result = await caller.client.addRecipients(id, addresses, signal);
161
+ return ok(`Added ${result.added} recipient(s) to message ${id}: ${addresses.join(", ")}`, { ...result, recipients: addresses, add_to: addresses });
162
+ });
163
+ });
148
164
  server.registerTool("react", {
149
165
  title: "React to fmsg message",
150
166
  description: "Set or clear your emoji reaction on a message (one reaction per person; a new emoji replaces the previous). " +
@@ -161,6 +177,4 @@ export const registerSendTools = (server, deps) => {
161
177
  const structured = { id, reaction_id: result.id, time: isoTime(result.time), cleared: value === null };
162
178
  return ok(value ? `Reacted ${value} to message ${id}` : `Cleared your reaction on message ${id}`, structured);
163
179
  }));
164
- // Kept read-only tools' annotation import in use for symmetry with other files.
165
- void READ_ONLY;
166
180
  };
@@ -3,7 +3,11 @@ import { resolveAddress } from "../address.js";
3
3
  import { assembleThread, renderThread } from "../thread.js";
4
4
  import { DATA_NOT_INSTRUCTIONS, fence, headerValue, messageData, messageLine } from "../render.js";
5
5
  import { waitForMessage } from "../wait.js";
6
- import { READ_ONLY, idSchema, messageItem, ok, resolverFor, toItem, withCaller } from "./common.js";
6
+ import { READ_ONLY, UNTRUSTED, idSchema, messageItem, ok, resolverFor, toItem, untrustedNotice, withCaller } from "./common.js";
7
+ /** Skipped reactions as one line each, for the data block. */
8
+ function reactionLines(skipped) {
9
+ return skipped.filter((s) => s.emoji !== undefined).map((s) => `- ${s.id} from ${headerValue(s.from)}: ${s.emoji ? `reacted ${headerValue(s.emoji)}` : "cleared a reaction"}${s.reaction_to ? ` on message ${s.reaction_to}` : ""}`);
10
+ }
7
11
  export const registerWaitTools = (server, deps) => {
8
12
  const maxWait = deps.config.waitMaxSeconds;
9
13
  server.registerTool("wait_for_message", {
@@ -11,10 +15,15 @@ export const registerWaitTools = (server, deps) => {
11
15
  description: "Block until the next inbound message arrives (pushed over the fmsg host's WebSocket) and return it with its " +
12
16
  "thread context so you can answer with reply. Use this when the user asks you to chat, converse, keep replying, " +
13
17
  "auto-reply, or respond to the next message. Loop: wait → reply → wait again passing the after_id from the " +
14
- "previous result. On status \"timeout\" simply call again with the same arguments. Messages arriving on the " +
15
- "same thread within settle_seconds are batched into ONE result; reply once, to the newest (reply_target_id). " +
16
- "Your own messages, reactions and no-reply messages never qualify. Each call blocks at most " +
17
- `timeout_seconds (max ${maxWait}); stop looping when the user interrupts or the limits they set are reached.`,
18
+ "previous result. On status \"timeout\" simply call again with the same arguments and the returned after_id, " +
19
+ "which is never older than the newest inbox message when the call started, so nothing is replayed (\"0\" means " +
20
+ "the inbox was empty and is safe to pass). Messages arriving on the same thread within settle_seconds are " +
21
+ "batched into ONE result; reply once, to the newest (reply_target_id); the result's next field says what to do. " +
22
+ "Your own messages, reactions and no-reply messages never qualify; they are listed in skipped. Each call blocks " +
23
+ `at most timeout_seconds (max ${maxWait}); stop looping when the user interrupts or the limits they set are reached. ` +
24
+ "In chat hosts each wait is a model turn: tell the user you are listening and for how long rather than " +
25
+ "looping silently for long periods. Status \"interrupted\" means the server is restarting: call again with the " +
26
+ "returned after_id; no messages are lost.",
18
27
  inputSchema: z.object({
19
28
  after_id: idSchema.optional().describe("only messages with a greater id qualify; pass the after_id from the previous result. Omit on the first call to wait for messages arriving from now on"),
20
29
  thread_of: idSchema.optional().describe("only accept messages in this message's thread"),
@@ -24,16 +33,25 @@ export const registerWaitTools = (server, deps) => {
24
33
  include_thread: z.boolean().default(true).describe("include the assembled thread context of the newest message"),
25
34
  }),
26
35
  outputSchema: z.object({
27
- status: z.enum(["message", "timeout"]),
36
+ status: z.enum(["message", "timeout", "interrupted"]).describe("interrupted: the server is restarting; call again with after_id"),
28
37
  after_id: z.string().describe("pass this as after_id on the next call"),
29
38
  thread_root_id: z.string().nullable(),
39
+ thread_topic: z.string().nullable().describe("the thread root's topic (replies carry none); null when unknown"),
30
40
  reply_target_id: z.string().nullable().describe("newest message of the batch; reply to this one"),
31
41
  messages: z.array(messageItem.extend({ body: z.string().nullable() })),
32
42
  pending_other_threads: z.array(z.object({ id: z.string(), from: z.string(), root_id: z.string().nullable() })),
33
- skipped: z.array(z.object({ id: z.string(), reason: z.enum(["own", "reaction", "no_reply", "from_mismatch", "other_thread"]) })).describe("messages deliberately passed over; after_id has advanced past them"),
43
+ skipped: z.array(z.object({
44
+ id: z.string(),
45
+ reason: z.enum(["own", "reaction", "no_reply", "from_mismatch", "other_thread"]),
46
+ from: z.string(),
47
+ emoji: z.string().optional().describe("for reactions: the emoji (\"\" clears a reaction)"),
48
+ reaction_to: z.string().nullable().optional().describe("for reactions: the message reacted to"),
49
+ })).describe("messages deliberately passed over; after_id has advanced past them"),
34
50
  unclassified: z.array(z.object({ id: z.string(), from: z.string(), error: z.string() })).describe("messages whose thread could not be determined; after_id is held before them, call again to retry"),
35
51
  transport: z.enum(["websocket", "poll"]),
36
52
  note: z.string().nullable(),
53
+ next: z.string().describe("what to do with this result"),
54
+ ...untrustedNotice,
37
55
  }),
38
56
  annotations: { ...READ_ONLY, idempotentHint: false },
39
57
  }, async ({ after_id, thread_of, from, timeout_seconds, settle_seconds, include_thread }, ctx) => withCaller(deps, ctx, async (caller, signal) => {
@@ -42,6 +60,7 @@ export const registerWaitTools = (server, deps) => {
42
60
  ...(after_id !== undefined ? { afterId: after_id } : {}),
43
61
  ...(thread_of !== undefined ? { threadOf: thread_of } : {}),
44
62
  ...(from !== undefined ? { from: resolveAddress(from, resolverFor(deps, caller)).address } : {}),
63
+ ...(deps.shutdown ? { interrupt: deps.shutdown } : {}),
45
64
  timeoutMs: timeout_seconds * 1000,
46
65
  settleMs: settle_seconds * 1000,
47
66
  onTick: (elapsed) => {
@@ -57,10 +76,19 @@ export const registerWaitTools = (server, deps) => {
57
76
  }, signal);
58
77
  const messages = await Promise.all(result.messages.map(async (m) => ({ ...toItem(m, caller.address), body: await caller.client.getText(m, signal) })));
59
78
  const newest = result.messages[result.messages.length - 1];
79
+ const keepWaiting = `call wait_for_message with after_id "${result.after_id}"${thread_of !== undefined ? ` and thread_of "${thread_of}"` : ""} to keep listening`;
80
+ const next = result.status === "interrupted"
81
+ ? `The server is restarting; ${keepWaiting}. No messages are lost.`
82
+ : !newest
83
+ ? `No new message yet; ${keepWaiting}, unless the user's time limit is reached.`
84
+ : newest.terminal
85
+ ? `Message ${newest.id} is terminal and cannot be replied to; ${keepWaiting}.`
86
+ : `Reply to message ${newest.id} with the reply tool, then ${keepWaiting}.`;
60
87
  const structured = {
61
88
  status: result.status,
62
89
  after_id: result.after_id,
63
90
  thread_root_id: result.thread_root_id,
91
+ thread_topic: result.thread_topic,
64
92
  reply_target_id: newest?.id ?? null,
65
93
  messages,
66
94
  pending_other_threads: result.pending_other_threads,
@@ -68,14 +96,22 @@ export const registerWaitTools = (server, deps) => {
68
96
  unclassified: result.unclassified,
69
97
  transport: result.transport,
70
98
  note: result.note,
99
+ next,
100
+ ...UNTRUSTED,
71
101
  };
102
+ const reactions = reactionLines(result.skipped);
103
+ if (result.status === "interrupted")
104
+ return ok(`${next} (after_id ${result.after_id})`, structured);
72
105
  if (result.status === "timeout") {
73
- return ok(`No qualifying message arrived within ${timeout_seconds}s (after_id ${result.after_id}, ${result.transport})${result.note ? `; ${result.note}` : ""}. Call again to keep waiting.`, structured);
106
+ const waited = `No qualifying message arrived within ${timeout_seconds}s (after_id ${result.after_id}, ${result.transport})${result.note ? `; ${result.note}` : ""}. Call again to keep waiting.`;
107
+ return ok(reactions.length ? `${waited}\n\nSkipped reactions:\n${messageData(reactions.join("\n"))}` : waited, structured);
74
108
  }
75
109
  const lines = [
76
110
  `${result.messages.length} new message${result.messages.length === 1 ? "" : "s"} (after_id ${result.after_id}, ${result.transport}):`,
77
111
  messageData(result.messages.map((m) => messageLine(m, caller.address)).join("\n")),
78
112
  ];
113
+ if (reactions.length)
114
+ lines.push(`Skipped reactions:\n${messageData(reactions.join("\n"))}`);
79
115
  if (result.pending_other_threads.length) {
80
116
  lines.push(`Also waiting on other threads: ${result.pending_other_threads.map((p) => p.id).join(", ")}`);
81
117
  }
@@ -90,7 +126,7 @@ export const registerWaitTools = (server, deps) => {
90
126
  maxBodyBytesPerMessage: 16_384,
91
127
  maxTotalBytes: 262_144,
92
128
  }, signal);
93
- lines.push("", renderThread(thread));
129
+ lines.push("", renderThread(thread), "", next);
94
130
  }
95
131
  else if (newest) {
96
132
  lines.push("", DATA_NOT_INSTRUCTIONS);
@@ -98,7 +134,7 @@ export const registerWaitTools = (server, deps) => {
98
134
  if (m.body !== null)
99
135
  lines.push("", `--- message ${m.id} from ${headerValue(m.from)} ---`, fence(m.body));
100
136
  lines.push("", "End of message data.");
101
- lines.push("", `Reply to message ${newest.id} with the reply tool.`);
137
+ lines.push("", next);
102
138
  }
103
139
  return ok(lines.join("\n"), structured);
104
140
  }));
package/dist/wait.d.ts CHANGED
@@ -15,7 +15,10 @@ export type WaitOptions = {
15
15
  /** How long to wait for the WebSocket to open before falling back to polling. */
16
16
  wsOpenTimeoutMs?: number;
17
17
  onTick?: (elapsedMs: number) => void;
18
+ /** Server shutdown: end the wait early with status "interrupted" and a resumable cursor. */
19
+ interrupt?: AbortSignal;
18
20
  };
21
+ export declare const INTERRUPTED_NOTE = "the server is restarting; call wait_for_message again with the after_id returned here; no messages are lost";
19
22
  export type Pending = {
20
23
  id: string;
21
24
  from: string;
@@ -25,6 +28,10 @@ export type SkipReason = "own" | "reaction" | "no_reply" | "from_mismatch" | "ot
25
28
  export type Skipped = {
26
29
  id: string;
27
30
  reason: SkipReason;
31
+ from: string;
32
+ /** For reactions: the emoji ("" clears) and the message reacted to. */
33
+ emoji?: string;
34
+ reaction_to?: string | null;
28
35
  };
29
36
  export type Unclassified = {
30
37
  id: string;
@@ -32,9 +39,12 @@ export type Unclassified = {
32
39
  error: string;
33
40
  };
34
41
  export type WaitResult = {
35
- status: "message" | "timeout";
42
+ /** "interrupted": the server is shutting down; resume from after_id. */
43
+ status: "message" | "timeout" | "interrupted";
36
44
  after_id: string;
37
45
  thread_root_id: string | null;
46
+ /** The root's topic when the root was readable; replies carry no topic of their own. */
47
+ thread_topic: string | null;
38
48
  messages: FmsgMessage[];
39
49
  pending_other_threads: Pending[];
40
50
  /** Messages deliberately passed over (the cursor advances past these). */
package/dist/wait.js CHANGED
@@ -4,6 +4,7 @@ import { compareMessageIds, maxMessageId, minMessageId } from "./client/message-
4
4
  import { safeErrorMessage } from "./client/redact.js";
5
5
  import { openFmsgWebSocket, parseWsEvent, webSocketTokenExpiresAt } from "./client/ws.js";
6
6
  import { OAuthRequestError } from "./oauth/errors.js";
7
+ export const INTERRUPTED_NOTE = "the server is restarting; call wait_for_message again with the after_id returned here; no messages are lost";
7
8
  /**
8
9
  * Block until the next qualifying inbound message (plus any that arrive on the
9
10
  * same thread within the settle window), or until the deadline.
@@ -42,9 +43,15 @@ export async function waitForMessage(client, self, options, signal, deps = {}) {
42
43
  }
43
44
  };
44
45
  const rootCache = new Map();
46
+ /** Root topics seen while resolving roots, so results can name the thread without another request. */
47
+ const rootTopics = new Map();
45
48
  const lookupRoot = async (id) => {
46
49
  try {
47
- return (await client.getThreadMessages(id, retrySignal)).root_id;
50
+ const thread = await client.getThreadMessages(id, retrySignal);
51
+ const root = thread.messages[0];
52
+ if (root?.visible && root.id === thread.root_id)
53
+ rootTopics.set(root.id, root.topic ?? "");
54
+ return thread.root_id;
48
55
  }
49
56
  catch (error) {
50
57
  if (authorizationFailure(error))
@@ -53,8 +60,10 @@ export async function waitForMessage(client, self, options, signal, deps = {}) {
53
60
  let cur = id;
54
61
  for (let i = 0; i < 100; i++) {
55
62
  const m = await client.getMessage(cur, retrySignal);
56
- if (!m.pid)
63
+ if (!m.pid) {
64
+ rootTopics.set(m.id, m.topic ?? "");
57
65
  return m.id;
66
+ }
58
67
  cur = m.pid;
59
68
  }
60
69
  throw error;
@@ -122,9 +131,11 @@ export async function waitForMessage(client, self, options, signal, deps = {}) {
122
131
  clearInterval(pollTimer);
123
132
  clearInterval(tickTimer);
124
133
  signal?.removeEventListener("abort", onAbort);
134
+ options.interrupt?.removeEventListener("abort", onInterrupt);
125
135
  if (socket)
126
136
  disposeSocket(socket);
127
137
  };
138
+ let interrupted = false;
128
139
  const finish = () => {
129
140
  if (finished)
130
141
  return;
@@ -144,9 +155,10 @@ export async function waitForMessage(client, self, options, signal, deps = {}) {
144
155
  note = note ? `${note}; ${held}` : held;
145
156
  }
146
157
  resolve({
147
- status: batch.length ? "message" : "timeout",
158
+ status: batch.length ? "message" : interrupted ? "interrupted" : "timeout",
148
159
  after_id: afterId,
149
160
  thread_root_id: batchRoot,
161
+ thread_topic: batchRoot === null ? null : (rootTopics.get(batchRoot) ?? null),
150
162
  messages: [...batch].sort((a, b) => compareMessageIds(a.id, b.id)),
151
163
  pending_other_threads: pending,
152
164
  skipped: [...skipped].sort((a, b) => compareMessageIds(a.id, b.id)),
@@ -165,6 +177,12 @@ export async function waitForMessage(client, self, options, signal, deps = {}) {
165
177
  note = "cancelled";
166
178
  finish();
167
179
  };
180
+ // Messages already collected are still returned; their cursor covers them.
181
+ const onInterrupt = () => {
182
+ interrupted = true;
183
+ note = batch.length ? "the server is restarting, which cut the settle window short" : INTERRUPTED_NOTE;
184
+ finish();
185
+ };
168
186
  const deadlineTimer = setTimeout(() => {
169
187
  if (batch.length && settleTimer)
170
188
  note = "the time limit cut the settle window short";
@@ -172,8 +190,11 @@ export async function waitForMessage(client, self, options, signal, deps = {}) {
172
190
  }, Math.max(0, deadline - Date.now()));
173
191
  const tickTimer = setInterval(() => options.onTick?.(Date.now() - start), 20_000);
174
192
  signal?.addEventListener("abort", onAbort, { once: true });
193
+ options.interrupt?.addEventListener("abort", onInterrupt, { once: true });
175
194
  if (signal?.aborted)
176
195
  return onAbort();
196
+ if (options.interrupt?.aborted)
197
+ return onInterrupt();
177
198
  const consider = async (m) => {
178
199
  if (finished || seen.has(m.id))
179
200
  return;
@@ -184,7 +205,8 @@ export async function waitForMessage(client, self, options, signal, deps = {}) {
184
205
  if (compareMessageIds(m.id, floor) <= 0)
185
206
  return;
186
207
  const skip = (reason) => {
187
- skipped.push({ id: m.id, reason });
208
+ const reaction = typeof m.reaction === "string" ? { emoji: m.reaction, reaction_to: m.pid ?? null } : {};
209
+ skipped.push({ id: m.id, reason, from: m.from, ...reaction });
188
210
  if (compareMessageIds(m.id, skippedMax) > 0)
189
211
  skippedMax = m.id;
190
212
  };
@@ -69,7 +69,9 @@ the public same-origin value, so explicitly listing that origin is optional.
69
69
  `https://mcp.example.com/mcp/attachments/123/report.pdf`. The host fetches it with an authenticated
70
70
  GET, using the same `Authorization: Bearer ...` header as its MCP connection. OAuth requires
71
71
  `fmsg:read`; the server exchanges the incoming token for a Web API token as usual. The Web API
72
- checks visibility on every download, even when a link was obtained earlier.
72
+ checks visibility on every download, even when a link was obtained earlier. Hosts that cannot send
73
+ the connection's Authorization header with an arbitrary GET (many chat hosts cannot) should use
74
+ `download_attachment` instead; the tool's description and result say so.
73
75
 
74
76
  Set `FMSG_MCP_PUBLIC_URL` to the exact external MCP endpoint when using API keys. OAuth defaults
75
77
  to `FMSG_MCP_OAUTH_RESOURCE_URL`; if both are set, they must match. These URLs require HTTPS outside
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": "0.2.5",
4
+ "version": "0.2.6",
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": "0.2.5",
11
+ "version": "0.2.6",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "registryBaseUrl": "https://registry.npmjs.org",
16
16
  "identifier": "@markmnl/fmsg-mcp",
17
- "version": "0.2.5",
17
+ "version": "0.2.6",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  },