@askmesh/mcp 0.1.0
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/dist/client/askmesh_client.d.ts +43 -0
- package/dist/client/askmesh_client.js +68 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +44 -0
- package/dist/sse/sse_listener.d.ts +12 -0
- package/dist/sse/sse_listener.js +29 -0
- package/dist/tools/answer_pending.d.ts +3 -0
- package/dist/tools/answer_pending.js +50 -0
- package/dist/tools/ask_agent.d.ts +3 -0
- package/dist/tools/ask_agent.js +19 -0
- package/dist/tools/get_status.d.ts +3 -0
- package/dist/tools/get_status.js +18 -0
- package/dist/tools/list_agents.d.ts +3 -0
- package/dist/tools/list_agents.js +17 -0
- package/dist/tools/set_context.d.ts +3 -0
- package/dist/tools/set_context.js +18 -0
- package/package.json +43 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare class AskMeshClient {
|
|
2
|
+
private baseUrl;
|
|
3
|
+
private token;
|
|
4
|
+
constructor(baseUrl: string, token: string);
|
|
5
|
+
private headers;
|
|
6
|
+
askAgent(toUsername: string, question: string, context?: string): Promise<{
|
|
7
|
+
requestId: number;
|
|
8
|
+
status: string;
|
|
9
|
+
}>;
|
|
10
|
+
getPendingRequests(): Promise<{
|
|
11
|
+
requests: Array<{
|
|
12
|
+
id: number;
|
|
13
|
+
fromAgentId: number;
|
|
14
|
+
question: string;
|
|
15
|
+
context: string | null;
|
|
16
|
+
status: string;
|
|
17
|
+
createdAt: string;
|
|
18
|
+
expiresAt: string;
|
|
19
|
+
}>;
|
|
20
|
+
}>;
|
|
21
|
+
answerRequest(requestId: number, answer: string): Promise<{
|
|
22
|
+
id: number;
|
|
23
|
+
status: string;
|
|
24
|
+
answeredAt: string;
|
|
25
|
+
}>;
|
|
26
|
+
listAgents(): Promise<{
|
|
27
|
+
agents: Array<{
|
|
28
|
+
id: number;
|
|
29
|
+
username: string;
|
|
30
|
+
status: string;
|
|
31
|
+
lastSeenAt: string | null;
|
|
32
|
+
}>;
|
|
33
|
+
}>;
|
|
34
|
+
getAgentStatus(username: string): Promise<{
|
|
35
|
+
username: string;
|
|
36
|
+
status: string;
|
|
37
|
+
lastSeenAt: string | null;
|
|
38
|
+
}>;
|
|
39
|
+
setContext(context: string): Promise<{
|
|
40
|
+
message: string;
|
|
41
|
+
context: string;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export class AskMeshClient {
|
|
2
|
+
baseUrl;
|
|
3
|
+
token;
|
|
4
|
+
constructor(baseUrl, token) {
|
|
5
|
+
this.baseUrl = baseUrl;
|
|
6
|
+
this.token = token;
|
|
7
|
+
}
|
|
8
|
+
headers() {
|
|
9
|
+
return {
|
|
10
|
+
Authorization: `Bearer ${this.token}`,
|
|
11
|
+
'Content-Type': 'application/json',
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
async askAgent(toUsername, question, context) {
|
|
15
|
+
const res = await fetch(`${this.baseUrl}/api/v1/requests`, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: this.headers(),
|
|
18
|
+
body: JSON.stringify({ toUsername, question, context }),
|
|
19
|
+
});
|
|
20
|
+
if (!res.ok)
|
|
21
|
+
throw new Error(`askAgent failed: ${res.status} ${await res.text()}`);
|
|
22
|
+
return res.json();
|
|
23
|
+
}
|
|
24
|
+
async getPendingRequests() {
|
|
25
|
+
const res = await fetch(`${this.baseUrl}/api/v1/requests/pending`, {
|
|
26
|
+
headers: this.headers(),
|
|
27
|
+
});
|
|
28
|
+
if (!res.ok)
|
|
29
|
+
throw new Error(`getPending failed: ${res.status}`);
|
|
30
|
+
return res.json();
|
|
31
|
+
}
|
|
32
|
+
async answerRequest(requestId, answer) {
|
|
33
|
+
const res = await fetch(`${this.baseUrl}/api/v1/requests/${requestId}/answer`, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: this.headers(),
|
|
36
|
+
body: JSON.stringify({ answer }),
|
|
37
|
+
});
|
|
38
|
+
if (!res.ok)
|
|
39
|
+
throw new Error(`answerRequest failed: ${res.status} ${await res.text()}`);
|
|
40
|
+
return res.json();
|
|
41
|
+
}
|
|
42
|
+
async listAgents() {
|
|
43
|
+
const res = await fetch(`${this.baseUrl}/api/v1/agents`, {
|
|
44
|
+
headers: this.headers(),
|
|
45
|
+
});
|
|
46
|
+
if (!res.ok)
|
|
47
|
+
throw new Error(`listAgents failed: ${res.status}`);
|
|
48
|
+
return res.json();
|
|
49
|
+
}
|
|
50
|
+
async getAgentStatus(username) {
|
|
51
|
+
const res = await fetch(`${this.baseUrl}/api/v1/agents/${username}/status`, {
|
|
52
|
+
headers: this.headers(),
|
|
53
|
+
});
|
|
54
|
+
if (!res.ok)
|
|
55
|
+
throw new Error(`getAgentStatus failed: ${res.status}`);
|
|
56
|
+
return res.json();
|
|
57
|
+
}
|
|
58
|
+
async setContext(context) {
|
|
59
|
+
const res = await fetch(`${this.baseUrl}/api/v1/agents/context`, {
|
|
60
|
+
method: 'PUT',
|
|
61
|
+
headers: this.headers(),
|
|
62
|
+
body: JSON.stringify({ context }),
|
|
63
|
+
});
|
|
64
|
+
if (!res.ok)
|
|
65
|
+
throw new Error(`setContext failed: ${res.status}`);
|
|
66
|
+
return res.json();
|
|
67
|
+
}
|
|
68
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { AskMeshClient } from './client/askmesh_client.js';
|
|
5
|
+
import { SseListener } from './sse/sse_listener.js';
|
|
6
|
+
import { registerAskAgent } from './tools/ask_agent.js';
|
|
7
|
+
import { registerListAgents } from './tools/list_agents.js';
|
|
8
|
+
import { registerGetStatus } from './tools/get_status.js';
|
|
9
|
+
import { registerAnswerPending } from './tools/answer_pending.js';
|
|
10
|
+
import { registerSetContext } from './tools/set_context.js';
|
|
11
|
+
const TOKEN = process.env.ASKMESH_TOKEN;
|
|
12
|
+
const URL = process.env.ASKMESH_URL || 'https://api.askmesh.dev';
|
|
13
|
+
if (!TOKEN) {
|
|
14
|
+
console.error('[AskMesh] ASKMESH_TOKEN is required');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const client = new AskMeshClient(URL, TOKEN);
|
|
18
|
+
const server = new McpServer({
|
|
19
|
+
name: 'askmesh',
|
|
20
|
+
version: '0.1.0',
|
|
21
|
+
});
|
|
22
|
+
// Register all tools
|
|
23
|
+
registerAskAgent(server, client);
|
|
24
|
+
registerListAgents(server, client);
|
|
25
|
+
registerGetStatus(server, client);
|
|
26
|
+
registerAnswerPending(server, client);
|
|
27
|
+
registerSetContext(server, client);
|
|
28
|
+
// Start SSE listener (marks agent online + receives incoming requests)
|
|
29
|
+
const sse = new SseListener();
|
|
30
|
+
sse.start(URL, TOKEN, (request) => {
|
|
31
|
+
console.error(`[AskMesh] Incoming request from @${request.fromUsername}: "${request.question}"`);
|
|
32
|
+
});
|
|
33
|
+
// Cleanup on exit
|
|
34
|
+
process.on('SIGINT', () => {
|
|
35
|
+
sse.stop();
|
|
36
|
+
process.exit(0);
|
|
37
|
+
});
|
|
38
|
+
process.on('SIGTERM', () => {
|
|
39
|
+
sse.stop();
|
|
40
|
+
process.exit(0);
|
|
41
|
+
});
|
|
42
|
+
// Start MCP server via stdio
|
|
43
|
+
const transport = new StdioServerTransport();
|
|
44
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface IncomingRequest {
|
|
2
|
+
id: number;
|
|
3
|
+
fromAgentId: number;
|
|
4
|
+
fromUsername: string;
|
|
5
|
+
question: string;
|
|
6
|
+
context: string | null;
|
|
7
|
+
}
|
|
8
|
+
export declare class SseListener {
|
|
9
|
+
private es;
|
|
10
|
+
start(baseUrl: string, token: string, onRequest: (req: IncomingRequest) => void): void;
|
|
11
|
+
stop(): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import EventSource from 'eventsource';
|
|
2
|
+
export class SseListener {
|
|
3
|
+
es = null;
|
|
4
|
+
start(baseUrl, token, onRequest) {
|
|
5
|
+
this.es = new EventSource(`${baseUrl}/api/v1/sse/subscribe`, {
|
|
6
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
7
|
+
});
|
|
8
|
+
this.es.addEventListener('request_incoming', ((e) => {
|
|
9
|
+
try {
|
|
10
|
+
const payload = JSON.parse(e.data);
|
|
11
|
+
onRequest(payload);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// ignore parse errors
|
|
15
|
+
}
|
|
16
|
+
}));
|
|
17
|
+
this.es.addEventListener('ping', () => {
|
|
18
|
+
// keepalive — nothing to do
|
|
19
|
+
});
|
|
20
|
+
this.es.onerror = () => {
|
|
21
|
+
// EventSource handles automatic reconnection
|
|
22
|
+
console.error('[AskMesh SSE] Connection error — will reconnect');
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
stop() {
|
|
26
|
+
this.es?.close();
|
|
27
|
+
this.es = null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerAnswerPending(server, client) {
|
|
3
|
+
server.tool('answer_pending', 'Liste les questions en attente ou répond à une question spécifique', {
|
|
4
|
+
requestId: z
|
|
5
|
+
.number()
|
|
6
|
+
.optional()
|
|
7
|
+
.describe('ID de la requête à laquelle répondre (omit pour lister)'),
|
|
8
|
+
answer: z.string().optional().describe('Réponse à envoyer (requis si requestId fourni)'),
|
|
9
|
+
}, async ({ requestId, answer }) => {
|
|
10
|
+
// Mode listing
|
|
11
|
+
if (!requestId) {
|
|
12
|
+
const { requests } = await client.getPendingRequests();
|
|
13
|
+
if (requests.length === 0) {
|
|
14
|
+
return {
|
|
15
|
+
content: [{ type: 'text', text: 'Aucune question en attente.' }],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const lines = requests.map((r) => `#${r.id} — "${r.question}"${r.context ? ` (contexte: ${r.context})` : ''}`);
|
|
19
|
+
return {
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: 'text',
|
|
23
|
+
text: `Questions en attente:\n${lines.join('\n')}\n\nUtilise answer_pending avec requestId et answer pour répondre.`,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
// Mode réponse
|
|
29
|
+
if (!answer) {
|
|
30
|
+
return {
|
|
31
|
+
content: [
|
|
32
|
+
{
|
|
33
|
+
type: 'text',
|
|
34
|
+
text: "Le paramètre 'answer' est requis pour répondre à une requête.",
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
isError: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const result = await client.answerRequest(requestId, answer);
|
|
41
|
+
return {
|
|
42
|
+
content: [
|
|
43
|
+
{
|
|
44
|
+
type: 'text',
|
|
45
|
+
text: `Réponse envoyée pour la requête #${result.id}\nStatut: ${result.status}`,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerAskAgent(server, client) {
|
|
3
|
+
server.tool('ask_agent', 'Pose une question à un agent de ton équipe par son username', {
|
|
4
|
+
username: z.string().describe("Username de l'agent cible (ex: manu)"),
|
|
5
|
+
question: z.string().describe('La question à poser'),
|
|
6
|
+
context: z.string().optional().describe('Contexte additionnel (optionnel)'),
|
|
7
|
+
}, async ({ username, question, context }) => {
|
|
8
|
+
const target = username.replace(/^@/, '');
|
|
9
|
+
const result = await client.askAgent(target, question, context);
|
|
10
|
+
return {
|
|
11
|
+
content: [
|
|
12
|
+
{
|
|
13
|
+
type: 'text',
|
|
14
|
+
text: `Question envoyée à @${target} (request #${result.requestId})\nStatut: ${result.status}`,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerGetStatus(server, client) {
|
|
3
|
+
server.tool('get_status', "Retourne le statut d'un agent spécifique", {
|
|
4
|
+
username: z.string().describe("Username de l'agent (ex: manu)"),
|
|
5
|
+
}, async ({ username }) => {
|
|
6
|
+
const target = username.replace(/^@/, '');
|
|
7
|
+
const result = await client.getAgentStatus(target);
|
|
8
|
+
const status = result.status === 'online' ? '🟢 online' : '⚫ offline';
|
|
9
|
+
return {
|
|
10
|
+
content: [
|
|
11
|
+
{
|
|
12
|
+
type: 'text',
|
|
13
|
+
text: `@${result.username} — ${status}${result.lastSeenAt ? `\nDernière activité: ${result.lastSeenAt}` : ''}`,
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function registerListAgents(server, client) {
|
|
2
|
+
server.tool('list_agents', 'Liste les agents de ton équipe avec leur statut online/offline', {}, async () => {
|
|
3
|
+
const { agents } = await client.listAgents();
|
|
4
|
+
if (agents.length === 0) {
|
|
5
|
+
return {
|
|
6
|
+
content: [{ type: 'text', text: 'Aucun agent trouvé.' }],
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
const lines = agents.map((a) => {
|
|
10
|
+
const status = a.status === 'online' ? '🟢' : '⚫';
|
|
11
|
+
return `${status} @${a.username} — ${a.status}`;
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerSetContext(server, client) {
|
|
3
|
+
server.tool('set_context', 'Partage ton contexte projet (CLAUDE.md) avec les autres agents AskMesh', {
|
|
4
|
+
context: z
|
|
5
|
+
.string()
|
|
6
|
+
.describe('Le contenu de ton contexte projet (ex: contenu de CLAUDE.md)'),
|
|
7
|
+
}, async ({ context }) => {
|
|
8
|
+
const result = await client.setContext(context);
|
|
9
|
+
return {
|
|
10
|
+
content: [
|
|
11
|
+
{
|
|
12
|
+
type: 'text',
|
|
13
|
+
text: `Contexte mis à jour (${context.length} caractères).`,
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@askmesh/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AskMesh MCP server — connect your AI coding agent to your team's mesh network",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"askmesh-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsc --watch",
|
|
16
|
+
"prepublishOnly": "tsc"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"claude",
|
|
21
|
+
"ai-agent",
|
|
22
|
+
"askmesh",
|
|
23
|
+
"claude-code",
|
|
24
|
+
"cursor",
|
|
25
|
+
"windsurf"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/mantoine/askmesh",
|
|
30
|
+
"directory": "apps/mcp"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://askmesh.dev",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
36
|
+
"eventsource": "^2.0.2",
|
|
37
|
+
"zod": "^4.3.6"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^22.0.0",
|
|
41
|
+
"typescript": "~5.6"
|
|
42
|
+
}
|
|
43
|
+
}
|