fastmcp
Version:
A TypeScript framework for building MCP servers.
270 lines (227 loc) ⢠7.78 kB
text/typescript
/**
* Example demonstrating session context support in FastMCP stdio transport
*
* This example demonstrates the fix for issue #144:
* Session context is now properly passed to tool execution handlers
* when using stdio transport with an authenticate function.
*
* To run this example:
* npx fastmcp dev src/examples/session-context.ts
*/
import { z } from "zod";
import { FastMCP } from "../FastMCP.js";
interface UserSession {
[key: string]: unknown;
permissions: string[];
role: "admin" | "guest" | "user";
userId: string;
username: string;
}
const server = new FastMCP<UserSession>({
authenticate: async (request) => {
if (!request) {
console.log(
"[Auth] Authenticating stdio transport using environment variables",
);
const userId = process.env.USER_ID || "default-user";
const username = process.env.USERNAME || "Anonymous";
const role =
(process.env.USER_ROLE as "admin" | "guest" | "user") || "guest";
// Mock permissions based on role
const permissions =
role === "admin"
? ["read", "write", "delete", "admin"]
: role === "user"
? ["read", "write"]
: ["read"];
const session: UserSession = {
authenticatedAt: new Date().toISOString(),
permissions,
role,
userId,
username,
};
console.log(`[Auth] Authenticated user: ${username} (${role})`);
return session;
}
// For HTTP transport (request contains headers)
console.log("[Auth] Authenticating HTTP transport using headers");
const authHeader = request.headers["authorization"] as string;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
throw new Response("Missing or invalid authorization header", {
status: 401,
});
}
const token = authHeader.substring(7);
// Mock token validation (in real implementation, validate against your auth service)
if (token === "admin-token") {
return {
authenticatedAt: new Date().toISOString(),
permissions: ["read", "write", "delete", "admin"],
role: "admin" as const,
userId: "admin-001",
username: "Administrator",
};
} else if (token === "user-token") {
return {
authenticatedAt: new Date().toISOString(),
permissions: ["read", "write"],
role: "user" as const,
userId: "user-001",
username: "Regular User",
};
}
throw new Response("Invalid token", { status: 401 });
},
name: "Session Context Demo",
version: "1.0.0",
});
// Tool that demonstrates session context access
server.addTool({
description: "Get information about the current authenticated user",
execute: async (_args, context) => {
if (!context.session)
return "No session context available (this shouldn't happen after the fix!)";
const { session } = context;
return `ā Session Context Available!
User Info:
- User ID: ${session.userId}
- Username: ${session.username}
- Role: ${session.role}
- Permissions: ${session.permissions.join(", ")}
- Authenticated At: ${session.authenticatedAt}`;
},
name: "whoami",
});
// Tool that demonstrates role-based access
server.addTool({
description: "Perform an admin-only operation (requires admin role)",
execute: async (args, context) => {
if (!context.session)
return "No session context - cannot verify permissions";
if (context.session.role !== "admin")
return `Access denied. Current role: ${context.session.role}, required: admin`;
if (!context.session.permissions.includes("admin"))
return "Insufficient permissions for admin operations";
return `ā Admin operation "${args.action}" executed successfully by ${context.session.username}`;
},
name: "admin-operation",
parameters: z.object({
action: z.string().describe("The admin action to perform"),
}),
});
// Tool that demonstrates permission checks
server.addTool({
description: "Check what permissions the current user has",
execute: async (args, context) => {
if (!context.session) return "No session context available";
const hasPermission = context.session.permissions.includes(args.operation);
return `Permission Check for "${args.operation}":
${hasPermission ? "ā ALLOWED" : "! DENIED"}
Your permissions: ${context.session.permissions.join(", ")}
Your role: ${context.session.role}`;
},
name: "check-permissions",
parameters: z.object({
operation: z.string().describe("Operation to check permission for"),
}),
});
// Resource that uses session context
server.addResource({
description: "Get detailed information about the current authenticated user",
load: async (auth) => {
if (!auth) {
return {
text: JSON.stringify(
{
authenticated: false,
error: "No authentication context available",
},
null,
2,
),
};
}
return {
text: JSON.stringify(
{
authenticated: true,
user: {
authenticatedAt: auth.authenticatedAt,
id: auth.userId,
permissions: auth.permissions,
role: auth.role,
username: auth.username,
},
},
null,
2,
),
};
},
mimeType: "application/json",
name: "Current User Information",
uri: "session://current-user",
});
// Prompt that uses session context
server.addPrompt({
arguments: [
{
description: "Greeting style (formal, casual, friendly)",
name: "style",
required: false,
},
],
description: "Generate a personalized greeting based on the current user",
load: async (args, auth) => {
const style = args.style || "friendly";
if (!auth) {
return "Hello! I don't have access to your session information.";
}
const greetings = {
casual: `Hey ${auth.username}! Nice to see you again.`,
formal: `Good day, ${auth.username}. You are logged in with ${auth.role} privileges.`,
friendly: `Hello ${auth.username}! š You're logged in as a ${auth.role}. How can I help you today?`,
};
return greetings[style as keyof typeof greetings] || greetings.friendly;
},
name: "personalized-greeting",
});
// Start the server
if (process.argv.includes("--http-stream")) {
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
server.start({
httpStream: { port: PORT },
transportType: "httpStream",
});
console.log(`
š Session Context Demo server running on HTTP Stream!
Try these endpoints:
- MCP: http://localhost:${PORT}/mcp
- Health: http://localhost:${PORT}/health
Test with curl:
curl -H "Authorization: Bearer admin-token" \\
-H "Content-Type: application/json" \\
-d '{"method":"tools/call","params":{"name":"whoami","arguments":{}}}' \\
http://localhost:${PORT}/mcp
`);
} else {
server.start({ transportType: "stdio" });
console.log(`
š Session Context Demo server started with stdio transport!
Environment variables for authentication:
- USER_ID=${process.env.USER_ID || "(not set - will use 'default-user')"}
- USERNAME=${process.env.USERNAME || "(not set - will use 'Anonymous')"}
- USER_ROLE=${process.env.USER_ROLE || "(not set - will use 'guest')"}
To test with different user roles:
USER_ID=admin001 USERNAME="John Admin" USER_ROLE=admin npx fastmcp dev src/examples/session-context.ts
Available tools:
- whoami: Get current user info
- admin-operation: Test admin permissions
- check-permissions: Check specific permissions
Available resources:
- session://current-user: Current user JSON data
Available prompts:
- personalized-greeting: Get a personalized greeting
`);
}