-
Notifications
You must be signed in to change notification settings - Fork 220
Description
Hello! Thanks for your work on FastMCP!
I'm having some trouble with the recently released custom logger feature, and I suspect it's a bug.
My customer logger is getting picked up my the framework for its internal logging, but any logs from a tool call are getting swallowed.
Here's my index.ts
file:
import { FastMCP } from "@punkpeye/fastmcp";
import { z } from "@zod/zod";
import Logger from "@/logger.ts";
export function run() {
const logger = new Logger();
logger.info("Hello from the logger, outside of FastMCP.");
const server = new FastMCP({
logger: new Logger(),
name: "My Server",
version: "1.0.0",
});
server.addTool({
name: "add",
description: "Add two numbers",
parameters: z.object({
a: z.number(),
b: z.number(),
}),
execute: async (args, { log }) => {
log.info("This message is getting sent into a blackhole.");
console.log("Proof the tool call is running.");
return String(args.a + args.b);
},
});
server.start({
transportType: "httpStream",
httpStream: {
endpoint: "/sse",
port: 3001,
},
});
}
And here is my logging implementation. I'm using logtape, and it's coming out lovely! Aside from this one small issue.
import {
configure,
getConsoleSink,
getLogger,
Logger as TapeLogger,
} from "@logtape/logtape";
import { getPrettyFormatter } from "@logtape/pretty";
import { Logger } from "@punkpeye/fastmcp";
class PrettyLogger implements Logger {
readonly logger: TapeLogger;
constructor() {
this.logger = getLogger("last.engineer");
}
debug(...args: unknown[]): void {
const msg = args.shift();
// If we've been given additional arguments, then we print them.
// Otherwise, we omit them from the format string.
const logline = args.length ? `${msg} {*}` : `${msg}`;
this.logger.debug(logline, { ...args });
}
info(...args: unknown[]): void {
console.log(
"Proof that the info function is not called from the tool (this line will not appear after the tool call)",
);
const msg = args.shift();
// If we've been given additional arguments, then we print them.
// Otherwise, we omit them from the format string.
const logline = args.length ? `${msg} {*}` : `${msg}`;
this.logger.info(logline, { ...args });
}
log(...args: unknown[]): void {
this.info(args);
}
warn(...args: unknown[]): void {
const msg = args.shift();
// If we've been given additional arguments, then we print them.
// Otherwise, we omit them from the format string.
const logline = args.length ? `${msg} {*}` : `${msg}`;
this.logger.warn(logline, { ...args });
}
error(...args: unknown[]): void {
const msg = args.shift();
// If we've been given additional arguments, then we print them.
// Otherwise, we omit them from the format string.
const logline = args.length ? `${msg} {*}` : `${msg}`;
this.logger.error(logline, { ...args });
}
}
await configure({
sinks: {
console: getConsoleSink({
formatter: getPrettyFormatter(),
}),
},
loggers: [
{
category: "last.engineer",
lowestLevel: "info",
sinks: ["console"],
},
// Disable the metalogger. This is not really best for production, but it's not
// clear how we would want to represent these meta-logs.
{ category: ["logtape", "meta"], sinks: [] },
],
});
export default PrettyLogger;
As you'll be able to see, the tool call runs successfully, and the console.log
s show that there's nothing wrong with e.g. stdout. It's just that the log
argument in the tool call context does not actually call .info
. If it did, the console.log
I have in there would fire. Since we can see console.log
working from the tool call, but not from inside log.info
, I have to conclude the issue is with the injected logger.
I can give a full repo to reproduce if you like; I'm running Deno, so it may not be a one-liner for you to reproduce since you'd need to set up the same import mapping as me.
Thanks for your time and assistance.