Capture consistent logs, metrics, and trace metadata for Fentaris proxy operations.
Start with autoLog, add stable tags in middleware, then forward structured entries to your telemetry pipeline when stdout is not enough.
Quick Start
Enable automatic proxy logging on the high-level proxy declaration.
import { fentaris, mcp, stdio } from "@fentaris/core";
const proxy = fentaris({
servers: [
mcp("filesystem", {
transport: stdio({
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "/srv/shared"],
}),
}),
],
autoLog: true,
});
This records request timing, subject metadata, server name, tool name, policy outcome, credential source metadata, and lifecycle events where available.
For JSON logs on stdout, pass jsonConsoleLogger() as the proxy logger.
import { fentaris, jsonConsoleLogger } from "@fentaris/core";
const proxy = fentaris({
logger: jsonConsoleLogger(),
autoLog: true,
});
Add Contextual Metadata
Use middleware to attach correlation tags that should appear on every log entry for a request.
proxy.use(async (ctx, next) => {
ctx.log.setTag("traceId", ctx.transport.requestId ?? "no-trace");
ctx.log.setTag("tenantId", String(ctx.subject?.tenant?.id ?? "unknown"));
ctx.log.setTag("subject", ctx.subject?.id ?? "anonymous");
return next();
});
Prefer stable tags such as traceId, tenantId, subject, and server. Avoid raw prompts, raw tool arguments, API keys, bearer tokens, and decrypted credentials.
Use server-scoped routes when you need custom logs for one upstream MCP server.
proxy.mcp("github").tool("create_issue", async (ctx, next) => {
const started = Date.now();
const result = await next();
ctx.log.info("github.create_issue.completed", {
durationMs: Date.now() - started,
policyAllowed: ctx.policy.allowed,
});
return result;
});
Keep event names stable. They become query keys in log stores and dashboards.
Trace Resources And Prompts
Resource, prompt, and completion operations emit lifecycle events as well as tool calls.
proxy.mcp("docs").operation("resource:read", async (ctx, next) => {
ctx.log.info("docs.resource.read", {
uri: ctx.resource?.uri,
allowed: ctx.policy.allowed,
});
return next();
});
proxy.on("prompt:success", async ({ ctx, durationMs }) => {
ctx.log.info("prompt.success", {
prompt: ctx.prompt?.name,
durationMs,
});
});
Audit logs should include operation, subject, server, target, policy outcome, and credential source metadata.
Local capabilities declared with app.local(name) emit the same event families. The local namespace is reported as ctx.server?.name.
proxy.on("tool:success", ({ ctx, durationMs }) => {
if (ctx.server?.name === "workspace") {
ctx.log.info("workspace.tool.success", {
tool: ctx.tool?.name,
durationMs,
});
}
});
proxy.on("resource:error", ({ ctx, error }) => {
ctx.log.warn("resource.error", {
namespace: ctx.server?.name,
uri: ctx.resource?.uri,
error: error?.message,
});
});
Use A Custom Driver
Pass a logger to fentaris(...) when stdout is not the right destination.
import { Logger, fentaris, mcp, stdio } from "@fentaris/core";
const logger = new Logger({
driver: {
write(entry) {
sendToTelemetry({
level: entry.level,
message: entry.message,
tags: entry.tags,
metadata: entry.metadata,
timestamp: entry.timestamp,
});
},
},
});
const proxy = fentaris({
logger,
autoLog: true,
servers: [
mcp("github", {
transport: stdio({ command: "github-mcp-server" }),
}),
],
});
If you use Datadog, Honeycomb, OpenTelemetry, or another structured telemetry system, forward the full entry and map tags to indexed fields.
Recommended Signals
Track these signals for production operations:
- Tool call counts by server, tool, subject, and policy outcome.
- Denied calls and denial reasons.
- Latency by server, tool, operation, and upstream transport.
- Session start, session close, and transport errors.
- Resource, prompt, and completion operations by target.
Metrics Aggregation
If you prefer metrics over logs, wrap the logger driver to emit counters and histograms while still writing structured logs.
const logger = new Logger({
driver: {
write(entry) {
metrics.increment(`fentaris.${entry.message}`, entry.tags);
logs.write(entry);
},
},
});
Metrics should use bounded labels. Do not use raw resource URIs, prompt text, or user input as high-cardinality metric labels.