-
Notifications
You must be signed in to change notification settings - Fork 88
Expand file tree
/
Copy pathroute.ts
More file actions
123 lines (111 loc) · 3.37 KB
/
Copy pathroute.ts
File metadata and controls
123 lines (111 loc) · 3.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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 };