example and more documentation

This commit is contained in:
Marcelo Paternostro 2025-04-13 23:17:59 -04:00
commit 112eed999c
No known key found for this signature in database
3 changed files with 74 additions and 5 deletions

View file

@ -12,6 +12,21 @@ const server = new McpServer({
version: '1.0.0',
}, { capabilities: { logging: {} } });
// Log the capability invocation details
server.onCapabilityChange((event) => {
switch (event.action) {
case 'invoked':
console.log(`${event.capabilityType} invocation ${event.invocationIndex}: '${event.capabilityName}' started`);
break;
case 'completed':
console.log(`${event.capabilityType} invocation ${event.invocationIndex}: '${event.capabilityName}' completed in ${event.durationMs}ms`);
break;
case 'error':
console.log(`${event.capabilityType} invocation ${event.invocationIndex}: '${event.capabilityName}' failed in ${event.durationMs}ms: ${event.error}`);
break;
}
});
// Register a simple tool that returns a greeting
server.tool(
'greet',
@ -291,4 +306,4 @@ process.on('SIGINT', async () => {
await server.close();
console.log('Server shutdown complete');
process.exit(0);
});
});

View file

@ -65,8 +65,11 @@ export class McpServer {
private _registeredPrompts: { [name: string]: RegisteredPrompt } = {};
private _onCapabilityChange = createEventNotifier<CapabilityEvent>();
/** Counter for unique resource invocation indexes, used to correlate resource invocation events */
private _resourceInvocationIndex = 0;
/** Counter for unique tool invocation indexes, used to correlate tool invocation events */
private _toolInvocationIndex = 0;
/** Counter for unique prompt invocation indexes, used to correlate prompt invocation events */
private _promptInvocationIndex = 0;
constructor(serverInfo: Implementation, options?: ServerOptions) {
@ -94,9 +97,18 @@ export class McpServer {
* Event notifier for capability changes. Listeners will be notified when capabilities are added, updated, removed,
* enabled, disabled, invoked, completed, or when errors occur.
*
* This provides a way to monitor and respond to all capability-related activities in the server,
* including both lifecycle changes and invocation events. Each capability type (resource, tool, prompt)
* maintains its own sequence of invocation indexes, which can be used to correlate invocations
* with their completions or errors.
*
* @example
* const subscription = server.onCapabilityChange((event) => {
* console.log(`${event.capabilityType} ${event.capabilityName} ${event.action}`);
* if (event.action === "invoked") {
* console.log(`${event.capabilityType} ${event.capabilityName} invoked with index ${event.invocationIndex}`);
* } else if (event.action === "completed" || event.action === "error") {
* console.log(`${event.capabilityType} operation completed in ${event.durationMs}ms`);
* }
* });
*
* // Later, to stop listening:
@ -1799,29 +1811,71 @@ const EMPTY_COMPLETION_RESULT: CompleteResult = {
},
};
/**
* Represents events emitted when capabilities (tools, resources, prompts) change state or are invoked.
*
* These events allow tracking the lifecycle and usage of all capabilities registered with an McpServer.
* Events include capability registration, updates, invocation, completion, and errors.
*
* Each capability type (tool, resource, prompt) maintains its own sequence of invocation indexes.
* The invocationIndex can be used to correlate "invoked" events with their corresponding
* "completed" or "error" events for the same capability type.
*/
export type CapabilityEvent = {
/** Information about the server that generated this event */
readonly serverInfo: { readonly name: string; readonly version: string };
/** The type of capability this event relates to */
readonly capabilityType: "resource" | "tool" | "prompt";
/** The name (or URI for resources) of the specific capability */
readonly capabilityName: string;
} & (
| {
/**
* Lifecycle events for capability registration and status changes.
* - "added": The capability was registered
* - "updated": The capability was modified
* - "removed": The capability was unregistered
* - "enabled": The capability was enabled
* - "disabled": The capability was disabled
*/
readonly action: "added" | "updated" | "removed" | "enabled" | "disabled";
}
| {
/** Emitted when a capability is invoked */
readonly action: "invoked";
/**
* Monotonically increasing index for each invocation, per capability type.
* This index can be used to correlate this "invoked" event with a later
* "completed" or "error" event with the same capabilityType and invocationIndex.
*/
readonly invocationIndex: number;
/** The arguments passed to the capability, if any */
readonly arguments?: unknown;
}
| {
/** Emitted when a capability invocation completes successfully */
readonly action: "completed";
/**
* The invocationIndex from the corresponding "invoked" event.
* This allows correlating the completion with its invocation.
*/
readonly invocationIndex: number;
/** The result returned by the capability, if any */
readonly result?: unknown;
/** The duration of the operation in milliseconds, measured from invocation to completion */
readonly durationMs?: number;
}
| {
/** Emitted when a capability invocation fails with an error */
readonly action: "error";
/**
* The invocationIndex from the corresponding "invoked" event.
* This allows correlating the error with its invocation.
*/
readonly invocationIndex: number;
/** The error that occurred during capability execution */
readonly error: unknown;
/** The duration from invocation to error in milliseconds */
readonly durationMs?: number;
}
);

View file

@ -35,7 +35,7 @@ export type EventNotifier<T, E = Error> = {
* ```
*
* @param listener A function that will be called with the notified event
* @returns A SyncCloseable that, when closed, will unregister the listener
* @returns A closeable to unregister the listener (all listeners are unregistered when the notifier is closed).
*/
onEvent: (listener: (event: T) => unknown) => { close: () => void };
@ -69,7 +69,7 @@ export type EventNotifier<T, E = Error> = {
/**
* Sets an error handler for the notifier. This handler will be called when a listener throws an error.
*
* @param handler A function that will be called with any errors thrown by listeners
* @param handler A function that will be called with any errors thrown by listeners.
*/
onError: (handler: (error: E) => void) => void;
@ -101,7 +101,7 @@ export type EventNotifier<T, E = Error> = {
*
* @template T The type of events this notifier will handle
* @template E The type of error that can be handled (defaults to Error)
* @returns A new EventNotifier instance
* @returns A new EventNotifier instance.
*/
export const createEventNotifier = <T, E = Error>(): EventNotifier<T, E> => {
const listeners = new Set<(event: T) => unknown>();