Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions examples/auth-observability/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types";
import {
createMcpHandler,
withMcpAuth,
withObservability,
type ObservabilityConfig,
addSpanAttribute,
} from "mcp-handler";
import { z } from "zod";

// Define the handler with proper parameter validation
const handler = createMcpHandler(
(server) => {
server.tool(
"secure-echo",
"Echo a message back with both authentication and observability",
{
message: z.string().describe("The message to echo back"),
},
async ({ message }, extra) => {
// Add custom attributes to the current span
if (extra.authInfo?.clientId) {
addSpanAttribute("user.client_id", extra.authInfo.clientId);
}

addSpanAttribute("operation.type", "echo");
addSpanAttribute("message.length", message.length);

return {
content: [
{
type: "text",
text: `Secure Echo: ${message}${
extra.authInfo?.token
? ` (authenticated as ${extra.authInfo.clientId})`
: ""
}`,
},
],
};
}
);
},
// Server capabilities
{
capabilities: {
auth: {
type: "bearer",
required: true,
},
tools: {},
},
},
// Route configuration
{
streamableHttpEndpoint: "/mcp",
sseEndpoint: "/sse",
sseMessageEndpoint: "/message",
basePath: "/api/mcp",
redisUrl: process.env.REDIS_URL,
}
);

/**
* Verify the bearer token and return auth information
* In a real implementation, this would validate against your auth service
*/
const verifyToken = async (
req: Request,
bearerToken?: string
): Promise<AuthInfo | undefined> => {
if (!bearerToken) return undefined;

// Add tracing for auth verification
addSpanAttribute("auth.token_present", true);

// TODO: Replace with actual token verification logic
const isValid = bearerToken.startsWith("__TEST_VALUE__");

addSpanAttribute("auth.validation_result", isValid);

if (!isValid) return undefined;

return {
token: bearerToken,
scopes: ["read:messages", "write:messages"],
clientId: "example-client",
extra: {
userId: "user-123",
permissions: ["user"],
timestamp: new Date().toISOString(),
},
};
};

// Observability configuration
const observabilityConfig: ObservabilityConfig = {
serviceName: "secure-mcp-service",
serviceVersion: "1.0.0",
traceIdHeader: "x-trace-id",
spanIdHeader: "x-span-id",
customAttributes: {
"service.environment": process.env.NODE_ENV || "development",
"service.instance": process.env.HOSTNAME || "local",
"service.auth_enabled": "true",
},
enableRequestLogging: true,
enableErrorTracking: true,
ignoreEndpoints: ["/health", "/metrics", "/.well-known/oauth-protected-resource"],
samplingRate: 1.0,
};

// Apply wrappers in order: observability first, then auth
// This ensures auth errors are also traced
const observabilityHandler = withObservability(handler, observabilityConfig);
const authAndObservabilityHandler = withMcpAuth(observabilityHandler, verifyToken, {
required: true,
requiredScopes: ["read:messages"],
resourceMetadataPath: "/.well-known/oauth-protected-resource",
});

// Export the handler for both GET and POST methods
export { authAndObservabilityHandler as GET, authAndObservabilityHandler as POST };
70 changes: 70 additions & 0 deletions examples/observability/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {
createMcpHandler,
withObservability,
type ObservabilityConfig,
} from "mcp-handler";
import { z } from "zod";

// Define the handler with proper parameter validation
const handler = createMcpHandler(
(server) => {
server.tool(
"echo-with-tracing",
"Echo a message back with observability tracing",
{
message: z.string().describe("The message to echo back"),
},
async ({ message }, extra) => {
// You can add custom span attributes within your handler
if (typeof req !== 'undefined' && req.traceId) {
console.log(`Processing echo request with trace ID: ${req.traceId}`);
}

return {
content: [
{
type: "text",
text: `Echo: ${message}`,
},
],
};
}
);
},
// Server capabilities
{
capabilities: {
tools: {},
},
},
// Route configuration
{
streamableHttpEndpoint: "/mcp",
sseEndpoint: "/sse",
sseMessageEndpoint: "/message",
basePath: "/api/mcp",
redisUrl: process.env.REDIS_URL,
}
);

// Observability configuration
const observabilityConfig: ObservabilityConfig = {
serviceName: "mcp-echo-service",
serviceVersion: "1.0.0",
traceIdHeader: "x-trace-id",
spanIdHeader: "x-span-id",
customAttributes: {
"service.environment": process.env.NODE_ENV || "development",
"service.instance": process.env.HOSTNAME || "local",
},
enableRequestLogging: true,
enableErrorTracking: true,
ignoreEndpoints: ["/health", "/metrics"],
samplingRate: 1.0, // 100% sampling for demo, adjust as needed
};

// Wrap the handler with observability
const observabilityHandler = withObservability(handler, observabilityConfig);

// Export the handler for both GET and POST methods
export { observabilityHandler as GET, observabilityHandler as POST };
9 changes: 9 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,12 @@ export {
generateProtectedResourceMetadata,
metadataCorsOptionsRequestHandler,
} from "./auth/auth-metadata";

export {
withObservability,
createObservabilitySpan,
getCurrentSpan,
addSpanAttribute,
addSpanEvent,
type ObservabilityConfig,
} from "./observability";
8 changes: 8 additions & 0 deletions src/observability/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export {
withObservability,
createObservabilitySpan,
getCurrentSpan,
addSpanAttribute,
addSpanEvent,
type ObservabilityConfig,
} from './observability-wrapper';
Loading