first commit
This commit is contained in:
+108
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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"),
|
||||
};
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user