#!/usr/bin/env node
/**
* Vreme Time Service MCP Server
* Connects AI assistants to Vreme Time Service API
*
* Features:
* - General time queries across 9 calendar systems
* - Islamic prayer times with qibla direction
* - Activity appropriateness checks with cultural awareness
* - Religious fasting and work restriction detection
* - Astronomical data (sunrise, sunset, moon phases)
* - Cross-cultural temporal intelligence
*/
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
// API Configuration
const VREME_API_URL = process.env.VREME_API_URL || "https://api.vreme.ai";
interface QueryRequest {
query: string;
user_timezone?: string;
}
interface QueryResponse {
query: string;
parsed: {
intent: string;
location: string | null;
timezone: string;
culture: string | null;
calendar_system: string | null;
confidence: number;
};
context: {
current_time: string;
timezone: string;
human_readable: string;
astronomical: any;
cultural_calendars: any[];
activity_appropriateness: any;
upcoming_events: any[];
relative_to_user: any;
prayer_times?: any; // Prayer times (if query was prayer-related)
};
answer: string;
type: string;
execution_time_ms: number;
}
/**
* Query the Vreme Time Service API
*/
async function queryVremeAPI(request: QueryRequest): Promise<QueryResponse> {
const response = await fetch(`${VREME_API_URL}/query`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(request),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`API request failed: ${response.status} ${errorText}`);
}
return (await response.json()) as QueryResponse;
}
/**
* Format response for Claude
*/
function formatResponse(response: QueryResponse): string {
let output = `## ${response.answer}\n\n`;
// Add context details
output += `### Context\n\n`;
output += `**Location:** ${response.parsed.location || response.context.timezone}\n`;
output += `**Time:** ${response.context.human_readable}\n\n`;
// Prayer Times (if present)
if (response.context.prayer_times) {
const prayers = response.context.prayer_times;
output += `### π Prayer Times\n\n`;
output += `**Method:** ${prayers.method}\n\n`;
// Prayer times table
if (prayers.prayers) {
output += `| Prayer | Time |\n`;
output += `|--------|------|\n`;
const prayerNames = ["fajr", "sunrise", "dhuhr", "asr", "maghrib", "isha"];
const prayerLabels: Record<string, string> = {
fajr: "Fajr",
sunrise: "Sunrise",
dhuhr: "Dhuhr",
asr: "Asr",
maghrib: "Maghrib",
isha: "Isha"
};
for (const prayerName of prayerNames) {
const prayerTime = prayers.prayers[prayerName];
if (prayerTime) {
const time = new Date(prayerTime);
output += `| ${prayerLabels[prayerName]} | ${time.toLocaleTimeString()} |\n`;
}
}
output += `\n`;
}
// Next prayer
if (prayers.next_prayer) {
output += `**β° Next Prayer:** ${prayers.next_prayer.prayer} in ${prayers.next_prayer.time_until_human}\n\n`;
}
// Qibla direction
if (prayers.qibla_direction) {
output += `**π§ Qibla Direction:** ${prayers.qibla_direction.description}\n\n`;
}
}
// Astronomical information
if (response.context.astronomical) {
const astro = response.context.astronomical;
output += `### Astronomical Information\n\n`;
if (astro.sunrise) {
const sunrise = new Date(astro.sunrise);
output += `π
**Sunrise:** ${sunrise.toLocaleTimeString()}\n`;
}
if (astro.sunset) {
const sunset = new Date(astro.sunset);
output += `π **Sunset:** ${sunset.toLocaleTimeString()}\n`;
}
if (astro.day_length_hours) {
const hours = Math.floor(astro.day_length_hours);
const minutes = Math.round((astro.day_length_hours - hours) * 60);
output += `β±οΈ **Day length:** ${hours}h ${minutes}m\n`;
}
if (astro.moon_phase) {
output += `π **Moon:** ${astro.moon_phase} (${Math.round(astro.moon_illumination * 100)}% illuminated)\n`;
}
output += `\n`;
}
// Cultural calendars
if (response.context.cultural_calendars && response.context.cultural_calendars.length > 0) {
output += `### Cultural Calendars\n\n`;
for (const calendar of response.context.cultural_calendars) {
output += `**${calendar.calendar_type.charAt(0).toUpperCase() + calendar.calendar_type.slice(1)} Calendar:** ${calendar.date}\n`;
if (calendar.special_observance) {
output += `β¨ **Observance:** ${calendar.special_observance}\n`;
}
if (calendar.notes && calendar.notes.length > 0) {
output += `π **Notes:** ${calendar.notes.join(", ")}\n`;
}
// NEW: Show fasting status
if (calendar.is_fasting_day) {
output += `π½οΈ **Fasting Day**\n`;
}
// NEW: Show work permission status
if (calendar.work_permitted === false) {
output += `π« **No work permitted**\n`;
}
// NEW: Show zodiac (for Chinese calendar)
if (calendar.zodiac && calendar.element) {
output += `π **Zodiac:** ${calendar.element} ${calendar.zodiac}\n`;
}
output += `\n`;
}
}
// Activity appropriateness
if (response.context.activity_appropriateness) {
const activity = response.context.activity_appropriateness;
output += `### Activity Appropriateness\n\n`;
output += `**Time of day:** ${activity.time_of_day}\n`;
output += `**Appropriate for calls:** ${activity.appropriate_for_calls ? "β
Yes" : "β No"}\n`;
output += `**Appropriate for work:** ${activity.appropriate_for_work ? "β
Yes" : "β No"}\n`;
output += `**Appropriate for meetings:** ${activity.appropriate_for_meetings ? "β
Yes" : "β No"}\n`;
if (activity.considerations && activity.considerations.length > 0) {
output += `**Considerations:** ${activity.considerations.join(", ")}\n`;
}
// NEW: Show fasting observances
if (activity.fasting_observances && activity.fasting_observances.length > 0) {
output += `\n**π½οΈ Fasting Observances:**\n`;
for (const fast of activity.fasting_observances) {
output += `- ${fast.religion}: ${fast.observance}\n`;
if (fast.notes && fast.notes.length > 0) {
output += ` ${fast.notes.join(", ")}\n`;
}
}
}
// NEW: Show work restrictions
if (activity.work_restrictions && activity.work_restrictions.length > 0) {
output += `\n**π« Work Restrictions:**\n`;
for (const restriction of activity.work_restrictions) {
output += `- ${restriction.culture}: ${restriction.observance}\n`;
output += ` ${restriction.reason}\n`;
}
}
output += `\n`;
}
// Upcoming events
if (response.context.upcoming_events && response.context.upcoming_events.length > 0) {
output += `### Upcoming Events\n\n`;
for (const event of response.context.upcoming_events.slice(0, 3)) {
output += `β° ${event.description}\n`;
}
output += `\n`;
}
// Relative time
if (response.context.relative_to_user) {
output += `### Relative Time\n\n`;
output += `${response.context.relative_to_user.description}\n\n`;
}
// Execution time
output += `---\n`;
output += `*Response generated in ${response.execution_time_ms.toFixed(2)}ms*\n`;
return output;
}
/**
* Create and configure MCP server
*/
const server = new Server(
{
name: "vreme-time-service",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
/**
* List available tools
*/
server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools: Tool[] = [
{
name: "query_time",
description:
"Query temporal information using natural language. Get current time, timezone info, 9 cultural calendars (Hebrew, Islamic, Chinese, Hindu, Persian, Buddhist, BahΓ‘'Γ, Ethiopian, Mayan), astronomical events, religious fasting status, work restrictions, and activity appropriateness for any location. Supports queries like 'What time is it in Tokyo?', 'Is it a good time to call Berlin?', 'Is it Shabbat in Jerusalem?', 'When is sunrise in Paris?', 'Is it Ramadan?', etc.",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description:
"Natural language temporal query (e.g., 'What time is it in Tokyo?', 'Is it a good time to call Berlin?', 'Is it Ramadan?', 'When is sunrise in Paris?')",
},
user_timezone: {
type: "string",
description:
"Optional: Your timezone for relative time calculations (e.g., 'America/Los_Angeles', 'Europe/London')",
},
},
required: ["query"],
},
},
{
name: "query_prayer_times",
description:
"Get Islamic prayer times (Salah/Namaz) for any location. Returns all 5 daily prayers (Fajr, Dhuhr, Asr, Maghrib, Isha), next prayer time, and Qibla direction to Mecca. Calculation method is automatically selected based on location (MWL, ISNA, Egypt, Makkah, Karachi, Tehran, Jafari). Supports queries for specific prayers or all prayers.",
inputSchema: {
type: "object",
properties: {
location: {
type: "string",
description:
"Location name (e.g., 'Dubai', 'Istanbul', 'New York', 'London')",
},
prayer: {
type: "string",
description:
"Optional: Specific prayer to query ('fajr', 'dhuhr', 'asr', 'maghrib', 'isha'). If omitted, returns all prayers.",
},
},
required: ["location"],
},
},
{
name: "check_activity_appropriateness",
description:
"Check if the current time is appropriate for specific activities (calls, work, meetings) in a given location. Takes into account time of day, cultural/religious observances across 9 calendar systems (Shabbat, Ramadan fasting, religious holidays from Hebrew, Islamic, Hindu, Buddhist, and other traditions), work restrictions, and local customs. Perfect for 'Can I call someone in X?' or 'Is it a good time to contact Y?' queries.",
inputSchema: {
type: "object",
properties: {
location: {
type: "string",
description:
"Location name (e.g., 'Berlin', 'Jerusalem', 'Tokyo')",
},
activity: {
type: "string",
description:
"Optional: Type of activity to check ('call', 'work', 'meeting'). If omitted, checks all activities.",
},
user_timezone: {
type: "string",
description:
"Optional: Your timezone for relative time calculations",
},
},
required: ["location"],
},
},
];
return { tools };
});
/**
* Handle tool calls
*/
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const toolName = request.params.name;
const args = request.params.arguments as any;
try {
let queryRequest: QueryRequest;
switch (toolName) {
case "query_time":
// General time query
if (!args.query) {
throw new Error("Query parameter is required");
}
queryRequest = {
query: args.query,
user_timezone: args.user_timezone,
};
break;
case "query_prayer_times":
// Prayer times query
if (!args.location) {
throw new Error("Location parameter is required");
}
// Build prayer query
let prayerQuery = `What are prayer times in ${args.location}?`;
if (args.prayer) {
prayerQuery = `When is ${args.prayer} in ${args.location}?`;
}
queryRequest = {
query: prayerQuery,
};
break;
case "check_activity_appropriateness":
// Activity appropriateness check
if (!args.location) {
throw new Error("Location parameter is required");
}
// Build activity query
let activityQuery = `Is it a good time to call someone in ${args.location}?`;
if (args.activity === "work") {
activityQuery = `Is it appropriate for work in ${args.location}?`;
} else if (args.activity === "meeting") {
activityQuery = `Is it a good time for a meeting in ${args.location}?`;
}
queryRequest = {
query: activityQuery,
user_timezone: args.user_timezone,
};
break;
default:
throw new Error(`Unknown tool: ${toolName}`);
}
// Query the API
const response = await queryVremeAPI(queryRequest);
// Format and return response
const formattedResponse = formatResponse(response);
return {
content: [
{
type: "text",
text: formattedResponse,
},
],
};
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
return {
content: [
{
type: "text",
text: `Error querying Vreme Time Service: ${errorMessage}\n\nPlease ensure the Vreme API is running at: ${VREME_API_URL}`,
},
],
isError: true,
};
}
});
/**
* Start the server
*/
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
// Log to stderr (stdout is reserved for MCP protocol)
console.error("Vreme Time Service MCP Server v1.0.0 running");
console.error(`API URL: ${VREME_API_URL}`);
console.error("Available tools: query_time, query_prayer_times, check_activity_appropriateness");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});