@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 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
@@ -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
+ }