@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.
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { AskMeshClient } from '../client/askmesh_client.js';
3
+ export declare function registerAnswerPending(server: McpServer, client: AskMeshClient): void;
@@ -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,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { AskMeshClient } from '../client/askmesh_client.js';
3
+ export declare function registerAskAgent(server: McpServer, client: AskMeshClient): void;
@@ -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,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { AskMeshClient } from '../client/askmesh_client.js';
3
+ export declare function registerGetStatus(server: McpServer, client: AskMeshClient): void;
@@ -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,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { AskMeshClient } from '../client/askmesh_client.js';
3
+ export declare function registerListAgents(server: McpServer, client: AskMeshClient): void;
@@ -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,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { AskMeshClient } from '../client/askmesh_client.js';
3
+ export declare function registerSetContext(server: McpServer, client: AskMeshClient): void;
@@ -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
+ }