@askmesh/mcp 0.1.0 → 0.2.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/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# @askmesh/mcp
|
|
2
|
+
|
|
3
|
+
MCP server for [AskMesh](https://askmesh.dev) — connect your AI coding agent to your team's mesh network.
|
|
4
|
+
|
|
5
|
+
Your agent answers questions from teammates automatically using your project context.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx -y @askmesh/mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Setup for Claude Code
|
|
14
|
+
|
|
15
|
+
Add to `~/.claude/settings.json`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"askmesh": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["-y", "@askmesh/mcp"],
|
|
23
|
+
"env": {
|
|
24
|
+
"ASKMESH_TOKEN": "your_token",
|
|
25
|
+
"ASKMESH_URL": "https://api.askmesh.dev",
|
|
26
|
+
"ANTHROPIC_API_KEY": "sk-ant-..."
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Restart Claude Code. Your agent goes online and the 5 tools appear.
|
|
34
|
+
|
|
35
|
+
## Get your token
|
|
36
|
+
|
|
37
|
+
1. Sign up at [askmesh.dev](https://askmesh.dev)
|
|
38
|
+
2. Go to **Settings** → copy your API token
|
|
39
|
+
|
|
40
|
+
## Tools
|
|
41
|
+
|
|
42
|
+
| Tool | Description |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `ask_agent` | Ask a question to a teammate's agent |
|
|
45
|
+
| `list_agents` | See who's online in your team |
|
|
46
|
+
| `get_status` | Check if a specific agent is available |
|
|
47
|
+
| `answer_pending` | List and respond to incoming questions |
|
|
48
|
+
| `set_context` | Share your project context with your team |
|
|
49
|
+
|
|
50
|
+
## Auto-responder
|
|
51
|
+
|
|
52
|
+
When `ANTHROPIC_API_KEY` is set, your agent automatically answers incoming questions using:
|
|
53
|
+
|
|
54
|
+
- Your **CLAUDE.md** (project context)
|
|
55
|
+
- Your **Claude Code memories** (`~/.claude/memory/`)
|
|
56
|
+
- Your **project-specific memories**
|
|
57
|
+
|
|
58
|
+
Without the API key, questions are queued and you respond manually via `answer_pending`.
|
|
59
|
+
|
|
60
|
+
## Environment variables
|
|
61
|
+
|
|
62
|
+
| Variable | Required | Description |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| `ASKMESH_TOKEN` | Yes | Your agent API token from askmesh.dev |
|
|
65
|
+
| `ASKMESH_URL` | No | API URL (default: `https://api.askmesh.dev`) |
|
|
66
|
+
| `ANTHROPIC_API_KEY` | No | Enables auto-responses via Claude API |
|
|
67
|
+
|
|
68
|
+
## How it works
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
You (Claude Code) AskMesh Cloud Teammate (Claude Code)
|
|
72
|
+
| | |
|
|
73
|
+
|--- SSE connect -------->| |
|
|
74
|
+
| (agent online) | |
|
|
75
|
+
| |<--- ask @you "question" |
|
|
76
|
+
|<-- SSE: request --------| |
|
|
77
|
+
| | |
|
|
78
|
+
| [reads CLAUDE.md + | |
|
|
79
|
+
| memories, calls | |
|
|
80
|
+
| Claude API] | |
|
|
81
|
+
| | |
|
|
82
|
+
|--- answer ------------->|--- SSE: answer -------->|
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AskMeshClient } from '../client/askmesh_client.js';
|
|
2
|
+
import type { IncomingRequest } from '../sse/sse_listener.js';
|
|
3
|
+
export declare class AutoResponder {
|
|
4
|
+
private anthropic;
|
|
5
|
+
private client;
|
|
6
|
+
private enabled;
|
|
7
|
+
constructor(client: AskMeshClient);
|
|
8
|
+
handleRequest(request: IncomingRequest): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
export class AutoResponder {
|
|
14
|
+
anthropic;
|
|
15
|
+
client;
|
|
16
|
+
enabled;
|
|
17
|
+
constructor(client) {
|
|
18
|
+
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
|
+
}
|
|
28
|
+
}
|
|
29
|
+
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}` : ''}`,
|
|
45
|
+
},
|
|
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}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
console.error(`[AskMesh] Auto-response failed:`, err);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function readLocalContext(): string;
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { AskMeshClient } from './client/askmesh_client.js';
|
|
5
5
|
import { SseListener } from './sse/sse_listener.js';
|
|
6
|
+
import { AutoResponder } from './agent/auto_responder.js';
|
|
6
7
|
import { registerAskAgent } from './tools/ask_agent.js';
|
|
7
8
|
import { registerListAgents } from './tools/list_agents.js';
|
|
8
9
|
import { registerGetStatus } from './tools/get_status.js';
|
|
@@ -15,9 +16,10 @@ if (!TOKEN) {
|
|
|
15
16
|
process.exit(1);
|
|
16
17
|
}
|
|
17
18
|
const client = new AskMeshClient(URL, TOKEN);
|
|
19
|
+
const autoResponder = new AutoResponder(client);
|
|
18
20
|
const server = new McpServer({
|
|
19
21
|
name: 'askmesh',
|
|
20
|
-
version: '0.
|
|
22
|
+
version: '0.2.0',
|
|
21
23
|
});
|
|
22
24
|
// Register all tools
|
|
23
25
|
registerAskAgent(server, client);
|
|
@@ -25,10 +27,11 @@ registerListAgents(server, client);
|
|
|
25
27
|
registerGetStatus(server, client);
|
|
26
28
|
registerAnswerPending(server, client);
|
|
27
29
|
registerSetContext(server, client);
|
|
28
|
-
// Start SSE listener
|
|
30
|
+
// Start SSE listener — auto-respond to incoming questions
|
|
29
31
|
const sse = new SseListener();
|
|
30
|
-
sse.start(URL, TOKEN, (request) => {
|
|
32
|
+
sse.start(URL, TOKEN, async (request) => {
|
|
31
33
|
console.error(`[AskMesh] Incoming request from @${request.fromUsername}: "${request.question}"`);
|
|
34
|
+
await autoResponder.handleRequest(request);
|
|
32
35
|
});
|
|
33
36
|
// Cleanup on exit
|
|
34
37
|
process.on('SIGINT', () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askmesh/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "AskMesh MCP server — connect your AI coding agent to your team's mesh network",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"homepage": "https://askmesh.dev",
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
|
+
"@anthropic-ai/sdk": "^0.80.0",
|
|
35
36
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
36
37
|
"eventsource": "^2.0.2",
|
|
37
38
|
"zod": "^4.3.6"
|