@askmesh/mcp 0.6.0 → 0.7.1
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.
|
@@ -17,6 +17,11 @@ export class AutoResponder {
|
|
|
17
17
|
}
|
|
18
18
|
async handleRequest(request) {
|
|
19
19
|
console.error(`[AskMesh] Question from @${request.fromUsername}: "${request.question}"`);
|
|
20
|
+
// Mark thread as in_progress before processing
|
|
21
|
+
try {
|
|
22
|
+
await this.client.updateThreadStatus(request.id, 'in_progress');
|
|
23
|
+
}
|
|
24
|
+
catch { }
|
|
20
25
|
// Strategy 1: MCP sampling — asks the active Claude Code to respond
|
|
21
26
|
// Uses the user's existing Claude subscription, no API key needed
|
|
22
27
|
if (this.mcpServer) {
|
|
@@ -57,11 +62,11 @@ export class AutoResponder {
|
|
|
57
62
|
return;
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
|
-
catch {
|
|
61
|
-
console.error('[AskMesh] MCP sampling
|
|
65
|
+
catch (err) {
|
|
66
|
+
console.error('[AskMesh] MCP sampling failed:', err instanceof Error ? err.message : err);
|
|
62
67
|
}
|
|
63
68
|
}
|
|
64
|
-
// Strategy 2: Anthropic API fallback (optional
|
|
69
|
+
// Strategy 2: Anthropic API fallback (optional)
|
|
65
70
|
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
66
71
|
if (apiKey) {
|
|
67
72
|
try {
|
|
@@ -77,8 +82,23 @@ export class AutoResponder {
|
|
|
77
82
|
console.error('[AskMesh] Anthropic API failed:', err);
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
|
-
// Strategy 3:
|
|
81
|
-
|
|
85
|
+
// Strategy 3: Notify Claude Code and revert to pending
|
|
86
|
+
// Revert status so the message can be answered manually
|
|
87
|
+
try {
|
|
88
|
+
await this.client.updateThreadStatus(request.id, 'pending');
|
|
89
|
+
}
|
|
90
|
+
catch { }
|
|
91
|
+
// Send logging notification to Claude Code
|
|
92
|
+
if (this.mcpServer) {
|
|
93
|
+
try {
|
|
94
|
+
await this.mcpServer.sendLoggingMessage({
|
|
95
|
+
level: 'warning',
|
|
96
|
+
data: `📩 AskMesh — @${request.fromUsername} asks: "${request.question.slice(0, 200)}" → Use askmesh(action:"pending") to see and reply.`,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch { }
|
|
100
|
+
}
|
|
101
|
+
console.error(`[AskMesh] Question #${request.id} from @${request.fromUsername} — auto-response failed, use askmesh(action:"pending") to respond`);
|
|
82
102
|
}
|
|
83
103
|
async callAnthropicAPI(apiKey, request, context) {
|
|
84
104
|
const model = process.env.ANTHROPIC_MODEL || 'claude-sonnet-4-20250514';
|
|
@@ -3,7 +3,7 @@ export declare class AskMeshClient {
|
|
|
3
3
|
private token;
|
|
4
4
|
constructor(baseUrl: string, token: string);
|
|
5
5
|
private headers;
|
|
6
|
-
askAgent(toUsername: string, question: string, context?: string): Promise<{
|
|
6
|
+
askAgent(toUsername: string, question: string, context?: string, parentThreadId?: number): Promise<{
|
|
7
7
|
requestId: number;
|
|
8
8
|
status: string;
|
|
9
9
|
}>;
|
|
@@ -84,4 +84,29 @@ export declare class AskMeshClient {
|
|
|
84
84
|
createdAt: string;
|
|
85
85
|
}>;
|
|
86
86
|
}>;
|
|
87
|
+
updateThreadStatus(requestId: number, status: 'pending' | 'in_progress' | 'delegated'): Promise<{
|
|
88
|
+
id: number;
|
|
89
|
+
status: string;
|
|
90
|
+
}>;
|
|
91
|
+
getMyThreads(): Promise<{
|
|
92
|
+
threads: Array<{
|
|
93
|
+
id: number;
|
|
94
|
+
fromUsername: string;
|
|
95
|
+
toUsername: string;
|
|
96
|
+
question: string;
|
|
97
|
+
status: string;
|
|
98
|
+
replyCount: number;
|
|
99
|
+
parentThreadId: number | null;
|
|
100
|
+
createdAt: string;
|
|
101
|
+
updatedAt: string;
|
|
102
|
+
}>;
|
|
103
|
+
}>;
|
|
104
|
+
getTeamBoard(teamId: number): Promise<{
|
|
105
|
+
columns: {
|
|
106
|
+
pending: any[];
|
|
107
|
+
in_progress: any[];
|
|
108
|
+
active: any[];
|
|
109
|
+
closed: any[];
|
|
110
|
+
};
|
|
111
|
+
}>;
|
|
87
112
|
}
|
|
@@ -11,11 +11,11 @@ export class AskMeshClient {
|
|
|
11
11
|
'Content-Type': 'application/json',
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
-
async askAgent(toUsername, question, context) {
|
|
14
|
+
async askAgent(toUsername, question, context, parentThreadId) {
|
|
15
15
|
const res = await fetch(`${this.baseUrl}/api/v1/requests`, {
|
|
16
16
|
method: 'POST',
|
|
17
17
|
headers: this.headers(),
|
|
18
|
-
body: JSON.stringify({ toUsername, question, context }),
|
|
18
|
+
body: JSON.stringify({ toUsername, question, context, parentThreadId }),
|
|
19
19
|
});
|
|
20
20
|
if (!res.ok)
|
|
21
21
|
throw new Error(`askAgent failed: ${res.status} ${await res.text()}`);
|
|
@@ -108,4 +108,30 @@ export class AskMeshClient {
|
|
|
108
108
|
throw new Error(`getThread failed: ${res.status}`);
|
|
109
109
|
return res.json();
|
|
110
110
|
}
|
|
111
|
+
async updateThreadStatus(requestId, status) {
|
|
112
|
+
const res = await fetch(`${this.baseUrl}/api/v1/requests/${requestId}/status`, {
|
|
113
|
+
method: 'PUT',
|
|
114
|
+
headers: this.headers(),
|
|
115
|
+
body: JSON.stringify({ status }),
|
|
116
|
+
});
|
|
117
|
+
if (!res.ok)
|
|
118
|
+
throw new Error(`updateThreadStatus failed: ${res.status} ${await res.text()}`);
|
|
119
|
+
return res.json();
|
|
120
|
+
}
|
|
121
|
+
async getMyThreads() {
|
|
122
|
+
const res = await fetch(`${this.baseUrl}/api/v1/requests/my-threads`, {
|
|
123
|
+
headers: this.headers(),
|
|
124
|
+
});
|
|
125
|
+
if (!res.ok)
|
|
126
|
+
throw new Error(`getMyThreads failed: ${res.status}`);
|
|
127
|
+
return res.json();
|
|
128
|
+
}
|
|
129
|
+
async getTeamBoard(teamId) {
|
|
130
|
+
const res = await fetch(`${this.baseUrl}/api/v1/teams/${teamId}/board`, {
|
|
131
|
+
headers: this.headers(),
|
|
132
|
+
});
|
|
133
|
+
if (!res.ok)
|
|
134
|
+
throw new Error(`getTeamBoard failed: ${res.status}`);
|
|
135
|
+
return res.json();
|
|
136
|
+
}
|
|
111
137
|
}
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ const client = new AskMeshClient(URL, TOKEN);
|
|
|
15
15
|
const autoResponder = new AutoResponder(client);
|
|
16
16
|
const server = new McpServer({
|
|
17
17
|
name: 'askmesh',
|
|
18
|
-
version: '0.
|
|
18
|
+
version: '0.7.0',
|
|
19
19
|
});
|
|
20
20
|
// Single unified tool
|
|
21
21
|
registerAskMesh(server, client);
|
package/dist/tools/askmesh.js
CHANGED
|
@@ -12,20 +12,25 @@ Actions:
|
|
|
12
12
|
- "reply" : ajouter une réponse à un thread existant
|
|
13
13
|
- "thread" : voir le thread complet d'une question
|
|
14
14
|
- "close" : clôturer un thread
|
|
15
|
+
- "my-threads" : voir tous tes threads actifs
|
|
16
|
+
- "board" : voir le kanban board d'une team
|
|
17
|
+
- "progress" : marquer un thread comme "en cours de traitement"
|
|
15
18
|
- "context" : partager ton contexte projet avec ta team`, {
|
|
16
|
-
action: z.enum(['ask', 'list', 'status', 'pending', 'inbox', 'answer', 'reply', 'thread', 'close', 'context']).describe('Action à effectuer'),
|
|
19
|
+
action: z.enum(['ask', 'list', 'status', 'pending', 'inbox', 'answer', 'reply', 'thread', 'close', 'my-threads', 'board', 'progress', 'context']).describe('Action à effectuer'),
|
|
17
20
|
username: z.string().optional().describe("Username de l'agent cible (pour ask/status)"),
|
|
18
21
|
question: z.string().optional().describe('Question à poser (pour ask)'),
|
|
19
|
-
requestId: z.number().optional().describe('ID de la requête (pour answer/reply/thread/close)'),
|
|
22
|
+
requestId: z.number().optional().describe('ID de la requête (pour answer/reply/thread/close/progress)'),
|
|
20
23
|
message: z.string().optional().describe('Réponse ou contexte à envoyer (pour answer/reply/context)'),
|
|
21
|
-
|
|
24
|
+
parentThreadId: z.number().optional().describe('ID du thread parent (pour ask, lie les threads entre eux)'),
|
|
25
|
+
teamId: z.number().optional().describe('ID de la team (pour board)'),
|
|
26
|
+
}, async ({ action, username, question, requestId, message, parentThreadId, teamId }) => {
|
|
22
27
|
switch (action) {
|
|
23
28
|
case 'ask': {
|
|
24
29
|
if (!username || !question) {
|
|
25
30
|
return text("Paramètres requis : username et question");
|
|
26
31
|
}
|
|
27
32
|
const target = username.replace(/^@/, '');
|
|
28
|
-
const result = await client.askAgent(target, question);
|
|
33
|
+
const result = await client.askAgent(target, question, undefined, parentThreadId);
|
|
29
34
|
const reqId = result.requestId;
|
|
30
35
|
// Poll for answer (max 60s)
|
|
31
36
|
for (let i = 0; i < 20; i++) {
|
|
@@ -123,6 +128,47 @@ Actions:
|
|
|
123
128
|
const result = await client.closeThread(requestId);
|
|
124
129
|
return text(`Thread #${result.id} clôturé.`);
|
|
125
130
|
}
|
|
131
|
+
case 'my-threads': {
|
|
132
|
+
const { threads } = await client.getMyThreads();
|
|
133
|
+
if (threads.length === 0)
|
|
134
|
+
return text('Aucun thread actif.');
|
|
135
|
+
const statusIcons = {
|
|
136
|
+
pending: '⏳', in_progress: '🔧', active: '💬', delegated: '📤', closed: '✅',
|
|
137
|
+
};
|
|
138
|
+
const lines = threads.map((t) => {
|
|
139
|
+
const icon = statusIcons[t.status] || '❓';
|
|
140
|
+
let line = `${icon} #${t.id} [${t.status}] @${t.fromUsername} → @${t.toUsername}: "${t.question}"`;
|
|
141
|
+
if (t.replyCount > 0)
|
|
142
|
+
line += ` (${t.replyCount} replies)`;
|
|
143
|
+
if (t.parentThreadId)
|
|
144
|
+
line += ` [lié au thread #${t.parentThreadId}]`;
|
|
145
|
+
return line;
|
|
146
|
+
});
|
|
147
|
+
return text(`Mes threads:\n\n${lines.join('\n')}`);
|
|
148
|
+
}
|
|
149
|
+
case 'board': {
|
|
150
|
+
if (!teamId)
|
|
151
|
+
return text("Paramètre requis : teamId");
|
|
152
|
+
const board = await client.getTeamBoard(teamId);
|
|
153
|
+
const formatCol = (name, items) => {
|
|
154
|
+
if (items.length === 0)
|
|
155
|
+
return `**${name}**: (vide)`;
|
|
156
|
+
return `**${name}** (${items.length}):\n${items.map((t) => ` #${t.id} @${t.fromUsername}→@${t.toUsername}: "${t.question}" [${t.replyCount} replies]`).join('\n')}`;
|
|
157
|
+
};
|
|
158
|
+
const lines = [
|
|
159
|
+
formatCol('Pending', board.columns.pending),
|
|
160
|
+
formatCol('In Progress', board.columns.in_progress),
|
|
161
|
+
formatCol('Active', board.columns.active),
|
|
162
|
+
formatCol('Closed (24h)', board.columns.closed),
|
|
163
|
+
];
|
|
164
|
+
return text(`Team Board:\n\n${lines.join('\n\n')}`);
|
|
165
|
+
}
|
|
166
|
+
case 'progress': {
|
|
167
|
+
if (!requestId)
|
|
168
|
+
return text("Paramètre requis : requestId");
|
|
169
|
+
const result = await client.updateThreadStatus(requestId, 'in_progress');
|
|
170
|
+
return text(`Thread #${result.id} marqué comme en cours de traitement.`);
|
|
171
|
+
}
|
|
126
172
|
case 'context': {
|
|
127
173
|
if (!message)
|
|
128
174
|
return text("Paramètre requis : message (le contenu du contexte)");
|