Skip to content

Commit d7796d9

Browse files
authored
Add logging (#18)
* Add logging * Add mock 'fetch' factory for the API * Tweak example log messages * Extend mock Response shape to cover error paths Add missing fields to mock Response objects: - Success case: status, statusText - Error case: statusText, text() This ensures the mock matches McpdClient expectations for non-2xx responses and prevents runtime errors when tests hit unmapped routes. * Clean up logging tests Remove unused consoleErrorSpy and use vi.stubGlobal() consistently: - Remove consoleErrorSpy from beforeEach/afterEach (never asserted) - Replace direct global.fetch assignments with vi.stubGlobal('fetch', ...) - Aligns with outer suite's mocking pattern for easier test cleanup * Clean-up * Remove serverCacheTtl from docs/comments as it was never implemented * Extract constants and defaults from magic numbers in constructor * Checked/updated all TSDocs in client.ts * Make a private agent tools function actually private * Type safety for log levels * Updated examples (deps, README)
1 parent 3a07f34 commit d7796d9

File tree

18 files changed

+6662
-664
lines changed

18 files changed

+6662
-664
lines changed

README.md

Lines changed: 139 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ const client = new McpdClient({
7373
apiEndpoint: "http://localhost:8090",
7474
apiKey: "optional-key", // Optional API key
7575
healthCacheTtl: 10, // Cache health checks for 10 seconds
76-
serverCacheTtl: 60, // Cache server/tool metadata for 60 seconds
7776
});
7877

7978
// Full type safety and autocomplete
@@ -107,11 +106,114 @@ const client = new McpdClient({
107106
apiEndpoint: "http://localhost:8090", // Required
108107
apiKey: "optional-key", // Optional: API key for authentication
109108
healthCacheTtl: 10, // Optional: TTL in seconds for health cache (default: 10)
110-
serverCacheTtl: 60, // Optional: TTL in seconds for server/tools cache (default: 60)
111109
timeout: 30000, // Optional: Request timeout in ms (default: 30000)
112110
});
113111
```
114112

113+
### Logging
114+
115+
The SDK includes optional logging for warnings about unhealthy or non-existent servers that are skipped during operations.
116+
117+
**Important:** Logging is disabled by default. Only enable logging in non-MCP-server contexts. MCP servers using stdio transport for JSON-RPC communication should never enable logging, as it will contaminate stdout/stderr and break the protocol.
118+
119+
#### Using Environment Variable
120+
121+
Set the `MCPD_LOG_LEVEL` environment variable to control logging:
122+
123+
```bash
124+
# Valid levels: trace, debug, info, warn, error, off (default)
125+
export MCPD_LOG_LEVEL=warn
126+
```
127+
128+
**Available Log Levels:**
129+
130+
| Level | Description |
131+
| ------- | --------------------------------------------------------------- |
132+
| `trace` | Verbose information (includes `debug`, `info`, `warn`, `error`) |
133+
| `debug` | Debug information (includes `info`, `warn`, `error`) |
134+
| `info` | General informational messages (includes `warn`, `error`) |
135+
| `warn` | Warning messages only (includes `error`) |
136+
| `error` | Error messages only |
137+
| `off` | (...or unset) Logging disabled (default) |
138+
139+
```typescript
140+
// Logging is automatically enabled based on MCPD_LOG_LEVEL
141+
const client = new McpdClient({
142+
apiEndpoint: "http://localhost:8090",
143+
});
144+
```
145+
146+
#### Using Custom Logger
147+
148+
For advanced use cases, inject your own logger implementation.
149+
150+
**Partial Logger Support:** You can provide only the methods you want to customize. Any omitted methods will fall back to the default logger, which respects `MCPD_LOG_LEVEL`.
151+
152+
```typescript
153+
import { McpdClient } from "@mozilla-ai/mcpd";
154+
155+
// Full custom logger
156+
const client = new McpdClient({
157+
apiEndpoint: "http://localhost:8090",
158+
logger: {
159+
trace: (...args) => myLogger.trace(args),
160+
debug: (...args) => myLogger.debug(args),
161+
info: (...args) => myLogger.info(args),
162+
warn: (...args) => myLogger.warn(args),
163+
error: (...args) => myLogger.error(args),
164+
},
165+
});
166+
167+
// Partial logger: custom warn/error, default (MCPD_LOG_LEVEL-aware) for others
168+
const client2 = new McpdClient({
169+
apiEndpoint: "http://localhost:8090",
170+
logger: {
171+
warn: (msg) => console.warn(`[mcpd] ${msg}`),
172+
error: (msg) => console.error(`[mcpd] ${msg}`),
173+
// trace, debug, info use default logger (respects MCPD_LOG_LEVEL)
174+
},
175+
});
176+
```
177+
178+
#### Disabling Logging
179+
180+
To disable logging, simply ensure `MCPD_LOG_LEVEL` is unset or set to `off` (the default):
181+
182+
```typescript
183+
// Logging is disabled by default (no configuration needed)
184+
const client = new McpdClient({
185+
apiEndpoint: "http://localhost:8090",
186+
});
187+
```
188+
189+
If you need to disable logging even when `MCPD_LOG_LEVEL` is set (rare case), provide a custom logger with no-op implementations:
190+
191+
```typescript
192+
// Override MCPD_LOG_LEVEL to force disable
193+
const client = new McpdClient({
194+
apiEndpoint: "http://localhost:8090",
195+
logger: {
196+
trace: () => {},
197+
debug: () => {},
198+
info: () => {},
199+
warn: () => {},
200+
error: () => {},
201+
},
202+
});
203+
```
204+
205+
When logging is enabled, warnings are emitted for:
206+
207+
- Unhealthy servers that are skipped (e.g., status `timeout`, `unreachable`)
208+
- Non-existent servers specified in filter options
209+
210+
Example warning messages:
211+
212+
```text
213+
Skipping unhealthy server 'time' with status 'timeout'
214+
Skipping non-existent server 'unknown'
215+
```
216+
115217
### Core Methods
116218

117219
#### `client.listServers()`
@@ -357,19 +459,28 @@ if (await client.isServerHealthy("time")) {
357459

358460
#### `client.getAgentTools(options?)`
359461

360-
Generate callable functions that work directly with AI agent frameworks. No conversion layers needed.
462+
Generate (cached) callable functions that work directly with AI agent frameworks. No conversion layers needed.
463+
464+
> [!IMPORTANT]
465+
> If you want to get agent tools mutiple times using different filter options, you need to call `client.clearAgentToolsCache()` to force regeneration.
361466
362-
**Why filter tools?**
467+
##### Supports filtering by servers and by tools
363468

364-
AI agents perform better with focused tool sets.
469+
AI agents perform better with focused tool sets they need to complete the given task.
365470
Tool filtering enables progressive disclosure - operators can expose a subset of server tools via `mcpd` configuration,
366471
then agents can further narrow down to only the tools needed for their specific task.
367472
This prevents overwhelming the model's context window and improves response quality.
368473

474+
##### Examples
475+
369476
```typescript
370477
// Options: { servers?: string[], tools?: string[], format?: 'array' | 'object' | 'map' }
371478
// Default format is 'array' (for LangChain)
479+
```
372480

481+
LangChain
482+
483+
```typescript
373484
// Use with LangChain JS (array format is default)
374485
import { ChatOpenAI } from "@langchain/openai";
375486

@@ -387,7 +498,11 @@ const agent = await createOpenAIToolsAgent({
387498
tools: langchainTools,
388499
prompt,
389500
});
501+
```
502+
503+
Vercel-AI
390504

505+
```typescript
391506
// Use with Vercel AI SDK (expects object format)
392507
import { generateText } from "ai";
393508

@@ -397,42 +512,60 @@ const result = await generateText({
397512
tools: vercelTools,
398513
prompt: "What time is it in Tokyo?",
399514
});
515+
```
516+
517+
Filtering examples
400518

519+
```typescript
401520
// Filter to specific servers
402521
const timeTools = await client.getAgentTools({
403522
servers: ["time"],
404523
format: "array",
405524
});
525+
```
406526

527+
```typescript
407528
// Filter by tool names (cross-cutting across all servers)
408529
const mathTools = await client.getAgentTools({
409530
tools: ["add", "multiply"],
410531
});
532+
```
411533

534+
```typescript
412535
// Filter by qualified tool names (server-specific)
413536
const specificTools = await client.getAgentTools({
414537
tools: ["time__get_current_time", "math__add"],
415538
});
539+
```
416540

541+
```typescript
417542
// Combine server and tool filtering
418543
const filteredTools = await client.getAgentTools({
419544
servers: ["time", "math"],
420545
tools: ["add", "get_current_time"],
421546
});
547+
```
422548

549+
```typescript
423550
// Tool filtering works with different formats
424551
const toolsObject = await client.getAgentTools({
425552
tools: ["add", "multiply"],
426553
format: "object",
427554
});
428555

556+
// Clear cached generated functions before recreating
557+
client.clearAgentToolsCache();
558+
429559
// Use with Map for efficient lookups
430560
const toolMap = await client.getAgentTools({ format: "map" });
431561
const timeTool = toolMap.get("time__get_current_time");
432562
if (timeTool) {
433563
const result = await timeTool({ timezone: "UTC" });
434564
}
435565

566+
// Clear cached generated functions before recreating
567+
client.clearAgentToolsCache();
568+
436569
// Each function has metadata for both frameworks
437570
const tools = await client.getAgentTools();
438571
for (const tool of tools) {
@@ -448,7 +581,7 @@ for (const tool of tools) {
448581
Clear the cache of generated agent tools functions.
449582

450583
```typescript
451-
// Clear cache to regenerate tools with latest schemas
584+
// Clear cache to regenerate tools with latest schemas, or latest client side server/tool filters.
452585
client.clearAgentToolsCache();
453586
const freshTools = await client.getAgentTools();
454587
```

examples/basic/.mcpd.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[[servers]]
2+
name = "fetch"
3+
package = "uvx::mcp-server-fetch@2025.4.7"
4+
tools = ["fetch"]
5+
6+
[[servers]]
7+
name = "time"
8+
package = "uvx::mcp-server-time@2025.8.4"
9+
tools = ["get_current_time", "convert_time"]

examples/basic/README.md

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,65 @@ Basic example demonstrating core features of the mcpd JavaScript/TypeScript SDK.
1111
- Calling tools dynamically
1212
- Error handling
1313

14-
## Prerequisites
14+
## Requirements
1515

16-
- Node.js 22 LTS or higher
17-
- mcpd daemon running locally on port 8090
18-
- At least one MCP server configured (example uses 'time' server)
16+
- [Node.js](https://nodejs.org/) (version 22.10 LTS or higher)
17+
- [mcpd](https://mozilla-ai.github.io/mcpd/installation/) - install via: `brew install mcpd`
1918

20-
## Installation
19+
## Installing mcpd
20+
21+
The easiest way to install mcpd is via Homebrew:
22+
23+
```bash
24+
brew tap mozilla-ai/tap
25+
brew install mcpd
26+
```
27+
28+
For other installation methods, see the [mcpd installation guide](https://mozilla-ai.github.io/mcpd/installation/).
29+
30+
## Starting mcpd
31+
32+
### Execution context config file
33+
34+
`~/.config/mcpd/secrets.dev.toml` is the file that is used to provide user specific configuration to MCP servers via `mcpd`.
35+
36+
Here is an example of some custom configuration for the `mcp-server-time` (time) server:
37+
38+
```toml
39+
[servers]
40+
[servers.time]
41+
args = ["--local-timezone=Europe/London"]
42+
```
43+
44+
Run the following command to create this file if you don't want the time MCP Server to use defaults:
45+
46+
```bash
47+
mcpd config args set time -- --local-timezone=Europe/London
48+
```
49+
50+
### Project configuration file
51+
52+
The `.mcpd.toml` file in this folder is used to start specific versions of MCP servers:
53+
54+
```bash
55+
mcpd daemon --log-level=DEBUG --log-path=$(pwd)/mcpd.log
56+
```
57+
58+
The `mcpd` daemon will start the servers, emitting messages to the terminal. You can tail the log to see more info:
59+
60+
```bash
61+
tail -f mcpd.log
62+
```
63+
64+
## Running the Example
65+
66+
### 1. Install dependencies
2167

2268
```bash
2369
npm install
2470
```
2571

26-
## Running
72+
### 2. Run the example
2773

2874
```bash
2975
npm start

0 commit comments

Comments
 (0)