server.ts•20.1 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from '@modelcontextprotocol/sdk/types.js';
import { OuraClient, OuraClientConfig, OuraAPIError } from './oura-client.js';
import { DateRangeParams, WebhookSubscriptionCreateParams } from './types.js';
interface MCPServerConfig {
oura: OuraClientConfig;
}
export class OuraMCPServer {
private server: Server;
private ouraClient: OuraClient;
constructor(config: MCPServerConfig) {
this.server = new Server(
{
name: 'oura-ring-mcp',
version: '0.1.0',
}
);
this.ouraClient = new OuraClient(config.oura);
this.setupHandlers();
}
private setupHandlers(): void {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: this.getAvailableTools(),
};
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
return await this.handleToolCall(name, args);
} catch (error) {
if (error instanceof OuraAPIError) {
throw new Error(`Oura API Error: ${error.message}`);
}
throw error;
}
});
}
private getAvailableTools(): Tool[] {
return [
// Personal Info
{
name: 'get_personal_info',
description: 'Get user personal information including age, weight, height, biological sex, and timezone',
inputSchema: {
type: 'object',
properties: {},
},
},
// Sleep Data
{
name: 'get_sleep',
description: 'Get sleep data including sleep scores, contributors, and sleep stages',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
end_date: {
type: 'string',
description: 'End date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
next_token: {
type: 'string',
description: 'Token for pagination',
},
},
},
},
// Activity Data
{
name: 'get_activity',
description: 'Get activity data including steps, calories, and activity levels',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
end_date: {
type: 'string',
description: 'End date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
next_token: {
type: 'string',
description: 'Token for pagination',
},
},
},
},
// Readiness Data
{
name: 'get_readiness',
description: 'Get readiness data including readiness score and contributing factors',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
end_date: {
type: 'string',
description: 'End date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
next_token: {
type: 'string',
description: 'Token for pagination',
},
},
},
},
// Heart Rate Data
{
name: 'get_heart_rate',
description: 'Get heart rate data with timestamps and sources',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
end_date: {
type: 'string',
description: 'End date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
next_token: {
type: 'string',
description: 'Token for pagination',
},
},
},
},
// Workout Data
{
name: 'get_workouts',
description: 'Get workout data including activity type, duration, calories, and distance',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
end_date: {
type: 'string',
description: 'End date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
next_token: {
type: 'string',
description: 'Token for pagination',
},
},
},
},
// Session Data
{
name: 'get_sessions',
description: 'Get session data for breathing, meditation, naps, and other activities',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
end_date: {
type: 'string',
description: 'End date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
next_token: {
type: 'string',
description: 'Token for pagination',
},
},
},
},
// Tag Data
{
name: 'get_tags',
description: 'Get user-created tags with timestamps',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
end_date: {
type: 'string',
description: 'End date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
next_token: {
type: 'string',
description: 'Token for pagination',
},
},
},
},
// Enhanced Tag Data
{
name: 'get_enhanced_tags',
description: 'Get enhanced tags with additional metadata',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
end_date: {
type: 'string',
description: 'End date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
next_token: {
type: 'string',
description: 'Token for pagination',
},
},
},
},
// Daily Activity Data
{
name: 'get_daily_activity',
description: 'Get daily activity summaries with scores and metrics',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
end_date: {
type: 'string',
description: 'End date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
next_token: {
type: 'string',
description: 'Token for pagination',
},
},
},
},
// Daily Sleep Data
{
name: 'get_daily_sleep',
description: 'Get daily sleep summaries with scores and contributors',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
end_date: {
type: 'string',
description: 'End date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
next_token: {
type: 'string',
description: 'Token for pagination',
},
},
},
},
// Daily Readiness Data
{
name: 'get_daily_readiness',
description: 'Get daily readiness summaries with scores and temperature data',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
end_date: {
type: 'string',
description: 'End date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
next_token: {
type: 'string',
description: 'Token for pagination',
},
},
},
},
// Daily Stress Data
{
name: 'get_daily_stress',
description: 'Get daily stress levels and recovery data',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
end_date: {
type: 'string',
description: 'End date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
next_token: {
type: 'string',
description: 'Token for pagination',
},
},
},
},
// Sleep Time Data
{
name: 'get_sleep_time',
description: 'Get sleep time data including bedtime and wake time',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
end_date: {
type: 'string',
description: 'End date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
next_token: {
type: 'string',
description: 'Token for pagination',
},
},
},
},
// Rest Mode Periods
{
name: 'get_rest_mode_periods',
description: 'Get rest mode period data when user has enabled rest mode',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
end_date: {
type: 'string',
description: 'End date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
next_token: {
type: 'string',
description: 'Token for pagination',
},
},
},
},
// Ring Configuration
{
name: 'get_ring_configuration',
description: 'Get ring configuration settings and preferences',
inputSchema: {
type: 'object',
properties: {
start_date: {
type: 'string',
description: 'Start date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
end_date: {
type: 'string',
description: 'End date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
next_token: {
type: 'string',
description: 'Token for pagination',
},
},
},
},
// Webhook Subscriptions
{
name: 'get_webhook_subscriptions',
description: 'Get list of active webhook subscriptions',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'create_webhook_subscription',
description: 'Create a new webhook subscription for real-time data updates',
inputSchema: {
type: 'object',
properties: {
callback_url: {
type: 'string',
description: 'The URL where webhook events will be sent',
format: 'uri',
},
verification_token: {
type: 'string',
description: 'Secret token used to verify webhook events',
},
event_type: {
type: 'string',
enum: ['create', 'update', 'delete'],
description: 'Type of events to subscribe to',
},
data_type: {
type: 'string',
enum: [
'tag',
'enhanced_tag',
'workout',
'session',
'sleep',
'daily_sleep',
'daily_readiness',
'daily_activity',
'daily_stress',
],
description: 'Type of data to receive webhooks for',
},
},
required: ['callback_url', 'verification_token', 'event_type', 'data_type'],
},
},
{
name: 'delete_webhook_subscription',
description: 'Delete an existing webhook subscription',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'ID of the webhook subscription to delete',
},
},
required: ['id'],
},
},
];
}
private async handleToolCall(name: string, args: any): Promise<any> {
const dateRangeParams: DateRangeParams = {
start_date: args?.start_date,
end_date: args?.end_date,
next_token: args?.next_token,
};
switch (name) {
case 'get_personal_info':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getPersonalInfo(), null, 2),
},
],
};
case 'get_sleep':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getSleep(dateRangeParams), null, 2),
},
],
};
case 'get_activity':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getActivity(dateRangeParams), null, 2),
},
],
};
case 'get_readiness':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getReadiness(dateRangeParams), null, 2),
},
],
};
case 'get_heart_rate':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getHeartRate(dateRangeParams), null, 2),
},
],
};
case 'get_workouts':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getWorkouts(dateRangeParams), null, 2),
},
],
};
case 'get_sessions':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getSessions(dateRangeParams), null, 2),
},
],
};
case 'get_tags':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getTags(dateRangeParams), null, 2),
},
],
};
case 'get_enhanced_tags':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getEnhancedTags(dateRangeParams), null, 2),
},
],
};
case 'get_daily_activity':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getDailyActivity(dateRangeParams), null, 2),
},
],
};
case 'get_daily_sleep':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getDailySleep(dateRangeParams), null, 2),
},
],
};
case 'get_daily_readiness':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getDailyReadiness(dateRangeParams), null, 2),
},
],
};
case 'get_daily_stress':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getDailyStress(dateRangeParams), null, 2),
},
],
};
case 'get_sleep_time':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getSleepTime(dateRangeParams), null, 2),
},
],
};
case 'get_rest_mode_periods':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getRestModePeriods(dateRangeParams), null, 2),
},
],
};
case 'get_ring_configuration':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getRingConfiguration(dateRangeParams), null, 2),
},
],
};
case 'get_webhook_subscriptions':
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.getWebhookSubscriptions(), null, 2),
},
],
};
case 'create_webhook_subscription':
const createParams: WebhookSubscriptionCreateParams = {
callback_url: args.callback_url,
verification_token: args.verification_token,
event_type: args.event_type,
data_type: args.data_type,
};
return {
content: [
{
type: 'text',
text: JSON.stringify(await this.ouraClient.createWebhookSubscription(createParams), null, 2),
},
],
};
case 'delete_webhook_subscription':
await this.ouraClient.deleteWebhookSubscription(args.id);
return {
content: [
{
type: 'text',
text: `Webhook subscription ${args.id} deleted successfully`,
},
],
};
default:
throw new Error(`Unknown tool: ${name}`);
}
}
public getServer(): Server {
return this.server;
}
}