Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cloudscope-mcp",
"version": "0.3.1",
"version": "0.3.2",
"mcpName": "io.github.alexpota/cloudscope",
"description": "Multi-cloud cost management MCP server (Azure + GCP): spending, forecasts, anomalies, budgets, idle resources, tags.",
"type": "module",
Expand Down
4 changes: 2 additions & 2 deletions server.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
"url": "https://github.com/alexpota/cloudscope-mcp",
"source": "github"
},
"version": "0.3.1",
"version": "0.3.2",
"packages": [
{
"registryType": "npm",
"registryBaseUrl": "https://registry.npmjs.org",
"identifier": "cloudscope-mcp",
"version": "0.3.1",
"version": "0.3.2",
"transport": {
"type": "stdio"
},
Expand Down
15 changes: 15 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ declare const __PKG_VERSION__: string;
export const PACKAGE_NAME = 'cloudscope-mcp';
export const PACKAGE_VERSION: string = __PKG_VERSION__;

// Tool annotations
export const TOOL_ANNOTATIONS_READ_ONLY = {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
} as const;

export const TOOL_ANNOTATIONS_LOCAL = {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
} as const;

// Tool defaults
export const DEFAULT_ANOMALY_DAYS = 7;
export const DEFAULT_ANOMALY_THRESHOLD = 20;
Expand Down
3 changes: 3 additions & 0 deletions src/tools/register-azure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { AzureCostClient } from '../providers/azure/client.js';
import { handleListSubscriptions } from './list-subscriptions.js';
import { handleCrossSubscriptionCosts } from './cross-subscription-costs.js';
import { toolError } from './types.js';
import { TOOL_ANNOTATIONS_READ_ONLY } from '../constants.js';

export function registerAzureTools(
server: McpServer,
Expand All @@ -14,6 +15,7 @@ export function registerAzureTools(
'get_cross_subscription_costs',
{
title: 'Cross-Subscription Cost Summary',
annotations: TOOL_ANNOTATIONS_READ_ONLY,
description:
'Returns a combined cost breakdown across multiple Azure subscriptions sorted by total spend. Each subscription shows its name, total cost in USD, and percentage of the combined total. Handles partial failures gracefully — if some subscriptions are inaccessible, returns results for the rest with a warning. Use this when the user asks about costs across all subscriptions, wants to compare subscription spending, or needs an organization-wide cost overview.',
inputSchema: {
Expand Down Expand Up @@ -52,6 +54,7 @@ export function registerAzureTools(
'list_subscriptions',
{
title: 'List Azure Subscriptions',
annotations: TOOL_ANNOTATIONS_READ_ONLY,
description:
'Returns all Azure subscriptions the current credential can access, with name, ID, and state. Shows which subscription is currently active. Use this when the user has multiple subscriptions and wants to see which ones are available, or to confirm which subscription is being queried.',
inputSchema: {
Expand Down
3 changes: 3 additions & 0 deletions src/tools/register-gcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { GcpProjectInfo } from '../providers/gcp/discovery.js';
import { handleListProjects } from './list-projects.js';
import { handleCrossProjectCosts } from './cross-project-costs.js';
import { toolError, type Providers } from './types.js';
import { TOOL_ANNOTATIONS_READ_ONLY } from '../constants.js';

export function registerGcpTools(
server: McpServer,
Expand All @@ -14,6 +15,7 @@ export function registerGcpTools(
'list_projects',
{
title: 'List GCP Projects',
annotations: TOOL_ANNOTATIONS_READ_ONLY,
description:
'Returns all GCP projects the current credential can access, with name, ID, and state. Shows which project is currently active. Use this when the user has multiple GCP projects and wants to see which ones are available, or before calling get_cross_project_costs.',
inputSchema: {
Expand All @@ -34,6 +36,7 @@ export function registerGcpTools(
'get_cross_project_costs',
{
title: 'Cross-Project Cost Summary',
annotations: TOOL_ANNOTATIONS_READ_ONLY,
description:
'Returns a combined cost breakdown across multiple GCP projects sorted by total spend. Each project shows its name, total cost in USD, and percentage of the combined total. Use this when the user asks about costs across all GCP projects, wants to compare project spending, or needs an organization-wide cost overview.',
inputSchema: {
Expand Down
13 changes: 13 additions & 0 deletions src/tools/register-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {
DEFAULT_FORECAST_DAYS,
DEFAULT_TOP_RESOURCES_LIMIT,
DEFAULT_TOP_RESOURCES_DAYS,
TOOL_ANNOTATIONS_READ_ONLY,
TOOL_ANNOTATIONS_LOCAL,
} from '../constants.js';

export function registerSharedTools(
Expand All @@ -31,6 +33,7 @@ export function registerSharedTools(
'get_cost_summary',
{
title: 'Cloud Cost Summary',
annotations: TOOL_ANNOTATIONS_READ_ONLY,
description:
'Returns a cost breakdown for a date range grouped by service, resource group, tag, or region. Defaults to current month if dates are omitted. Output includes a sorted table with each group name, cost in USD, and percentage of total. Includes a total row, daily average, and collapses groups beyond the top 10 into an "Other" row. Returns an error if the date range is invalid. Use this when the user asks "how much am I spending", "what costs the most", "show me my cloud bill", or wants a spending overview.',
inputSchema: {
Expand All @@ -53,6 +56,7 @@ export function registerSharedTools(
'detect_anomalies',
{
title: 'Detect Cost Anomalies',
annotations: TOOL_ANNOTATIONS_READ_ONLY,
description:
'Compares daily spending over the last N days against the prior N days to find cost spikes. Returns a list of services where spending increased above the threshold percentage, sorted by increase amount. Each entry includes service name, previous cost, current cost, percentage change, and absolute change in USD. Returns an empty list if no anomalies found. Use this when the user asks about unexpected cost increases, billing surprises, or wants to know if anything changed recently.',
inputSchema: {
Expand All @@ -74,6 +78,7 @@ export function registerSharedTools(
'list_recommendations',
{
title: 'Cost Optimization Recommendations',
annotations: TOOL_ANNOTATIONS_READ_ONLY,
description:
'Fetches cost-saving recommendations filtered by category. Returns a list of recommendations each containing: title, category, impact level (high/medium/low), estimated annual savings in USD, affected resource ID, and a short description of the suggested action. Returns an empty list if no recommendations exist for the selected category. Use this when the user wants to reduce costs, find waste, or optimize resource usage.',
inputSchema: {
Expand All @@ -91,6 +96,7 @@ export function registerSharedTools(
'get_cost_forecast',
{
title: 'Cost Forecast',
annotations: TOOL_ANNOTATIONS_READ_ONLY,
description:
'Projects future cloud spending for the next N days using a linear trend based on the last 30 days of actual costs. Returns the forecast period dates, projected total cost in USD, average daily projected cost, and the confidence basis (number of historical days used). Use this when the user asks "how much will I spend this month", wants to predict upcoming bills, or needs to plan budgets. Returns an error if insufficient historical data exists.',
inputSchema: {
Expand All @@ -108,6 +114,7 @@ export function registerSharedTools(
'check_budgets',
{
title: 'Budget Status',
annotations: TOOL_ANNOTATIONS_READ_ONLY,
description:
'Check budget status: current spend vs limit, percentage used, forecast, and overage risk. For GCP, requires GCP_BILLING_ACCOUNT_ID to be set.',
inputSchema: {
Expand All @@ -121,6 +128,7 @@ export function registerSharedTools(
'compare_periods',
{
title: 'Compare Cost Periods',
annotations: TOOL_ANNOTATIONS_READ_ONLY,
description:
'Compare costs between two date ranges, showing per-service absolute and percentage changes.',
inputSchema: {
Expand All @@ -142,6 +150,7 @@ export function registerSharedTools(
'top_spending_resources',
{
title: 'Top Spending Resources',
annotations: TOOL_ANNOTATIONS_READ_ONLY,
description:
'Find the N most expensive individual resources over a time period. On GCP, requires the detailed billing export for resource-level data.',
inputSchema: {
Expand All @@ -163,6 +172,7 @@ export function registerSharedTools(
'get_cost_by_tag',
{
title: 'Cost by Tag',
annotations: TOOL_ANNOTATIONS_READ_ONLY,
description:
'Breaks down costs by a specific tag or label key such as team, environment, or project. Returns a sorted table with each tag value, cost in USD, and percentage of total. Includes a total row and daily average. Returns an error if the date range is invalid or no tagged costs exist. Use this when the user asks about costs per team, per environment, cost allocation, chargeback, or wants to understand spending by any custom tag or label.',
inputSchema: {
Expand All @@ -187,6 +197,7 @@ export function registerSharedTools(
'find_idle_resources',
{
title: 'Find Idle Resources',
annotations: TOOL_ANNOTATIONS_READ_ONLY,
description:
'Finds cloud resources that are provisioned but not actively used — unattached disks, orphaned network interfaces, unused IPs, idle VMs, and empty compute plans. Returns each resource with its name, type, resource group/project, reason it is idle, and estimated monthly cost in USD. Returns an empty list if no idle resources are found. Use this when the user asks about waste, idle or unused resources, cleanup opportunities, or wants to find resources to delete to reduce costs.',
inputSchema: {
Expand All @@ -200,6 +211,7 @@ export function registerSharedTools(
'find_untagged_resources',
{
title: 'Find Untagged Resources',
annotations: TOOL_ANNOTATIONS_READ_ONLY,
description:
'Finds resources that have no tags or labels applied. Returns each resource with its name, type, resource group/project, and location. Untagged resources cannot be attributed to teams or projects, making cost allocation and chargeback impossible. Returns an empty list if all resources are tagged. Use this when the user asks about tagging compliance, governance, cost attribution gaps, or wants to identify resources that need tags or labels.',
inputSchema: {
Expand All @@ -213,6 +225,7 @@ export function registerSharedTools(
'get_current_date',
{
title: 'Current Date',
annotations: TOOL_ANNOTATIONS_LOCAL,
description:
"Returns today's date and the start/end of current and previous months in YYYY-MM-DD format",
inputSchema: {},
Expand Down
Loading