@markusvankempen/wxo-agent-mcp 1.0.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.
- package/.env.example +8 -0
- package/DOCUMENTATION.md +554 -0
- package/LICENSE +21 -0
- package/README.md +131 -0
- package/dist/auth.js +50 -0
- package/dist/config.js +53 -0
- package/dist/index.js +139 -0
- package/examples/mcp.json +13 -0
- package/package.json +58 -0
package/.env.example
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Watson Orchestrate credentials (required)
|
|
2
|
+
WO_API_KEY=your-ibm-cloud-api-key
|
|
3
|
+
WO_INSTANCE_URL=https://your-instance-id.orchestrate.ibm.com
|
|
4
|
+
|
|
5
|
+
# Agent to invoke (required) - single agent defined by env
|
|
6
|
+
# WO_AGENT_ID=your-agent-id
|
|
7
|
+
# Or WO_AGENT_IDs=agent-id-1,agent-id-2 (first is used)
|
|
8
|
+
WO_AGENT_IDs=your-agent-id
|
package/DOCUMENTATION.md
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
# WxO Agent MCP – Documentation
|
|
2
|
+
|
|
3
|
+
**Version:** 1.0.0
|
|
4
|
+
**Author:** Markus van Kempen
|
|
5
|
+
**License:** Apache-2.0
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
- [Architecture](#architecture) – MCP as proxy, data flow, sequence, supported clients
|
|
12
|
+
- [Overview](#overview) – Purpose and comparison with wxo-builder-mcp-server
|
|
13
|
+
- [Project Structure](#project-structure)
|
|
14
|
+
- [Configuration](#configuration)
|
|
15
|
+
- [MCP Tools](#mcp-tools)
|
|
16
|
+
- [Question Examples](#question-examples)
|
|
17
|
+
- [Verification](#verification)
|
|
18
|
+
- [Running the Server](#running-the-server)
|
|
19
|
+
- [Testing in VS Code](#testing-locally-in-vs-code)
|
|
20
|
+
- [Testing in Langflow](#testing-in-langflow)
|
|
21
|
+
- [Troubleshooting](#troubleshooting)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Architecture
|
|
26
|
+
|
|
27
|
+
### MCP as Proxy to Watson Orchestrate
|
|
28
|
+
|
|
29
|
+
WxO Agent MCP acts as a **proxy** between MCP-enabled clients (IDEs, AI assistants, flow builders) and IBM Watson Orchestrate. Clients communicate via **MCP over stdio** (JSON-RPC); the MCP server translates tool calls into Watson Orchestrate REST API requests.
|
|
30
|
+
|
|
31
|
+
```mermaid
|
|
32
|
+
flowchart LR
|
|
33
|
+
subgraph Clients["MCP Clients (IDEs & Tools)"]
|
|
34
|
+
Cursor
|
|
35
|
+
VSCode["VS Code Copilot"]
|
|
36
|
+
Langflow
|
|
37
|
+
Claude["Claude Desktop"]
|
|
38
|
+
Antigravity
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
subgraph MCP["WxO Agent MCP"]
|
|
42
|
+
direction TB
|
|
43
|
+
invoke[invoke_agent]
|
|
44
|
+
get[get_agent]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
subgraph WXO["Watson Orchestrate"]
|
|
48
|
+
API[REST API]
|
|
49
|
+
Agent[Agent + Tools + LLM]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
Cursor -->|stdio / JSON-RPC| MCP
|
|
53
|
+
VSCode -->|stdio / JSON-RPC| MCP
|
|
54
|
+
Langflow -->|stdio / JSON-RPC| MCP
|
|
55
|
+
Claude -->|stdio / JSON-RPC| MCP
|
|
56
|
+
Antigravity -->|stdio / JSON-RPC| MCP
|
|
57
|
+
|
|
58
|
+
MCP -->|HTTP + Bearer token| API
|
|
59
|
+
API --> Agent
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Data Flow
|
|
63
|
+
|
|
64
|
+
```mermaid
|
|
65
|
+
flowchart TB
|
|
66
|
+
subgraph User["User"]
|
|
67
|
+
Ask["Ask: 'What is the weather in Amsterdam?'"]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
subgraph IDE["IDE / Client"]
|
|
71
|
+
Chat["Copilot Chat / Cursor / Langflow"]
|
|
72
|
+
MCPClient["MCP Client (spawns wxo-agent-mcp)"]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
subgraph WxOAgent["wxo-agent-mcp"]
|
|
76
|
+
Tools["invoke_agent / get_agent"]
|
|
77
|
+
Auth["IAM Token"]
|
|
78
|
+
Fetch["woFetch"]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
subgraph IBM["IBM Cloud"]
|
|
82
|
+
IAM["IAM (token exchange)"]
|
|
83
|
+
WXO["Watson Orchestrate"]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
Ask --> Chat
|
|
87
|
+
Chat -->|tool: invoke_agent| MCPClient
|
|
88
|
+
MCPClient -->|stdio| Tools
|
|
89
|
+
Tools --> Auth
|
|
90
|
+
Auth -->|API key| IAM
|
|
91
|
+
IAM -->|Bearer token| Auth
|
|
92
|
+
Tools --> Fetch
|
|
93
|
+
Fetch -->|POST /runs, GET /messages| WXO
|
|
94
|
+
WXO -->|agent response| Fetch
|
|
95
|
+
Fetch --> Tools
|
|
96
|
+
Tools --> MCPClient
|
|
97
|
+
MCPClient --> Chat
|
|
98
|
+
Chat --> User
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### invoke_agent Sequence
|
|
102
|
+
|
|
103
|
+
```mermaid
|
|
104
|
+
sequenceDiagram
|
|
105
|
+
participant User
|
|
106
|
+
participant IDE as Cursor / VS Code / Langflow
|
|
107
|
+
participant MCP as wxo-agent-mcp
|
|
108
|
+
participant IAM as IBM IAM
|
|
109
|
+
participant WXO as Watson Orchestrate
|
|
110
|
+
|
|
111
|
+
User->>IDE: "Ask: What is the weather?"
|
|
112
|
+
IDE->>MCP: Call tool: invoke_agent(message)
|
|
113
|
+
MCP->>IAM: Exchange API key for Bearer token
|
|
114
|
+
IAM-->>MCP: access_token
|
|
115
|
+
|
|
116
|
+
MCP->>WXO: POST /v1/orchestrate/runs
|
|
117
|
+
Note over MCP,WXO: agent_id, message
|
|
118
|
+
WXO-->>MCP: thread_id
|
|
119
|
+
|
|
120
|
+
loop Poll (~30s max)
|
|
121
|
+
MCP->>WXO: GET /threads/{id}/messages
|
|
122
|
+
WXO-->>MCP: messages
|
|
123
|
+
MCP->>MCP: Check for assistant response
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
MCP-->>IDE: { success, response, thread_id }
|
|
127
|
+
IDE-->>User: Agent reply: "Current weather in Amsterdam..."
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Supported Clients
|
|
131
|
+
|
|
132
|
+
| Client | Config | Transport |
|
|
133
|
+
|--------|--------|-----------|
|
|
134
|
+
| **Cursor** | `.cursor/mcp.json` | stdio |
|
|
135
|
+
| **VS Code Copilot** | `.vscode/mcp.json` | stdio |
|
|
136
|
+
| **Langflow** | MCP Tools component (STDIO) | stdio |
|
|
137
|
+
| **Claude Desktop** | `claude_desktop_config.json` | stdio |
|
|
138
|
+
| **Antigravity** | MCP servers config | stdio |
|
|
139
|
+
|
|
140
|
+
All clients spawn the MCP server as a subprocess and communicate via **stdio** (stdin/stdout) using the MCP JSON-RPC protocol.
|
|
141
|
+
|
|
142
|
+
### Tool → API Mapping
|
|
143
|
+
|
|
144
|
+
```mermaid
|
|
145
|
+
flowchart LR
|
|
146
|
+
subgraph MCPTools["MCP Tools"]
|
|
147
|
+
invoke[invoke_agent]
|
|
148
|
+
get[get_agent]
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
subgraph WXOAPI["Watson Orchestrate API"]
|
|
152
|
+
runs["POST /v1/orchestrate/runs"]
|
|
153
|
+
messages["GET .../threads/{id}/messages"]
|
|
154
|
+
agents["GET /v1/orchestrate/agents/{id}"]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
invoke --> runs
|
|
158
|
+
invoke --> messages
|
|
159
|
+
get --> agents
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Overview
|
|
165
|
+
|
|
166
|
+
**WxO Agent MCP** is a minimal MCP (Model Context Protocol) server that invokes a single IBM Watson Orchestrate agent over HTTP. It is intended for AI assistants (Cursor, Claude Desktop, VS Code Copilot, etc.) that need to chat with one specific agent without managing tools, flows, or connections.
|
|
167
|
+
|
|
168
|
+
### Purpose
|
|
169
|
+
|
|
170
|
+
- Invoke a Watson Orchestrate agent remotely via MCP tool calls.
|
|
171
|
+
- Expose only two tools: `invoke_agent` (chat) and `get_agent` (agent details).
|
|
172
|
+
- Configure a single agent via environment variables; no runtime agent selection.
|
|
173
|
+
|
|
174
|
+
### Compared to wxo-builder-mcp-server
|
|
175
|
+
|
|
176
|
+
| Aspect | wxo-agent-mcp | wxo-builder-mcp-server |
|
|
177
|
+
|--------|---------------|------------------------|
|
|
178
|
+
| Purpose | Chat with one agent | Full dev toolkit |
|
|
179
|
+
| Agent config | Single `WO_AGENT_ID` or `WO_AGENT_IDs` | Multiple agents, `WO_AGENT_IDs` |
|
|
180
|
+
| Tools | 2 (`invoke_agent`, `get_agent`) | 30+ (list_skills, deploy_tool, etc.) |
|
|
181
|
+
| Use case | “Ask my agent” | Build and manage Watson Orchestrate resources |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Project Structure
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
wxo-agent-mcp/
|
|
189
|
+
├── src/
|
|
190
|
+
│ ├── index.ts # MCP server, tool handlers, invoke_agent & get_agent logic
|
|
191
|
+
│ ├── config.ts # Env loading, WO_* config, URL normalization
|
|
192
|
+
│ └── auth.ts # IAM token, woFetch for Watson Orchestrate API
|
|
193
|
+
├── tests/
|
|
194
|
+
│ └── verify.ts # Verification script (get_agent + invoke_agent)
|
|
195
|
+
├── examples/
|
|
196
|
+
│ └── mcp.json # Example MCP client config
|
|
197
|
+
├── package.json
|
|
198
|
+
├── tsconfig.json
|
|
199
|
+
├── .env.example
|
|
200
|
+
├── README.md
|
|
201
|
+
├── DOCUMENTATION.md
|
|
202
|
+
└── LICENSE
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Source Files
|
|
206
|
+
|
|
207
|
+
| File | Role |
|
|
208
|
+
|------|------|
|
|
209
|
+
| **src/index.ts** | MCP server setup, `invoke_agent` and `get_agent` tools. Uses Watson Orchestrate Runs API (`POST /v1/orchestrate/runs`), polls for messages via `GET /v1/orchestrate/threads/{id}/messages`. |
|
|
210
|
+
| **src/config.ts** | Loads `.env`, normalizes `WO_INSTANCE_URL` (fixes `ttps://` typo, adds `https://` when missing), resolves `WO_AGENT_ID` or `WO_AGENT_IDs` (first ID). |
|
|
211
|
+
| **src/auth.ts** | IAM token acquisition (IBM Cloud API key → Bearer token), `woFetch` for authenticated requests to Watson Orchestrate. |
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Configuration
|
|
216
|
+
|
|
217
|
+
### Environment Variables
|
|
218
|
+
|
|
219
|
+
| Variable | Required | Description |
|
|
220
|
+
|----------|----------|-------------|
|
|
221
|
+
| **WO_API_KEY** | Yes | IBM Cloud API key for Watson Orchestrate. |
|
|
222
|
+
| **WO_INSTANCE_URL** | Yes | Watson Orchestrate instance URL (e.g. `https://api.us-south.watson-orchestrate.cloud.ibm.com/instances/{id}` or `https://{id}.orchestrate.ibm.com`). |
|
|
223
|
+
| **WO_AGENT_ID** | One of WO_AGENT_ID / WO_AGENT_IDs | Agent ID to invoke. |
|
|
224
|
+
| **WO_AGENT_IDs** | One of WO_AGENT_ID / WO_AGENT_IDs | Comma-separated agent IDs; first is used. |
|
|
225
|
+
| **IAM_TOKEN_URL** | No | Default `https://iam.cloud.ibm.com/identity/token`. Override for private IAM. |
|
|
226
|
+
|
|
227
|
+
### URL Normalization
|
|
228
|
+
|
|
229
|
+
- Trailing slashes are removed.
|
|
230
|
+
- `ttps://` is corrected to `https://`.
|
|
231
|
+
- URLs without a scheme get `https://` prepended.
|
|
232
|
+
|
|
233
|
+
### Configuration Sources
|
|
234
|
+
|
|
235
|
+
1. `.env` in the project root.
|
|
236
|
+
2. `.env` in the current working directory.
|
|
237
|
+
3. MCP client `env` block (e.g. in Cursor `mcp.json`).
|
|
238
|
+
4. Shell environment variables.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## MCP Tools
|
|
243
|
+
|
|
244
|
+
### invoke_agent
|
|
245
|
+
|
|
246
|
+
Sends a user message to the configured agent and returns the assistant reply.
|
|
247
|
+
|
|
248
|
+
**Input:**
|
|
249
|
+
|
|
250
|
+
- `message` (string, required): User message to send.
|
|
251
|
+
|
|
252
|
+
**Output:** JSON like:
|
|
253
|
+
|
|
254
|
+
```json
|
|
255
|
+
{
|
|
256
|
+
"success": true,
|
|
257
|
+
"response": "The agent's reply text.",
|
|
258
|
+
"thread_id": "uuid"
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Behavior:**
|
|
263
|
+
|
|
264
|
+
1. Calls `POST /v1/orchestrate/runs` with `agent_id` and `message`.
|
|
265
|
+
2. Polls `GET /v1/orchestrate/threads/{thread_id}/messages` for assistant messages.
|
|
266
|
+
3. Returns the last assistant text content (up to ~30s polling).
|
|
267
|
+
4. Throws on timeout or API errors.
|
|
268
|
+
|
|
269
|
+
### get_agent
|
|
270
|
+
|
|
271
|
+
Returns details of the configured agent.
|
|
272
|
+
|
|
273
|
+
**Input:** None.
|
|
274
|
+
|
|
275
|
+
**Output:** Full agent JSON from `GET /v1/orchestrate/agents/{id}` (name, description, tools, instructions, etc.).
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Question Examples
|
|
280
|
+
|
|
281
|
+
Example questions to ask in **Cursor**, **VS Code Copilot**, **Langflow**, or other MCP clients. The AI will call `invoke_agent` or `get_agent` to respond.
|
|
282
|
+
|
|
283
|
+
### invoke_agent (chat with the agent)
|
|
284
|
+
|
|
285
|
+
| Question | What the agent might do |
|
|
286
|
+
|----------|-------------------------|
|
|
287
|
+
| What is the weather in Amsterdam? | Use weather tool |
|
|
288
|
+
| What time is it in Tokyo? | Use World Time tool |
|
|
289
|
+
| Tell me a dad joke | Use Dad Jokes tool |
|
|
290
|
+
| What can you help me with? | Describe capabilities |
|
|
291
|
+
| Get the METAR for KJFK | Use aviation weather tool |
|
|
292
|
+
| Tell me about Brazil | Use REST Countries tool |
|
|
293
|
+
| What's the exchange rate for CAD to USD? | Use currency tool |
|
|
294
|
+
| Hello, who are you? | Introduce itself |
|
|
295
|
+
|
|
296
|
+
### get_agent (agent details)
|
|
297
|
+
|
|
298
|
+
| Question | Response |
|
|
299
|
+
|----------|----------|
|
|
300
|
+
| Use get_agent to show me the agent details | Agent name, description, tools, instructions |
|
|
301
|
+
| What tools does my agent have? | List of assigned tools |
|
|
302
|
+
| Show me the agent configuration | Full agent JSON |
|
|
303
|
+
|
|
304
|
+
### Natural-language style (AI selects the tool)
|
|
305
|
+
|
|
306
|
+
| Question |
|
|
307
|
+
|----------|
|
|
308
|
+
| Ask my Watson agent: What is the weather in London? |
|
|
309
|
+
| Chat with the agent: Tell me a dad joke |
|
|
310
|
+
| Use the agent to get the current time in New York |
|
|
311
|
+
|
|
312
|
+
### test:verify (terminal)
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
# Default
|
|
316
|
+
npm run test:verify
|
|
317
|
+
|
|
318
|
+
# Custom questions
|
|
319
|
+
npm run test:verify -- -ask "What is the weather in Amsterdam?"
|
|
320
|
+
npm run test:verify -- -ask "Tell me a dad joke"
|
|
321
|
+
npm run test:verify -- -ask "What time is it in Sydney?"
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Verification
|
|
327
|
+
|
|
328
|
+
The `test:verify` script exercises `get_agent` and `invoke_agent` against your env.
|
|
329
|
+
|
|
330
|
+
### Usage
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
# Default question: "Hello, who are you? What can you help me with?"
|
|
334
|
+
npm run test:verify
|
|
335
|
+
|
|
336
|
+
# Custom question
|
|
337
|
+
npm run test:verify -- -ask "What is the weather in Amsterdam?"
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Output
|
|
341
|
+
|
|
342
|
+
```
|
|
343
|
+
Config: { instanceUrl: '...', agentId: '...' }
|
|
344
|
+
|
|
345
|
+
--- get_agent ---
|
|
346
|
+
Agent: TimeWeatherAgent
|
|
347
|
+
Tools: 4
|
|
348
|
+
|
|
349
|
+
--- invoke_agent ---
|
|
350
|
+
User: What is the weather in Amsterdam?
|
|
351
|
+
|
|
352
|
+
Response from the Agent when invoke is:
|
|
353
|
+
|
|
354
|
+
TimeWeatherAgent replied:
|
|
355
|
+
|
|
356
|
+
Current weather in Amsterdam: ...
|
|
357
|
+
|
|
358
|
+
✅ wxo-agent-mcp verification passed
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Requirements
|
|
362
|
+
|
|
363
|
+
- `WO_API_KEY`, `WO_INSTANCE_URL`, and `WO_AGENT_ID` (or `WO_AGENT_IDs`) set.
|
|
364
|
+
- `tsx` dev dependency for running the TypeScript script.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Running the Server
|
|
369
|
+
|
|
370
|
+
### Local
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
npm install
|
|
374
|
+
npm run build
|
|
375
|
+
node dist/index.js
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
The server runs on stdio. MCP clients spawn it as a subprocess and communicate via JSON-RPC.
|
|
379
|
+
|
|
380
|
+
### MCP Client Config
|
|
381
|
+
|
|
382
|
+
**Cursor** (`.cursor/mcp.json`):
|
|
383
|
+
|
|
384
|
+
```json
|
|
385
|
+
{
|
|
386
|
+
"mcpServers": {
|
|
387
|
+
"wxo-agent": {
|
|
388
|
+
"command": "node",
|
|
389
|
+
"args": ["/absolute/path/to/wxo-agent-mcp/dist/index.js"],
|
|
390
|
+
"env": {
|
|
391
|
+
"WO_API_KEY": "your-key",
|
|
392
|
+
"WO_INSTANCE_URL": "https://xxx.orchestrate.ibm.com",
|
|
393
|
+
"WO_AGENT_ID": "your-agent-id"
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**With npx** (after publishing):
|
|
401
|
+
|
|
402
|
+
```json
|
|
403
|
+
{
|
|
404
|
+
"mcpServers": {
|
|
405
|
+
"wxo-agent": {
|
|
406
|
+
"command": "npx",
|
|
407
|
+
"args": ["-y", "@markusvankempen/wxo-agent-mcp"],
|
|
408
|
+
"env": { "WO_API_KEY": "...", "WO_INSTANCE_URL": "...", "WO_AGENT_ID": "..." }
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Testing Locally in VS Code
|
|
417
|
+
|
|
418
|
+
This section assumes the MCP server is named **`wxo-agent`** in `mcp.json` (workspace `.vscode/mcp.json` or Cursor `.cursor/mcp.json`).
|
|
419
|
+
|
|
420
|
+
### 1. Setup
|
|
421
|
+
|
|
422
|
+
1. Open the `wxo-agent-mcp` folder in VS Code (File → Open Folder).
|
|
423
|
+
2. Ensure `.env` has `WO_API_KEY`, `WO_INSTANCE_URL`, `WO_AGENT_ID` (or `WO_AGENT_IDs`).
|
|
424
|
+
3. Run `npm run build` so `dist/index.js` exists.
|
|
425
|
+
4. Ensure `.vscode/mcp.json` contains:
|
|
426
|
+
|
|
427
|
+
```json
|
|
428
|
+
{
|
|
429
|
+
"servers": {
|
|
430
|
+
"wxo-agent": {
|
|
431
|
+
"type": "stdio",
|
|
432
|
+
"command": "node",
|
|
433
|
+
"args": ["${workspaceFolder}/dist/index.js"],
|
|
434
|
+
"envFile": "${workspaceFolder}/.env"
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### 2. Verification script (terminal)
|
|
441
|
+
|
|
442
|
+
From the integrated terminal (Ctrl+`):
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
npm run test:verify
|
|
446
|
+
# Or with custom question:
|
|
447
|
+
npm run test:verify -- -ask "What is the weather in Amsterdam?"
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### 3. Invoking wxo-agent in Copilot Chat
|
|
451
|
+
|
|
452
|
+
1. Open **Copilot Chat** (View → Copilot Chat, or `Ctrl+Shift+I`).
|
|
453
|
+
2. On first use, VS Code may prompt to trust the MCP server; approve it.
|
|
454
|
+
3. The `wxo-agent` tools (`invoke_agent`, `get_agent`) become available when the server is connected.
|
|
455
|
+
|
|
456
|
+
**Example prompts that invoke the agent:**
|
|
457
|
+
|
|
458
|
+
| Prompt | Effect |
|
|
459
|
+
|--------|--------|
|
|
460
|
+
| *Use the invoke_agent tool to ask: What is the weather in Amsterdam?* | Direct tool call |
|
|
461
|
+
| *Call invoke_agent with message "What time is it in Tokyo?"* | Direct tool call |
|
|
462
|
+
| *Ask my Watson agent: Tell me a dad joke* | Natural; Copilot selects the tool |
|
|
463
|
+
| *Use get_agent to show me the agent details* | Returns agent config and tools |
|
|
464
|
+
| *Chat with the agent: What can you help me with?* | Natural; Copilot selects the tool |
|
|
465
|
+
|
|
466
|
+
**Tips if the tool is not invoked:**
|
|
467
|
+
|
|
468
|
+
- Mention the tool explicitly: *Use invoke_agent to ask the agent: [your question]*
|
|
469
|
+
|
|
470
|
+
**Skip get_agent for capability questions:** Copilot may call `get_agent` first to fetch config. To get only the agent's answer, ask: *Use invoke_agent to ask: What can you do?* The tool descriptions nudge the AI to prefer `invoke_agent` for "what can you do" / "list capabilities".
|
|
471
|
+
- Restart VS Code and reopen the folder
|
|
472
|
+
- Check that `dist/index.js` exists and `.env` is valid
|
|
473
|
+
|
|
474
|
+
### 4. Debug configurations
|
|
475
|
+
|
|
476
|
+
`.vscode/launch.json` provides:
|
|
477
|
+
|
|
478
|
+
- **Run wxo-agent-mcp** – launch the MCP server (F5)
|
|
479
|
+
- **Run test:verify** – run the verification script (F5)
|
|
480
|
+
|
|
481
|
+
Use Run and Debug (Ctrl+Shift+D) and select a configuration.
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
## Testing in Langflow
|
|
486
|
+
|
|
487
|
+
Langflow can use the **wxo-agent** MCP server via the MCP Tools component (STDIO mode). Node.js must be installed (Langflow uses it to run the server).
|
|
488
|
+
|
|
489
|
+
### Setup
|
|
490
|
+
|
|
491
|
+
1. Build the server: `npm run build` (produces `dist/index.js`).
|
|
492
|
+
2. Open Langflow and create or open a flow.
|
|
493
|
+
3. Add an **MCP Tools** component.
|
|
494
|
+
4. Click **Add MCP Server** and choose **STDIO**.
|
|
495
|
+
5. Configure:
|
|
496
|
+
|
|
497
|
+
| Field | Value |
|
|
498
|
+
|-------|-------|
|
|
499
|
+
| **Name** | `wxo-agent` |
|
|
500
|
+
| **Command** | `node` |
|
|
501
|
+
| **Arguments** | `["/absolute/path/to/wxo-agent-mcp/dist/index.js"]` |
|
|
502
|
+
| **Environment Variables** | `WO_API_KEY`, `WO_INSTANCE_URL`, `WO_AGENT_ID` (or `WO_AGENT_IDs`) as key-value pairs |
|
|
503
|
+
|
|
504
|
+
**Alternative (npx, after publishing):**
|
|
505
|
+
|
|
506
|
+
- **Command:** `npx`
|
|
507
|
+
- **Arguments:** `["-y", "@markusvankempen/wxo-agent-mcp"]`
|
|
508
|
+
- **Environment Variables:** same as above
|
|
509
|
+
|
|
510
|
+
6. In the MCP Tools component, enable **Tool mode** in the header menu.
|
|
511
|
+
7. Connect the MCP Tools component’s **Toolset** port to an **Agent** component’s **Tools** port.
|
|
512
|
+
8. Add **Chat Input** and **Chat Output** if needed.
|
|
513
|
+
|
|
514
|
+
### Example prompts in Playground
|
|
515
|
+
|
|
516
|
+
- *Use invoke_agent to ask: What is the weather in Amsterdam?*
|
|
517
|
+
- *Ask my Watson agent: Tell me a dad joke*
|
|
518
|
+
- *Use get_agent to show the agent’s tools*
|
|
519
|
+
|
|
520
|
+
### Troubleshooting
|
|
521
|
+
|
|
522
|
+
- **500 Internal Server Error:** Tool output is flattened for Langflow's DataFrame. Rebuild (`npm run build`) and retry. If it persists: ensure Node.js is installed; use absolute path to `dist/index.js`; set env vars in MCP config or Langflow's `.env`.
|
|
523
|
+
- **Node.js in Docker:** If Langflow runs in Docker, Node.js must be installed in the image. Rebuild the container if needed.
|
|
524
|
+
- **Absolute path:** Use the full path to `dist/index.js` (e.g. `/Users/you/projects/wxo-agent-mcp/dist/index.js`).
|
|
525
|
+
- **Env vars:** Langflow passes env from its `.env` to MCP. Add `WO_API_KEY`, `WO_INSTANCE_URL`, `WO_AGENT_ID` to Langflow’s `.env` if you prefer not to set them in the UI.
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
## Troubleshooting
|
|
530
|
+
|
|
531
|
+
| Symptom | Cause | Fix |
|
|
532
|
+
|---------|-------|-----|
|
|
533
|
+
| `URL scheme "ttps" is not supported` | Typo in `WO_INSTANCE_URL` | Use `https://` or rely on auto-correction. |
|
|
534
|
+
| `Missing required environment variables` | Missing env vars | Set `WO_API_KEY`, `WO_INSTANCE_URL`, `WO_AGENT_ID` (or `WO_AGENT_IDs`). |
|
|
535
|
+
| `Timed out waiting for agent response` | Slow agent / tool use | Increase poll count or delay in `index.ts`; check agent responsiveness. |
|
|
536
|
+
| `IAM failed` | Invalid API key | Verify `WO_API_KEY` and regenerate if needed. |
|
|
537
|
+
| `Get agent failed: 404` | Invalid agent ID | Check `WO_AGENT_ID` against your Watson Orchestrate instance. |
|
|
538
|
+
| `Run failed: 500` (Langflow) | DataFrame / output format | Rebuild; tool output is now flattened. Check Node.js and env vars. |
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## API Dependencies
|
|
543
|
+
|
|
544
|
+
- **Watson Orchestrate** (HTTP REST):
|
|
545
|
+
- `POST /v1/orchestrate/runs` – start a run
|
|
546
|
+
- `GET /v1/orchestrate/threads/{id}/messages` – get thread messages
|
|
547
|
+
- `GET /v1/orchestrate/agents/{id}` – get agent
|
|
548
|
+
- **IBM Cloud IAM** – exchange API key for Bearer token.
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## License
|
|
553
|
+
|
|
554
|
+
Apache-2.0. See [LICENSE](LICENSE).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
WxO Agent MCP (@markusvankempen/wxo-agent-mcp)
|
|
2
|
+
|
|
3
|
+
Apache License
|
|
4
|
+
Version 2.0, January 2004
|
|
5
|
+
http://www.apache.org/licenses/
|
|
6
|
+
|
|
7
|
+
Copyright 2026 Markus van Kempen <markus.van.kempen@gmail.com>
|
|
8
|
+
|
|
9
|
+
SPDX-License-Identifier: Apache-2.0
|
|
10
|
+
|
|
11
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
+
you may not use this file except in compliance with the License.
|
|
13
|
+
You may obtain a copy of the License at
|
|
14
|
+
|
|
15
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
+
|
|
17
|
+
Unless required by applicable law or agreed to in writing, software
|
|
18
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
+
See the License for the specific language governing permissions and
|
|
21
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# WxO Agent MCP
|
|
2
|
+
|
|
3
|
+
Simple MCP (Model Context Protocol) server that **invokes a single Watson Orchestrate agent** remotely. The agent is defined once via environment variables or MCP config.
|
|
4
|
+
|
|
5
|
+
Use this when you want a lightweight MCP that only chats with one agent—no tool management, no agent listing, no flows. Just `invoke_agent(message)` and `get_agent()`.
|
|
6
|
+
|
|
7
|
+
**Full documentation:** [DOCUMENTATION.md](DOCUMENTATION.md)
|
|
8
|
+
|
|
9
|
+
### Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌──────────────────────────────────────────────────┐ stdio ┌─────────────────────┐ HTTP ┌──────────────────────┐
|
|
13
|
+
│ Cursor • VS Code • Langflow • Claude • etc. │ ◄────────► │ wxo-agent-mcp │ ──────► │ Watson Orchestrate │
|
|
14
|
+
│ (MCP clients) │ JSON-RPC │ invoke_agent │ REST │ (agent + tools+LLM) │
|
|
15
|
+
└──────────────────────────────────────────────────┘ │ get_agent │ └──────────────────────┘
|
|
16
|
+
└─────────────────────┘
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Tools
|
|
20
|
+
|
|
21
|
+
| Tool | Description |
|
|
22
|
+
|------|-------------|
|
|
23
|
+
| **invoke_agent** | Send a message to the configured Watson Orchestrate agent. The agent responds using its tools and LLM. |
|
|
24
|
+
| **get_agent** | Get details of the configured agent (name, description, tools, instructions). |
|
|
25
|
+
|
|
26
|
+
## Configuration
|
|
27
|
+
|
|
28
|
+
Set these in `.env` or your MCP client config (e.g. Cursor `mcp.json`):
|
|
29
|
+
|
|
30
|
+
```env
|
|
31
|
+
WO_API_KEY=your-ibm-cloud-api-key
|
|
32
|
+
WO_INSTANCE_URL=https://your-instance-id.orchestrate.ibm.com
|
|
33
|
+
WO_AGENT_ID=your-agent-id
|
|
34
|
+
# Or WO_AGENT_IDs=id1,id2 (first is used)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install
|
|
41
|
+
npm run build
|
|
42
|
+
cp .env.example .env
|
|
43
|
+
# Edit .env with your credentials and agent ID
|
|
44
|
+
WO_API_KEY=... WO_INSTANCE_URL=... WO_AGENT_ID=... node dist/index.js
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Verify
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Default question ("Hello, who are you?")
|
|
51
|
+
WO_API_KEY=... WO_INSTANCE_URL=... WO_AGENT_IDs=... npm run test:verify
|
|
52
|
+
|
|
53
|
+
# Custom question
|
|
54
|
+
npm run test:verify -- -ask "What is the weather in Amsterdam?"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Runs `get_agent` and `invoke_agent` to confirm connectivity.
|
|
58
|
+
|
|
59
|
+
### Test in VS Code
|
|
60
|
+
|
|
61
|
+
1. Open the `wxo-agent-mcp` folder in VS Code.
|
|
62
|
+
2. Run `npm run build`.
|
|
63
|
+
3. `.vscode/mcp.json` registers the MCP server as **wxo-agent** and loads `.env`.
|
|
64
|
+
4. Open Copilot Chat (Ctrl+Shift+I) and ask: *Use invoke_agent to ask: What is the weather in Amsterdam?*
|
|
65
|
+
For "what can you do", use *Use invoke_agent to ask: What can you do?* to avoid Copilot calling `get_agent` first.
|
|
66
|
+
5. Or run `npm run test:verify` from the terminal (Ctrl+`).
|
|
67
|
+
|
|
68
|
+
See [DOCUMENTATION.md](DOCUMENTATION.md#testing-locally-in-vs-code) for more prompts and setup.
|
|
69
|
+
|
|
70
|
+
**Question examples:** [Full list →](DOCUMENTATION.md#question-examples)
|
|
71
|
+
|
|
72
|
+
| invoke_agent | get_agent |
|
|
73
|
+
|--------------|-----------|
|
|
74
|
+
| What is the weather in Amsterdam? | Use get_agent to show agent details |
|
|
75
|
+
| Tell me a dad joke | What tools does my agent have? |
|
|
76
|
+
| What time is it in Tokyo? | |
|
|
77
|
+
| What can you help me with? | |
|
|
78
|
+
|
|
79
|
+
**Langflow:** Add an MCP Tools component, choose STDIO, set Command=`node`, Args=`["/path/to/dist/index.js"]`, and env vars. See [DOCUMENTATION.md](DOCUMENTATION.md#testing-in-langflow).
|
|
80
|
+
|
|
81
|
+
## MCP Client Configuration
|
|
82
|
+
|
|
83
|
+
### Cursor (`.cursor/mcp.json`)
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"mcpServers": {
|
|
88
|
+
"wxo-agent": {
|
|
89
|
+
"command": "npx",
|
|
90
|
+
"args": ["-y", "@markusvankempen/wxo-agent-mcp"],
|
|
91
|
+
"env": {
|
|
92
|
+
"WO_API_KEY": "your-api-key",
|
|
93
|
+
"WO_INSTANCE_URL": "https://xxx.orchestrate.ibm.com",
|
|
94
|
+
"WO_AGENT_ID": "your-agent-id"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### VS Code Copilot (`.vscode/mcp.json`)
|
|
102
|
+
|
|
103
|
+
The server is named **wxo-agent**. Use `envFile` to load `.env`:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"servers": {
|
|
108
|
+
"wxo-agent": {
|
|
109
|
+
"type": "stdio",
|
|
110
|
+
"command": "node",
|
|
111
|
+
"args": ["${workspaceFolder}/dist/index.js"],
|
|
112
|
+
"envFile": "${workspaceFolder}/.env"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
For npx (after publishing), use `"command": "npx"`, `"args": ["-y", "@markusvankempen/wxo-agent-mcp"]`, and add `env` with your credentials.
|
|
119
|
+
|
|
120
|
+
## vs wxo-builder-mcp-server
|
|
121
|
+
|
|
122
|
+
| | wxo-agent-mcp | wxo-builder-mcp-server |
|
|
123
|
+
|---|---------------|------------------------|
|
|
124
|
+
| Purpose | Invoke one agent | Full dev toolkit (tools, agents, connections, flows) |
|
|
125
|
+
| Agent | Single `WO_AGENT_ID` | Multiple agents, `WO_AGENT_IDs` |
|
|
126
|
+
| Tools | `invoke_agent`, `get_agent` | 30+ tools (list_skills, deploy_tool, etc.) |
|
|
127
|
+
| Use case | Chat with a specific agent | Build and manage Watson Orchestrate resources |
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
Apache-2.0
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WxO Agent MCP – Authentication
|
|
3
|
+
*
|
|
4
|
+
* IAM token acquisition (IBM Cloud API key → Bearer token) and woFetch
|
|
5
|
+
* for authenticated requests to Watson Orchestrate.
|
|
6
|
+
*
|
|
7
|
+
* @author Markus van Kempen <mvk@ca.ibm.com>
|
|
8
|
+
* @author Markus van Kempen <markus.van.kempen@gmail.com>
|
|
9
|
+
* @date 21-Feb-2026
|
|
10
|
+
* @license Apache-2.0
|
|
11
|
+
*/
|
|
12
|
+
import { config } from './config.js';
|
|
13
|
+
import fetch from 'node-fetch';
|
|
14
|
+
let cachedToken = null;
|
|
15
|
+
let tokenExpiry = 0;
|
|
16
|
+
/** Get IAM Bearer token (cached). Exchanges API key at IBM Cloud IAM. */
|
|
17
|
+
export async function getIamToken() {
|
|
18
|
+
const now = Math.floor(Date.now() / 1000);
|
|
19
|
+
if (cachedToken && tokenExpiry > now + 60)
|
|
20
|
+
return cachedToken;
|
|
21
|
+
const params = new URLSearchParams();
|
|
22
|
+
params.append('grant_type', 'urn:ibm:params:oauth:grant-type:apikey');
|
|
23
|
+
params.append('apikey', config.apiKey);
|
|
24
|
+
const response = await fetch(config.iamTokenUrl, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
27
|
+
body: params,
|
|
28
|
+
});
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
const text = await response.text();
|
|
31
|
+
throw new Error(`IAM failed: ${response.status} ${text}`);
|
|
32
|
+
}
|
|
33
|
+
const data = (await response.json());
|
|
34
|
+
cachedToken = data.access_token;
|
|
35
|
+
tokenExpiry = now + data.expires_in;
|
|
36
|
+
return cachedToken;
|
|
37
|
+
}
|
|
38
|
+
/** Authenticated fetch to Watson Orchestrate. Prepend base URL for relative paths. */
|
|
39
|
+
export async function woFetch(endpoint, options = {}) {
|
|
40
|
+
const token = await getIamToken();
|
|
41
|
+
const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
|
42
|
+
const url = endpoint.startsWith('http') ? endpoint : `${config.instanceUrl}${path}`;
|
|
43
|
+
const headers = {
|
|
44
|
+
Authorization: `Bearer ${token}`,
|
|
45
|
+
'Content-Type': 'application/json',
|
|
46
|
+
Accept: 'application/json',
|
|
47
|
+
...options.headers,
|
|
48
|
+
};
|
|
49
|
+
return fetch(url, { ...options, headers });
|
|
50
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WxO Agent MCP – Configuration
|
|
3
|
+
*
|
|
4
|
+
* Loads WO_API_KEY, WO_INSTANCE_URL, WO_AGENT_ID (or WO_AGENT_IDs) from .env
|
|
5
|
+
* and environment. Normalizes instance URL (fixes ttps typo, adds https).
|
|
6
|
+
*
|
|
7
|
+
* @author Markus van Kempen <mvk@ca.ibm.com>
|
|
8
|
+
* @author Markus van Kempen <markus.van.kempen@gmail.com>
|
|
9
|
+
* @date 21-Feb-2026
|
|
10
|
+
* @license Apache-2.0
|
|
11
|
+
*/
|
|
12
|
+
import * as dotenv from 'dotenv';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
dotenv.config({ path: path.resolve(__dirname, '..', '.env') });
|
|
17
|
+
dotenv.config({ path: path.join(process.cwd(), '.env') });
|
|
18
|
+
/** Resolve agent ID from WO_AGENT_IDs (first) or WO_AGENT_ID. */
|
|
19
|
+
function resolveAgentId() {
|
|
20
|
+
const fromIds = (process.env.WO_AGENT_IDs || '').split(',').map((s) => s.trim()).filter(Boolean)[0];
|
|
21
|
+
const fromSingle = (process.env.WO_AGENT_ID || '').trim();
|
|
22
|
+
return fromIds || fromSingle;
|
|
23
|
+
}
|
|
24
|
+
/** Fix ttps typo, add https when missing, trim trailing slash. */
|
|
25
|
+
function normalizeInstanceUrl(url) {
|
|
26
|
+
const s = url.replace(/\/$/, '').trim();
|
|
27
|
+
if (s.startsWith('ttps://'))
|
|
28
|
+
return 'h' + s;
|
|
29
|
+
if (!s.startsWith('http://') && !s.startsWith('https://') && s.length > 0)
|
|
30
|
+
return 'https://' + s;
|
|
31
|
+
return s;
|
|
32
|
+
}
|
|
33
|
+
export const config = {
|
|
34
|
+
apiKey: process.env.WO_API_KEY || '',
|
|
35
|
+
instanceUrl: normalizeInstanceUrl(process.env.WO_INSTANCE_URL || ''),
|
|
36
|
+
agentId: resolveAgentId(),
|
|
37
|
+
iamTokenUrl: process.env.IAM_TOKEN_URL || 'https://iam.cloud.ibm.com/identity/token',
|
|
38
|
+
};
|
|
39
|
+
/** Return true if WO_API_KEY, WO_INSTANCE_URL, and agent ID are set. */
|
|
40
|
+
export function validateConfig() {
|
|
41
|
+
const missing = [];
|
|
42
|
+
if (!config.apiKey)
|
|
43
|
+
missing.push('WO_API_KEY');
|
|
44
|
+
if (!config.instanceUrl)
|
|
45
|
+
missing.push('WO_INSTANCE_URL');
|
|
46
|
+
if (!config.agentId)
|
|
47
|
+
missing.push('WO_AGENT_ID or WO_AGENT_IDs');
|
|
48
|
+
if (missing.length > 0) {
|
|
49
|
+
console.error('Missing required environment variables:', missing.join(', '));
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* WxO Agent MCP – Main entry point
|
|
4
|
+
*
|
|
5
|
+
* MCP server that invokes a single Watson Orchestrate agent. Exposes
|
|
6
|
+
* invoke_agent and get_agent tools. Agent is defined via WO_AGENT_ID
|
|
7
|
+
* (or WO_AGENT_IDs) in env or MCP config.
|
|
8
|
+
*
|
|
9
|
+
* @author Markus van Kempen <mvk@ca.ibm.com>
|
|
10
|
+
* @author Markus van Kempen <markus.van.kempen@gmail.com>
|
|
11
|
+
* @date 21-Feb-2026
|
|
12
|
+
* @license Apache-2.0
|
|
13
|
+
*/
|
|
14
|
+
import * as z from 'zod';
|
|
15
|
+
import * as dotenv from 'dotenv';
|
|
16
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
17
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
18
|
+
import { config, validateConfig } from './config.js';
|
|
19
|
+
import { woFetch } from './auth.js';
|
|
20
|
+
dotenv.config();
|
|
21
|
+
/** Flatten object to dict with only string values (Langflow process_output_item parses JSON and appends; must be a single dict, not array). */
|
|
22
|
+
function flattenToDict(obj) {
|
|
23
|
+
const out = {};
|
|
24
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
25
|
+
if (v === null || v === undefined)
|
|
26
|
+
out[k] = '';
|
|
27
|
+
else if (typeof v === 'string')
|
|
28
|
+
out[k] = v;
|
|
29
|
+
else if (typeof v === 'number' || typeof v === 'boolean')
|
|
30
|
+
out[k] = String(v);
|
|
31
|
+
else
|
|
32
|
+
out[k] = JSON.stringify(v);
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Normalize result to a single flat dict for Langflow MCP Tools.
|
|
38
|
+
* Langflow's process_output_item does json.loads(text) and appends the result.
|
|
39
|
+
* If we return an array, the whole array gets appended as one item (list, not dict) → DataFrame error.
|
|
40
|
+
* Return a single dict so tool_content = [dict] → DataFrame([dict]) works.
|
|
41
|
+
*/
|
|
42
|
+
function toDataFrameSafe(result) {
|
|
43
|
+
if (result !== null && typeof result === 'object' && !Array.isArray(result)) {
|
|
44
|
+
const flat = flattenToDict(result);
|
|
45
|
+
if (Object.keys(flat).length > 0)
|
|
46
|
+
return flat;
|
|
47
|
+
}
|
|
48
|
+
return { output: typeof result === 'string' ? result : JSON.stringify(result) };
|
|
49
|
+
}
|
|
50
|
+
/** Invoke the configured agent. Polls for assistant response. */
|
|
51
|
+
async function invokeAgent(message) {
|
|
52
|
+
const runRes = await woFetch('/v1/orchestrate/runs', {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
body: JSON.stringify({
|
|
55
|
+
agent_id: config.agentId,
|
|
56
|
+
message: { role: 'user', content: message },
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
if (!runRes.ok) {
|
|
60
|
+
const text = await runRes.text();
|
|
61
|
+
throw new Error(`Run failed: ${runRes.status} ${text}`);
|
|
62
|
+
}
|
|
63
|
+
const runData = (await runRes.json());
|
|
64
|
+
const threadId = runData.thread_id;
|
|
65
|
+
const maxPolls = 15;
|
|
66
|
+
for (let i = 0; i < maxPolls; i++) {
|
|
67
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
68
|
+
const msgRes = await woFetch(`/v1/orchestrate/threads/${threadId}/messages`, { method: 'GET' });
|
|
69
|
+
if (!msgRes.ok)
|
|
70
|
+
continue;
|
|
71
|
+
const raw = (await msgRes.json());
|
|
72
|
+
const messages = Array.isArray(raw) ? raw : raw?.data ?? [];
|
|
73
|
+
if (!Array.isArray(messages))
|
|
74
|
+
continue;
|
|
75
|
+
const assistantMsgs = messages.filter((m) => m.role === 'assistant');
|
|
76
|
+
if (assistantMsgs.length === 0)
|
|
77
|
+
continue;
|
|
78
|
+
const lastMsg = assistantMsgs[assistantMsgs.length - 1];
|
|
79
|
+
let responseText = '';
|
|
80
|
+
if (typeof lastMsg.content === 'string') {
|
|
81
|
+
responseText = lastMsg.content;
|
|
82
|
+
}
|
|
83
|
+
else if (Array.isArray(lastMsg.content)) {
|
|
84
|
+
responseText = lastMsg.content
|
|
85
|
+
.map((c) => (c.text && typeof c.text === 'object' ? c.text.value : c.text) || JSON.stringify(c))
|
|
86
|
+
.join(' ');
|
|
87
|
+
}
|
|
88
|
+
return { success: true, response: responseText || 'No content', thread_id: threadId };
|
|
89
|
+
}
|
|
90
|
+
throw new Error('Timed out waiting for agent response');
|
|
91
|
+
}
|
|
92
|
+
/** Fetch agent details from Watson Orchestrate. */
|
|
93
|
+
async function getAgent() {
|
|
94
|
+
const res = await woFetch(`/v1/orchestrate/agents/${config.agentId}`, { method: 'GET' });
|
|
95
|
+
if (!res.ok) {
|
|
96
|
+
const text = await res.text();
|
|
97
|
+
throw new Error(`Get agent failed: ${res.status} ${text}`);
|
|
98
|
+
}
|
|
99
|
+
return res.json();
|
|
100
|
+
}
|
|
101
|
+
async function main() {
|
|
102
|
+
const server = new McpServer({ name: 'wxo-agent-mcp', version: '1.0.0' }, { capabilities: {} });
|
|
103
|
+
server.registerTool('invoke_agent', {
|
|
104
|
+
description: `Send a message to the Watson Orchestrate agent. The agent responds using its tools and LLM. Prefer this for "what can you do", "list capabilities", "what do you do" — the agent answers in its own voice. Use get_agent only when raw config/JSON is needed.`,
|
|
105
|
+
inputSchema: { message: z.string() },
|
|
106
|
+
}, async (args) => {
|
|
107
|
+
if (!validateConfig()) {
|
|
108
|
+
throw new Error('Missing WO_API_KEY, WO_INSTANCE_URL, or WO_AGENT_ID in env');
|
|
109
|
+
}
|
|
110
|
+
const msg = args?.message;
|
|
111
|
+
if (!msg)
|
|
112
|
+
throw new Error('message is required');
|
|
113
|
+
const result = await invokeAgent(msg);
|
|
114
|
+
const safe = toDataFrameSafe(result);
|
|
115
|
+
return {
|
|
116
|
+
content: [{ type: 'text', text: JSON.stringify(safe) }],
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
server.registerTool('get_agent', {
|
|
120
|
+
description: `Get raw agent config (name, tools, instructions) from Watson Orchestrate. Use only when raw JSON is needed. For "what can you do" or "list capabilities", prefer invoke_agent instead — it lets the agent answer directly.`,
|
|
121
|
+
inputSchema: {},
|
|
122
|
+
}, async () => {
|
|
123
|
+
if (!validateConfig()) {
|
|
124
|
+
throw new Error('Missing WO_API_KEY, WO_INSTANCE_URL, or WO_AGENT_ID in env');
|
|
125
|
+
}
|
|
126
|
+
const agent = await getAgent();
|
|
127
|
+
const safe = toDataFrameSafe(agent);
|
|
128
|
+
return {
|
|
129
|
+
content: [{ type: 'text', text: JSON.stringify(safe) }],
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
const transport = new StdioServerTransport();
|
|
133
|
+
await server.connect(transport);
|
|
134
|
+
console.error(`WxO Agent MCP running on stdio (agent: ${config.agentId})`);
|
|
135
|
+
}
|
|
136
|
+
main().catch((err) => {
|
|
137
|
+
console.error('Fatal error:', err);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"wxo-agent": {
|
|
4
|
+
"command": "npx",
|
|
5
|
+
"args": ["-y", "@markusvankempen/wxo-agent-mcp"],
|
|
6
|
+
"env": {
|
|
7
|
+
"WO_API_KEY": "your-ibm-cloud-api-key",
|
|
8
|
+
"WO_INSTANCE_URL": "https://your-instance-id.orchestrate.ibm.com",
|
|
9
|
+
"WO_AGENT_ID": "your-agent-id"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@markusvankempen/wxo-agent-mcp",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Simple MCP server to invoke a single Watson Orchestrate agent remotely. Configure agent via env (WO_AGENT_ID).",
|
|
5
|
+
"mcpName": "io.github.markusvankempen/wxo-agent-mcp",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"wxo-agent-mcp": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"DOCUMENTATION.md",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
".env.example",
|
|
17
|
+
"examples"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"start": "node dist/index.js",
|
|
22
|
+
"test:verify": "npx tsx tests/verify.ts",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/markusvankempen/wxo-agent-mcp.git"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"mcp",
|
|
31
|
+
"model-context-protocol",
|
|
32
|
+
"watsonx",
|
|
33
|
+
"watson-orchestrate",
|
|
34
|
+
"wxo",
|
|
35
|
+
"ibm",
|
|
36
|
+
"ai",
|
|
37
|
+
"agent",
|
|
38
|
+
"cursor",
|
|
39
|
+
"claude",
|
|
40
|
+
"vscode"
|
|
41
|
+
],
|
|
42
|
+
"author": "Markus van Kempen <markus.van.kempen@gmail.com>",
|
|
43
|
+
"license": "Apache-2.0",
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
49
|
+
"dotenv": "^17.3.1",
|
|
50
|
+
"node-fetch": "^3.3.2",
|
|
51
|
+
"zod": "^4.3.6"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^20.0.0",
|
|
55
|
+
"tsx": "^4.7.0",
|
|
56
|
+
"typescript": "^5.3.0"
|
|
57
|
+
}
|
|
58
|
+
}
|