@imenam/mcp-messenger 1.0.10 → 1.0.13
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/README.md +40 -10
- package/dist/src/core/store.d.ts +23 -1
- package/dist/src/core/store.js +243 -152
- package/dist/src/core/types.d.ts +22 -0
- package/dist/src/mcp/start.js +5 -1
- package/dist/src/mcp/tools.js +29 -16
- package/dist/src/server/app.js +28 -0
- package/dist/src/server/ui/app.js +883 -372
- package/dist/src/server/ui/index.html +92 -27
- package/dist/src/server/ui/styles.css +1228 -368
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
package/dist/src/mcp/tools.js
CHANGED
|
@@ -55,14 +55,14 @@ export const TOOLS = [
|
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
name: "list_threads",
|
|
58
|
-
description: "List the conversations visible from this mailbox, most recently active first, with their name, participants and unread count. This is the readable map of the ongoing work: prefer it over list_messages when you want to know what is being discussed rather than what arrived last. Set
|
|
58
|
+
description: "List the conversations visible from this mailbox, most recently active first, with their name, participants and unread count. This is the readable map of the ongoing work: prefer it over list_messages when you want to know what is being discussed rather than what arrived last. Set with_mailbox to see everything exchanged with one correspondent. A thread with no name falls back to the subject of its opening message — name it with rename_thread.",
|
|
59
59
|
inputSchema: {
|
|
60
60
|
type: "object",
|
|
61
61
|
properties: {
|
|
62
62
|
unread_only: { type: "boolean", description: "Keep only threads with unread messages." },
|
|
63
|
-
|
|
63
|
+
with_mailbox: {
|
|
64
64
|
type: "string",
|
|
65
|
-
description: "
|
|
65
|
+
description: "A correspondent's mailbox address, as listed by list_agents: keep only the threads shared with that mailbox. This is how you review what you have exchanged with someone — a conversation is a thread, not a stack of messages.",
|
|
66
66
|
},
|
|
67
67
|
search: { type: "string", description: "Free text matched against thread name, description and subject." },
|
|
68
68
|
limit: { type: "number", description: "1-100, defaults to 25." },
|
|
@@ -72,7 +72,7 @@ export const TOOLS = [
|
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
name: "list_agents",
|
|
75
|
-
description: "List the
|
|
75
|
+
description: "List the mailboxes known to the server, grouped by agent name — one group per project, one line per session mailbox inside it, most recently active first. An agent name is shared by every session of the same project, so a group with several lines means several live sessions, each with its own mailbox address. Your own mailbox is never listed — you cannot write to yourself. Use it to find the mailbox address you want to write to; pass agent_name to expand a group whose sessions were not all listed.",
|
|
76
76
|
inputSchema: {
|
|
77
77
|
type: "object",
|
|
78
78
|
properties: {
|
|
@@ -86,11 +86,11 @@ export const TOOLS = [
|
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
name: "send_message",
|
|
89
|
-
description: "Send a message to one
|
|
89
|
+
description: "Send a message to one mailbox, identified by its address. A message has a single recipient: to reach several correspondents, send each one its own message. Set reply_to to answer an existing message and keep the thread together. Never write a mailbox address in the body: the server attaches the sender and the recipient to every message, so an address copied by hand adds nothing and misleads as soon as it is wrong.",
|
|
90
90
|
inputSchema: {
|
|
91
91
|
type: "object",
|
|
92
92
|
properties: {
|
|
93
|
-
to: { type: "string", description: "Recipient
|
|
93
|
+
to: { type: "string", description: "Recipient mailbox address, as listed by list_agents. Exactly one recipient." },
|
|
94
94
|
subject: { type: "string", description: "Short subject line." },
|
|
95
95
|
body: {
|
|
96
96
|
type: "string",
|
|
@@ -110,13 +110,13 @@ export const TOOLS = [
|
|
|
110
110
|
},
|
|
111
111
|
{
|
|
112
112
|
name: "list_messages",
|
|
113
|
-
description: "List messages in this
|
|
113
|
+
description: "List messages in this mailbox, newest first, with a short preview. Filter by folder, unread state, sender, thread or free text.",
|
|
114
114
|
inputSchema: {
|
|
115
115
|
type: "object",
|
|
116
116
|
properties: {
|
|
117
117
|
folder: { type: "string", description: FOLDER_HINT + " Defaults to inbox." },
|
|
118
118
|
unread_only: { type: "boolean", description: "Keep only unread messages." },
|
|
119
|
-
from: { type: "string", description: "Only messages sent by this
|
|
119
|
+
from: { type: "string", description: "Only messages sent by this mailbox address." },
|
|
120
120
|
thread_id: { type: "string", description: "Only messages of this thread." },
|
|
121
121
|
search: { type: "string", description: "Free text matched against subject and body." },
|
|
122
122
|
limit: { type: "number", description: "1-100, defaults to 25." },
|
|
@@ -140,7 +140,7 @@ export const TOOLS = [
|
|
|
140
140
|
},
|
|
141
141
|
{
|
|
142
142
|
name: "get_thread",
|
|
143
|
-
description: "Read every message of a thread visible to this
|
|
143
|
+
description: "Read every message of a thread visible to this mailbox, oldest first — the full conversation.",
|
|
144
144
|
inputSchema: {
|
|
145
145
|
type: "object",
|
|
146
146
|
properties: { thread_id: { type: "string", description: "Thread id shown on any message." } },
|
|
@@ -177,7 +177,7 @@ export const TOOLS = [
|
|
|
177
177
|
},
|
|
178
178
|
{
|
|
179
179
|
name: "delete_messages",
|
|
180
|
-
description: "Send messages to the trash folder, or erase them from this mailbox for good when permanent is true.
|
|
180
|
+
description: "Send messages to the trash folder, or erase them from this mailbox for good when permanent is true. The other party keeps its own copy in its own mailbox.",
|
|
181
181
|
inputSchema: {
|
|
182
182
|
type: "object",
|
|
183
183
|
properties: {
|
|
@@ -289,6 +289,10 @@ const schemas = {
|
|
|
289
289
|
}),
|
|
290
290
|
list_threads: z.object({
|
|
291
291
|
unread_only: z.boolean().optional(),
|
|
292
|
+
with_mailbox: z.string().trim().min(1).optional(),
|
|
293
|
+
// Ancien nom du même filtre, plus publié mais toujours accepté : une session l'a pu
|
|
294
|
+
// apprendre avant la mise à jour. Sans cette entrée, zod l'écarterait en silence et
|
|
295
|
+
// l'agent recevrait tous ses fils en croyant les avoir filtrés.
|
|
292
296
|
with_agent: z.string().trim().min(1).optional(),
|
|
293
297
|
search: z.string().trim().min(1).optional(),
|
|
294
298
|
limit: z.number().int().optional(),
|
|
@@ -386,8 +390,17 @@ export async function callTool(client, name, rawArgs) {
|
|
|
386
390
|
}
|
|
387
391
|
case "list_threads": {
|
|
388
392
|
const input = schemas.list_threads.parse(args);
|
|
389
|
-
|
|
390
|
-
|
|
393
|
+
// Une adresse désigne une boîte : les outils disent `with_mailbox`. Le protocole HTTP
|
|
394
|
+
// et le stockage gardent `with_agent`, du nom de la colonne, comme `thread_title`
|
|
395
|
+
// garde son nom face au `thread_name` des outils.
|
|
396
|
+
const correspondent = input.with_mailbox ?? input.with_agent;
|
|
397
|
+
const threads = await client.listThreads({
|
|
398
|
+
unread_only: input.unread_only,
|
|
399
|
+
search: input.search,
|
|
400
|
+
limit: input.limit,
|
|
401
|
+
with_agent: correspondent,
|
|
402
|
+
});
|
|
403
|
+
const scope = correspondent ? ` with ${correspondent}` : "";
|
|
391
404
|
if (threads.length === 0)
|
|
392
405
|
return `No thread in this mailbox${scope} matching these filters.`;
|
|
393
406
|
return [`${threads.length} thread(s)${scope}:`, "", ...threads.map(formatThread)].join("\n");
|
|
@@ -396,7 +409,7 @@ export async function callTool(client, name, rawArgs) {
|
|
|
396
409
|
const input = schemas.list_agents.parse(args);
|
|
397
410
|
const agents = await client.listAgents();
|
|
398
411
|
if (agents.length === 0)
|
|
399
|
-
return "No other
|
|
412
|
+
return "No other mailbox on this server yet.";
|
|
400
413
|
const groups = groupByAgentName(agents);
|
|
401
414
|
return input.agent_name ? formatAgentGroup(groups, input.agent_name) : formatDirectory(groups);
|
|
402
415
|
}
|
|
@@ -561,7 +574,7 @@ function groupByAgentName(agents) {
|
|
|
561
574
|
const groups = new Map();
|
|
562
575
|
for (const agent of agents) {
|
|
563
576
|
const name = agent.agent_name?.trim() ?? "";
|
|
564
|
-
const key = name ? name.toLowerCase() :
|
|
577
|
+
const key = name ? name.toLowerCase() : `\u0000${agent.id}`;
|
|
565
578
|
const group = groups.get(key);
|
|
566
579
|
if (group) {
|
|
567
580
|
group.mailboxes.push(agent);
|
|
@@ -597,8 +610,8 @@ function formatMailboxLine(agent) {
|
|
|
597
610
|
const DIRECTORY_FOOTER = [
|
|
598
611
|
"Addresses identify sessions; agent names do not. Several mailboxes above may carry the same",
|
|
599
612
|
"agent name as yours — they are other sessions of the same project, never you: your own mailbox",
|
|
600
|
-
"is never listed here. Always write to
|
|
601
|
-
"To see what you have already exchanged with one of them, call list_threads with its address in
|
|
613
|
+
"is never listed here. Always write to a mailbox address taken from this list, never to a name.",
|
|
614
|
+
"To see what you have already exchanged with one of them, call list_threads with its address in with_mailbox.",
|
|
602
615
|
];
|
|
603
616
|
function formatDirectory(groups) {
|
|
604
617
|
const mailboxes = groups.reduce((total, group) => total + group.mailboxes.length, 0);
|
package/dist/src/server/app.js
CHANGED
|
@@ -79,6 +79,16 @@ const waitQuerySchema = z.object({
|
|
|
79
79
|
watcher: z.enum(["true", "1"]).optional(),
|
|
80
80
|
});
|
|
81
81
|
const loginSchema = z.object({ token: z.string().min(1).max(1024) });
|
|
82
|
+
const adminThreadsSchema = z.object({
|
|
83
|
+
search: z.string().trim().min(1).optional(),
|
|
84
|
+
// Une ou plusieurs adresses, séparées par des virgules : un agent tient une boîte par
|
|
85
|
+
// session ouverte, et la console filtre sur toutes celles d'un projet à la fois.
|
|
86
|
+
agent: z.string().trim().min(1).max(4096).optional(),
|
|
87
|
+
unread_only: z.enum(["true", "1"]).optional(),
|
|
88
|
+
priority: z.enum(PRIORITIES).optional(),
|
|
89
|
+
limit: z.coerce.number().int().min(1).max(100).optional(),
|
|
90
|
+
offset: z.coerce.number().int().min(0).optional(),
|
|
91
|
+
});
|
|
82
92
|
const adminListSchema = z.object({
|
|
83
93
|
search: z.string().trim().min(1).optional(),
|
|
84
94
|
agent: z.string().trim().min(1).optional(),
|
|
@@ -162,6 +172,24 @@ export function createApp({ store, config }) {
|
|
|
162
172
|
admin.get("/messages/:id", (req, res) => {
|
|
163
173
|
res.json(store.getMessageAdmin(String(req.params.id)));
|
|
164
174
|
});
|
|
175
|
+
// Déclarée avant `/threads/:id` : la console lit d'abord les fils, le détail ensuite.
|
|
176
|
+
admin.get("/threads", (req, res) => {
|
|
177
|
+
const query = adminThreadsSchema.parse(req.query);
|
|
178
|
+
res.json({
|
|
179
|
+
threads: store.listAllThreadsAdmin({
|
|
180
|
+
search: query.search,
|
|
181
|
+
agents: query.agent
|
|
182
|
+
?.split(",")
|
|
183
|
+
.map((id) => id.trim())
|
|
184
|
+
.filter(Boolean)
|
|
185
|
+
.slice(0, 100),
|
|
186
|
+
unread_only: query.unread_only !== undefined,
|
|
187
|
+
priority: query.priority,
|
|
188
|
+
limit: query.limit,
|
|
189
|
+
offset: query.offset,
|
|
190
|
+
}),
|
|
191
|
+
});
|
|
192
|
+
});
|
|
165
193
|
admin.get("/threads/:id", (req, res) => {
|
|
166
194
|
const threadId = String(req.params.id);
|
|
167
195
|
const messages = store.listThreadAdmin(threadId);
|