agent-desk-mcp 0.1.4 → 1.0.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 +20 -25
- package/bin/agent-desk-mcp.js +11 -73
- package/dist/index.d.ts +1 -0
- package/dist/index.js +209 -0
- package/package.json +15 -6
- package/src/index.ts +276 -0
- package/tsconfig.json +14 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# agent-desk-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
MCP server for multi-agent interaction with users - enables blocking question capability with message queue management.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -9,6 +9,11 @@ npx wrapper for Agent Desk MCP Server - enables multi-agent blocking question ca
|
|
|
9
9
|
npx agent-desk-mcp --api-url http://localhost:8080
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- Node.js >= 18.0.0
|
|
15
|
+
- API Server running (see [agent-desk](https://github.com/weidwonder/agent-desk))
|
|
16
|
+
|
|
12
17
|
## Usage
|
|
13
18
|
|
|
14
19
|
### Basic Usage
|
|
@@ -18,31 +23,21 @@ npx agent-desk-mcp --api-url http://localhost:8080
|
|
|
18
23
|
npx agent-desk-mcp --api-url http://localhost:8080
|
|
19
24
|
```
|
|
20
25
|
|
|
21
|
-
### With
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
# Use custom database path
|
|
25
|
-
npx agent-desk-mcp --api-url http://localhost:8080 --db-path /path/to/data.db
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### Environment Variables
|
|
26
|
+
### With Claude Desktop
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
Add to your Claude Desktop configuration:
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"agent-desk": {
|
|
34
|
+
"command": "npx",
|
|
35
|
+
"args": ["agent-desk-mcp", "--api-url", "http://localhost:8080"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
# Clone and link
|
|
42
|
-
git clone https://github.com/weidwonder/agent-desk.git
|
|
43
|
-
cd agent-desk/mcp-wrapper
|
|
44
|
-
npm link
|
|
41
|
+
## License
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
agent-desk-mcp --api-url http://localhost:8080
|
|
48
|
-
```
|
|
43
|
+
MIT
|
package/bin/agent-desk-mcp.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* agent-desk-mcp
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* MCP Server for multi-agent interaction with users
|
|
6
6
|
* Usage: npx agent-desk-mcp --api-url http://localhost:8080
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -10,83 +10,22 @@ const { spawn } = require('child_process');
|
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
|
|
13
|
-
// Find Python interpreter
|
|
14
|
-
function findPython() {
|
|
15
|
-
// Check for python3 first, then python
|
|
16
|
-
const candidates = ['python3', 'python'];
|
|
17
|
-
|
|
18
|
-
for (const candidate of candidates) {
|
|
19
|
-
try {
|
|
20
|
-
const result = require('child_process').execSync(`which ${candidate}`, { encoding: 'utf8' }).trim();
|
|
21
|
-
if (result) return candidate;
|
|
22
|
-
} catch (e) {
|
|
23
|
-
// Continue to next candidate
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Try common paths
|
|
28
|
-
const commonPaths = [
|
|
29
|
-
'/usr/bin/python3',
|
|
30
|
-
'/usr/local/bin/python3',
|
|
31
|
-
'/opt/homebrew/bin/python3',
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
for (const p of commonPaths) {
|
|
35
|
-
if (fs.existsSync(p)) return p;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return 'python3';
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Find agent-desk source directory
|
|
42
|
-
function findAgentDeskSource() {
|
|
43
|
-
// First check AGENT_DESK_SOURCE env variable
|
|
44
|
-
if (process.env.AGENT_DESK_SOURCE) {
|
|
45
|
-
return process.env.AGENT_DESK_SOURCE;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Look for src/cli.py relative to this script
|
|
49
|
-
const scriptDir = path.dirname(__filename);
|
|
50
|
-
const wrapperDir = path.dirname(scriptDir);
|
|
51
|
-
const projectDir = path.dirname(wrapperDir);
|
|
52
|
-
const cliPath = path.join(projectDir, 'src', 'cli.py');
|
|
53
|
-
|
|
54
|
-
if (fs.existsSync(cliPath)) {
|
|
55
|
-
return projectDir;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Check current working directory
|
|
59
|
-
const cwdCliPath = path.join(process.cwd(), 'src', 'cli.py');
|
|
60
|
-
if (fs.existsSync(cwdCliPath)) {
|
|
61
|
-
return process.cwd();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Return default (assumes agent-desk is installed as a package)
|
|
65
|
-
return process.cwd();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
13
|
function main() {
|
|
69
14
|
const args = process.argv.slice(2);
|
|
70
|
-
const python = findPython();
|
|
71
|
-
const sourceDir = findAgentDeskSource();
|
|
72
15
|
|
|
73
|
-
//
|
|
74
|
-
const
|
|
75
|
-
const
|
|
16
|
+
// Find the dist/index.js
|
|
17
|
+
const scriptDir = path.dirname(__filename);
|
|
18
|
+
const distPath = path.join(scriptDir, '..', 'dist', 'index.js');
|
|
76
19
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
20
|
+
if (!fs.existsSync(distPath)) {
|
|
21
|
+
console.error("Error: agent-desk-mcp not built. Run 'npm install && npm run build' first.");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
81
24
|
|
|
82
|
-
// Spawn
|
|
83
|
-
const proc = spawn(
|
|
84
|
-
cwd: sourceDir,
|
|
25
|
+
// Spawn the server
|
|
26
|
+
const proc = spawn(process.execPath, [distPath, ...args], {
|
|
85
27
|
stdio: 'inherit',
|
|
86
|
-
env: {
|
|
87
|
-
...process.env,
|
|
88
|
-
PYTHONPATH: sourceDir,
|
|
89
|
-
},
|
|
28
|
+
env: { ...process.env },
|
|
90
29
|
});
|
|
91
30
|
|
|
92
31
|
proc.on('error', (err) => {
|
|
@@ -98,7 +37,6 @@ function main() {
|
|
|
98
37
|
process.exit(code || 0);
|
|
99
38
|
});
|
|
100
39
|
|
|
101
|
-
// Handle signals
|
|
102
40
|
process.on('SIGINT', () => proc.kill('SIGINT'));
|
|
103
41
|
process.on('SIGTERM', () => proc.kill('SIGTERM'));
|
|
104
42
|
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
class APIClient {
|
|
6
|
+
client;
|
|
7
|
+
constructor(baseUrl) {
|
|
8
|
+
this.client = axios.create({
|
|
9
|
+
baseURL: baseUrl.replace(/\/$/, ""),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
async healthCheck() {
|
|
13
|
+
try {
|
|
14
|
+
const resp = await this.client.get("/api/health");
|
|
15
|
+
return resp.status === 200;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async addAgent(sessionId, agentSummary, context, questions, timeoutSeconds) {
|
|
22
|
+
await this.client.post("/api/agents", {
|
|
23
|
+
session_id: sessionId,
|
|
24
|
+
agent_summary: agentSummary,
|
|
25
|
+
context: context.slice(0, 1000),
|
|
26
|
+
questions,
|
|
27
|
+
timeout_seconds: timeoutSeconds,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async getAgent(sessionId) {
|
|
31
|
+
try {
|
|
32
|
+
const resp = await this.client.get(`/api/agents/${sessionId}`);
|
|
33
|
+
return resp.data;
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
if (axios.isAxiosError(e) && e.response?.status === 404) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
throw e;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
class AgentDeskServer {
|
|
44
|
+
server;
|
|
45
|
+
apiClient = null;
|
|
46
|
+
apiUrl;
|
|
47
|
+
constructor(apiUrl) {
|
|
48
|
+
this.apiUrl = apiUrl;
|
|
49
|
+
this.server = new Server({
|
|
50
|
+
name: "agent-desk",
|
|
51
|
+
version: "1.0.0",
|
|
52
|
+
});
|
|
53
|
+
this.setupHandlers();
|
|
54
|
+
}
|
|
55
|
+
setupHandlers() {
|
|
56
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
57
|
+
return {
|
|
58
|
+
tools: [
|
|
59
|
+
{
|
|
60
|
+
name: "ask_user",
|
|
61
|
+
description: `Ask user questions, block and wait for answer.
|
|
62
|
+
|
|
63
|
+
Arguments:
|
|
64
|
+
- agent_summary: Brief summary of your role/identity.
|
|
65
|
+
- context: Brief context (max 1000 chars) explaining why you are asking these questions.
|
|
66
|
+
- questions: List of questions with id, content, and optional options.
|
|
67
|
+
- timeout_seconds: Timeout in seconds. None means no timeout.
|
|
68
|
+
|
|
69
|
+
Returns: User's answers concatenated with newlines, or timeout message.`,
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {
|
|
73
|
+
agent_summary: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "Brief summary of your role/identity",
|
|
76
|
+
},
|
|
77
|
+
context: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "Brief context explaining why you are asking these questions",
|
|
80
|
+
},
|
|
81
|
+
questions: {
|
|
82
|
+
type: "array",
|
|
83
|
+
description: "List of questions",
|
|
84
|
+
items: {
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: {
|
|
87
|
+
id: {
|
|
88
|
+
type: "string",
|
|
89
|
+
description: "Unique question identifier",
|
|
90
|
+
},
|
|
91
|
+
content: {
|
|
92
|
+
type: "string",
|
|
93
|
+
description: "Question text",
|
|
94
|
+
},
|
|
95
|
+
options: {
|
|
96
|
+
type: "array",
|
|
97
|
+
description: "Optional list of choices",
|
|
98
|
+
items: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: {
|
|
101
|
+
value: { type: "string" },
|
|
102
|
+
label: { type: "string" },
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
required: ["id", "content"],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
timeout_seconds: {
|
|
111
|
+
type: "number",
|
|
112
|
+
description: "Timeout in seconds",
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
required: ["agent_summary", "context", "questions"],
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
122
|
+
const { name, arguments: args } = request.params;
|
|
123
|
+
if (name !== "ask_user") {
|
|
124
|
+
return {
|
|
125
|
+
content: [
|
|
126
|
+
{
|
|
127
|
+
type: "text",
|
|
128
|
+
text: `Unknown tool: ${name}`,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return this.handleAskUser(args ?? {});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
async handleAskUser(args) {
|
|
137
|
+
const { agent_summary, context, questions, timeout_seconds, } = args;
|
|
138
|
+
const sessionId = crypto.randomUUID();
|
|
139
|
+
// Ensure questions have IDs
|
|
140
|
+
const processedQuestions = questions.map((q, i) => ({
|
|
141
|
+
...q,
|
|
142
|
+
id: q.id || `Q${i + 1}`,
|
|
143
|
+
}));
|
|
144
|
+
await this.apiClient.addAgent(sessionId, agent_summary, context, processedQuestions, timeout_seconds);
|
|
145
|
+
// Poll for answer
|
|
146
|
+
const pollInterval = 1000; // 1 second
|
|
147
|
+
const startTime = Date.now();
|
|
148
|
+
while (true) {
|
|
149
|
+
// Check timeout
|
|
150
|
+
if (timeout_seconds && (Date.now() - startTime) >= timeout_seconds * 1000) {
|
|
151
|
+
return {
|
|
152
|
+
content: [
|
|
153
|
+
{
|
|
154
|
+
type: "text",
|
|
155
|
+
text: "User did not respond within the timeout period.",
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// Check for answer
|
|
161
|
+
const agent = await this.apiClient.getAgent(sessionId);
|
|
162
|
+
if (agent && agent.status === "answered") {
|
|
163
|
+
const answers = agent.questions
|
|
164
|
+
.filter((q) => q.answer)
|
|
165
|
+
.map((q) => `${q.id}: ${q.answer}`);
|
|
166
|
+
return {
|
|
167
|
+
content: [
|
|
168
|
+
{
|
|
169
|
+
type: "text",
|
|
170
|
+
text: answers.join("\n"),
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async run() {
|
|
179
|
+
this.apiClient = new APIClient(this.apiUrl);
|
|
180
|
+
// Check API health
|
|
181
|
+
const healthy = await this.apiClient.healthCheck();
|
|
182
|
+
if (!healthy) {
|
|
183
|
+
console.error(`Error: API Server at ${this.apiUrl} is not available.`);
|
|
184
|
+
console.error("Please ensure the API Server is running.");
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
const transport = new StdioServerTransport();
|
|
188
|
+
await this.server.connect(transport);
|
|
189
|
+
console.error("Agent Desk MCP Server started");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// CLI entry point
|
|
193
|
+
async function main() {
|
|
194
|
+
const args = process.argv.slice(2);
|
|
195
|
+
let apiUrl = "http://localhost:8080";
|
|
196
|
+
// Parse arguments
|
|
197
|
+
for (let i = 0; i < args.length; i++) {
|
|
198
|
+
if (args[i] === "--api-url" && i + 1 < args.length) {
|
|
199
|
+
apiUrl = args[i + 1];
|
|
200
|
+
i++;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const server = new AgentDeskServer(apiUrl);
|
|
204
|
+
await server.run();
|
|
205
|
+
}
|
|
206
|
+
main().catch((err) => {
|
|
207
|
+
console.error("Fatal error:", err);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
});
|
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-desk-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for multi-agent interaction with users
|
|
5
|
-
"main": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for multi-agent interaction with users",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"bin": {
|
|
7
8
|
"agent-desk-mcp": "bin/agent-desk-mcp.js"
|
|
8
9
|
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
9
14
|
"keywords": [
|
|
10
15
|
"mcp",
|
|
11
16
|
"agent",
|
|
@@ -14,9 +19,13 @@
|
|
|
14
19
|
],
|
|
15
20
|
"author": "weidwonder",
|
|
16
21
|
"license": "MIT",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
24
|
+
"axios": "^1.7.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.0.0",
|
|
28
|
+
"typescript": "^5.0.0"
|
|
20
29
|
},
|
|
21
30
|
"engines": {
|
|
22
31
|
"node": ">=18.0.0"
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import {
|
|
4
|
+
CallToolRequestSchema,
|
|
5
|
+
ListToolsRequestSchema,
|
|
6
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import axios, { AxiosInstance } from "axios";
|
|
8
|
+
|
|
9
|
+
interface Question {
|
|
10
|
+
id: string;
|
|
11
|
+
content: string;
|
|
12
|
+
options?: Array<{ value: string; label: string }>;
|
|
13
|
+
answer?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface AgentData {
|
|
17
|
+
session_id: string;
|
|
18
|
+
agent_summary: string;
|
|
19
|
+
context: string;
|
|
20
|
+
questions: Question[];
|
|
21
|
+
status: "pending" | "answered" | "ignored";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class APIClient {
|
|
25
|
+
private client: AxiosInstance;
|
|
26
|
+
|
|
27
|
+
constructor(baseUrl: string) {
|
|
28
|
+
this.client = axios.create({
|
|
29
|
+
baseURL: baseUrl.replace(/\/$/, ""),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async healthCheck(): Promise<boolean> {
|
|
34
|
+
try {
|
|
35
|
+
const resp = await this.client.get("/api/health");
|
|
36
|
+
return resp.status === 200;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async addAgent(
|
|
43
|
+
sessionId: string,
|
|
44
|
+
agentSummary: string,
|
|
45
|
+
context: string,
|
|
46
|
+
questions: Question[],
|
|
47
|
+
timeoutSeconds?: number
|
|
48
|
+
): Promise<void> {
|
|
49
|
+
await this.client.post("/api/agents", {
|
|
50
|
+
session_id: sessionId,
|
|
51
|
+
agent_summary: agentSummary,
|
|
52
|
+
context: context.slice(0, 1000),
|
|
53
|
+
questions,
|
|
54
|
+
timeout_seconds: timeoutSeconds,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async getAgent(sessionId: string): Promise<AgentData | null> {
|
|
59
|
+
try {
|
|
60
|
+
const resp = await this.client.get(`/api/agents/${sessionId}`);
|
|
61
|
+
return resp.data;
|
|
62
|
+
} catch (e) {
|
|
63
|
+
if (axios.isAxiosError(e) && e.response?.status === 404) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
throw e;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class AgentDeskServer {
|
|
72
|
+
private server: Server;
|
|
73
|
+
private apiClient: APIClient | null = null;
|
|
74
|
+
private apiUrl: string;
|
|
75
|
+
|
|
76
|
+
constructor(apiUrl: string) {
|
|
77
|
+
this.apiUrl = apiUrl;
|
|
78
|
+
this.server = new Server({
|
|
79
|
+
name: "agent-desk",
|
|
80
|
+
version: "1.0.0",
|
|
81
|
+
});
|
|
82
|
+
this.setupHandlers();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private setupHandlers(): void {
|
|
86
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
87
|
+
return {
|
|
88
|
+
tools: [
|
|
89
|
+
{
|
|
90
|
+
name: "ask_user",
|
|
91
|
+
description: `Ask user questions, block and wait for answer.
|
|
92
|
+
|
|
93
|
+
Arguments:
|
|
94
|
+
- agent_summary: Brief summary of your role/identity.
|
|
95
|
+
- context: Brief context (max 1000 chars) explaining why you are asking these questions.
|
|
96
|
+
- questions: List of questions with id, content, and optional options.
|
|
97
|
+
- timeout_seconds: Timeout in seconds. None means no timeout.
|
|
98
|
+
|
|
99
|
+
Returns: User's answers concatenated with newlines, or timeout message.`,
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
agent_summary: {
|
|
104
|
+
type: "string",
|
|
105
|
+
description: "Brief summary of your role/identity",
|
|
106
|
+
},
|
|
107
|
+
context: {
|
|
108
|
+
type: "string",
|
|
109
|
+
description: "Brief context explaining why you are asking these questions",
|
|
110
|
+
},
|
|
111
|
+
questions: {
|
|
112
|
+
type: "array",
|
|
113
|
+
description: "List of questions",
|
|
114
|
+
items: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {
|
|
117
|
+
id: {
|
|
118
|
+
type: "string",
|
|
119
|
+
description: "Unique question identifier",
|
|
120
|
+
},
|
|
121
|
+
content: {
|
|
122
|
+
type: "string",
|
|
123
|
+
description: "Question text",
|
|
124
|
+
},
|
|
125
|
+
options: {
|
|
126
|
+
type: "array",
|
|
127
|
+
description: "Optional list of choices",
|
|
128
|
+
items: {
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: {
|
|
131
|
+
value: { type: "string" },
|
|
132
|
+
label: { type: "string" },
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
required: ["id", "content"],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
timeout_seconds: {
|
|
141
|
+
type: "number",
|
|
142
|
+
description: "Timeout in seconds",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
required: ["agent_summary", "context", "questions"],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
153
|
+
const { name, arguments: args } = request.params;
|
|
154
|
+
|
|
155
|
+
if (name !== "ask_user") {
|
|
156
|
+
return {
|
|
157
|
+
content: [
|
|
158
|
+
{
|
|
159
|
+
type: "text",
|
|
160
|
+
text: `Unknown tool: ${name}`,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return this.handleAskUser(args ?? {});
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private async handleAskUser(args: Record<string, unknown>): Promise<{
|
|
171
|
+
content: Array<{ type: string; text: string }>;
|
|
172
|
+
}> {
|
|
173
|
+
const {
|
|
174
|
+
agent_summary,
|
|
175
|
+
context,
|
|
176
|
+
questions,
|
|
177
|
+
timeout_seconds,
|
|
178
|
+
} = args as {
|
|
179
|
+
agent_summary: string;
|
|
180
|
+
context: string;
|
|
181
|
+
questions: Question[];
|
|
182
|
+
timeout_seconds?: number;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const sessionId = crypto.randomUUID();
|
|
186
|
+
|
|
187
|
+
// Ensure questions have IDs
|
|
188
|
+
const processedQuestions = questions.map((q, i) => ({
|
|
189
|
+
...q,
|
|
190
|
+
id: q.id || `Q${i + 1}`,
|
|
191
|
+
}));
|
|
192
|
+
|
|
193
|
+
await this.apiClient!.addAgent(
|
|
194
|
+
sessionId,
|
|
195
|
+
agent_summary,
|
|
196
|
+
context,
|
|
197
|
+
processedQuestions,
|
|
198
|
+
timeout_seconds
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Poll for answer
|
|
202
|
+
const pollInterval = 1000; // 1 second
|
|
203
|
+
const startTime = Date.now();
|
|
204
|
+
|
|
205
|
+
while (true) {
|
|
206
|
+
// Check timeout
|
|
207
|
+
if (timeout_seconds && (Date.now() - startTime) >= timeout_seconds * 1000) {
|
|
208
|
+
return {
|
|
209
|
+
content: [
|
|
210
|
+
{
|
|
211
|
+
type: "text",
|
|
212
|
+
text: "User did not respond within the timeout period.",
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check for answer
|
|
219
|
+
const agent = await this.apiClient!.getAgent(sessionId);
|
|
220
|
+
if (agent && agent.status === "answered") {
|
|
221
|
+
const answers = agent.questions
|
|
222
|
+
.filter((q) => q.answer)
|
|
223
|
+
.map((q) => `${q.id}: ${q.answer}`);
|
|
224
|
+
return {
|
|
225
|
+
content: [
|
|
226
|
+
{
|
|
227
|
+
type: "text",
|
|
228
|
+
text: answers.join("\n"),
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async run(): Promise<void> {
|
|
239
|
+
this.apiClient = new APIClient(this.apiUrl);
|
|
240
|
+
|
|
241
|
+
// Check API health
|
|
242
|
+
const healthy = await this.apiClient.healthCheck();
|
|
243
|
+
if (!healthy) {
|
|
244
|
+
console.error(`Error: API Server at ${this.apiUrl} is not available.`);
|
|
245
|
+
console.error("Please ensure the API Server is running.");
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const transport = new StdioServerTransport();
|
|
250
|
+
await this.server.connect(transport);
|
|
251
|
+
|
|
252
|
+
console.error("Agent Desk MCP Server started");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// CLI entry point
|
|
257
|
+
async function main(): Promise<void> {
|
|
258
|
+
const args = process.argv.slice(2);
|
|
259
|
+
let apiUrl = "http://localhost:8080";
|
|
260
|
+
|
|
261
|
+
// Parse arguments
|
|
262
|
+
for (let i = 0; i < args.length; i++) {
|
|
263
|
+
if (args[i] === "--api-url" && i + 1 < args.length) {
|
|
264
|
+
apiUrl = args[i + 1];
|
|
265
|
+
i++;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const server = new AgentDeskServer(apiUrl);
|
|
270
|
+
await server.run();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
main().catch((err) => {
|
|
274
|
+
console.error("Fatal error:", err);
|
|
275
|
+
process.exit(1);
|
|
276
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"rootDir": "./src",
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"skipLibCheck": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|