first commit

This commit is contained in:
2026-03-24 16:24:16 +03:00
commit 28c24d8cb1
20 changed files with 4271 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
import { config } from "./config.js";
import {
buildSignature,
generateIdempotencyKey,
generateNonce,
generateTimestamp,
} from "./security.js";
interface TokenState {
accessToken: string;
refreshToken: string;
accessTokenExpiresAt: number;
refreshTokenExpiresAt: number;
}
let tokenState: TokenState | null = null;
function parseJwtExpiry(token: string): number {
const payload = token.split(".")[1];
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
// JWT exp is in seconds, convert to milliseconds
return decoded.exp * 1000;
}
async function callAuthEndpoint(
path: string,
body: Record<string, string>
): Promise<{ accessToken: string; refreshToken: string }> {
const method = "POST";
const bodyStr = JSON.stringify(body);
const timestamp = generateTimestamp();
const nonce = generateNonce();
const idempotencyKey = generateIdempotencyKey();
const signature = buildSignature(method, path, timestamp, bodyStr, config.hmacSecret);
const response = await fetch(`${config.baseUrl}${path}`, {
method,
headers: {
"Content-Type": "application/json",
"X-Signature": signature,
"X-Timestamp": timestamp,
"X-Nonce": nonce,
"X-Idempotency-Key": idempotencyKey,
},
body: bodyStr,
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(
`Auth failed [${response.status}]: ${(error as { message?: string }).message ?? response.statusText}`
);
}
const data = (await response.json()) as {
accessToken: string;
refreshToken: string;
};
return { accessToken: data.accessToken, refreshToken: data.refreshToken };
}
async function login(): Promise<void> {
const { accessToken, refreshToken } = await callAuthEndpoint("/v1/auth/login", {
username: config.username,
password: config.password,
});
tokenState = {
accessToken,
refreshToken,
accessTokenExpiresAt: parseJwtExpiry(accessToken),
refreshTokenExpiresAt: parseJwtExpiry(refreshToken),
};
}
async function refresh(): Promise<void> {
if (!tokenState) throw new Error("No token state to refresh");
const { accessToken, refreshToken } = await callAuthEndpoint("/v1/auth/refresh", {
refreshToken: tokenState.refreshToken,
});
tokenState = {
accessToken,
refreshToken,
accessTokenExpiresAt: parseJwtExpiry(accessToken),
refreshTokenExpiresAt: parseJwtExpiry(refreshToken),
};
}
// 30 second buffer to avoid using a token that expires mid-request
const EXPIRY_BUFFER_MS = 30_000;
export async function getValidAccessToken(): Promise<string> {
const now = Date.now();
if (tokenState && tokenState.accessTokenExpiresAt - EXPIRY_BUFFER_MS > now) {
return tokenState.accessToken;
}
if (tokenState && tokenState.refreshTokenExpiresAt - EXPIRY_BUFFER_MS > now) {
await refresh();
return tokenState!.accessToken;
}
await login();
return tokenState!.accessToken;
}
+109
View File
@@ -0,0 +1,109 @@
import { config } from "./config.js";
import { getValidAccessToken } from "./auth.js";
import {
buildSignature,
generateIdempotencyKey,
generateNonce,
generateTimestamp,
} from "./security.js";
function buildPathWithQuery(path: string, params?: Record<string, string>): string {
if (!params || Object.keys(params).length === 0) return path;
const qs = new URLSearchParams(params).toString();
return `${path}?${qs}`;
}
async function buildHeaders(
method: string,
pathWithQuery: string,
body: string,
withIdempotency: boolean
): Promise<Record<string, string>> {
const accessToken = await getValidAccessToken();
const timestamp = generateTimestamp();
const nonce = generateNonce();
const signature = buildSignature(method, pathWithQuery, timestamp, body, config.hmacSecret);
const headers: Record<string, string> = {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
"X-Signature": signature,
"X-Timestamp": timestamp,
"X-Nonce": nonce,
};
if (withIdempotency) {
headers["X-Idempotency-Key"] = generateIdempotencyKey();
}
return headers;
}
async function handleResponse<T>(response: Response): Promise<T> {
if (!response.ok) {
const error = await response.json().catch(() => ({}));
const msg = (error as { message?: string }).message ?? response.statusText;
throw new Error(`API error [${response.status}]: ${msg}`);
}
// 204 No Content
if (response.status === 204) return {} as T;
return response.json() as Promise<T>;
}
export async function apiGet<T>(
path: string,
params?: Record<string, string>
): Promise<T> {
const pathWithQuery = buildPathWithQuery(path, params);
const headers = await buildHeaders("GET", pathWithQuery, "", false);
const response = await fetch(`${config.baseUrl}${pathWithQuery}`, {
method: "GET",
headers,
});
return handleResponse<T>(response);
}
export async function apiPost<T>(
path: string,
body: unknown
): Promise<T> {
const bodyStr = JSON.stringify(body);
const headers = await buildHeaders("POST", path, bodyStr, true);
const response = await fetch(`${config.baseUrl}${path}`, {
method: "POST",
headers,
body: bodyStr,
});
return handleResponse<T>(response);
}
export async function apiPatch<T>(
path: string,
body: unknown
): Promise<T> {
const bodyStr = JSON.stringify(body);
const headers = await buildHeaders("PATCH", path, bodyStr, true);
const response = await fetch(`${config.baseUrl}${path}`, {
method: "PATCH",
headers,
body: bodyStr,
});
return handleResponse<T>(response);
}
export async function apiDelete<T>(path: string): Promise<T> {
const headers = await buildHeaders("DELETE", path, "", false);
const response = await fetch(`${config.baseUrl}${path}`, {
method: "DELETE",
headers,
});
return handleResponse<T>(response);
}
+34
View File
@@ -0,0 +1,34 @@
type Environment = "prod" | "test" | "local";
const BASE_URLS: Record<Environment, string> = {
prod: "https://api.iklim.co",
test: "https://api-test.iklim.co",
local: "http://localhost:8080",
};
function resolveBaseUrl(): string {
// Explicit URL override takes precedence
if (process.env.IKLIM_BASE_URL) return process.env.IKLIM_BASE_URL;
const env = (process.env.IKLIM_ENV ?? "prod") as Environment;
const url = BASE_URLS[env];
if (!url) {
throw new Error(
`Invalid IKLIM_ENV value: "${env}". Must be one of: ${Object.keys(BASE_URLS).join(", ")}`
);
}
return url;
}
function requireEnv(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Missing required environment variable: ${name}`);
return value;
}
export const config = {
baseUrl: resolveBaseUrl(),
hmacSecret: requireEnv("IKLIM_HMAC_SECRET"),
username: requireEnv("IKLIM_USERNAME"),
password: requireEnv("IKLIM_PASSWORD"),
};
+83
View File
@@ -0,0 +1,83 @@
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { lightningTools, handleLightningTool } from "./tools/lightnings.js";
import { thunderstormTools, handleThunderstormTool } from "./tools/thunderstorms.js";
import { precipitationTools, handlePrecipitationTool } from "./tools/precipitations.js";
import { forecastTools, handleForecastTool } from "./tools/forecasts.js";
import { authTools, handleAuthTool } from "./tools/auth.js";
import { accountTools, handleAccountTool } from "./tools/accounts.js";
import { pointAlarmTools, handlePointAlarmTool } from "./tools/point-alarms.js";
import { geoAlarmTools, handleGeoAlarmTool } from "./tools/geo-alarms.js";
import { forecastAlarmTools, handleForecastAlarmTool } from "./tools/forecast-alarms.js";
const toolGroups = [
{ tools: lightningTools, handler: handleLightningTool },
{ tools: thunderstormTools, handler: handleThunderstormTool },
{ tools: precipitationTools, handler: handlePrecipitationTool },
{ tools: forecastTools, handler: handleForecastTool },
{ tools: authTools, handler: handleAuthTool },
{ tools: accountTools, handler: handleAccountTool },
{ tools: pointAlarmTools, handler: handlePointAlarmTool },
{ tools: geoAlarmTools, handler: handleGeoAlarmTool },
{ tools: forecastAlarmTools, handler: handleForecastAlarmTool },
];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const allTools = toolGroups.flatMap((g) => g.tools as unknown as any[]);
const toolHandlerMap = new Map<string, (name: string, args: Record<string, unknown>) => Promise<unknown>>();
for (const { tools, handler } of toolGroups) {
for (const tool of tools) {
toolHandlerMap.set(tool.name, handler);
}
}
const server = new Server(
{ name: "iklim-co", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: allTools,
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const handler = toolHandlerMap.get(name);
if (!handler) {
return {
content: [{ type: "text", text: `Unknown tool: ${name}` }],
isError: true,
};
}
try {
const result = await handler(name, args as Record<string, unknown>);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
content: [{ type: "text", text: `Error: ${message}` }],
isError: true,
};
}
});
async function main(): Promise<void> {
const transport = new StdioServerTransport();
await server.connect(transport);
process.stderr.write("iklim.co MCP server running\n");
}
main().catch((error) => {
process.stderr.write(`Fatal error: ${error}\n`);
process.exit(1);
});
+24
View File
@@ -0,0 +1,24 @@
import crypto from "crypto";
export function generateTimestamp(): string {
return Date.now().toString();
}
export function generateNonce(): string {
return crypto.randomUUID();
}
export function generateIdempotencyKey(): string {
return crypto.randomUUID();
}
export function buildSignature(
method: string,
pathWithQuery: string,
timestamp: string,
body: string,
secret: string
): string {
const dataToSign = `${method.toUpperCase()}|${pathWithQuery}|${timestamp}|${body}`;
return crypto.createHmac("sha256", secret).update(dataToSign).digest("hex");
}
+233
View File
@@ -0,0 +1,233 @@
import { z } from "zod";
import { apiGet, apiPatch, apiPost } from "../client.js";
const SUBSCRIPTION_PLANS = [
"NONE", "TRIAL", "BASIC_MONTHLY", "BASIC_YEARLY",
"PREMIUM_MONTHLY", "PREMIUM_YEARLY", "CUSTOM",
] as const;
export const accountTools = [
{
name: "account_get",
description: "Get account details for a given user ID.",
inputSchema: {
type: "object" as const,
properties: {
userId: { type: "string", description: "User UUID" },
},
required: ["userId"],
},
},
{
name: "account_create",
description: "Create a new account for an existing user.",
inputSchema: {
type: "object" as const,
properties: {
userId: { type: "string", description: "User UUID" },
type: {
type: "string",
enum: ["INDIVIDUAL", "ORGANIZATION"],
description: "Account type",
},
subscriptionPlan: {
type: "string",
enum: SUBSCRIPTION_PLANS,
description: "Initial subscription plan",
},
mobilePhoneNumber: { type: "string", description: "Mobile phone (e.g. 905321112233)" },
location: { type: "string", description: "Address" },
company: { type: "string", description: "Company name (optional)" },
industry: { type: "string", description: "Industry (optional)" },
profilePictureUrl: { type: "string", description: "Profile picture URL (optional)" },
},
required: ["userId", "type", "subscriptionPlan"],
},
},
{
name: "account_update",
description: "Update an existing account's fields.",
inputSchema: {
type: "object" as const,
properties: {
accountId: { type: "string", description: "Account UUID" },
type: { type: "string", enum: ["INDIVIDUAL", "ORGANIZATION"], description: "Account type (optional)" },
mobilePhoneNumber: { type: "string", description: "Mobile phone (optional)" },
location: { type: "string", description: "Address (optional)" },
company: { type: "string", description: "Company name (optional)" },
industry: { type: "string", description: "Industry (optional)" },
profilePictureUrl: { type: "string", description: "Profile picture URL (optional)" },
},
required: ["accountId"],
},
},
{
name: "account_activation_request",
description: "Initiate account activation by sending a verification email to the user.",
inputSchema: {
type: "object" as const,
properties: {
userId: { type: "string", description: "User UUID" },
type: { type: "string", enum: ["INDIVIDUAL", "ORGANIZATION"], description: "Account type" },
accountActivationPageLink: { type: "string", description: "URL of the activation page" },
mobilePhoneNumber: { type: "string", description: "Mobile phone (optional)" },
location: { type: "string", description: "Address (optional)" },
company: { type: "string", description: "Company name (optional)" },
industry: { type: "string", description: "Industry (optional)" },
profilePictureUrl: { type: "string", description: "Profile picture URL (optional)" },
},
required: ["userId", "type", "accountActivationPageLink"],
},
},
{
name: "account_activate",
description: "Activate an account using the email verification token.",
inputSchema: {
type: "object" as const,
properties: {
userId: { type: "string", description: "User UUID" },
accountId: { type: "string", description: "Account UUID" },
emailVerificationToken: { type: "string", description: "Token received by email" },
},
required: ["userId", "accountId", "emailVerificationToken"],
},
},
{
name: "account_phone_activation_request",
description: "Initiate phone number activation by sending a verification SMS.",
inputSchema: {
type: "object" as const,
properties: {
userId: { type: "string", description: "User UUID" },
accountId: { type: "string", description: "Account UUID" },
phoneNumberActivationPageLink: { type: "string", description: "URL of the phone activation page" },
},
required: ["userId", "accountId", "phoneNumberActivationPageLink"],
},
},
{
name: "account_activate_phone",
description: "Activate a phone number using the SMS verification token.",
inputSchema: {
type: "object" as const,
properties: {
userId: { type: "string", description: "User UUID" },
accountId: { type: "string", description: "Account UUID" },
phoneNumberVerificationToken: { type: "string", description: "Token received by SMS" },
},
required: ["userId", "accountId", "phoneNumberVerificationToken"],
},
},
{
name: "account_update_subscription",
description: "Update the subscription plan for an account.",
inputSchema: {
type: "object" as const,
properties: {
userId: { type: "string", description: "User UUID" },
accountId: { type: "string", description: "Account UUID" },
subscriptionPlan: {
type: "string",
enum: SUBSCRIPTION_PLANS,
description: "New subscription plan",
},
},
required: ["userId", "accountId", "subscriptionPlan"],
},
},
] as const;
const AccountType = z.enum(["INDIVIDUAL", "ORGANIZATION"]);
const SubscriptionPlan = z.enum(SUBSCRIPTION_PLANS);
const CreateAccountSchema = z.object({
userId: z.string(),
type: AccountType,
subscriptionPlan: SubscriptionPlan,
mobilePhoneNumber: z.string().optional(),
location: z.string().optional(),
company: z.string().optional(),
industry: z.string().optional(),
profilePictureUrl: z.string().optional(),
});
const UpdateAccountSchema = z.object({
accountId: z.string(),
type: AccountType.optional(),
mobilePhoneNumber: z.string().optional(),
location: z.string().optional(),
company: z.string().optional(),
industry: z.string().optional(),
profilePictureUrl: z.string().optional(),
});
const ActivationRequestSchema = z.object({
userId: z.string(),
type: AccountType,
accountActivationPageLink: z.string(),
mobilePhoneNumber: z.string().optional(),
location: z.string().optional(),
company: z.string().optional(),
industry: z.string().optional(),
profilePictureUrl: z.string().optional(),
});
const PhoneActivationRequestSchema = z.object({
userId: z.string(),
accountId: z.string(),
phoneNumberActivationPageLink: z.string(),
});
const UpdateSubscriptionSchema = z.object({
userId: z.string(),
accountId: z.string(),
subscriptionPlan: SubscriptionPlan,
});
export async function handleAccountTool(
name: string,
args: Record<string, unknown>
): Promise<unknown> {
switch (name) {
case "account_get": {
const userId = z.string().parse(args.userId);
return apiGet(`/v1/accounts/${userId}`);
}
case "account_create":
return apiPost("/v1/accounts/create", CreateAccountSchema.parse(args));
case "account_update":
return apiPatch("/v1/accounts/update", UpdateAccountSchema.parse(args));
case "account_activation_request":
return apiPost("/v1/accounts/account-activation-request", ActivationRequestSchema.parse(args));
case "account_activate": {
const { userId, accountId, emailVerificationToken } = z.object({
userId: z.string(),
accountId: z.string(),
emailVerificationToken: z.string(),
}).parse(args);
return apiGet("/v1/accounts/activate-account", { userId, accountId, emailVerificationToken });
}
case "account_phone_activation_request":
return apiPost("/v1/accounts/phone-number-activation-request", PhoneActivationRequestSchema.parse(args));
case "account_activate_phone": {
const { userId, accountId, phoneNumberVerificationToken } = z.object({
userId: z.string(),
accountId: z.string(),
phoneNumberVerificationToken: z.string(),
}).parse(args);
return apiGet("/v1/accounts/activate-phone-number", { userId, accountId, phoneNumberVerificationToken });
}
case "account_update_subscription":
return apiPost("/v1/accounts/update-subscription", UpdateSubscriptionSchema.parse(args));
default:
throw new Error(`Unknown account tool: ${name}`);
}
}
+304
View File
@@ -0,0 +1,304 @@
import { z } from "zod";
import { apiGet, apiPatch, apiPost } from "../client.js";
export const authTools = [
{
name: "auth_register",
description: "Register a new user account. No authentication required.",
inputSchema: {
type: "object" as const,
properties: {
username: { type: "string", description: "User email address" },
password: { type: "string", description: "Password" },
firstName: { type: "string", description: "First name" },
midName: { type: "string", description: "Middle name (optional)" },
lastName: { type: "string", description: "Last name" },
locale: { type: "string", description: "Locale (e.g. tr_TR, en_US)" },
timezone: { type: "string", description: "Timezone (e.g. Europe/Istanbul)" },
},
required: ["username", "password", "firstName", "lastName", "locale", "timezone"],
},
},
{
name: "auth_logout",
description: "Logout by invalidating the given JWT access token.",
inputSchema: {
type: "object" as const,
properties: {
jwtToken: { type: "string", description: "The access token to invalidate" },
},
required: ["jwtToken"],
},
},
{
name: "user_get_me",
description: "Get the profile of the currently authenticated user.",
inputSchema: {
type: "object" as const,
properties: {},
required: [],
},
},
{
name: "user_get",
description: "Get a user's profile by their user ID.",
inputSchema: {
type: "object" as const,
properties: {
userId: { type: "string", description: "User UUID" },
},
required: ["userId"],
},
},
{
name: "user_create",
description: "Create a new user (admin operation).",
inputSchema: {
type: "object" as const,
properties: {
username: { type: "string", description: "User email address" },
password: { type: "string", description: "Initial password" },
firstName: { type: "string", description: "First name" },
midName: { type: "string", description: "Middle name (optional)" },
lastName: { type: "string", description: "Last name" },
locale: { type: "string", description: "Locale (e.g. tr_TR, en_US)" },
timezone: { type: "string", description: "Timezone (e.g. Europe/Istanbul)" },
roles: {
type: "array",
items: {
type: "string",
enum: ["GUEST", "API_USER", "STANDARD_USER", "POWER_USER", "EXTENDED_USER", "ADMIN"],
},
description: "Roles to assign",
},
status: {
type: "string",
enum: ["INACTIVE", "ACTIVE", "EXPIRED", "BLOCKED", "DELETED"],
description: "Initial account status",
},
},
required: ["username", "firstName", "lastName", "locale", "timezone", "roles", "status"],
},
},
{
name: "user_update",
description: "Update an existing user's profile fields.",
inputSchema: {
type: "object" as const,
properties: {
userId: { type: "string", description: "User UUID to update" },
username: { type: "string", description: "New email address (optional)" },
firstName: { type: "string", description: "New first name (optional)" },
midName: { type: "string", description: "New middle name (optional)" },
lastName: { type: "string", description: "New last name (optional)" },
locale: { type: "string", description: "New locale (optional)" },
timezone: { type: "string", description: "New timezone (optional)" },
roles: {
type: "array",
items: {
type: "string",
enum: ["GUEST", "API_USER", "STANDARD_USER", "POWER_USER", "EXTENDED_USER", "ADMIN"],
},
description: "Updated roles (optional)",
},
status: {
type: "string",
enum: ["INACTIVE", "ACTIVE", "EXPIRED", "BLOCKED", "DELETED"],
description: "Updated status (optional)",
},
},
required: ["userId"],
},
},
{
name: "user_list",
description: "List users with optional filters and pagination.",
inputSchema: {
type: "object" as const,
properties: {
roles: {
type: "array",
items: {
type: "string",
enum: ["GUEST", "API_USER", "STANDARD_USER", "POWER_USER", "EXTENDED_USER", "ADMIN"],
},
description: "Filter by roles (optional)",
},
status: {
type: "array",
items: {
type: "string",
enum: ["INACTIVE", "ACTIVE", "EXPIRED", "BLOCKED", "DELETED"],
},
description: "Filter by statuses (optional)",
},
pageNumber: { type: "number", description: "Zero-based page number (default: 0)" },
pageSize: { type: "number", description: "Results per page, max 100 (default: 10)" },
includeAccount: { type: "boolean", description: "Include account details in response" },
},
required: [],
},
},
{
name: "user_unblock",
description: "Unblock a blocked user by their user ID.",
inputSchema: {
type: "object" as const,
properties: {
userId: { type: "string", description: "User UUID to unblock" },
},
required: ["userId"],
},
},
{
name: "user_change_password",
description: "Change the password for a user by providing old and new passwords.",
inputSchema: {
type: "object" as const,
properties: {
userId: { type: "string", description: "User UUID" },
oldPassword: { type: "string", description: "Current password" },
newPassword: { type: "string", description: "New password" },
},
required: ["userId", "oldPassword", "newPassword"],
},
},
{
name: "user_password_reset_request",
description: "Initiate a password reset flow by sending a reset link to the user's email.",
inputSchema: {
type: "object" as const,
properties: {
userName: { type: "string", description: "User email address" },
passwordResetPageLink: { type: "string", description: "URL of the password reset page" },
},
required: ["userName", "passwordResetPageLink"],
},
},
{
name: "user_password_reset",
description: "Complete a password reset using the token received by email.",
inputSchema: {
type: "object" as const,
properties: {
userId: { type: "string", description: "User UUID" },
token: { type: "string", description: "Password reset token from email" },
newPassword: { type: "string", description: "New password to set" },
loginPageLink: { type: "string", description: "Login page URL (optional)" },
},
required: ["userId", "token", "newPassword"],
},
},
] as const;
const RegisterSchema = z.object({
username: z.string(),
password: z.string(),
firstName: z.string(),
midName: z.string().optional(),
lastName: z.string(),
locale: z.string(),
timezone: z.string(),
});
const UserRoles = z.enum(["GUEST", "API_USER", "STANDARD_USER", "POWER_USER", "EXTENDED_USER", "ADMIN"]);
const UserStatus = z.enum(["INACTIVE", "ACTIVE", "EXPIRED", "BLOCKED", "DELETED"]);
const CreateUserSchema = z.object({
username: z.string(),
password: z.string().optional(),
firstName: z.string(),
midName: z.string().optional(),
lastName: z.string(),
locale: z.string(),
timezone: z.string(),
roles: z.array(UserRoles),
status: UserStatus,
});
const UpdateUserSchema = z.object({
userId: z.string(),
username: z.string().optional(),
firstName: z.string().optional(),
midName: z.string().optional(),
lastName: z.string().optional(),
locale: z.string().optional(),
timezone: z.string().optional(),
roles: z.array(UserRoles).optional(),
status: UserStatus.optional(),
});
const ListUsersSchema = z.object({
roles: z.array(UserRoles).optional(),
status: z.array(UserStatus).optional(),
pageNumber: z.number().int().min(0).default(0),
pageSize: z.number().int().min(1).max(100).default(10),
includeAccount: z.boolean().optional(),
});
const ChangePasswordSchema = z.object({
userId: z.string(),
oldPassword: z.string(),
newPassword: z.string(),
});
const PasswordResetRequestSchema = z.object({
userName: z.string(),
passwordResetPageLink: z.string(),
});
const PasswordResetSchema = z.object({
userId: z.string(),
token: z.string(),
newPassword: z.string(),
loginPageLink: z.string().optional(),
});
export async function handleAuthTool(
name: string,
args: Record<string, unknown>
): Promise<unknown> {
switch (name) {
case "auth_register":
return apiPost("/v1/auth/register", RegisterSchema.parse(args));
case "auth_logout":
return apiPost("/v1/auth/logout", { jwtToken: z.string().parse(args.jwtToken) });
case "user_get_me":
return apiGet("/v1/users/me");
case "user_get": {
const userId = z.string().parse(args.userId);
return apiGet(`/v1/users/${userId}`);
}
case "user_create":
return apiPost("/v1/users/create", CreateUserSchema.parse(args));
case "user_update": {
const { userId, ...rest } = UpdateUserSchema.parse(args);
return apiPatch("/v1/users/update", { userId, ...rest });
}
case "user_list":
return apiPost("/v1/users/page", ListUsersSchema.parse(args));
case "user_unblock": {
const userId = z.string().parse(args.userId);
return apiGet(`/v1/users/unblock/${userId}`);
}
case "user_change_password":
return apiPost("/v1/users/change-password", ChangePasswordSchema.parse(args));
case "user_password_reset_request":
return apiPost("/v1/users/password-reset-request", PasswordResetRequestSchema.parse(args));
case "user_password_reset":
return apiPost("/v1/users/password-reset", PasswordResetSchema.parse(args));
default:
throw new Error(`Unknown auth tool: ${name}`);
}
}
+261
View File
@@ -0,0 +1,261 @@
import { z } from "zod";
import { apiDelete, apiGet, apiPatch, apiPost } from "../client.js";
const webhookDescription =
"Webhook callback config. Example: { url: 'https://...', httpMethod: 'POST', contentType: 'application/json', authentication: { type: 'BASIC', username: 'u', password: 'p' }, deliveryPolicy: { timeout: 60, maxRetries: 3, retryDelay: 10 } }";
const boundaryDescription =
"Boundary definition. One of:\n" +
"- Administrative: { type: 'ADMINISTRATIVE', cityId: 6, districtId: 557 }\n" +
"- Point: { type: 'POINT', point: { lat: 39.93, lng: 32.85 } }";
export const forecastAlarmTools = [
{
name: "forecast_alarm_register",
description:
"Create a new forecast alarm registration. Triggers alerts when forecast thresholds (precipitation, snow, wind, temperature) are met.",
inputSchema: {
type: "object" as const,
properties: {
recipientId: { type: "string", description: "Recipient identifier (US-ASCII)" },
boundary: { type: "object", description: boundaryDescription },
forecastDays: {
type: "number",
description: "Number of forecast days ahead to evaluate (1-7)",
},
forecastAlarmDelivery: {
type: "array",
items: { type: "string", enum: ["MORNING", "EVENING"] },
description: "Delivery times: MORNING (04:00 UTC) and/or EVENING (16:00 UTC)",
},
webhook: { type: "object", description: webhookDescription },
precipitationThreshold: {
type: "string",
enum: ["DRIZZLE", "LIGHT", "MODERATE", "HEAVY", "VERY_HEAVY", "EXTREME"],
description: "Minimum precipitation intensity to trigger an alarm (optional)",
},
snowFallThreshold: {
type: "string",
enum: ["LIGHT", "MODERATE", "HEAVY"],
description: "Minimum snowfall depth category to trigger an alarm (optional)",
},
windGustThreshold: {
type: "string",
enum: ["STRONG_WIND", "STORM", "SEVERE_STORM", "HURRICANE"],
description: "Minimum wind gust category to trigger an alarm (optional)",
},
hotTemperatureThreshold: {
type: "string",
enum: ["HOT_SNAP", "HEAVY_HOT_SNAP", "EXTREME_HOT_SNAP"],
description: "Minimum heat-wave category to trigger an alarm (optional)",
},
coldTemperatureThreshold: {
type: "string",
enum: ["EXTREME_COLD_SNAP", "HEAVY_COLD_SNAP", "COLD_SNAP"],
description: "Minimum cold-snap category to trigger an alarm (optional)",
},
},
required: ["recipientId", "boundary"],
},
},
{
name: "forecast_alarm_update",
description: "Update an existing forecast alarm registration.",
inputSchema: {
type: "object" as const,
properties: {
registrationId: { type: "string", description: "Registration UUID to update" },
recipientId: { type: "string", description: "Recipient identifier (optional)" },
boundary: { type: "object", description: boundaryDescription + " (optional)" },
forecastDays: { type: "number", description: "Forecast days ahead (1-7, optional)" },
forecastAlarmDelivery: {
type: "array",
items: { type: "string", enum: ["MORNING", "EVENING"] },
description: "Delivery windows (optional)",
},
webhook: { type: "object", description: webhookDescription + " (optional)" },
precipitationThreshold: { type: "string", description: "Updated precipitation threshold (optional)" },
snowFallThreshold: { type: "string", description: "Updated snowfall threshold (optional)" },
windGustThreshold: { type: "string", description: "Updated wind gust threshold (optional)" },
hotTemperatureThreshold: { type: "string", description: "Updated heat-wave threshold (optional)" },
coldTemperatureThreshold: { type: "string", description: "Updated cold-snap threshold (optional)" },
},
required: ["registrationId"],
},
},
{
name: "forecast_alarm_delete",
description: "Delete (unregister) a forecast alarm registration by its UUID.",
inputSchema: {
type: "object" as const,
properties: {
registrationId: { type: "string", description: "Registration UUID to delete" },
},
required: ["registrationId"],
},
},
{
name: "forecast_alarm_get_by_id",
description: "Get a single forecast alarm registration by its UUID.",
inputSchema: {
type: "object" as const,
properties: {
registrationId: { type: "string", description: "Registration UUID" },
},
required: ["registrationId"],
},
},
{
name: "forecast_alarm_get_by_recipient",
description: "List all forecast alarm registrations for a given recipient ID.",
inputSchema: {
type: "object" as const,
properties: {
recipientId: { type: "string", description: "Recipient identifier" },
},
required: ["recipientId"],
},
},
{
name: "forecast_alarm_list",
description: "List forecast alarm registrations with pagination and optional filters.",
inputSchema: {
type: "object" as const,
properties: {
pageNumber: { type: "number", description: "Zero-based page number (default: 0)" },
pageSize: { type: "number", description: "Results per page, max 100 (default: 20)" },
filterByRecipientIds: {
type: "array",
items: { type: "string" },
description: "Filter by specific recipient IDs (optional)",
},
accountId: { type: "string", description: "Query on behalf of another account (admin only, optional)" },
},
required: [],
},
},
{
name: "forecast_alarm_list_cities",
description: "List all cities in Türkiye available for forecast alarm boundaries.",
inputSchema: {
type: "object" as const,
properties: {},
required: [],
},
},
{
name: "forecast_alarm_get_city",
description: "Get boundary details for a specific city by its ID.",
inputSchema: {
type: "object" as const,
properties: {
cityId: { type: "number", description: "City ID (e.g. 6 for Ankara)" },
},
required: ["cityId"],
},
},
{
name: "forecast_alarm_list_districts",
description: "List all districts for a given city.",
inputSchema: {
type: "object" as const,
properties: {
cityId: { type: "string", description: "City ID" },
},
required: ["cityId"],
},
},
{
name: "forecast_alarm_get_district",
description: "Get boundary details for a specific district by its ID.",
inputSchema: {
type: "object" as const,
properties: {
districtId: { type: "number", description: "District ID (e.g. 557 for Çankaya)" },
},
required: ["districtId"],
},
},
] as const;
function buildForecastAlarmPayload(args: Record<string, unknown>): Record<string, unknown> {
const {
precipitationThreshold,
snowFallThreshold,
windGustThreshold,
hotTemperatureThreshold,
coldTemperatureThreshold,
...rest
} = args;
const payload: Record<string, unknown> = { ...rest };
const filter: Record<string, unknown> = {};
if (precipitationThreshold) filter.precipitationThreshold = precipitationThreshold;
if (snowFallThreshold) filter.snowFallThreshold = snowFallThreshold;
if (windGustThreshold) filter.windGustThreshold = windGustThreshold;
if (hotTemperatureThreshold) filter.hotTemperatureThreshold = hotTemperatureThreshold;
if (coldTemperatureThreshold) filter.coldTemperatureThreshold = coldTemperatureThreshold;
if (Object.keys(filter).length > 0) payload.filter = filter;
return payload;
}
const ListSchema = z.object({
pageNumber: z.number().int().min(0).default(0),
pageSize: z.number().int().min(1).max(100).default(20),
filterByRecipientIds: z.array(z.string()).optional(),
accountId: z.string().optional(),
});
export async function handleForecastAlarmTool(
name: string,
args: Record<string, unknown>
): Promise<unknown> {
switch (name) {
case "forecast_alarm_register":
return apiPost("/v1/alarms/forecasts/register", buildForecastAlarmPayload(args));
case "forecast_alarm_update":
return apiPatch("/v1/alarms/forecasts/update", buildForecastAlarmPayload(args));
case "forecast_alarm_delete": {
const registrationId = z.string().parse(args.registrationId);
return apiDelete(`/v1/alarms/forecasts/unregister/${registrationId}`);
}
case "forecast_alarm_get_by_id": {
const registrationId = z.string().parse(args.registrationId);
return apiGet(`/v1/alarms/forecasts/get-by-id/${registrationId}`);
}
case "forecast_alarm_get_by_recipient": {
const recipientId = z.string().parse(args.recipientId);
return apiGet(`/v1/alarms/forecasts/get-by-recipient-id/${recipientId}`);
}
case "forecast_alarm_list":
return apiPost("/v1/alarms/forecasts/page", ListSchema.parse(args));
case "forecast_alarm_list_cities":
return apiGet("/v1/alarms/forecasts/cities");
case "forecast_alarm_get_city": {
const cityId = z.number().parse(args.cityId);
return apiGet(`/v1/alarms/forecasts/city/${cityId}`);
}
case "forecast_alarm_list_districts": {
const cityId = z.string().parse(args.cityId);
return apiGet(`/v1/alarms/forecasts/districts/${cityId}`);
}
case "forecast_alarm_get_district": {
const districtId = z.number().parse(args.districtId);
return apiGet(`/v1/alarms/forecasts/district/${districtId}`);
}
default:
throw new Error(`Unknown forecast alarm tool: ${name}`);
}
}
+201
View File
@@ -0,0 +1,201 @@
import { z } from "zod";
import { apiPost } from "../client.js";
const ALL_METRICS = [
"WEATHER_ICON",
"TEMPERATURE",
"TEMPERATURE_AVG",
"TEMPERATURE_MIN",
"TEMPERATURE_MAX",
"APPARENT_TEMPERATURE",
"APPARENT_TEMPERATURE_AVG",
"APPARENT_TEMPERATURE_MIN",
"APPARENT_TEMPERATURE_MAX",
"HUMIDITY",
"HUMIDITY_AVG",
"HUMIDITY_MIN",
"HUMIDITY_MAX",
"CLOUD_COVER",
"CLOUD_COVER_AVG",
"CLOUD_COVER_MIN",
"CLOUD_COVER_MAX",
"WIND_SPEED",
"WIND_SPEED_AVG",
"WIND_SPEED_MIN",
"WIND_SPEED_MAX",
"WIND_GUSTS",
"WIND_GUSTS_AVG",
"WIND_GUSTS_MIN",
"WIND_GUSTS_MAX",
"WIND_DIRECTION",
"SNOWFALL",
"PRECIPITATION",
"PRECIPITATION_PROBABILITY",
"PRECIPITATION_PROBABILITY_AVG",
"PRECIPITATION_PROBABILITY_MIN",
"PRECIPITATION_PROBABILITY_MAX",
"REF_EVAPORATION_TRANSPIRATION_ET0",
"REF_EVAPORATION_TRANSPIRATION_ET0_SUM",
"DEW_POINT",
"DEW_POINT_AVG",
"DEW_POINT_MIN",
"DEW_POINT_MAX",
"SOIL_TEMPERATURE",
"SOIL_MOISTURE",
"GLOBAL_TILTED_RADIATION_GTI",
"DIRECT_NORMAL_IRRADIANCE_DNI",
"SHORTWAVE_SOLAR_RADIATION_GHI",
"SHORTWAVE_RADIATION_SUM",
"VISIBILITY",
"VISIBILITY_AVG",
"VISIBILITY_MIN",
"VISIBILITY_MAX",
] as const;
const metricsDescription =
"List of weather metrics to return. Available values: " + ALL_METRICS.join(", ");
const locationProperties = {
latitude: {
type: "number" as const,
description: "Latitude of the location (-90 to 90)",
},
longitude: {
type: "number" as const,
description: "Longitude of the location (-180 to 180)",
},
};
export const forecastTools = [
{
name: "get_hourly_forecast",
description:
"Returns hour-by-hour weather metrics for the requested location. Supports up to 14 forecast days.",
inputSchema: {
type: "object" as const,
properties: {
...locationProperties,
forecastDays: {
type: "number",
description: "Number of forecast days to return (1-14, default: 7)",
},
startTime: {
type: "number",
description: "Optional start time in epoch milliseconds for the forecast horizon",
},
metrics: {
type: "array",
items: { type: "string" as const, enum: ALL_METRICS },
description: metricsDescription,
},
solarPanelTiltForRadiation: {
type: "number",
description: "Solar panel tilt angle in degrees for radiation metrics",
},
solarPanelAzimuthForRadiation: {
type: "number",
description: "Solar panel azimuth angle in degrees for radiation metrics",
},
},
required: ["latitude", "longitude", "metrics"],
},
},
{
name: "get_daily_forecast",
description:
"Returns aggregated daily weather metrics for the requested location. Supports up to 14 forecast days.",
inputSchema: {
type: "object" as const,
properties: {
...locationProperties,
forecastDays: {
type: "number",
description: "Number of forecast days to return (1-14, default: 7)",
},
startTime: {
type: "number",
description: "Optional start time in epoch milliseconds for the forecast horizon",
},
metrics: {
type: "array",
items: { type: "string" as const, enum: ALL_METRICS },
description: metricsDescription,
},
},
required: ["latitude", "longitude", "metrics"],
},
},
{
name: "get_current_weather",
description:
"Returns the latest available weather metrics for the requested location.",
inputSchema: {
type: "object" as const,
properties: {
...locationProperties,
metrics: {
type: "array",
items: { type: "string" as const, enum: ALL_METRICS },
description:
"Metrics for current weather. Recommended: WEATHER_ICON, TEMPERATURE, APPARENT_TEMPERATURE, HUMIDITY, CLOUD_COVER, WIND_SPEED, WIND_GUSTS, WIND_DIRECTION, SNOWFALL, PRECIPITATION",
},
},
required: ["latitude", "longitude", "metrics"],
},
},
] as const;
const MetricSchema = z.enum(ALL_METRICS);
const HourlySchema = z.object({
latitude: z.number().min(-90).max(90),
longitude: z.number().min(-180).max(180),
forecastDays: z.number().int().min(1).max(14).default(7),
startTime: z.number().int().optional(),
metrics: z.array(MetricSchema).min(1),
solarPanelTiltForRadiation: z.number().optional(),
solarPanelAzimuthForRadiation: z.number().optional(),
});
const DailySchema = z.object({
latitude: z.number().min(-90).max(90),
longitude: z.number().min(-180).max(180),
forecastDays: z.number().int().min(1).max(14).default(7),
startTime: z.number().int().optional(),
metrics: z.array(MetricSchema).min(1),
});
const CurrentSchema = z.object({
latitude: z.number().min(-90).max(90),
longitude: z.number().min(-180).max(180),
metrics: z.array(MetricSchema).min(1),
});
function toApiPayload(
parsed: { latitude: number; longitude: number } & Record<string, unknown>
): Record<string, unknown> {
const { latitude, longitude, ...rest } = parsed;
return { location: { lat: latitude, lng: longitude }, ...rest };
}
export async function handleForecastTool(
name: string,
args: Record<string, unknown>
): Promise<unknown> {
switch (name) {
case "get_hourly_forecast": {
const parsed = HourlySchema.parse(args);
return apiPost("/v1/forecasts/hourly", toApiPayload(parsed));
}
case "get_daily_forecast": {
const parsed = DailySchema.parse(args);
return apiPost("/v1/forecasts/daily", toApiPayload(parsed));
}
case "get_current_weather": {
const parsed = CurrentSchema.parse(args);
return apiPost("/v1/forecasts/current", toApiPayload(parsed));
}
default:
throw new Error(`Unknown forecast tool: ${name}`);
}
}
+255
View File
@@ -0,0 +1,255 @@
import { z } from "zod";
import { apiDelete, apiGet, apiPatch, apiPost } from "../client.js";
const webhookDescription =
"Webhook callback config. Example: { url: 'https://...', httpMethod: 'POST', contentType: 'application/json', authentication: { type: 'BASIC', username: 'u', password: 'p' }, deliveryPolicy: { timeout: 60, maxRetries: 3, retryDelay: 10 } }";
const boundaryDescription =
"Boundary definition. One of:\n" +
"- Administrative: { type: 'ADMINISTRATIVE', cityId: 6, districtId: 557, neighborhoodId: 55766 }\n" +
"- Polygon: { type: 'POLYGON', polygon: { exterior: [{lat, lng}, ...] } }\n" +
"- H3 Index: { type: 'H3INDEX', h3Address: '8928308280fffff' }";
export const geoAlarmTools = [
{
name: "geo_alarm_register",
description:
"Create a new geo alarm registration using an administrative boundary, polygon, or H3 index.",
inputSchema: {
type: "object" as const,
properties: {
recipientId: { type: "string", description: "Recipient identifier (US-ASCII)" },
boundary: { type: "object", description: boundaryDescription },
webhook: { type: "object", description: webhookDescription },
lightningFilter: {
type: "object",
description: "Lightning filter: { type: 'FLASH_CLOUD_TO_GROUND'|'PULSE_IN_CLOUD', peakCurrent: number, inCloudHeight: number }",
},
thunderstormFilter: {
type: "object",
description: "Thunderstorm filter: { intersectsAffectedPolygon: boolean, intersectsCellPolygon: boolean, severityThreshold: 'LOW'|'MEDIUM'|'HIGH', speedThreshold: number }",
},
precipitationFilter: {
type: "object",
description: "Precipitation filter: { intensities: ['DRIZZLE'|'LIGHT'|'MODERATE'|'HEAVY'|'VERY_HEAVY'|'EXTREME'] }",
},
},
required: ["recipientId", "boundary"],
},
},
{
name: "geo_alarm_update",
description: "Update an existing geo alarm registration.",
inputSchema: {
type: "object" as const,
properties: {
registrationId: { type: "string", description: "Registration UUID to update" },
recipientId: { type: "string", description: "Recipient identifier (optional)" },
boundary: { type: "object", description: boundaryDescription + " (optional)" },
webhook: { type: "object", description: webhookDescription + " (optional)" },
lightningFilter: { type: "object", description: "Updated lightning filter (optional)" },
thunderstormFilter: { type: "object", description: "Updated thunderstorm filter (optional)" },
precipitationFilter: { type: "object", description: "Updated precipitation filter (optional)" },
},
required: ["registrationId"],
},
},
{
name: "geo_alarm_delete",
description: "Delete (unregister) a geo alarm registration by its UUID.",
inputSchema: {
type: "object" as const,
properties: {
registrationId: { type: "string", description: "Registration UUID to delete" },
},
required: ["registrationId"],
},
},
{
name: "geo_alarm_get_by_id",
description: "Get a single geo alarm registration by its UUID.",
inputSchema: {
type: "object" as const,
properties: {
registrationId: { type: "string", description: "Registration UUID" },
},
required: ["registrationId"],
},
},
{
name: "geo_alarm_get_by_recipient",
description: "List all geo alarm registrations for a given recipient ID.",
inputSchema: {
type: "object" as const,
properties: {
recipientId: { type: "string", description: "Recipient identifier" },
},
required: ["recipientId"],
},
},
{
name: "geo_alarm_list",
description: "List geo alarm registrations with pagination and optional filters.",
inputSchema: {
type: "object" as const,
properties: {
pageNumber: { type: "number", description: "Zero-based page number (default: 0)" },
pageSize: { type: "number", description: "Results per page, max 100 (default: 10)" },
filterByRecipientIds: {
type: "array",
items: { type: "string" },
description: "Filter by specific recipient IDs (optional)",
},
accountId: { type: "string", description: "Query on behalf of another account (admin only, optional)" },
},
required: [],
},
},
{
name: "geo_alarm_list_cities",
description: "List all cities in Türkiye available for geo alarm boundaries.",
inputSchema: {
type: "object" as const,
properties: {},
required: [],
},
},
{
name: "geo_alarm_get_city",
description: "Get boundary details for a specific city by its ID.",
inputSchema: {
type: "object" as const,
properties: {
cityId: { type: "number", description: "City ID (e.g. 6 for Ankara)" },
},
required: ["cityId"],
},
},
{
name: "geo_alarm_list_districts",
description: "List all districts for a given city.",
inputSchema: {
type: "object" as const,
properties: {
cityId: { type: "string", description: "City ID" },
},
required: ["cityId"],
},
},
{
name: "geo_alarm_get_district",
description: "Get boundary details for a specific district by its ID.",
inputSchema: {
type: "object" as const,
properties: {
districtId: { type: "number", description: "District ID (e.g. 557 for Çankaya)" },
},
required: ["districtId"],
},
},
{
name: "geo_alarm_list_neighbourhoods",
description: "List all neighbourhoods for a given district.",
inputSchema: {
type: "object" as const,
properties: {
districtId: { type: "string", description: "District ID" },
},
required: ["districtId"],
},
},
{
name: "geo_alarm_get_neighbourhood",
description: "Get boundary details for a specific neighbourhood by its ID.",
inputSchema: {
type: "object" as const,
properties: {
neighbourhoodId: { type: "number", description: "Neighbourhood ID" },
},
required: ["neighbourhoodId"],
},
},
] as const;
function buildGeoRegistrationPayload(args: Record<string, unknown>): Record<string, unknown> {
const { lightningFilter, thunderstormFilter, precipitationFilter, ...rest } = args;
const payload: Record<string, unknown> = { ...rest };
const filter: Record<string, unknown> = {};
if (lightningFilter) filter.lightning = lightningFilter;
if (thunderstormFilter) filter.thunderstorm = thunderstormFilter;
if (precipitationFilter) filter.precipitation = precipitationFilter;
if (Object.keys(filter).length > 0) payload.filter = filter;
return payload;
}
const ListSchema = z.object({
pageNumber: z.number().int().min(0).default(0),
pageSize: z.number().int().min(1).max(100).default(10),
filterByRecipientIds: z.array(z.string()).optional(),
accountId: z.string().optional(),
});
export async function handleGeoAlarmTool(
name: string,
args: Record<string, unknown>
): Promise<unknown> {
switch (name) {
case "geo_alarm_register":
return apiPost("/v1/alarms/geometries/register", buildGeoRegistrationPayload(args));
case "geo_alarm_update":
return apiPatch("/v1/alarms/geometries/update", buildGeoRegistrationPayload(args));
case "geo_alarm_delete": {
const registrationId = z.string().parse(args.registrationId);
return apiDelete(`/v1/alarms/geometries/unregister/${registrationId}`);
}
case "geo_alarm_get_by_id": {
const registrationId = z.string().parse(args.registrationId);
return apiGet(`/v1/alarms/geometries/get-by-id/${registrationId}`);
}
case "geo_alarm_get_by_recipient": {
const recipientId = z.string().parse(args.recipientId);
return apiGet(`/v1/alarms/geometries/get-by-recipient-id/${recipientId}`);
}
case "geo_alarm_list":
return apiPost("/v1/alarms/geometries/page", ListSchema.parse(args));
case "geo_alarm_list_cities":
return apiGet("/v1/alarms/geometries/cities");
case "geo_alarm_get_city": {
const cityId = z.number().parse(args.cityId);
return apiGet(`/v1/alarms/geometries/city/${cityId}`);
}
case "geo_alarm_list_districts": {
const cityId = z.string().parse(args.cityId);
return apiGet(`/v1/alarms/geometries/districts/${cityId}`);
}
case "geo_alarm_get_district": {
const districtId = z.number().parse(args.districtId);
return apiGet(`/v1/alarms/geometries/district/${districtId}`);
}
case "geo_alarm_list_neighbourhoods": {
const districtId = z.string().parse(args.districtId);
return apiGet(`/v1/alarms/geometries/neighbourhoods/${districtId}`);
}
case "geo_alarm_get_neighbourhood": {
const neighbourhoodId = z.number().parse(args.neighbourhoodId);
return apiGet(`/v1/alarms/geometries/neighbourhood/${neighbourhoodId}`);
}
default:
throw new Error(`Unknown geo alarm tool: ${name}`);
}
}
+106
View File
@@ -0,0 +1,106 @@
import { z } from "zod";
import { apiPost } from "../client.js";
export const lightningTools = [
{
name: "get_lightnings_within",
description:
"Query lightning strikes within a given center point and radius. Maximum radius is 50 km, maximum backward interval is 30 days.",
inputSchema: {
type: "object" as const,
properties: {
latitude: {
type: "number",
description: "Latitude of center point in decimal degrees (-90 to 90)",
},
longitude: {
type: "number",
description: "Longitude of center point in decimal degrees (-180 to 180)",
},
radius: {
type: "number",
description: "Search radius in meters (0 to 50000)",
},
backwardInterval: {
type: "number",
description: "Time range in seconds counting back from endTimeEpoch (60 to 2592000)",
},
endTimeEpoch: {
type: "number",
description: "Query end time in epoch milliseconds",
},
pageNumber: {
type: "number",
description: "Zero-based page number (default: 0)",
},
pageSize: {
type: "number",
description: "Number of results per page, max 100 (default: 10)",
},
},
required: ["latitude", "longitude", "radius", "backwardInterval", "endTimeEpoch"],
},
},
{
name: "get_lightnings_page",
description:
"Query lightning strikes by time range with pagination. Maximum backward interval is 5 days.",
inputSchema: {
type: "object" as const,
properties: {
backwardInterval: {
type: "number",
description: "Time range in seconds counting back from endTimeEpoch (60 to 432000)",
},
endTimeEpoch: {
type: "number",
description: "Query end time in epoch milliseconds",
},
pageNumber: {
type: "number",
description: "Zero-based page number (default: 0)",
},
pageSize: {
type: "number",
description: "Number of results per page, max 100 (default: 10)",
},
},
required: ["backwardInterval", "endTimeEpoch"],
},
},
] as const;
const WithinSchema = z.object({
latitude: z.number().min(-90).max(90),
longitude: z.number().min(-180).max(180),
radius: z.number().min(0).max(50000),
backwardInterval: z.number().int().min(60).max(2592000),
endTimeEpoch: z.number().int().min(0),
pageNumber: z.number().int().min(0).default(0),
pageSize: z.number().int().min(1).max(100).default(10),
});
const PageSchema = z.object({
backwardInterval: z.number().int().min(60).max(432000),
endTimeEpoch: z.number().int().min(0),
pageNumber: z.number().int().min(0).default(0),
pageSize: z.number().int().min(1).max(100).default(10),
});
export async function handleLightningTool(
name: string,
args: Record<string, unknown>
): Promise<unknown> {
switch (name) {
case "get_lightnings_within": {
const params = WithinSchema.parse(args);
return apiPost("/v1/lightnings/within", params);
}
case "get_lightnings_page": {
const params = PageSchema.parse(args);
return apiPost("/v1/lightnings/page", params);
}
default:
throw new Error(`Unknown lightning tool: ${name}`);
}
}
+168
View File
@@ -0,0 +1,168 @@
import { z } from "zod";
import { apiDelete, apiGet, apiPatch, apiPost } from "../client.js";
const webhookDescription =
"Webhook callback config. Example: { url: 'https://...', httpMethod: 'POST', contentType: 'application/json', authentication: { type: 'BASIC', username: 'u', password: 'p' }, deliveryPolicy: { timeout: 60, maxRetries: 3, retryDelay: 10 } }";
export const pointAlarmTools = [
{
name: "point_alarm_register",
description:
"Create a new point alarm registration. Triggers alerts when lightning, thunderstorm, or precipitation events occur within the specified radius of a center point.",
inputSchema: {
type: "object" as const,
properties: {
recipientId: { type: "string", description: "Recipient identifier (US-ASCII)" },
latitude: { type: "number", description: "Center point latitude (-90 to 90)" },
longitude: { type: "number", description: "Center point longitude (-180 to 180)" },
radius: { type: "number", description: "Alert radius in meters (0 to 50000)" },
webhook: { type: "object", description: webhookDescription },
lightningFilter: {
type: "object",
description: "Lightning filter: { type: 'FLASH_CLOUD_TO_GROUND'|'PULSE_IN_CLOUD', peakCurrent: number, inCloudHeight: number }",
},
thunderstormFilter: {
type: "object",
description: "Thunderstorm filter: { intersectsAffectedPolygon: boolean, intersectsCellPolygon: boolean, severityThreshold: 'LOW'|'MEDIUM'|'HIGH', speedThreshold: number }",
},
precipitationFilter: {
type: "object",
description: "Precipitation filter: { intensities: ['DRIZZLE'|'LIGHT'|'MODERATE'|'HEAVY'|'VERY_HEAVY'|'EXTREME'] }",
},
},
required: ["recipientId", "latitude", "longitude", "radius"],
},
},
{
name: "point_alarm_update",
description: "Update an existing point alarm registration.",
inputSchema: {
type: "object" as const,
properties: {
registrationId: { type: "string", description: "Registration UUID to update" },
recipientId: { type: "string", description: "Recipient identifier (optional)" },
latitude: { type: "number", description: "New center latitude (optional)" },
longitude: { type: "number", description: "New center longitude (optional)" },
radius: { type: "number", description: "New radius in meters (optional)" },
webhook: { type: "object", description: webhookDescription + " (optional)" },
lightningFilter: { type: "object", description: "Updated lightning filter (optional)" },
thunderstormFilter: { type: "object", description: "Updated thunderstorm filter (optional)" },
precipitationFilter: { type: "object", description: "Updated precipitation filter (optional)" },
},
required: ["registrationId"],
},
},
{
name: "point_alarm_delete",
description: "Delete (unregister) a point alarm registration by its ID.",
inputSchema: {
type: "object" as const,
properties: {
registrationId: { type: "string", description: "Registration UUID to delete" },
},
required: ["registrationId"],
},
},
{
name: "point_alarm_get_by_id",
description: "Get a single point alarm registration by its UUID.",
inputSchema: {
type: "object" as const,
properties: {
registrationId: { type: "string", description: "Registration UUID" },
},
required: ["registrationId"],
},
},
{
name: "point_alarm_get_by_recipient",
description: "List all point alarm registrations for a given recipient ID.",
inputSchema: {
type: "object" as const,
properties: {
recipientId: { type: "string", description: "Recipient identifier" },
},
required: ["recipientId"],
},
},
{
name: "point_alarm_list",
description: "List point alarm registrations with pagination and optional filters.",
inputSchema: {
type: "object" as const,
properties: {
pageNumber: { type: "number", description: "Zero-based page number (default: 0)" },
pageSize: { type: "number", description: "Results per page, max 100 (default: 10)" },
filterByRecipientIds: {
type: "array",
items: { type: "string" },
description: "Filter by specific recipient IDs (optional)",
},
accountId: { type: "string", description: "Query on behalf of another account (admin only, optional)" },
},
required: [],
},
},
] as const;
function buildPointRegistrationPayload(args: Record<string, unknown>): Record<string, unknown> {
const { latitude, longitude, radius, lightningFilter, thunderstormFilter, precipitationFilter, ...rest } = args;
const payload: Record<string, unknown> = { ...rest };
if (latitude !== undefined && longitude !== undefined) {
payload.boundary = {
point: { lat: latitude, lng: longitude },
radius: radius ?? 0,
};
}
const filter: Record<string, unknown> = {};
if (lightningFilter) filter.lightning = lightningFilter;
if (thunderstormFilter) filter.thunderstorm = thunderstormFilter;
if (precipitationFilter) filter.precipitation = precipitationFilter;
if (Object.keys(filter).length > 0) payload.filter = filter;
return payload;
}
const ListSchema = z.object({
pageNumber: z.number().int().min(0).default(0),
pageSize: z.number().int().min(1).max(100).default(10),
filterByRecipientIds: z.array(z.string()).optional(),
accountId: z.string().optional(),
});
export async function handlePointAlarmTool(
name: string,
args: Record<string, unknown>
): Promise<unknown> {
switch (name) {
case "point_alarm_register":
return apiPost("/v1/alarms/points/register", buildPointRegistrationPayload(args));
case "point_alarm_update":
return apiPatch("/v1/alarms/points/update", buildPointRegistrationPayload(args));
case "point_alarm_delete": {
const registrationId = z.string().parse(args.registrationId);
return apiDelete(`/v1/alarms/points/unregister/${registrationId}`);
}
case "point_alarm_get_by_id": {
const registrationId = z.string().parse(args.registrationId);
return apiGet(`/v1/alarms/points/get-by-id/${registrationId}`);
}
case "point_alarm_get_by_recipient": {
const recipientId = z.string().parse(args.recipientId);
return apiGet(`/v1/alarms/points/get-by-recipient-id/${recipientId}`);
}
case "point_alarm_list":
return apiPost("/v1/alarms/points/page", ListSchema.parse(args));
default:
throw new Error(`Unknown point alarm tool: ${name}`);
}
}
+125
View File
@@ -0,0 +1,125 @@
import { z } from "zod";
import { apiPost } from "../client.js";
const INTENSITY_VALUES = ["DRIZZLE", "LIGHT", "MODERATE", "HEAVY", "VERY_HEAVY", "EXTREME"] as const;
export const precipitationTools = [
{
name: "get_precipitations_within",
description:
"Query precipitation data within a circular area and time range. Maximum radius is 50 km, maximum backward interval is 30 days.",
inputSchema: {
type: "object" as const,
properties: {
latitude: {
type: "number",
description: "Latitude of center point in decimal degrees (-90 to 90)",
},
longitude: {
type: "number",
description: "Longitude of center point in decimal degrees (-180 to 180)",
},
radius: {
type: "number",
description: "Search radius in meters (0 to 50000)",
},
backwardInterval: {
type: "number",
description: "Time range in seconds counting back from endTimeEpoch (60 to 2592000)",
},
endTimeEpoch: {
type: "number",
description: "Query end time in epoch milliseconds (can be up to 2 hours in the future)",
},
intensityThreshold: {
type: "string",
enum: INTENSITY_VALUES,
description:
"Minimum precipitation intensity to include: DRIZZLE < LIGHT < MODERATE < HEAVY < VERY_HEAVY < EXTREME",
},
pageNumber: {
type: "number",
description: "Zero-based page number (default: 0)",
},
pageSize: {
type: "number",
description: "Number of results per page, max 100 (default: 10)",
},
},
required: ["latitude", "longitude", "radius", "backwardInterval", "endTimeEpoch"],
},
},
{
name: "get_precipitations_page",
description:
"Query precipitation data by time range with pagination within subscription geographic boundaries. Maximum backward interval is 5 days.",
inputSchema: {
type: "object" as const,
properties: {
backwardInterval: {
type: "number",
description: "Time range in seconds counting back from endTimeEpoch (60 to 432000)",
},
endTimeEpoch: {
type: "number",
description: "Query end time in epoch milliseconds",
},
intensityThreshold: {
type: "string",
enum: INTENSITY_VALUES,
description:
"Minimum precipitation intensity to include: DRIZZLE < LIGHT < MODERATE < HEAVY < VERY_HEAVY < EXTREME",
},
pageNumber: {
type: "number",
description: "Zero-based page number (default: 0)",
},
pageSize: {
type: "number",
description: "Number of results per page, max 100 (default: 10)",
},
},
required: ["backwardInterval", "endTimeEpoch", "intensityThreshold"],
},
},
] as const;
const WithinSchema = z.object({
latitude: z.number().min(-90).max(90),
longitude: z.number().min(-180).max(180),
radius: z.number().min(0).max(50000),
backwardInterval: z.number().int().min(60).max(2592000),
endTimeEpoch: z.number().int().min(0),
intensityThreshold: z.enum(INTENSITY_VALUES).optional(),
pageNumber: z.number().int().min(0).default(0),
pageSize: z.number().int().min(1).max(100).default(10),
});
const PageSchema = z.object({
backwardInterval: z.number().int().min(60).max(432000),
endTimeEpoch: z.number().int().min(0),
intensityThreshold: z.enum(INTENSITY_VALUES),
pageNumber: z.number().int().min(0).default(0),
pageSize: z.number().int().min(1).max(100).default(10),
});
export async function handlePrecipitationTool(
name: string,
args: Record<string, unknown>
): Promise<unknown> {
switch (name) {
case "get_precipitations_within": {
const { latitude, longitude, ...rest } = WithinSchema.parse(args);
return apiPost("/v1/precipitations/within", {
center: { lat: latitude, lng: longitude },
...rest,
});
}
case "get_precipitations_page": {
const params = PageSchema.parse(args);
return apiPost("/v1/precipitations/page", params);
}
default:
throw new Error(`Unknown precipitation tool: ${name}`);
}
}
+145
View File
@@ -0,0 +1,145 @@
import { z } from "zod";
import { apiPost } from "../client.js";
export const thunderstormTools = [
{
name: "get_thunderstorms_within",
description:
"Query thunderstorm events within a given center point and radius. Maximum radius is 50 km, maximum backward interval is 30 days.",
inputSchema: {
type: "object" as const,
properties: {
latitude: {
type: "number",
description: "Latitude of center point in decimal degrees (-90 to 90)",
},
longitude: {
type: "number",
description: "Longitude of center point in decimal degrees (-180 to 180)",
},
radius: {
type: "number",
description: "Search radius in meters (0 to 50000)",
},
backwardInterval: {
type: "number",
description: "Time range in seconds counting back from endTimeEpoch (60 to 2592000)",
},
endTimeEpoch: {
type: "number",
description: "Query end time in epoch milliseconds",
},
pageNumber: {
type: "number",
description: "Zero-based page number (default: 0)",
},
pageSize: {
type: "number",
description: "Number of results per page, max 100 (default: 10)",
},
intersectsWith: {
type: "string",
enum: ["THREAT_POLYGON", "CELL_POLYGON"],
description:
"Which polygon to use for intersection check: THREAT_POLYGON (expected affected area) or CELL_POLYGON (storm cell area)",
},
},
required: ["latitude", "longitude", "radius", "backwardInterval", "endTimeEpoch"],
},
},
{
name: "get_thunderstorms_page",
description:
"Query thunderstorm events by time range with pagination. Maximum backward interval is 5 days.",
inputSchema: {
type: "object" as const,
properties: {
backwardInterval: {
type: "number",
description: "Time range in seconds counting back from endTimeEpoch (60 to 432000)",
},
endTimeEpoch: {
type: "number",
description: "Query end time in epoch milliseconds",
},
pageNumber: {
type: "number",
description: "Zero-based page number (default: 0)",
},
pageSize: {
type: "number",
description: "Number of results per page, max 100 (default: 10)",
},
},
required: ["backwardInterval", "endTimeEpoch"],
},
},
{
name: "get_thunderstorm_details",
description: "Get detailed historic information about a specific thunderstorm event by its event ID.",
inputSchema: {
type: "object" as const,
properties: {
eventId: {
type: "string",
description: "The event ID of the thunderstorm (e.g. EVT20240413001)",
},
pageNumber: {
type: "number",
description: "Zero-based page number (default: 0)",
},
pageSize: {
type: "number",
description: "Number of results per page, max 100 (default: 10)",
},
},
required: ["eventId"],
},
},
] as const;
const WithinSchema = z.object({
latitude: z.number().min(-90).max(90),
longitude: z.number().min(-180).max(180),
radius: z.number().min(0).max(50000),
backwardInterval: z.number().int().min(60).max(2592000),
endTimeEpoch: z.number().int().min(0),
pageNumber: z.number().int().min(0).default(0),
pageSize: z.number().int().min(1).max(100).default(10),
intersectsWith: z.enum(["THREAT_POLYGON", "CELL_POLYGON"]).optional(),
});
const PageSchema = z.object({
backwardInterval: z.number().int().min(60).max(432000),
endTimeEpoch: z.number().int().min(0),
pageNumber: z.number().int().min(0).default(0),
pageSize: z.number().int().min(1).max(100).default(10),
});
const EventSchema = z.object({
eventId: z.string().min(1),
pageNumber: z.number().int().min(0).default(0),
pageSize: z.number().int().min(1).max(100).default(10),
});
export async function handleThunderstormTool(
name: string,
args: Record<string, unknown>
): Promise<unknown> {
switch (name) {
case "get_thunderstorms_within": {
const params = WithinSchema.parse(args);
return apiPost("/v1/thunderstorms/within", params);
}
case "get_thunderstorms_page": {
const params = PageSchema.parse(args);
return apiPost("/v1/thunderstorms/page", params);
}
case "get_thunderstorm_details": {
const params = EventSchema.parse(args);
return apiPost("/v1/thunderstorms/get", params);
}
default:
throw new Error(`Unknown thunderstorm tool: ${name}`);
}
}