fa-mcp-sdk 0.4.124 → 0.4.134
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/cli-template/AGENTS.md +1 -1
- package/cli-template/FA-MCP-SDK-DOC/00-FA-MCP-SDK-index.md +19 -5
- package/cli-template/FA-MCP-SDK-DOC/02-1-tools-and-api.md +63 -0
- package/cli-template/FA-MCP-SDK-DOC/06-utilities.md +133 -5
- package/cli-template/FA-MCP-SDK-DOC/07-testing-and-operations.md +85 -0
- package/cli-template/FA-MCP-SDK-DOC/08-agent-tester-and-headless-api.md +284 -0
- package/cli-template/FA-MCP-SDK-DOC/10-mcp-apps.md +90 -0
- package/cli-template/examples/mcp-apps-canonical/README.md +62 -0
- package/cli-template/examples/mcp-apps-canonical/server.ts +95 -0
- package/cli-template/examples/mcp-apps-canonical/widget/index.html +147 -0
- package/cli-template/package.json +2 -1
- package/config/_local.yaml +6 -0
- package/config/custom-environment-variables.yaml +5 -0
- package/config/default.yaml +15 -0
- package/dist/core/_types_/config.d.ts +20 -0
- package/dist/core/_types_/config.d.ts.map +1 -1
- package/dist/core/_types_/types.d.ts +13 -0
- package/dist/core/_types_/types.d.ts.map +1 -1
- package/dist/core/agent-tester/agent-tester-router.d.ts.map +1 -1
- package/dist/core/agent-tester/agent-tester-router.js +79 -2
- package/dist/core/agent-tester/agent-tester-router.js.map +1 -1
- package/dist/core/agent-tester/services/TesterAgentService.d.ts +14 -0
- package/dist/core/agent-tester/services/TesterAgentService.d.ts.map +1 -1
- package/dist/core/agent-tester/services/TesterAgentService.js +101 -1
- package/dist/core/agent-tester/services/TesterAgentService.js.map +1 -1
- package/dist/core/agent-tester/services/TesterMcpClientService.d.ts +1 -0
- package/dist/core/agent-tester/services/TesterMcpClientService.d.ts.map +1 -1
- package/dist/core/agent-tester/services/TesterMcpClientService.js +46 -19
- package/dist/core/agent-tester/services/TesterMcpClientService.js.map +1 -1
- package/dist/core/agent-tester/services/mcp-apps-utils.d.ts +22 -0
- package/dist/core/agent-tester/services/mcp-apps-utils.d.ts.map +1 -0
- package/dist/core/agent-tester/services/mcp-apps-utils.js +77 -0
- package/dist/core/agent-tester/services/mcp-apps-utils.js.map +1 -0
- package/dist/core/agent-tester/types.d.ts +65 -0
- package/dist/core/agent-tester/types.d.ts.map +1 -1
- package/dist/core/index.d.ts +4 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +4 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/init-mcp-server.d.ts.map +1 -1
- package/dist/core/init-mcp-server.js +46 -5
- package/dist/core/init-mcp-server.js.map +1 -1
- package/dist/core/mcp/builtin-debug-tools.d.ts +41 -0
- package/dist/core/mcp/builtin-debug-tools.d.ts.map +1 -0
- package/dist/core/mcp/builtin-debug-tools.js +75 -0
- package/dist/core/mcp/builtin-debug-tools.js.map +1 -0
- package/dist/core/mcp/debug-trace.d.ts +26 -0
- package/dist/core/mcp/debug-trace.d.ts.map +1 -0
- package/dist/core/mcp/debug-trace.js +79 -0
- package/dist/core/mcp/debug-trace.js.map +1 -0
- package/dist/core/mcp/prompts.d.ts.map +1 -1
- package/dist/core/mcp/prompts.js +11 -0
- package/dist/core/mcp/prompts.js.map +1 -1
- package/dist/core/mcp/resources.d.ts.map +1 -1
- package/dist/core/mcp/resources.js +11 -0
- package/dist/core/mcp/resources.js.map +1 -1
- package/dist/core/utils/formatToolResult.d.ts +39 -0
- package/dist/core/utils/formatToolResult.d.ts.map +1 -1
- package/dist/core/utils/formatToolResult.js +58 -0
- package/dist/core/utils/formatToolResult.js.map +1 -1
- package/dist/core/utils/testing/debug-tool.d.ts +35 -0
- package/dist/core/utils/testing/debug-tool.d.ts.map +1 -0
- package/dist/core/utils/testing/debug-tool.js +146 -0
- package/dist/core/utils/testing/debug-tool.js.map +1 -0
- package/dist/core/web/server-http.d.ts.map +1 -1
- package/dist/core/web/server-http.js +26 -1
- package/dist/core/web/server-http.js.map +1 -1
- package/dist/core/web/static/agent-tester/index.html +55 -0
- package/dist/core/web/static/agent-tester/script.js +986 -9
- package/dist/core/web/static/agent-tester/styles.css +416 -0
- package/package.json +1 -1
package/cli-template/AGENTS.md
CHANGED
|
@@ -128,7 +128,7 @@ Detailed fa-mcp-sdk docs are in `FA-MCP-SDK-DOC/`:
|
|
|
128
128
|
| `05-ad-authorization.md` | AD group authorization, AD config |
|
|
129
129
|
| `06-utilities.md` | Error handling, logging, Consul |
|
|
130
130
|
| `07-testing-and-operations.md` | Test clients (STDIO, HTTP, SSE, Streamable HTTP) |
|
|
131
|
-
| `08-agent-tester-and-headless-api.md` | Agent Tester, Headless API, structured logging, automated testing |
|
|
131
|
+
| `08-agent-tester-and-headless-api.md` | Agent Tester, Headless API, structured logging, automated testing, MCP Apps mode (capability negotiation, `appCalls[]`, widget rendering, App Inspector) |
|
|
132
132
|
| `09-database.md` | PostgreSQL sugar layer (`queryMAIN`, `execMAIN`, upserts, `mergeByBatch`), `pgvector`, secondary DBs |
|
|
133
133
|
| `10-mcp-apps.md` | Building / extending MCP Apps (UI-augmented tools) — protocol contract, SDK surface, patterns, pitfalls |
|
|
134
134
|
|
|
@@ -18,11 +18,11 @@ npm install fa-mcp-sdk
|
|
|
18
18
|
| [03-configuration](03-configuration.md) | `appConfig`, YAML config, access points for external services, cache | Server configuration, external services |
|
|
19
19
|
| [04-authentication](04-authentication.md) | JWT, Basic auth, server tokens, `createAuthMW()`, Token Generator, CLI Token Generator, JWT Generation API | Authentication setup |
|
|
20
20
|
| [05-ad-authorization](05-ad-authorization.md) | AD group authorization at HTTP/tool levels | AD group restrictions |
|
|
21
|
-
| [06-utilities](06-utilities.md) | `ServerError`, `normalizeHeaders`, logging, MCP debug switches (`DEBUG=mcp:*`), Consul, graceful shutdown | Error handling, utilities, request tracing |
|
|
22
|
-
| [07-testing-and-operations](07-testing-and-operations.md) | Test clients (STDIO, HTTP, SSE, Streamable HTTP) | Testing, deployment |
|
|
23
|
-
| [08-agent-tester-and-headless-api](08-agent-tester-and-headless-api.md) | Agent Tester, Headless API, structured logging, automated testing, UI `data-testid` reference | Agent-driven tool development, CLI automation, UI E2E tests |
|
|
21
|
+
| [06-utilities](06-utilities.md) | `ServerError`, `normalizeHeaders`, logging, MCP debug switches (`DEBUG=mcp:*`), JSON-lines sink (`mcp.debug.logFile` → `emitTrace`), built-in debug tools (`mcp.debug.builtinTools`), Consul, graceful shutdown | Error handling, utilities, request tracing, post-mortem analysis |
|
|
22
|
+
| [07-testing-and-operations](07-testing-and-operations.md) | Test clients (STDIO, HTTP, SSE, Streamable HTTP); universal `debug-tool` fixture covering every `CallToolResult` shape | Testing, deployment, exercising client code against image/audio/resource/error/delay variants |
|
|
23
|
+
| [08-agent-tester-and-headless-api](08-agent-tester-and-headless-api.md) | Agent Tester, Headless API, structured logging, automated testing, UI `data-testid` reference. **MCP Apps mode**: capability negotiation, `appCalls[]` / `app_calls[]`, widget iframe bridge, App Inspector tab | Agent-driven tool development, CLI automation, UI E2E tests, MCP Apps host for development |
|
|
24
24
|
| [09-database](09-database.md) | PostgreSQL sugar layer (`queryMAIN`, `execMAIN`, `getInsertSqlMAIN`, `getMergeSqlMAIN`, `mergeByBatch`), `pgvector`, secondary DBs | Database access, upserts, batching |
|
|
25
|
-
| [10-mcp-apps](10-mcp-apps.md) | Self-contained digest of the MCP Apps protocol + SDK pinned to `@modelcontextprotocol/ext-apps v1.7.2` (spec 2026-01-26): `ui://` resources, `_meta.ui`, JSON-RPC messages, `App` class, host context, patterns, pitfalls | Building / extending MCP Apps (UI-augmented tools) |
|
|
25
|
+
| [10-mcp-apps](10-mcp-apps.md) | Self-contained digest of the MCP Apps protocol + SDK pinned to `@modelcontextprotocol/ext-apps v1.7.2` (spec 2026-01-26): `ui://` resources, `_meta.ui`, JSON-RPC messages, `App` class, host context, patterns, pitfalls. **Canonical example** (`examples/mcp-apps-canonical/`, `npm run example:mcp-apps`) and widget-side debug helpers (`mcp-debug-log`, `mcp-debug-refresh`). Cross-links to Agent Tester as a dev-host (doc 08) | Building / extending MCP Apps (UI-augmented tools) |
|
|
26
26
|
|
|
27
27
|
## Key Exports
|
|
28
28
|
|
|
@@ -35,7 +35,9 @@ import { createAuthMW, generateToken, getAuthHeadersForTests, TTokenType, genera
|
|
|
35
35
|
|
|
36
36
|
// Tools & Errors
|
|
37
37
|
import {
|
|
38
|
-
formatToolResult,
|
|
38
|
+
formatToolResult, formatToolError, // formatToolError → sets isError: true
|
|
39
|
+
asTextContent, asTextError, asJson, asJsonError, // direct-shape helpers (text + structured, ok + error)
|
|
40
|
+
getJsonFromResult,
|
|
39
41
|
TToolHandlerResponse, IToolHandlerTextResponse, IToolHandlerStructuredResponse,
|
|
40
42
|
ToolExecutionError, ServerError, BaseMcpError, ValidationError, getTools,
|
|
41
43
|
} from 'fa-mcp-sdk';
|
|
@@ -55,6 +57,18 @@ import { logger, fileLogger, Logger, applyLoggerSettings, trim, ppj, toError, to
|
|
|
55
57
|
// MCP debug switches (DEBUG=mcp:tool|mcp:resource|mcp:prompt|mcp:notification or DEBUG=mcp:*)
|
|
56
58
|
import { debugMcpTool, debugMcpResource, debugMcpPrompt, debugMcpNotification, debugTokenAuth } from 'fa-mcp-sdk';
|
|
57
59
|
|
|
60
|
+
// JSON-lines debug sink (mcp.debug.logFile) — see 06-utilities → "JSON-lines Sink"
|
|
61
|
+
import { emitTrace, configureDebugSink, initDebugTraceFromConfig } from 'fa-mcp-sdk';
|
|
62
|
+
|
|
63
|
+
// Built-in debug tools (enabled by mcp.debug.builtinTools=true)
|
|
64
|
+
// — see 06-utilities → "Built-in Debug Tools", 07-testing → "Universal debug-tool", 10-mcp-apps § 8.14
|
|
65
|
+
import {
|
|
66
|
+
BUILTIN_MCP_DEBUG_TOOLS, BUILTIN_MCP_DEBUG_TOOL_NAMES,
|
|
67
|
+
MCP_DEBUG_LOG_TOOL_NAME, MCP_DEBUG_REFRESH_TOOL_NAME,
|
|
68
|
+
isBuiltinDebugTool, handleBuiltinDebugTool,
|
|
69
|
+
DEBUG_TOOL, DEBUG_TOOL_NAME, handleDebugTool, registerDebugTool,
|
|
70
|
+
} from 'fa-mcp-sdk';
|
|
71
|
+
|
|
58
72
|
// Test Clients
|
|
59
73
|
import { McpHttpClient, McpStdioClient, McpSseClient, McpStreamableHttpClient } from 'fa-mcp-sdk';
|
|
60
74
|
|
|
@@ -56,6 +56,69 @@ The handler must return `TToolHandlerResponse` — a discriminated union of
|
|
|
56
56
|
the value as-is to the MCP client over STDIO, SSE, and HTTP. Use `formatToolResult()`
|
|
57
57
|
to pick the right shape based on `appConfig.mcp.tools.answerAs`.
|
|
58
58
|
|
|
59
|
+
### Returning errors — `isError: true` vs `throw`
|
|
60
|
+
|
|
61
|
+
The MCP spec distinguishes two error classes, and the LLM behaves very differently for each:
|
|
62
|
+
|
|
63
|
+
| Error class | How to return | What the LLM sees |
|
|
64
|
+
|------------------------|----------------------------------------------------------|----------------------------------------------------|
|
|
65
|
+
| **Tool-level** | `return formatToolError(msg)` (`isError: true` in result) | Error text inside the conversation — can self-correct, retry, ask the user |
|
|
66
|
+
| **Protocol-level** | `throw new ToolExecutionError(name, msg)` | JSON-RPC `error` envelope — most clients surface this as a hard sandbox failure the model cannot react to |
|
|
67
|
+
|
|
68
|
+
**Use `formatToolError()` for:**
|
|
69
|
+
|
|
70
|
+
- resource not found (`Issue AITECH-1 not found`)
|
|
71
|
+
- business validation (`Date must be in the past`)
|
|
72
|
+
- upstream API returned a recoverable error (404, 422, "rate limited, retry later")
|
|
73
|
+
- partial success the LLM should explain to the user
|
|
74
|
+
|
|
75
|
+
**Throw for:**
|
|
76
|
+
|
|
77
|
+
- unknown tool name (`switch` default branch)
|
|
78
|
+
- missing required transport feature (e.g. no `Mcp-Session-Id` for stateful clients)
|
|
79
|
+
- genuine infrastructure failure (DB connection dead, secret missing) that the LLM cannot work around
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import {
|
|
83
|
+
formatToolResult, formatToolError, ToolExecutionError,
|
|
84
|
+
IToolHandlerParams, TToolHandlerResponse,
|
|
85
|
+
} from 'fa-mcp-sdk';
|
|
86
|
+
|
|
87
|
+
export const handleToolCall = async (
|
|
88
|
+
params: IToolHandlerParams,
|
|
89
|
+
): Promise<TToolHandlerResponse> => {
|
|
90
|
+
const { name, arguments: args } = params;
|
|
91
|
+
|
|
92
|
+
switch (name) {
|
|
93
|
+
case 'get_issue': {
|
|
94
|
+
const issue = await jira.findIssue(args.key);
|
|
95
|
+
if (!issue) {
|
|
96
|
+
// Tool-level: LLM sees "Issue X not found" and can ask the user to clarify.
|
|
97
|
+
return formatToolError(`Issue ${args.key} not found`);
|
|
98
|
+
}
|
|
99
|
+
return formatToolResult(issue);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
default:
|
|
103
|
+
// Protocol-level: client routing problem, not something the LLM should retry.
|
|
104
|
+
throw new ToolExecutionError(name, `Unknown tool: ${name}`);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Direct-shape helpers (ignore `tools.answerAs`):
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { asTextError, asJsonError } from 'fa-mcp-sdk';
|
|
113
|
+
|
|
114
|
+
asTextError('Not found'); // { content: [{type:'text', text:'Not found'}], isError: true }
|
|
115
|
+
asJsonError({ code: 'NOT_FOUND', key: 'X' }); // { structuredContent: {...}, isError: true }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> **Migration tip.** If your current handler does `throw new ToolExecutionError(name, 'Not found: ...')`
|
|
119
|
+
> for missing resources, convert those branches to `return formatToolError('Not found: ...')`. The
|
|
120
|
+
> LLM will start surfacing "Such an issue does not exist" to the user instead of failing the call.
|
|
121
|
+
|
|
59
122
|
### Headers Access
|
|
60
123
|
|
|
61
124
|
Headers are normalized to lowercase. Available in HTTP/SSE transports:
|
|
@@ -77,7 +77,8 @@ const normalized = normalizeHeaders({
|
|
|
77
77
|
|
|
78
78
|
```typescript
|
|
79
79
|
import {
|
|
80
|
-
getTools, formatToolResult,
|
|
80
|
+
getTools, formatToolResult, formatToolError, getJsonFromResult,
|
|
81
|
+
asTextContent, asTextError, asJson, asJsonError,
|
|
81
82
|
TToolHandlerResponse, IToolHandlerTextResponse, IToolHandlerStructuredResponse,
|
|
82
83
|
} from 'fa-mcp-sdk';
|
|
83
84
|
|
|
@@ -87,27 +88,40 @@ const tools = await getTools(); // Get registered tools
|
|
|
87
88
|
// Return type: TToolHandlerResponse<T> = IToolHandlerTextResponse | IToolHandlerStructuredResponse<T>
|
|
88
89
|
const result = formatToolResult<{ message: string; data: object }>({ message: 'Done', data: {} });
|
|
89
90
|
|
|
91
|
+
// Tool-level error — `isError: true` so the LLM sees it in conversation
|
|
92
|
+
// and can self-correct instead of treating it as a protocol failure.
|
|
93
|
+
const fail = formatToolError(`Issue ${key} not found`);
|
|
94
|
+
|
|
90
95
|
// Returns structuredContent or JSON from text depending on appConfig.mcp.tools.answerAs
|
|
91
96
|
const original = getJsonFromResult<T>(result);
|
|
92
97
|
|
|
93
98
|
// Direct formatting helpers (ignore tools.answerAs config):
|
|
94
|
-
asTextContent('Hello');
|
|
95
|
-
asJson({ status: 'ok' });
|
|
99
|
+
asTextContent('Hello'); // { content: [{ type: 'text', text: 'Hello' }] }
|
|
100
|
+
asJson({ status: 'ok' }); // { structuredContent: { status: 'ok' } }
|
|
101
|
+
asTextError('Not found'); // { content: [{ type: 'text', text: 'Not found' }], isError: true }
|
|
102
|
+
asJsonError({ code: 'NOT_FOUND' }); // { structuredContent: { code: 'NOT_FOUND' }, isError: true }
|
|
96
103
|
```
|
|
97
104
|
|
|
98
105
|
### Return Type Signatures
|
|
99
106
|
|
|
100
107
|
```typescript
|
|
101
108
|
function formatToolResult<T = any>(json: T): TToolHandlerResponse<T>;
|
|
109
|
+
function formatToolError<T = any>(json: T): TToolHandlerResponse<T>; // sets isError: true
|
|
102
110
|
function asTextContent(text: string): IToolHandlerTextResponse;
|
|
111
|
+
function asTextError(text: string): IToolHandlerTextResponse; // sets isError: true
|
|
103
112
|
function asJson<T = any>(json: T): IToolHandlerStructuredResponse<T>;
|
|
113
|
+
function asJsonError<T = any>(json: T): IToolHandlerStructuredResponse<T>; // sets isError: true
|
|
104
114
|
function getJsonFromResult<T = any>(result: TToolHandlerResponse | any): T;
|
|
105
115
|
```
|
|
106
116
|
|
|
107
117
|
### When to Use Which
|
|
108
118
|
|
|
109
|
-
- **`formatToolResult()`** — Primary
|
|
110
|
-
|
|
119
|
+
- **`formatToolResult()` / `formatToolError()`** — Primary choices in tool handlers. Respect
|
|
120
|
+
`appConfig.mcp.tools.answerAs`. Use `formatToolError()` for *tool-level* failures (not found,
|
|
121
|
+
validation, upstream 4xx) so the LLM sees them in conversation. See
|
|
122
|
+
[02-1-tools-and-api.md → "Returning errors"](./02-1-tools-and-api.md) for the full guide.
|
|
123
|
+
- **`asTextContent()` / `asTextError()` / `asJson()` / `asJsonError()`** — Direct formatting,
|
|
124
|
+
ignore `tools.answerAs`. Use when a specific shape is required.
|
|
111
125
|
- **`getJsonFromResult()`** — Inverse of `formatToolResult()`. Extracts JSON from either format. Use in tests.
|
|
112
126
|
|
|
113
127
|
## Network Utilities
|
|
@@ -291,6 +305,120 @@ the category is off. The four built-in `debugMcpTool`/`debugMcpResource`/`debugM
|
|
|
291
305
|
them from your own code (e.g. emit a custom line inside `handle-tool-call.ts` whenever
|
|
292
306
|
`debugMcpTool.enabled` is true).
|
|
293
307
|
|
|
308
|
+
## JSON-lines Sink (`mcp.debug.logFile`)
|
|
309
|
+
|
|
310
|
+
`DEBUG=mcp:*` writes ANSI-coloured human-readable text to stderr — perfect for live development,
|
|
311
|
+
useless for post-mortem (colours, interleaved process output, no structured fields). Set
|
|
312
|
+
`mcp.debug.logFile` to an absolute path and the SDK additionally mirrors every `mcp:tool`,
|
|
313
|
+
`mcp:resource`, `mcp:prompt` event as one JSON object per line. The stderr stream is unchanged —
|
|
314
|
+
the sink is purely additive.
|
|
315
|
+
|
|
316
|
+
```yaml
|
|
317
|
+
# config/default.yaml — or any environment override
|
|
318
|
+
mcp:
|
|
319
|
+
debug:
|
|
320
|
+
logFile: /var/log/mcp/server-debug.jsonl # absolute path; parent dir is created on first event
|
|
321
|
+
builtinTools: false # see next section
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Or via env (mapped through `config/custom-environment-variables.yaml`):
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
MCP_DEBUG_LOG_FILE=/var/log/mcp/server.jsonl yarn start
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Event Shape
|
|
331
|
+
|
|
332
|
+
Each line is a self-contained JSON object. `ts` (ISO timestamp) and `ch` (channel) are always
|
|
333
|
+
present; remaining fields depend on the channel and `kind`.
|
|
334
|
+
|
|
335
|
+
```jsonl
|
|
336
|
+
{"ts":"2026-05-19T12:34:56.124Z","ch":"mcp:tool","kind":"req","name":"get_rate","args":{"from":"EUR"},"corr":"a3f1c0d2"}
|
|
337
|
+
{"ts":"2026-05-19T12:34:56.171Z","ch":"mcp:tool","kind":"res","name":"get_rate","ms":47,"corr":"a3f1c0d2","ok":true}
|
|
338
|
+
{"ts":"2026-05-19T12:34:57.012Z","ch":"mcp:tool","kind":"err","name":"get_rate","ms":2998,"corr":"b9c20f3a","error":"Connection timeout"}
|
|
339
|
+
{"ts":"2026-05-19T12:34:57.045Z","ch":"mcp:resource","kind":"read-res","uri":"ui://weather/view.html","ms":3}
|
|
340
|
+
{"ts":"2026-05-19T12:34:57.090Z","ch":"mcp:prompt","kind":"get-res","name":"agent_prompt","ms":1}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
| Channel | `kind` values | Useful fields |
|
|
344
|
+
|-----------------|----------------------------------------------------------------|------------------------------|
|
|
345
|
+
| `mcp:tool` | `req` / `res` / `err` | `name`, `args`, `ms`, `corr` |
|
|
346
|
+
| `mcp:resource` | `list-req` / `list-res` / `read-req` / `read-res` / `read-err` | `uri`, `count`, `ms` |
|
|
347
|
+
| `mcp:prompt` | `list-req` / `list-res` / `get-req` / `get-res` / `get-err` | `name`, `count`, `ms` |
|
|
348
|
+
| `app:view-log` | `log` (emitted by built-in `mcp-debug-log` tool) | `type`, `payload` |
|
|
349
|
+
|
|
350
|
+
`corr` is an 8-char hex correlation ID — pair `req` ↔ `res`/`err` for one tool call.
|
|
351
|
+
|
|
352
|
+
### Working With The File
|
|
353
|
+
|
|
354
|
+
Standard JSON toolchain works as-is:
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
# p95 latency by tool
|
|
358
|
+
jq -r 'select(.ch=="mcp:tool" and .kind=="res") | "\(.name)\t\(.ms)"' /var/log/mcp/*.jsonl \
|
|
359
|
+
| sort | datamash -g 1 perc:95 2
|
|
360
|
+
|
|
361
|
+
# all errors of the last hour
|
|
362
|
+
jq 'select((.kind|test("err$")) and (.ts > "2026-05-19T11:00:00"))' /var/log/mcp/*.jsonl
|
|
363
|
+
|
|
364
|
+
# events pushed by widgets via mcp-debug-log
|
|
365
|
+
jq 'select(.ch=="app:view-log")' /var/log/mcp/*.jsonl
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Programmatic Access
|
|
369
|
+
|
|
370
|
+
If you need to write into the same channel from your own code (e.g. tag a domain event so it shows
|
|
371
|
+
up alongside MCP traffic), use the helpers directly:
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
import { emitTrace, configureDebugSink } from 'fa-mcp-sdk';
|
|
375
|
+
|
|
376
|
+
// At startup the SDK already calls configureDebugSink(appConfig.mcp.debug.logFile);
|
|
377
|
+
// re-configure on the fly only in tests.
|
|
378
|
+
configureDebugSink('/tmp/mcp-test.jsonl');
|
|
379
|
+
|
|
380
|
+
emitTrace('app:billing', { kind: 'charge', userId, amountCents });
|
|
381
|
+
// → {"ts":"…","ch":"app:billing","kind":"charge","userId":"…","amountCents":1299}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
`emitTrace` is a no-op when no sink is configured — the guard is cheap, leave the calls in.
|
|
385
|
+
|
|
386
|
+
## Built-in Debug Tools (`mcp.debug.builtinTools`)
|
|
387
|
+
|
|
388
|
+
A single flag registers three SDK-provided tools that exist to be called from widget code or
|
|
389
|
+
integration tests, never by the LLM. All three are marked `_meta.ui.visibility: ['app']`, so MCP App
|
|
390
|
+
hosts (Agent Tester, Claude Desktop with apps support, etc.) hide them from the agent's tool list.
|
|
391
|
+
|
|
392
|
+
```yaml
|
|
393
|
+
mcp:
|
|
394
|
+
debug:
|
|
395
|
+
builtinTools: true # or MCP_DEBUG_BUILTIN_TOOLS=true
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
| Tool name | Caller | Purpose |
|
|
399
|
+
|---------------------|----------------|-----------------------------------------------------------------------------|
|
|
400
|
+
| `mcp-debug-log` | Widget | Push a structured event into the same channel as `DEBUG=mcp:*` / JSON-lines |
|
|
401
|
+
| `mcp-debug-refresh` | Widget | Read back lightweight server state (timestamp + counter) without the LLM |
|
|
402
|
+
| `debug-tool` | Test client | Universal CallToolResult fixture — see [07-testing-and-operations](07-testing-and-operations.md) → "Universal `debug-tool` for Integration Tests" |
|
|
403
|
+
|
|
404
|
+
The widget-facing tools are covered in [10-mcp-apps](10-mcp-apps.md) → "Widget-side debug helpers"
|
|
405
|
+
(the canonical example calls them through `app.callServerTool(...)`). Names and constants are
|
|
406
|
+
exported when you need to reference them in test code:
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
import {
|
|
410
|
+
MCP_DEBUG_LOG_TOOL_NAME, // 'mcp-debug-log'
|
|
411
|
+
MCP_DEBUG_REFRESH_TOOL_NAME, // 'mcp-debug-refresh'
|
|
412
|
+
DEBUG_TOOL_NAME, // 'debug-tool'
|
|
413
|
+
BUILTIN_MCP_DEBUG_TOOLS, // Tool[] descriptors for the two widget tools
|
|
414
|
+
DEBUG_TOOL, // Tool descriptor for the test fixture
|
|
415
|
+
} from 'fa-mcp-sdk';
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
> Leave `builtinTools: false` in production unless a widget genuinely needs `mcp-debug-log` /
|
|
419
|
+
> `mcp-debug-refresh` at runtime. The tools are inert to the LLM, but they still occupy space in the
|
|
420
|
+
> `tools/list` payload and add a small amount of routing overhead per call.
|
|
421
|
+
|
|
294
422
|
## Event System
|
|
295
423
|
|
|
296
424
|
```typescript
|
|
@@ -102,6 +102,91 @@ const result = await client.callTool('my_tool', { query: 'test' }, headers);
|
|
|
102
102
|
- **Transport parity** — same behavior across STDIO, HTTP, SSE
|
|
103
103
|
- **Edge cases** — empty strings, large payloads, special characters
|
|
104
104
|
|
|
105
|
+
## Universal `debug-tool` for Integration Tests
|
|
106
|
+
|
|
107
|
+
When the system-under-test is a **client** (Agent Tester, custom MCP host, CI smoke test) rather
|
|
108
|
+
than the server, you usually need a server that produces every kind of `CallToolResult` on demand —
|
|
109
|
+
text, image, audio, embedded resources, mixed blocks, `isError: true`, slow responses, large
|
|
110
|
+
payloads. The SDK ships a single parameterised fixture so test code never has to roll its own
|
|
111
|
+
fake server.
|
|
112
|
+
|
|
113
|
+
Enable it together with the other built-ins ([06-utilities](06-utilities.md) → "Built-in Debug
|
|
114
|
+
Tools"):
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
mcp:
|
|
118
|
+
debug:
|
|
119
|
+
builtinTools: true
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This appends a tool named `debug-tool` to the server's `tools/list`, hidden from the LLM via
|
|
123
|
+
`_meta.ui.visibility: ['app']`.
|
|
124
|
+
|
|
125
|
+
### Input Schema
|
|
126
|
+
|
|
127
|
+
| Argument | Type / values | Default | Purpose |
|
|
128
|
+
|----------------------------|---------------------------------------------------------------|---------|--------------------------------------|
|
|
129
|
+
| `contentType` | `text` \| `image` \| `audio` \| `resource` \| `resourceLink` \| `mixed` | `text` | Which content-block type to emit. `mixed` returns one of each (ignores `multipleBlocks`) |
|
|
130
|
+
| `multipleBlocks` | `boolean` | `true` | Emit 3 blocks of the chosen type vs. 1 |
|
|
131
|
+
| `includeStructuredContent` | `boolean` | `true` | Include `result.structuredContent` with `{ config, timestamp, counter, largeInputLength? }` |
|
|
132
|
+
| `includeMeta` | `boolean` | `true` | Include `result._meta.debugInfo` |
|
|
133
|
+
| `simulateError` | `boolean` | `false` | Set `result.isError = true` (call still resolves) |
|
|
134
|
+
| `delayMs` | `number` ≥ 0 | none | Artificial latency for timeout / loading-state tests |
|
|
135
|
+
| `largeInput` | `string` | none | Large payload — echoed back as `structuredContent.largeInputLength` |
|
|
136
|
+
|
|
137
|
+
### Example: Single Server, Every Variation
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// tests/agent-tester/content-types.test.ts
|
|
141
|
+
import { McpHttpClient } from 'fa-mcp-sdk';
|
|
142
|
+
|
|
143
|
+
const client = new McpHttpClient('http://localhost:9876');
|
|
144
|
+
|
|
145
|
+
test('renders mixed text + image + audio', async () => {
|
|
146
|
+
const result = await client.callTool('debug-tool', { contentType: 'mixed' });
|
|
147
|
+
expect(result.content).toHaveLength(3);
|
|
148
|
+
expect(result.content.map((b: any) => b.type)).toEqual(['text', 'image', 'audio']);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('isError: true is surfaced', async () => {
|
|
152
|
+
const result = await client.callTool('debug-tool', {
|
|
153
|
+
contentType: 'text',
|
|
154
|
+
simulateError: true,
|
|
155
|
+
});
|
|
156
|
+
expect((result as any).isError).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('respects delayMs for loading-state tests', async () => {
|
|
160
|
+
const t0 = Date.now();
|
|
161
|
+
await client.callTool('debug-tool', { contentType: 'text', delayMs: 800 });
|
|
162
|
+
expect(Date.now() - t0).toBeGreaterThanOrEqual(800);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('large payload survives the round trip', async () => {
|
|
166
|
+
const big = 'x'.repeat(200_000);
|
|
167
|
+
const result = await client.callTool('debug-tool', { contentType: 'text', largeInput: big });
|
|
168
|
+
expect((result as any).structuredContent.largeInputLength).toBe(200_000);
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Standalone Test Server
|
|
173
|
+
|
|
174
|
+
If you need a throw-away server outside `initMcpServer` (e.g. spinning up a bare
|
|
175
|
+
`@modelcontextprotocol/sdk` `McpServer` for an in-process test), use `registerDebugTool` directly:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
179
|
+
import { registerDebugTool, DEBUG_TOOL_NAME } from 'fa-mcp-sdk';
|
|
180
|
+
|
|
181
|
+
const server = new McpServer({ name: 'test-fixture', version: '0.0.0' });
|
|
182
|
+
registerDebugTool(server);
|
|
183
|
+
// → callTool(DEBUG_TOOL_NAME, { contentType: 'mixed' }) works against this server.
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The helper accepts any object with `registerTool(name, def, handler)` — structurally compatible
|
|
187
|
+
with the high-level SDK API — so the SDK does not pull in a hard dependency on
|
|
188
|
+
`@modelcontextprotocol/sdk/server/mcp.js`.
|
|
189
|
+
|
|
105
190
|
## Best Practices
|
|
106
191
|
|
|
107
192
|
### Project Organization
|