@askmesh/mcp 0.2.0 → 0.2.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.
@@ -1,9 +1,10 @@
1
1
  import type { AskMeshClient } from '../client/askmesh_client.js';
2
2
  import type { IncomingRequest } from '../sse/sse_listener.js';
3
+ import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
4
  export declare class AutoResponder {
4
- private anthropic;
5
5
  private client;
6
- private enabled;
6
+ private mcpServer;
7
7
  constructor(client: AskMeshClient);
8
+ setServer(server: Server): void;
8
9
  handleRequest(request: IncomingRequest): Promise<void>;
9
10
  }
@@ -1,61 +1,51 @@
1
- import Anthropic from '@anthropic-ai/sdk';
2
- import { readLocalContext } from './context_reader.js';
3
- const SYSTEM_PROMPT = `You are an AI coding agent responding on behalf of a developer through AskMesh.
4
- You have access to the developer's project context, CLAUDE.md, and memories below.
5
- Use this context to answer the question as if you were the developer's agent.
6
-
7
- Rules:
8
- - Answer concisely and technically
9
- - Reference specific files, conventions, or decisions from the context when relevant
10
- - If the context doesn't contain enough info to answer, say so honestly
11
- - Keep responses under 500 words unless the question requires more detail
12
- - Answer in the same language as the question`;
13
1
  export class AutoResponder {
14
- anthropic;
15
2
  client;
16
- enabled;
3
+ mcpServer = null;
17
4
  constructor(client) {
18
5
  this.client = client;
19
- this.enabled = !!process.env.ANTHROPIC_API_KEY;
20
- if (this.enabled) {
21
- this.anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
22
- console.error('[AskMesh] Auto-responder enabled');
23
- }
24
- else {
25
- this.anthropic = null;
26
- console.error('[AskMesh] Auto-responder disabled (no ANTHROPIC_API_KEY)');
27
- }
6
+ }
7
+ setServer(server) {
8
+ this.mcpServer = server;
28
9
  }
29
10
  async handleRequest(request) {
30
- if (!this.enabled) {
31
- console.error(`[AskMesh] Question from @${request.fromUsername}manual response needed (no API key)`);
32
- return;
33
- }
34
- console.error(`[AskMesh] Auto-responding to @${request.fromUsername}: "${request.question}"`);
35
- try {
36
- const context = readLocalContext();
37
- const response = await this.anthropic.messages.create({
38
- model: 'claude-sonnet-4-20250514',
39
- max_tokens: 2048,
40
- system: SYSTEM_PROMPT,
41
- messages: [
42
- {
43
- role: 'user',
44
- content: `Here is my project context:\n\n${context}\n\n---\n\nQuestion from @${request.fromUsername}:\n${request.question}${request.context ? `\n\nAdditional context: ${request.context}` : ''}`,
11
+ console.error(`[AskMesh] Question from @${request.fromUsername}: "${request.question}"`);
12
+ // Try MCP samplingasks the active Claude Code to generate a response
13
+ if (this.mcpServer) {
14
+ try {
15
+ const result = (await this.mcpServer.request({
16
+ method: 'sampling/createMessage',
17
+ params: {
18
+ messages: [
19
+ {
20
+ role: 'user',
21
+ content: {
22
+ type: 'text',
23
+ text: [
24
+ `A teammate @${request.fromUsername} is asking you a question via AskMesh.`,
25
+ ``,
26
+ `Question: "${request.question}"`,
27
+ request.context ? `\nContext: ${request.context}` : '',
28
+ ``,
29
+ `Answer based on your knowledge of this project. Be concise and technical.`,
30
+ `Answer in the same language as the question.`,
31
+ ].join('\n'),
32
+ },
33
+ },
34
+ ],
35
+ maxTokens: 2048,
45
36
  },
46
- ],
47
- });
48
- const answer = response.content
49
- .filter((block) => block.type === 'text')
50
- .map((block) => block.text)
51
- .join('\n');
52
- if (answer) {
53
- await this.client.answerRequest(request.id, answer);
54
- console.error(`[AskMesh] Auto-responded to request #${request.id}`);
37
+ }, {}));
38
+ const answer = result?.content?.text || result?.content?.[0]?.text;
39
+ if (answer) {
40
+ await this.client.answerRequest(request.id, answer);
41
+ console.error(`[AskMesh] Responded to #${request.id} via Claude Code`);
42
+ return;
43
+ }
44
+ }
45
+ catch {
46
+ console.error('[AskMesh] Sampling not available — question queued');
55
47
  }
56
48
  }
57
- catch (err) {
58
- console.error(`[AskMesh] Auto-response failed:`, err);
59
- }
49
+ console.error(`[AskMesh] Question queued — use answer_pending to respond`);
60
50
  }
61
51
  }
package/dist/index.js CHANGED
@@ -27,10 +27,11 @@ registerListAgents(server, client);
27
27
  registerGetStatus(server, client);
28
28
  registerAnswerPending(server, client);
29
29
  registerSetContext(server, client);
30
+ // Give the auto-responder access to the underlying MCP server for sampling
31
+ autoResponder.setServer(server.server);
30
32
  // Start SSE listener — auto-respond to incoming questions
31
33
  const sse = new SseListener();
32
34
  sse.start(URL, TOKEN, async (request) => {
33
- console.error(`[AskMesh] Incoming request from @${request.fromUsername}: "${request.question}"`);
34
35
  await autoResponder.handleRequest(request);
35
36
  });
36
37
  // Cleanup on exit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askmesh/mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "AskMesh MCP server — connect your AI coding agent to your team's mesh network",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- export declare function readLocalContext(): string;
@@ -1,68 +0,0 @@
1
- import { readFileSync, readdirSync, existsSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { homedir } from 'node:os';
4
- export function readLocalContext() {
5
- const parts = [];
6
- // 1. Read CLAUDE.md from current working directory
7
- const claudeMdPath = join(process.cwd(), 'CLAUDE.md');
8
- if (existsSync(claudeMdPath)) {
9
- parts.push('=== CLAUDE.md (project context) ===');
10
- parts.push(readSafe(claudeMdPath));
11
- }
12
- // 2. Read Claude Code memories
13
- const memoryDir = join(homedir(), '.claude', 'memory');
14
- if (existsSync(memoryDir)) {
15
- try {
16
- const files = readdirSync(memoryDir).filter((f) => f.endsWith('.md'));
17
- if (files.length > 0) {
18
- parts.push('=== Claude Code memories ===');
19
- for (const file of files) {
20
- const content = readSafe(join(memoryDir, file));
21
- if (content) {
22
- parts.push(`--- ${file} ---`);
23
- parts.push(content);
24
- }
25
- }
26
- }
27
- }
28
- catch { }
29
- }
30
- // 3. Read project-specific memories
31
- const cwd = process.cwd();
32
- const projectMemoryDir = join(homedir(), '.claude', 'projects');
33
- if (existsSync(projectMemoryDir)) {
34
- try {
35
- // Claude Code stores project memories in a hashed directory
36
- const dirs = readdirSync(projectMemoryDir);
37
- for (const dir of dirs) {
38
- // Check if this directory matches our project
39
- if (dir.includes(cwd.replace(/\//g, '-').replace(/^-/, ''))) {
40
- const memDir = join(projectMemoryDir, dir, 'memory');
41
- if (existsSync(memDir)) {
42
- const files = readdirSync(memDir).filter((f) => f.endsWith('.md'));
43
- if (files.length > 0) {
44
- parts.push('=== Project-specific memories ===');
45
- for (const file of files) {
46
- const content = readSafe(join(memDir, file));
47
- if (content) {
48
- parts.push(`--- ${file} ---`);
49
- parts.push(content);
50
- }
51
- }
52
- }
53
- }
54
- }
55
- }
56
- }
57
- catch { }
58
- }
59
- return parts.join('\n\n') || 'No local context available.';
60
- }
61
- function readSafe(path) {
62
- try {
63
- return readFileSync(path, 'utf-8').trim();
64
- }
65
- catch {
66
- return '';
67
- }
68
- }