Osiris SDK
Comprehensive framework for building authenticated, stateful, and policy-driven MCPs
The Osiris SDK is a comprehensive framework for building authenticated, stateful, and policy-driven MCPs (Model Context Protocol) with enterprise-grade security. It handles authentication, state management, and policy enforcement automatically, letting you focus on building intelligent MCP tools.
Installation
Option 1: Using CLI (Recommended)
The fastest way to get started is using the Osiris CLI:
# Install CLI
npm install -g @osiris-ai/cli
# Register account and get credentials
npx @osiris-ai/cli register
# Create OAuth Client
npx @osiris-ai/cli create-client
# Connect authentication services
npx @osiris-ai/cli connect-auth
# Create your project
npx @osiris-ai/cli create-app
This creates a complete project with:
- Authentication setup
- Database configuration
- Example tools and resources
- Environment configuration
- TypeScript/JavaScript templates
Option 2: Manual Installation
npm install @osiris-ai/sdk
Quick Start
CLI-Generated Project
When you run osiris create-app
, you get a complete project structure:
your-project/
├── index.ts # Main MCP server entry point
├── client.ts # MCP client setup and configuration
├── tools/
│ └── hello-world.ts # Example tool
├── resources/
│ └── hello-world.ts # Example resource
├── prompts/
│ └── hello-world.ts # Example prompt
├── schema/
│ └── index.ts # Zod schema definitions
├── .env # Environment variables (auto-configured)
├── .env.example # Environment template
├── osiris.json # Project configuration
└── package.json # Dependencies and scripts
Generated Environment Configuration
The CLI automatically configures your .env
file:
# Osiris Hub Configuration
HUB_BASE_URL=https://api.osirislabs.xyz/v1
# OAuth Credentials (auto-filled by CLI)
OAUTH_CLIENT_ID=your-oauth-client-id
OAUTH_CLIENT_SECRET=your-oauth-client-secret
# MCP Server Configuration
PORT=3000
NODE_ENV=development
# Database Configuration
DATABASE_ADAPTER=postgres
DATABASE_URL=postgresql://username:password@localhost:5432/your-project
Generated Server Code
Here's what the CLI generates in index.ts
:
import { createMcpServer } from '@osiris-ai/sdk';
import { PostgresDatabaseAdapter } from '@osiris-ai/sdk';
import { configureClient } from './client.js';
import dotenv from 'dotenv';
dotenv.config();
const databaseAdapter = new PostgresDatabaseAdapter({
connectionString: process.env.DATABASE_URL!
});
await createMcpServer({
name: 'your-project',
version: '1.0.0',
auth: {
useHub: true,
hubConfig: {
baseUrl: process.env.HUB_BASE_URL!,
clientId: process.env.OAUTH_CLIENT_ID!,
clientSecret: process.env.OAUTH_CLIENT_SECRET!,
},
database: databaseAdapter,
},
server: {
port: parseInt(process.env.PORT || '3000'),
mcpPath: '/mcp',
callbackBasePath: '/callback',
},
configure: configureClient
});
Generated Client Configuration
The client.ts
file registers your tools, resources, and prompts:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { registerHelloWorldTool } from './tools/hello-world.js';
import { registerHelloWorldResource } from './resources/hello-world.js';
import { registerHelloWorldPrompt } from './prompts/hello-world.js';
export function configureClient(server: McpServer) {
// Register tools
registerHelloWorldTool(server);
// Register resources
registerHelloWorldResource(server);
// Register prompts
registerHelloWorldPrompt(server);
}
Authentication Architecture
Osiris SDK provides dual-layer authentication that secures both user access and LLM interactions, with two distinct approaches: Hub Authentication and Local Authentication.
Hub vs Local Authentication
Hub Authentication (Centralized)
Best for: Production MCPs, marketplace distribution, simplified setup
await createMcpServer({
name: 'my-mcp',
version: '1.0.0',
auth: {
useHub: true,
hubConfig: {
baseUrl: 'https://hub.osiris.dev/v1',
clientId: process.env.OAUTH_CLIENT_ID!,
clientSecret: process.env.OAUTH_CLIENT_SECRET!,
},
database: new PostgresDatabaseAdapter({
connectionString: process.env.DATABASE_URL!
})
}
});
How it works:
- User authenticates once with Osiris Hub
- Hub manages all OAuth tokens centrally
- MCPs receive tokens via hub's proxy system
- All API calls route through the hub
- Hub handles token refresh automatically
Benefits:
- ✅ Single Sign-On: One login for all MCPs
- ✅ Zero Token Management: Hub handles everything
- ✅ Enterprise Security: Secrets never touch your server
- ✅ Automatic Refresh: No token expiry issues
- ✅ Marketplace Ready: Built for distribution
Local Authentication (Direct)
Best for: Internal tools, custom flows, maximum control
import { GoogleAuthenticator } from '@osiris-ai/google-sdk';
import { GitHubAuthenticator } from '@osiris-ai/github-sdk';
import { PostgresSecretSharingAuthenticator } from '@osiris-ai/postgres-sdk';
await createMcpServer({
name: 'my-mcp',
version: '1.0.0',
auth: {
useHub: false,
directAuth: {
google: new GoogleAuthenticator({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
redirectUri: 'http://localhost:3000/google/callback',
allowedScopes: ['gmail.readonly', 'calendar.readonly']
}),
github: new GitHubAuthenticator({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
redirectUri: 'http://localhost:3000/github/callback',
allowedScopes: ['repo', 'user']
})
},
database: new PostgresDatabaseAdapter({
connectionString: process.env.DATABASE_URL!
})
}
});
How it works:
- Users authenticate directly with each service
- MCP server manages tokens locally
- Direct API calls to service providers
- Local database stores credentials
Benefits:
- ✅ Full Control: Custom authentication logic
- ✅ No Dependencies: Independent of external services
- ✅ Flexible Storage: Choose your credential storage
- ✅ Custom Flows: Build unique authentication experiences
- ✅ Enterprise Compliance: Meet specific security requirements
Layer 1: Service Authentication Types
OAuth Authentication
Best for: API integrations with Google, GitHub, Discord, etc.
// Hub-based OAuth (recommended)
const context = getAuthContext();
const googleToken = context.getToken('google');
const gmail = createHubGmailClient({ hubBaseUrl: process.env.HUB_BASE_URL! });
// Local OAuth (direct control)
const context = getAuthContext();
const googleToken = context.getToken('google');
const gmail = new GoogleClient({ accessToken: googleToken.access_token });
Security Model: OAuth scopes define MCP access permissions
- ✅ Industry-standard OAuth 2.0 flows
- ✅ User controls permissions via scopes
- ✅ Automatic or manual token refresh
Secret Sharing (Direct Access)
Best for: Database connections, API keys, internal services
// Hub-based secrets
const context = getAuthContext();
const dbCredentials = context.getTokens('osiris');
// Local secrets
// Currently not supported
Security Model: Direct credential management
- ✅ Encrypted storage and transmission
- ✅ User-controlled access and sharing
- ✅ Custom validation and connection testing
Embedded Wallets (Policy Engine Security)
Best for: Blockchain interactions, financial operations
// Policy-enforced wallet interactions
const { token, context } = getAuthContext("osiris");
const client = new EVMWalletClient(
hubBaseUrl,
token.access_token,
context.deploymentId,
chainId
);
const walletRecords = await client.getWalletRecords();
const account = await client.getViemAccount(walletRecords[0].metadata.wallet.addresses[0]);
Security Model: Policy engine enforced at wallet level
- ✅ User sets policies in their wallet
- ✅ MCP/LLM cannot execute invalid transactions
- ✅ Hardware-backed wallet security
- ✅ Multi-signature support
Layer 2: LLM ↔ MCP Authentication
Prevents unauthorized LLM access to your MCP:
When a user creates a new deployment, the Hub generates a unique deployment ID that can be used for MCP authentication. With the upcoming MCP Router feature, users won't need to deploy individual MCPs - they can use a single MCP to securely call other MCPs. This reduces context load on LLMs while maintaining secure authentication.
The Osiris SDK provides clear error messages that LLMs can interpret to guide users through the MCP authentication process. This enables:
- ✅ Automated deployment ID generation
- ✅ Simplified MCP routing and authentication
- ✅ Reduced LLM context requirements
- ✅ Clear authentication guidance
Local Authentication APIs
When using local authentication (useHub: false
), the SDK automatically exposes REST APIs to help users authenticate and manage connections. These APIs enable building custom authentication UIs and workflows.
Session Management
Create Authentication Session
Start a new authentication session for connecting services:
POST /deployments/sessions
Content-Type: application/json
{
"packageId": "my-gmail-mcp",
"name": "Gmail Integration",
"description": "Access Gmail for email management"
}
Response:
{
"status": "success",
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"availableServices": [
{
"name": "google",
"type": "oauth",
"displayName": "Google",
"description": "Google OAuth authentication",
"required": false
},
{
"name": "postgres",
"type": "secret",
"displayName": "PostgreSQL",
"description": "Database connection",
"required": false
}
],
"expiresAt": "2024-01-15T10:30:00Z"
}
Get Session Status
Check authentication progress and connected services:
GET /deployments/sessions/{sessionId}
Response:
{
"status": "success",
"data": {
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"name": "Gmail Integration",
"packageId": "my-gmail-mcp",
"connectedServices": [
{
"service": "google",
"status": "connected",
"userId": "user@gmail.com",
"connectedAt": "2024-01-15T10:25:00Z"
}
],
"readyToCreate": true,
"expiresAt": "2024-01-15T10:30:00Z"
}
}
OAuth Authentication Flow
Get Authorization URL
Generate OAuth authorization URL for a service:
GET /deployments/sessions/{sessionId}/auth/{service}/authorize
Response:
{
"status": "success",
"data": {
"authUrl": "https://accounts.google.com/oauth/authorize?client_id=...",
"state": "550e8400-e29b-41d4-a716-446655440000"
}
}
OAuth Callback
The SDK automatically handles OAuth callbacks:
GET /{service}/callback?code={auth_code}&state={session_id}
Users are redirected here after OAuth consent. The SDK processes the callback automatically.
Secret-Based Authentication
Connect with Credentials
For database connections and API keys:
POST /deployments/sessions/{sessionId}/auth/{service}/connect
Content-Type: application/json
{
"host": "localhost",
"port": 5432,
"database": "myapp",
"username": "appuser",
"password": "secretpass"
}
Response:
{
"status": "success",
"data": {
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"service": "postgres",
"connectionTest": "passed"
}
}
Deployment Creation
Finalize Deployment
Create permanent deployment with all connected services:
POST /deployments/sessions/{sessionId}/create
Response:
{
"status": "success",
"deploymentId": "deployment-123",
"connectedServices": ["google", "postgres"],
"mcpEndpoint": "http://localhost:3000/mcp"
}
Action Execution
Execute MCP Actions
Perform authenticated actions on behalf of users:
POST /deployments/{deploymentId}/mcp/{service}/action
Content-Type: application/json
{
"method": "GET",
"url": "/gmail/v1/users/me/messages",
"data": {},
"service": "google"
}
Building Authentication UIs
Use these APIs to build custom authentication interfaces:
// React example for OAuth flow
const startOAuthFlow = async (sessionId: string, service: string) => {
const response = await fetch(
`/deployments/sessions/${sessionId}/auth/${service}/authorize`
);
const { data } = await response.json();
// Redirect user to OAuth provider
window.location.href = data.authUrl;
};
// Check authentication status
const checkAuthStatus = async (sessionId: string) => {
const response = await fetch(`/deployments/sessions/${sessionId}`);
const { data } = await response.json();
return {
connectedServices: data.connectedServices,
readyToCreate: data.readyToCreate
};
};
Manual MCP Server Setup
If you prefer to set up manually instead of using the CLI:
Basic MCP Server
import { createMcpServer, PostgresDatabaseAdapter } from '@osiris-ai/sdk';
await createMcpServer({
name: 'my-mcp',
version: '1.0.0',
auth: {
useHub: true,
hubConfig: {
baseUrl: process.env.HUB_BASE_URL!,
clientId: process.env.OAUTH_CLIENT_ID!,
clientSecret: process.env.OAUTH_CLIENT_SECRET!,
},
database: new PostgresDatabaseAdapter({
connectionString: process.env.DATABASE_URL!
}),
},
server: {
port: 3001,
mcpPath: '/mcp',
callbackBasePath: '/callback',
},
configure: (server) => {
server.tool('example_tool', 'Example tool', {
message: { type: 'string', description: 'Message to process' }
}, async ({ message }) => {
return {
content: [{ type: 'text', text: `Processed: ${message}` }]
};
});
}
});
Gmail MCP Example
import { createMcpServer, getAuthContext, createHubGmailClient } from '@osiris-ai/sdk';
class GmailMCP {
private hubBaseUrl: string;
constructor(hubBaseUrl: string) {
this.hubBaseUrl = hubBaseUrl;
}
getGmailClient() {
const context = getAuthContext();
if (!context.tokens) {
throw new Error('No Google token available. Please authenticate with Google first.');
}
return createHubGmailClient({
hubBaseUrl: this.hubBaseUrl
});
}
configureServer(server) {
server.tool(
'send_email',
'Send an email through Gmail',
{
to: { type: 'string', description: 'Recipient email' },
subject: { type: 'string', description: 'Email subject' },
body: { type: 'string', description: 'Email content' }
},
async ({ to, subject, body }) => {
const gmail = this.getGmailClient();
const emailContent = [
`To: ${to}`,
`Subject: ${subject}`,
'MIME-Version: 1.0',
'Content-Type: text/plain; charset=UTF-8',
'',
body
];
const raw = Buffer.from(emailContent.join('\r\n'))
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_');
const result = await gmail.users.messages.send({
userId: 'me',
requestBody: { raw }
});
return {
content: [{
type: 'text',
text: `✅ Email sent successfully! Message ID: ${result.data.id}`
}]
};
}
);
}
}
// Start server
const gmailMcp = new GmailMCP(process.env.HUB_BASE_URL!);
await createMcpServer({
name: 'gmail-mcp',
version: '1.0.0',
auth: {
useHub: true,
hubConfig: {
baseUrl: process.env.HUB_BASE_URL!,
clientId: process.env.OAUTH_CLIENT_ID!,
clientSecret: process.env.OAUTH_CLIENT_SECRET!,
},
database: new PostgresDatabaseAdapter({
connectionString: process.env.DATABASE_URL!
}),
},
server: {
port: 3001,
mcpPath: '/mcp',
callbackBasePath: '/callback',
},
configure: (server) => {
gmailMcp.configureServer(server);
}
});
Stateful MCP Architecture
Osiris MCPs maintain persistent state across conversations:
State Storage Options
Option 1: User's Database (Recommended)
const { token, context } = getAuthContext("osiris");
const response = await fetch(`https://api.osirislabs.xyz/v1/hub/action/${deploymentId}/postgres`, {
method: "POST",
body: JSON.stringify({
sql: `SELECT * FROM user_data WHERE user_id = $1`,
params: [context.userId]
})
});
Option 2: MCP's Database
database: new PostgresDatabaseAdapter({
connectionString: process.env.MCP_DATABASE_URL!,
})
Database Adapters
Development
import { MemoryDatabaseAdapter } from '@osiris-ai/sdk';
database: new MemoryDatabaseAdapter() // Data stored in memory (lost on restart)
Production
import { PostgresDatabaseAdapter } from '@osiris-ai/sdk';
database: new PostgresDatabaseAdapter({
connectionString: process.env.DATABASE_URL!
})
Other Options
import { MongoDBDatabaseAdapter, RedisDatabaseAdapter, SQLiteDatabaseAdapter } from '@osiris-ai/sdk';
// MongoDB
database: new MongoDBDatabaseAdapter({
connectionString: 'mongodb://localhost:27017/osiris'
})
// Redis
database: new RedisDatabaseAdapter({
host: 'localhost',
port: 6379
})
// SQLite
database: new SQLiteDatabaseAdapter({
filepath: './mcp_data.db'
})
Token Context System
The Osiris SDK provides request-scoped authentication context:
Basic Context Usage
import { getAuthContext } from '@osiris-ai/sdk';
server.tool('context_example', 'Shows how to use auth context', schema, async (params) => {
// For OAuth services
const context = getAuthContext();
if (!context.tokens) {
throw new Error('No tokens available. Please authenticate first.');
}
// For embedded wallets
const { token, context: walletContext } = getAuthContext("osiris");
if (!token || !walletContext) {
throw new Error("No wallet token or context found");
}
return { content: [{ type: 'text', text: 'Context accessed successfully' }] };
});
Multi-Tenant Isolation
Each deployment automatically isolates user data:
// Context is automatically scoped to current user
const context = getAuthContext();
console.log('User ID:', context.userId);
console.log('Deployment ID:', context.deploymentId);
// All database operations are automatically user-scoped
const userEmails = await fetchUserEmails(context.userId);
Error Handling
Authentication Errors
import { AuthContextError } from '@osiris-ai/sdk';
server.tool('robust_tool', 'Tool with error handling', schema, async (params) => {
try {
const gmail = createHubGmailClient({ hubBaseUrl: process.env.HUB_BASE_URL! });
const result = await gmail.users.messages.list({ userId: 'me' });
return {
content: [{ type: 'text', text: 'Success!' }]
};
} catch (error) {
if (error instanceof AuthContextError) {
return {
content: [{
type: 'text',
text: `🔐 Authentication required: ${error.authorizationUrl}`
}],
isError: true
};
}
return {
content: [{
type: 'text',
text: `❌ Error: ${error.message}`
}],
isError: true
};
}
});
Wallet Transaction Errors
server.tool('wallet_tool', 'Wallet operation', schema, async (params) => {
try {
const { token, context } = getAuthContext("osiris");
if (!token || !context) {
throw new Error("Wallet authentication required");
}
// Wallet operation - policy engine validates automatically
const result = await performWalletOperation();
return {
content: [{ type: 'text', text: `✅ Transaction: ${result.hash}` }]
};
} catch (error) {
if (error.message.includes('policy')) {
return {
content: [{
type: 'text',
text: `🚫 Transaction blocked by wallet policy: ${error.message}`
}],
isError: true
};
}
return {
content: [{
type: 'text',
text: `💳 Wallet error: ${error.message}`
}],
isError: true
};
}
});
Development Workflow
1. Start Development Server
# From your CLI-generated project
npm run dev
# Or for TypeScript projects
npm run dev # Uses tsx for hot reload
2. Testing Your MCP
The generated project includes example tools you can test immediately:
# Test the hello world tool
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{"method": "tools/call", "params": {"name": "hello_world", "arguments": {"name": "Alice"}}}'
3. Adding New Tools
Create a new tool in the tools/
directory:
// tools/my-new-tool.ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
export function registerMyNewTool(server: McpServer) {
const MyToolSchema = z.object({
param1: z.string().describe('Parameter description')
});
server.tool('my_new_tool', 'Description of what this tool does', MyToolSchema, async ({ param1 }) => {
// Your tool logic here
return {
content: [{
type: 'text',
text: `Tool result: ${param1}`
}]
};
});
}
Then register it in client.ts
:
import { registerMyNewTool } from './tools/my-new-tool.js';
export function configureClient(server: McpServer) {
registerHelloWorldTool(server);
registerMyNewTool(server); // Add this line
// ... other registrations
}
Environment Variables
# Core Configuration
HUB_BASE_URL=https://api.osirislabs.xyz/v1
PORT=3000
# OAuth Authentication (auto-configured by CLI)
OAUTH_CLIENT_ID=your-oauth-client-id
OAUTH_CLIENT_SECRET=your-oauth-client-secret
# Database
DATABASE_URL=postgresql://user:pass@localhost:5432/mcp_db
# Deployment Security
MCP_DEPLOYMENT_SECRET=your-deployment-secret
MCP Development Best Practices
1. Choose the Right Authentication Approach
Decision Framework: Hub for marketplace MCPs, Local for enterprise/custom solutions
Hub Authentication
Choose when:
- Building for marketplace distribution
- Want simplified setup and maintenance
- Need automatic token management
- Targeting non-technical users
// ✅ GOOD: Hub authentication for production MCPs
auth: {
useHub: true,
hubConfig: {
baseUrl: 'https://hub.osiris.dev/v1',
clientId: process.env.OAUTH_CLIENT_ID!,
clientSecret: process.env.OAUTH_CLIENT_SECRET!,
}
}
Local Authentication
Choose when:
- Building internal/enterprise tools
- Need custom authentication flows
- Have specific compliance requirements
- Want maximum control over user data
// ✅ GOOD: Local authentication for enterprise MCPs
auth: {
useHub: false,
directAuth: {
google: new GoogleAuthenticator({...}),
postgres: new PostgresAuthenticator({...})
}
}
2. Use the CLI for Initial Setup
✅ RECOMMENDED: Always start with CLI-generated projects
# Get properly configured authentication
osiris register
osiris connect-auth
osiris create-app
❌ AVOID: Manual credential management from scratch
# Don't manually configure OAuth without understanding security implications
3. Handle Authentication Context Properly
Basic Context Validation
// ✅ GOOD: Always validate auth context
const context = getAuthContext();
if (!context.tokens) {
throw new Error('Authentication required. Please connect your accounts first.');
}
// ❌ BAD: Assuming context exists
const context = getAuthContext();
const googleToken = context.getToken('google'); // May throw
Service-Specific Authentication
// ✅ GOOD: Handle service-specific auth
server.tool('gmail_tool', 'Access Gmail', schema, async (params) => {
const context = getAuthContext();
const googleToken = context.getToken('google');
if (!googleToken) {
return {
content: [{
type: 'text',
text: '🔐 Please connect your Google account to use Gmail features.'
}],
isError: true
};
}
// Use authenticated client
const gmail = createHubGmailClient({ hubBaseUrl: process.env.HUB_BASE_URL! });
// ... rest of implementation
});
Wallet Context for Blockchain Operations
// ✅ GOOD: Handle wallet context for DeFi/crypto operations
server.tool('swap_tokens', 'Swap tokens on Uniswap', schema, async (params) => {
const { token, context } = getAuthContext("osiris");
if (!token || !context) {
return {
content: [{
type: 'text',
text: '🔐 Please connect your wallet to perform token swaps.'
}],
isError: true
};
}
// Policy-enforced wallet operations
const client = new EVMWalletClient(hubBaseUrl, token.access_token, context.deploymentId, chainId);
// ... rest of implementation
});
4. Implement Intelligent Stateful Patterns
User Learning and Personalization
// ✅ GOOD: Learn from user behavior
server.tool('smart_email_assistant', 'AI email assistant', schema, async (params) => {
const context = getAuthContext();
// Load user preferences and history
const userProfile = await getUserProfile(context.userId);
const emailHistory = await getEmailHistory(context.userId);
// Make intelligent decisions based on data
const recommendation = await generateEmailRecommendation({
userProfile,
emailHistory,
currentRequest: params
});
// Store interaction for future learning
await storeUserInteraction(context.userId, {
tool: 'smart_email_assistant',
input: params,
output: recommendation,
timestamp: new Date(),
feedback: null // Will be updated if user provides feedback
});
return recommendation;
});
Cross-Session State Management
// ✅ GOOD: Maintain state across MCP sessions
class StatefulMCP {
private userSessions = new Map<string, UserSession>();
async initializeUserSession(userId: string) {
if (!this.userSessions.has(userId)) {
const session = await loadUserSession(userId);
this.userSessions.set(userId, session);
}
return this.userSessions.get(userId);
}
configureServer(server: McpServer) {
server.tool('contextual_tool', 'Context-aware tool', schema, async (params) => {
const context = getAuthContext();
const session = await this.initializeUserSession(context.userId);
// Use accumulated context for better responses
const result = await processWithContext(params, session.context);
// Update session state
session.context.push({ input: params, output: result, timestamp: new Date() });
await saveUserSession(context.userId, session);
return result;
});
}
}
5. Error Handling and User Experience
Actionable Error Messages
// ✅ GOOD: Provide specific, actionable error messages
catch (error) {
if (error instanceof AuthContextError) {
return {
content: [{
type: 'text',
text: `🔐 Authentication required. Please visit: ${error.authorizationUrl}`
}],
isError: true
};
}
if (error.code === 'INSUFFICIENT_PERMISSIONS') {
return {
content: [{
type: 'text',
text: `❌ Insufficient permissions. Please grant additional scopes: ${error.requiredScopes.join(', ')}`
}],
isError: true
};
}
// Log detailed error for debugging
console.error('Tool execution error:', {
error: error.message,
stack: error.stack,
userId: context.userId,
tool: 'tool_name'
});
return {
content: [{
type: 'text',
text: '❌ An error occurred. Please try again or contact support.'
}],
isError: true
};
}
// ❌ BAD: Generic, unhelpful errors
catch (error) {
return { content: [{ type: 'text', text: 'Error' }], isError: true };
}
Graceful Degradation
// ✅ GOOD: Provide alternative functionality when possible
server.tool('smart_calendar', 'Calendar management', schema, async (params) => {
const context = getAuthContext();
const googleToken = context.getToken('google');
if (!googleToken) {
// Fallback to read-only functionality or suggestions
return {
content: [{
type: 'text',
text: '📅 I can help you plan your calendar, but to create events I need access to your Google Calendar. Please connect your Google account for full functionality.'
}]
};
}
// Full functionality with authentication
const calendar = createHubCalendarClient({ hubBaseUrl: process.env.HUB_BASE_URL! });
// ... rest of implementation
});
6. Security Best Practices
Scope Validation
// ✅ GOOD: Validate required scopes before operations
const validateGoogleScopes = (requiredScopes: string[]) => {
const context = getAuthContext();
const googleToken = context.getToken('google');
if (!googleToken) return false;
const userScopes = googleToken.scope?.split(' ') || [];
return requiredScopes.every(scope => userScopes.includes(scope));
};
server.tool('delete_emails', 'Delete emails', schema, async (params) => {
if (!validateGoogleScopes(['gmail.modify'])) {
return {
content: [{
type: 'text',
text: '❌ This operation requires additional permissions. Please reconnect with modify access.'
}],
isError: true
};
}
// Proceed with delete operation
});
Input Validation and Sanitization
// ✅ GOOD: Validate and sanitize all inputs
import { z } from 'zod';
const emailSchema = z.object({
to: z.string().email(),
subject: z.string().max(998), // RFC 5322 limit
body: z.string().max(10000), // Reasonable limit
priority: z.enum(['low', 'normal', 'high']).optional()
});
server.tool('send_email', 'Send email', emailSchema, async (params) => {
// Zod validates automatically, but add business logic validation
if (params.body.includes('<script>')) {
throw new Error('Script tags are not allowed in email content');
}
// Safe to proceed with validated data
});
7. Performance and Scalability
Efficient Token Management
// ✅ GOOD: Cache frequently accessed data
class PerformantMCP {
private tokenCache = new Map<string, { token: any, expires: Date }>();
async getValidToken(service: string) {
const cacheKey = `${service}-${context.userId}`;
const cached = this.tokenCache.get(cacheKey);
if (cached && cached.expires > new Date()) {
return cached.token;
}
const context = getAuthContext();
const token = context.getToken(service);
// Cache with expiry buffer
const expires = new Date(Date.now() + (token.expires_in - 300) * 1000);
this.tokenCache.set(cacheKey, { token, expires });
return token;
}
}
8. Testing and Development
Mock Authentication for Testing
// ✅ GOOD: Create mock authentication for tests
if (process.env.NODE_ENV === 'test') {
// Override getAuthContext for testing
const mockContext = {
userId: 'test-user',
deploymentId: 'test-deployment',
tokens: new Map([
['google', { access_token: 'mock-token', expires_in: 3600 }]
]),
getToken: (service: string) => mockContext.tokens.get(service)
};
// Use mock context in tests
}
9. Documentation and Maintenance
Document Authentication Requirements
// ✅ GOOD: Clear documentation for each tool
server.tool(
'gmail_search',
'Search Gmail messages (requires Gmail read access)',
{
query: {
type: 'string',
description: 'Search query (Gmail search syntax)'
}
},
async (params) => {
// Implementation with clear authentication requirements
}
);
Version Compatibility
// ✅ GOOD: Handle API version changes gracefully
const handleApiCall = async (endpoint: string, data: any) => {
try {
return await apiCall(endpoint, data);
} catch (error) {
if (error.status === 410) { // API deprecated
// Fall back to newer API or provide migration guidance
return await handleApiMigration(endpoint, data);
}
throw error;
}
};