@askmesh/mcp 0.11.1 → 0.12.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/agent/auto_responder.d.ts +23 -0
- package/dist/agent/auto_responder.js +72 -1
- package/dist/client/askmesh_client.d.ts +8 -0
- package/dist/client/askmesh_client.js +8 -0
- package/dist/index.js +15 -2
- package/dist/sse/sse_listener.d.ts +1 -0
- package/dist/tools/askmesh.js +1 -1
- package/package.json +1 -1
|
@@ -4,8 +4,31 @@ 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
|
+
* Filter scopes by the requester's role.
|
|
16
|
+
*
|
|
17
|
+
* Scopes can be encoded with a minimum-role prefix:
|
|
18
|
+
* "code" → all members
|
|
19
|
+
* "lead:db-schema" → lead, admin, owner only
|
|
20
|
+
* "admin:db-data" → admin, owner only
|
|
21
|
+
* "owner:secrets" → owner only
|
|
22
|
+
*
|
|
23
|
+
* Returns the bare scope names that this requester is allowed to use.
|
|
24
|
+
*/
|
|
25
|
+
private filterScopesByRole;
|
|
26
|
+
/**
|
|
27
|
+
* Build a scope-aware extension to the system prompt.
|
|
28
|
+
* Only applies for server agents — dev/ci agents are unrestricted.
|
|
29
|
+
* `requesterRole` is used to filter role-tiered scopes per request.
|
|
30
|
+
*/
|
|
31
|
+
private scopePrompt;
|
|
9
32
|
/**
|
|
10
33
|
* Send a reply through the AskMesh client with a pre-send redaction scan.
|
|
11
34
|
* If secrets are detected, the reply is blocked and a desktop notification +
|
|
@@ -22,12 +22,80 @@ 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
|
+
* Filter scopes by the requester's role.
|
|
37
|
+
*
|
|
38
|
+
* Scopes can be encoded with a minimum-role prefix:
|
|
39
|
+
* "code" → all members
|
|
40
|
+
* "lead:db-schema" → lead, admin, owner only
|
|
41
|
+
* "admin:db-data" → admin, owner only
|
|
42
|
+
* "owner:secrets" → owner only
|
|
43
|
+
*
|
|
44
|
+
* Returns the bare scope names that this requester is allowed to use.
|
|
45
|
+
*/
|
|
46
|
+
filterScopesByRole(rawScopes, requesterRole) {
|
|
47
|
+
const ROLE_RANK = { member: 1, lead: 2, admin: 3, owner: 4 };
|
|
48
|
+
const requesterRank = requesterRole ? (ROLE_RANK[requesterRole] || 0) : 0;
|
|
49
|
+
const result = [];
|
|
50
|
+
for (const raw of rawScopes) {
|
|
51
|
+
const idx = raw.indexOf(':');
|
|
52
|
+
if (idx === -1) {
|
|
53
|
+
// No prefix → available to everyone
|
|
54
|
+
result.push(raw);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const minRole = raw.slice(0, idx);
|
|
58
|
+
const scope = raw.slice(idx + 1);
|
|
59
|
+
const minRank = ROLE_RANK[minRole];
|
|
60
|
+
if (!minRank) {
|
|
61
|
+
// Unknown prefix → treat as plain scope (e.g. "http:something")
|
|
62
|
+
result.push(raw);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (requesterRank >= minRank) {
|
|
66
|
+
result.push(scope);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build a scope-aware extension to the system prompt.
|
|
73
|
+
* Only applies for server agents — dev/ci agents are unrestricted.
|
|
74
|
+
* `requesterRole` is used to filter role-tiered scopes per request.
|
|
75
|
+
*/
|
|
76
|
+
scopePrompt(requesterRole = null) {
|
|
77
|
+
if (!this.profile || this.profile.agentType !== 'server')
|
|
78
|
+
return '';
|
|
79
|
+
const allScopes = this.profile.allowedScopes || [];
|
|
80
|
+
const scopes = this.filterScopesByRole(allScopes, requesterRole);
|
|
81
|
+
const roleHint = requesterRole ? ` (requester role: ${requesterRole})` : ' (requester role: unknown)';
|
|
82
|
+
if (scopes.length === 0) {
|
|
83
|
+
return [
|
|
84
|
+
'',
|
|
85
|
+
`SERVER AGENT — RESTRICTIVE MODE${roleHint}:`,
|
|
86
|
+
'No allowed scopes apply for this requester. Refuse to share any project content.',
|
|
87
|
+
'Reply with: "This server agent is not authorized to share information at your role level."',
|
|
88
|
+
].join('\n');
|
|
89
|
+
}
|
|
90
|
+
const lines = ['', `SERVER AGENT — ALLOWED SCOPES${roleHint}:`];
|
|
91
|
+
lines.push('You are a restricted server agent. You may ONLY share information that falls within these scopes:');
|
|
92
|
+
for (const s of scopes) {
|
|
93
|
+
lines.push(` • ${s}`);
|
|
94
|
+
}
|
|
95
|
+
lines.push('');
|
|
96
|
+
lines.push('Refuse anything outside these scopes with: "This server agent is not authorized to share that information."');
|
|
97
|
+
return lines.join('\n');
|
|
98
|
+
}
|
|
31
99
|
/**
|
|
32
100
|
* Send a reply through the AskMesh client with a pre-send redaction scan.
|
|
33
101
|
* If secrets are detected, the reply is blocked and a desktop notification +
|
|
@@ -73,6 +141,7 @@ export class AutoResponder {
|
|
|
73
141
|
if (this.mcpServer) {
|
|
74
142
|
try {
|
|
75
143
|
const context = readLocalContext();
|
|
144
|
+
const scopeRules = this.scopePrompt(request.fromUserRole || null);
|
|
76
145
|
const result = (await this.mcpServer.request({
|
|
77
146
|
method: 'sampling/createMessage',
|
|
78
147
|
params: {
|
|
@@ -83,6 +152,7 @@ export class AutoResponder {
|
|
|
83
152
|
type: 'text',
|
|
84
153
|
text: [
|
|
85
154
|
`A teammate @${request.fromUsername} is asking you a question via AskMesh.`,
|
|
155
|
+
scopeRules,
|
|
86
156
|
``,
|
|
87
157
|
`Your project context:`,
|
|
88
158
|
context,
|
|
@@ -150,6 +220,7 @@ export class AutoResponder {
|
|
|
150
220
|
}
|
|
151
221
|
async callAnthropicAPI(apiKey, request, context) {
|
|
152
222
|
const model = process.env.ANTHROPIC_MODEL || 'claude-sonnet-4-20250514';
|
|
223
|
+
const fullSystem = SYSTEM_PROMPT + this.scopePrompt(request.fromUserRole || null);
|
|
153
224
|
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
154
225
|
method: 'POST',
|
|
155
226
|
headers: {
|
|
@@ -160,7 +231,7 @@ export class AutoResponder {
|
|
|
160
231
|
body: JSON.stringify({
|
|
161
232
|
model,
|
|
162
233
|
max_tokens: 2048,
|
|
163
|
-
system:
|
|
234
|
+
system: fullSystem,
|
|
164
235
|
messages: [
|
|
165
236
|
{
|
|
166
237
|
role: 'user',
|
|
@@ -29,6 +29,7 @@ export declare class AskMeshClient {
|
|
|
29
29
|
id: number;
|
|
30
30
|
fromAgentId: number;
|
|
31
31
|
fromUsername: string;
|
|
32
|
+
fromUserRole?: string | null;
|
|
32
33
|
question: string;
|
|
33
34
|
context: string | null;
|
|
34
35
|
status: string;
|
|
@@ -54,6 +55,13 @@ export declare class AskMeshClient {
|
|
|
54
55
|
status: string;
|
|
55
56
|
lastSeenAt: string | null;
|
|
56
57
|
}>;
|
|
58
|
+
getMe(): Promise<{
|
|
59
|
+
id: number;
|
|
60
|
+
username: string;
|
|
61
|
+
agentType: 'dev' | 'server' | 'ci';
|
|
62
|
+
requireApproval: boolean;
|
|
63
|
+
allowedScopes: string[];
|
|
64
|
+
}>;
|
|
57
65
|
setContext(context: string): Promise<{
|
|
58
66
|
message: string;
|
|
59
67
|
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.
|
|
14
|
+
version: '0.12.0',
|
|
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) => {
|
|
@@ -49,7 +61,8 @@ else {
|
|
|
49
61
|
await autoResponder.handleRequest({
|
|
50
62
|
id: req.id,
|
|
51
63
|
fromAgentId: req.fromAgentId,
|
|
52
|
-
fromUsername: `agent#${req.fromAgentId}`,
|
|
64
|
+
fromUsername: req.fromUsername || `agent#${req.fromAgentId}`,
|
|
65
|
+
fromUserRole: req.fromUserRole || null,
|
|
53
66
|
question: req.question,
|
|
54
67
|
context: req.context,
|
|
55
68
|
});
|
package/dist/tools/askmesh.js
CHANGED
|
@@ -282,7 +282,7 @@ Si l'utilisateur n'a pas encore de compte, dirige-le vers https://askmesh.dev po
|
|
|
282
282
|
return text('Action inconnue.');
|
|
283
283
|
});
|
|
284
284
|
}
|
|
285
|
-
const CURRENT_VERSION = '0.
|
|
285
|
+
const CURRENT_VERSION = '0.12.0';
|
|
286
286
|
async function checkUpdateAndSetup() {
|
|
287
287
|
const lines = [];
|
|
288
288
|
lines.push(`Version actuelle : ${CURRENT_VERSION}`);
|