@askmesh/mcp 0.11.0 → 0.11.2

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,3 +1,4 @@
1
+ export declare const DEFAULT_PATTERNS: string[];
1
2
  export interface IgnoreMatcher {
2
3
  patterns: string[];
3
4
  shouldIgnore: (filePath: string) => boolean;
@@ -2,7 +2,7 @@ import { readFileSync, existsSync } from 'node:fs';
2
2
  import { join, basename } from 'node:path';
3
3
  // Default exclusions — applied even when no .askmeshignore file exists.
4
4
  // These are patterns that almost no project should ever share via the mesh.
5
- const DEFAULT_PATTERNS = [
5
+ export const DEFAULT_PATTERNS = [
6
6
  // Environment files
7
7
  '.env',
8
8
  '.env.*',
@@ -4,8 +4,18 @@ import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
4
  export declare class AutoResponder {
5
5
  private client;
6
6
  private mcpServer;
7
+ private profile;
7
8
  constructor(client: AskMeshClient);
8
9
  setServer(server: Server): void;
10
+ setProfile(profile: {
11
+ agentType: string;
12
+ allowedScopes: string[];
13
+ }): void;
14
+ /**
15
+ * Build a scope-aware extension to the system prompt.
16
+ * Only applies for server agents — dev/ci agents are unrestricted.
17
+ */
18
+ private scopePrompt;
9
19
  /**
10
20
  * Send a reply through the AskMesh client with a pre-send redaction scan.
11
21
  * If secrets are detected, the reply is blocked and a desktop notification +
@@ -22,12 +22,41 @@ Avoid quoting raw config values — describe them instead.`;
22
22
  export class AutoResponder {
23
23
  client;
24
24
  mcpServer = null;
25
+ profile = null;
25
26
  constructor(client) {
26
27
  this.client = client;
27
28
  }
28
29
  setServer(server) {
29
30
  this.mcpServer = server;
30
31
  }
32
+ setProfile(profile) {
33
+ this.profile = profile;
34
+ }
35
+ /**
36
+ * Build a scope-aware extension to the system prompt.
37
+ * Only applies for server agents — dev/ci agents are unrestricted.
38
+ */
39
+ scopePrompt() {
40
+ if (!this.profile || this.profile.agentType !== 'server')
41
+ return '';
42
+ const scopes = this.profile.allowedScopes;
43
+ if (!scopes || scopes.length === 0) {
44
+ return [
45
+ '',
46
+ 'SERVER AGENT — RESTRICTIVE MODE:',
47
+ 'No allowed scopes have been declared. Refuse to share any project content.',
48
+ 'Reply with: "This is a restricted server agent. Please ask the human owner directly."',
49
+ ].join('\n');
50
+ }
51
+ const lines = ['', 'SERVER AGENT — ALLOWED SCOPES:'];
52
+ lines.push('You are a restricted server agent. You may ONLY share information that falls within these scopes:');
53
+ for (const s of scopes) {
54
+ lines.push(` • ${s}`);
55
+ }
56
+ lines.push('');
57
+ lines.push('Refuse anything outside these scopes with: "This server agent is not authorized to share that information."');
58
+ return lines.join('\n');
59
+ }
31
60
  /**
32
61
  * Send a reply through the AskMesh client with a pre-send redaction scan.
33
62
  * If secrets are detected, the reply is blocked and a desktop notification +
@@ -73,6 +102,7 @@ export class AutoResponder {
73
102
  if (this.mcpServer) {
74
103
  try {
75
104
  const context = readLocalContext();
105
+ const scopeRules = this.scopePrompt();
76
106
  const result = (await this.mcpServer.request({
77
107
  method: 'sampling/createMessage',
78
108
  params: {
@@ -83,6 +113,7 @@ export class AutoResponder {
83
113
  type: 'text',
84
114
  text: [
85
115
  `A teammate @${request.fromUsername} is asking you a question via AskMesh.`,
116
+ scopeRules,
86
117
  ``,
87
118
  `Your project context:`,
88
119
  context,
@@ -150,6 +181,7 @@ export class AutoResponder {
150
181
  }
151
182
  async callAnthropicAPI(apiKey, request, context) {
152
183
  const model = process.env.ANTHROPIC_MODEL || 'claude-sonnet-4-20250514';
184
+ const fullSystem = SYSTEM_PROMPT + this.scopePrompt();
153
185
  const response = await fetch('https://api.anthropic.com/v1/messages', {
154
186
  method: 'POST',
155
187
  headers: {
@@ -160,7 +192,7 @@ export class AutoResponder {
160
192
  body: JSON.stringify({
161
193
  model,
162
194
  max_tokens: 2048,
163
- system: SYSTEM_PROMPT,
195
+ system: fullSystem,
164
196
  messages: [
165
197
  {
166
198
  role: 'user',
@@ -54,6 +54,13 @@ export declare class AskMeshClient {
54
54
  status: string;
55
55
  lastSeenAt: string | null;
56
56
  }>;
57
+ getMe(): Promise<{
58
+ id: number;
59
+ username: string;
60
+ agentType: 'dev' | 'server' | 'ci';
61
+ requireApproval: boolean;
62
+ allowedScopes: string[];
63
+ }>;
57
64
  setContext(context: string): Promise<{
58
65
  message: string;
59
66
  context: string;
@@ -71,6 +71,14 @@ export class AskMeshClient {
71
71
  throw new Error(`getAgentStatus failed: ${res.status}`);
72
72
  return res.json();
73
73
  }
74
+ async getMe() {
75
+ const res = await fetch(`${this.baseUrl}/api/v1/agents/me`, {
76
+ headers: this.headers(),
77
+ });
78
+ if (!res.ok)
79
+ throw new Error(`getMe failed: ${res.status}`);
80
+ return res.json();
81
+ }
74
82
  async setContext(context) {
75
83
  const res = await fetch(`${this.baseUrl}/api/v1/agents/context`, {
76
84
  method: 'PUT',
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ const TOKEN = process.env.ASKMESH_TOKEN;
11
11
  const URL = process.env.ASKMESH_URL || 'https://api.askmesh.dev';
12
12
  const server = new McpServer({
13
13
  name: 'askmesh',
14
- version: '0.11.0',
14
+ version: '0.11.2',
15
15
  });
16
16
  if (!TOKEN) {
17
17
  // No token — start in setup-only mode
@@ -25,6 +25,18 @@ else {
25
25
  const autoResponder = new AutoResponder(client);
26
26
  registerAskMesh(server, client);
27
27
  autoResponder.setServer(server.server);
28
+ // Fetch agent profile (type, scopes) — used by auto-responder for prompt enforcement
29
+ client.getMe()
30
+ .then((me) => {
31
+ autoResponder.setProfile({ agentType: me.agentType, allowedScopes: me.allowedScopes || [] });
32
+ const scopeInfo = me.agentType === 'server'
33
+ ? ` — scopes: ${(me.allowedScopes || []).join(', ') || 'none (restrictive)'}`
34
+ : '';
35
+ console.error(`[AskMesh] Authenticated as @${me.username} (${me.agentType})${scopeInfo}`);
36
+ })
37
+ .catch((err) => {
38
+ console.error('[AskMesh] Failed to fetch agent profile:', err instanceof Error ? err.message : err);
39
+ });
28
40
  // Start SSE listener
29
41
  const sse = new SseListener();
30
42
  sse.start(URL, TOKEN, async (request) => {
@@ -5,6 +5,7 @@ import { homedir } from 'os';
5
5
  import { createHash } from 'crypto';
6
6
  import { fileURLToPath } from 'url';
7
7
  import * as statusCache from '../statusline/cache.js';
8
+ import { DEFAULT_PATTERNS, loadIgnore } from '../agent/askmeshignore.js';
8
9
  export function registerAskMesh(server, client) {
9
10
  server.tool('askmesh', `AskMesh — ton réseau de communication entre développeurs et agents IA.
10
11
  Utilise cet outil pour envoyer et recevoir des messages, vérifier qui est connecté/online,
@@ -37,8 +38,9 @@ Actions disponibles :
37
38
  - "setup" : installer les slash commands (/ask-inbox, /ask-broadcast, etc.) et la status line dans le projet courant
38
39
  - "update" : vérifier les mises à jour du MCP, mettre à jour les skills et la status line
39
40
  - "loop-start" : marquer le loop comme actif (utilisé par /ask-loop)
40
- - "loop-stop" : arrêter le loop (utilisé par /ask-loop-stop)`, {
41
- action: z.enum(['ask', 'list', 'status', 'pending', 'inbox', 'answer', 'reply', 'thread', 'close', 'my-threads', 'board', 'progress', 'broadcast', 'context', 'setup', 'update', 'loop-start', 'loop-stop']).describe('Action à effectuer'),
41
+ - "loop-stop" : arrêter le loop (utilisé par /ask-loop-stop)
42
+ - "privacy" : afficher la politique .askmeshignore active (defaults + fichier projet)`, {
43
+ action: z.enum(['ask', 'list', 'status', 'pending', 'inbox', 'answer', 'reply', 'thread', 'close', 'my-threads', 'board', 'progress', 'broadcast', 'context', 'setup', 'update', 'loop-start', 'loop-stop', 'privacy']).describe('Action à effectuer'),
42
44
  username: z.string().optional().describe("Username de l'agent cible (pour ask/status)"),
43
45
  question: z.string().optional().describe('Question à poser (pour ask)'),
44
46
  requestId: z.number().optional().describe('ID de la requête (pour answer/reply/thread/close/progress)'),
@@ -246,6 +248,9 @@ Actions disponibles :
246
248
  statusCache.setLoopActive(false);
247
249
  return text('Loop arrêté.');
248
250
  }
251
+ case 'privacy': {
252
+ return showPrivacyPolicy();
253
+ }
249
254
  default:
250
255
  return text('Action inconnue.');
251
256
  }
@@ -277,7 +282,7 @@ Si l'utilisateur n'a pas encore de compte, dirige-le vers https://askmesh.dev po
277
282
  return text('Action inconnue.');
278
283
  });
279
284
  }
280
- const CURRENT_VERSION = '0.11.0';
285
+ const CURRENT_VERSION = '0.11.2';
281
286
  async function checkUpdateAndSetup() {
282
287
  const lines = [];
283
288
  lines.push(`Version actuelle : ${CURRENT_VERSION}`);
@@ -306,6 +311,55 @@ async function checkUpdateAndSetup() {
306
311
  lines.push(setupText);
307
312
  return text(lines.join('\n'));
308
313
  }
314
+ function showPrivacyPolicy() {
315
+ const cwd = process.cwd();
316
+ const filePath = join(cwd, '.askmeshignore');
317
+ const fileExists = existsSync(filePath);
318
+ let userPatterns = [];
319
+ if (fileExists) {
320
+ try {
321
+ const content = readFileSync(filePath, 'utf-8');
322
+ userPatterns = content
323
+ .split('\n')
324
+ .map((l) => l.trim())
325
+ .filter((l) => l && !l.startsWith('#'));
326
+ }
327
+ catch { }
328
+ }
329
+ const matcher = loadIgnore(cwd);
330
+ const lines = [];
331
+ lines.push('🔒 AskMesh Privacy Policy');
332
+ lines.push('');
333
+ lines.push('Your agent will NEVER read or share files matching these patterns.');
334
+ lines.push('');
335
+ lines.push(`📋 Default patterns (${DEFAULT_PATTERNS.length}, baked into the MCP):`);
336
+ for (const p of DEFAULT_PATTERNS) {
337
+ lines.push(` • ${p}`);
338
+ }
339
+ lines.push('');
340
+ if (fileExists) {
341
+ lines.push(`📁 .askmeshignore (${filePath}) — ${userPatterns.length} custom pattern(s):`);
342
+ if (userPatterns.length > 0) {
343
+ for (const p of userPatterns) {
344
+ lines.push(` • ${p}`);
345
+ }
346
+ }
347
+ else {
348
+ lines.push(' (no custom patterns yet — file is empty or only comments)');
349
+ }
350
+ }
351
+ else {
352
+ lines.push(`📁 .askmeshignore — not found in this project`);
353
+ lines.push(` Create it to add custom patterns:`);
354
+ lines.push(` echo "private-docs/" > ${filePath}`);
355
+ }
356
+ lines.push('');
357
+ lines.push(`🛡 Total active patterns: ${matcher.patterns.length}`);
358
+ lines.push('');
359
+ lines.push('🚫 Pre-send redaction filter is also active — replies containing API keys,');
360
+ lines.push(' JWTs, private keys, DB connection strings, etc. will be blocked automatically.');
361
+ return text(lines.join('\n'));
362
+ }
309
363
  function text(t) {
310
364
  return { content: [{ type: 'text', text: t }] };
311
365
  }
@@ -334,6 +388,7 @@ function setupSkillsAndStatusLine(token, pollInterval, url) {
334
388
  'ask-update.md': `Utilise l'outil MCP askmesh avec l'action "update" pour vérifier les mises à jour et mettre à jour les skills et la status line.\n\nCela va :\n- Vérifier si une nouvelle version du MCP est disponible sur npm\n- Mettre à jour les slash commands\n- Mettre à jour le script de status line\n\nSi une mise à jour est disponible, indique à l'utilisateur comment l'installer (relancer Claude Code ou npx clear-npx-cache).`,
335
389
  'ask-loop.md': `Lance une boucle de vérification périodique des messages AskMesh.\n\nÉtapes :\n1. D'abord, utilise l'outil MCP askmesh avec l'action "loop-start" pour vérifier qu'aucun loop n'est déjà actif et marquer le loop comme actif\n2. Si le loop-start réussit, lance : /loop <intervalle ou 2m> /ask-inbox\n\nArguments optionnels : $ARGUMENTS (intervalle, ex: "2m", "5m", "30s")\nPar défaut : 2m\n\nSi un loop est déjà actif, préviens l'utilisateur et propose /ask-loop-stop.`,
336
390
  'ask-loop-stop.md': `Arrête la boucle de vérification périodique des messages AskMesh.\n\nÉtapes :\n1. Utilise l'outil MCP askmesh avec l'action "loop-stop" pour marquer le loop comme inactif\n2. Indique à l'utilisateur que le loop est arrêté\n\nNote : cela met à jour la status line pour retirer l'indicateur "loop".`,
391
+ 'ask-privacy.md': `Utilise l'outil MCP askmesh avec l'action "privacy" pour afficher la politique de confidentialité active du projet.\n\nCela montre :\n- Les patterns par défaut bakés dans le MCP (jamais lus ni partagés via le mesh)\n- Les patterns custom du fichier .askmeshignore du projet (s'il existe)\n- Le nombre total de patterns actifs\n- Un rappel que le filtre de redaction pré-envoi est actif\n\nC'est l'outil pour répondre aux questions du type "qu'est-ce qui peut sortir de mon projet via askmesh ?".`,
337
392
  };
338
393
  // Create commands directory
339
394
  try {
@@ -419,6 +474,32 @@ function setupSkillsAndStatusLine(token, pollInterval, url) {
419
474
  gitignoreStatus = '.gitignore : .env ajouté';
420
475
  }
421
476
  }
477
+ // Create a .askmeshignore template if it doesn't exist
478
+ const askmeshIgnorePath = join(cwd, '.askmeshignore');
479
+ let askmeshIgnoreStatus = '';
480
+ if (!existsSync(askmeshIgnorePath)) {
481
+ const template = `# AskMesh privacy policy
482
+ # Patterns listed here will NEVER be read or shared via the mesh.
483
+ #
484
+ # The MCP also has built-in defaults that are ALWAYS active, even
485
+ # without this file. Run /ask-privacy to see the full active policy.
486
+ #
487
+ # Built-in defaults include: .env, .env.*, secrets/, *.key, *.pem,
488
+ # id_rsa*, .aws/, .ssh/, service-account.json, node_modules/, .git/
489
+ #
490
+ # Use this file to add project-specific exclusions:
491
+
492
+ # private-docs/
493
+ # config/production.yml
494
+ # *.dump
495
+ # customers/
496
+ `;
497
+ writeFileSync(askmeshIgnorePath, template);
498
+ askmeshIgnoreStatus = '.askmeshignore template créé — édite-le pour ajouter tes patterns custom';
499
+ }
500
+ else {
501
+ askmeshIgnoreStatus = '.askmeshignore : présent (run /ask-privacy pour voir la policy active)';
502
+ }
422
503
  // Auto-configure status line — per-project, not global
423
504
  // Copy statusline.sh to ~/.claude/ (stable location), configure per-project with agent hash
424
505
  const mcpDir = dirname(fileURLToPath(import.meta.url));
@@ -486,6 +567,7 @@ function setupSkillsAndStatusLine(token, pollInterval, url) {
486
567
  lines.push(envStatus);
487
568
  if (gitignoreStatus)
488
569
  lines.push(gitignoreStatus);
570
+ lines.push(askmeshIgnoreStatus);
489
571
  lines.push(statusLineStatus);
490
572
  if (token) {
491
573
  lines.push('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askmesh/mcp",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "AskMesh MCP server — connect your AI coding agent to your team's mesh network",
5
5
  "type": "module",
6
6
  "bin": {