From fa886a35add677aa5aa13e7c31501cf0357053c3 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 17 Apr 2026 11:10:27 +0300 Subject: [PATCH 1/4] feat: add MCP tool annotations to all 15 tools Mark all tools with readOnlyHint, destructiveHint, idempotentHint, and openWorldHint annotations. All tools are read-only and idempotent. get_current_date uses openWorldHint: false since it only returns the local date without accessing external APIs. --- src/tools/register-azure.ts | 12 +++++++ src/tools/register-gcp.ts | 12 +++++++ src/tools/register-shared.ts | 66 ++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/src/tools/register-azure.ts b/src/tools/register-azure.ts index be7bf88..d63253e 100644 --- a/src/tools/register-azure.ts +++ b/src/tools/register-azure.ts @@ -14,6 +14,12 @@ export function registerAzureTools( 'get_cross_subscription_costs', { title: 'Cross-Subscription Cost Summary', + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, 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: { @@ -52,6 +58,12 @@ export function registerAzureTools( 'list_subscriptions', { title: 'List Azure Subscriptions', + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, 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: { diff --git a/src/tools/register-gcp.ts b/src/tools/register-gcp.ts index 6d00367..f985687 100644 --- a/src/tools/register-gcp.ts +++ b/src/tools/register-gcp.ts @@ -14,6 +14,12 @@ export function registerGcpTools( 'list_projects', { title: 'List GCP Projects', + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, 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: { @@ -34,6 +40,12 @@ export function registerGcpTools( 'get_cross_project_costs', { title: 'Cross-Project Cost Summary', + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, 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: { diff --git a/src/tools/register-shared.ts b/src/tools/register-shared.ts index 62ee96e..663ab6d 100644 --- a/src/tools/register-shared.ts +++ b/src/tools/register-shared.ts @@ -31,6 +31,12 @@ export function registerSharedTools( 'get_cost_summary', { title: 'Cloud Cost Summary', + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, 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: { @@ -53,6 +59,12 @@ export function registerSharedTools( 'detect_anomalies', { title: 'Detect Cost Anomalies', + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, 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: { @@ -74,6 +86,12 @@ export function registerSharedTools( 'list_recommendations', { title: 'Cost Optimization Recommendations', + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, 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: { @@ -91,6 +109,12 @@ export function registerSharedTools( 'get_cost_forecast', { title: 'Cost Forecast', + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, 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: { @@ -108,6 +132,12 @@ export function registerSharedTools( 'check_budgets', { title: 'Budget Status', + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, 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: { @@ -121,6 +151,12 @@ export function registerSharedTools( 'compare_periods', { title: 'Compare Cost Periods', + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, description: 'Compare costs between two date ranges, showing per-service absolute and percentage changes.', inputSchema: { @@ -142,6 +178,12 @@ export function registerSharedTools( 'top_spending_resources', { title: 'Top Spending Resources', + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, description: 'Find the N most expensive individual resources over a time period. On GCP, requires the detailed billing export for resource-level data.', inputSchema: { @@ -163,6 +205,12 @@ export function registerSharedTools( 'get_cost_by_tag', { title: 'Cost by Tag', + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, 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: { @@ -187,6 +235,12 @@ export function registerSharedTools( 'find_idle_resources', { title: 'Find Idle Resources', + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, 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: { @@ -200,6 +254,12 @@ export function registerSharedTools( 'find_untagged_resources', { title: 'Find Untagged Resources', + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, 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: { @@ -213,6 +273,12 @@ export function registerSharedTools( 'get_current_date', { title: 'Current Date', + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: false, + }, description: "Returns today's date and the start/end of current and previous months in YYYY-MM-DD format", inputSchema: {}, From 13e7416a5f71eced24dedced51f037c180fb09e5 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 17 Apr 2026 11:33:40 +0300 Subject: [PATCH 2/4] chore: bump version to 0.3.2 --- package-lock.json | 4 ++-- package.json | 2 +- server.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d8c2adb..9aec8d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cloudscope-mcp", - "version": "0.3.1", + "version": "0.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cloudscope-mcp", - "version": "0.3.1", + "version": "0.3.2", "license": "MIT", "dependencies": { "@azure/arm-advisor": "^3.0.0", diff --git a/package.json b/package.json index 48edacd..7f4c248 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/server.json b/server.json index 77ef9e6..a82bdcc 100644 --- a/server.json +++ b/server.json @@ -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" }, From 8185b5a36deb0051f42b6a408e61b0cd11e3cb17 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 17 Apr 2026 11:37:56 +0300 Subject: [PATCH 3/4] fix: resolve protobufjs RCE and hono XSS vulnerabilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - protobufjs 7.5.4→7.5.5 (GHSA-xq3m-2v4x-88gg, CVSS 9.4 critical) - hono 4.12.12→4.12.14 (GHSA-458j-xx4x-4375, CVSS 4.3 moderate) --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9aec8d1..38b2570 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3834,9 +3834,9 @@ } }, "node_modules/hono": { - "version": "4.12.12", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz", - "integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==", + "version": "4.12.14", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz", + "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==", "license": "MIT", "peer": true, "engines": { @@ -4869,9 +4869,9 @@ } }, "node_modules/protobufjs": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", - "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz", + "integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { From 6bcedad840433c16d20950f0152181037a54f8c3 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 17 Apr 2026 12:00:19 +0300 Subject: [PATCH 4/4] refactor: extract tool annotations to shared constants --- src/constants.ts | 15 +++++++ src/tools/register-azure.ts | 15 ++----- src/tools/register-gcp.ts | 15 ++----- src/tools/register-shared.ts | 79 ++++++------------------------------ 4 files changed, 34 insertions(+), 90 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 17c4973..c928589 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -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; diff --git a/src/tools/register-azure.ts b/src/tools/register-azure.ts index d63253e..d1a517b 100644 --- a/src/tools/register-azure.ts +++ b/src/tools/register-azure.ts @@ -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, @@ -14,12 +15,7 @@ export function registerAzureTools( 'get_cross_subscription_costs', { title: 'Cross-Subscription Cost Summary', - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, + 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: { @@ -58,12 +54,7 @@ export function registerAzureTools( 'list_subscriptions', { title: 'List Azure Subscriptions', - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, + 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: { diff --git a/src/tools/register-gcp.ts b/src/tools/register-gcp.ts index f985687..4d1daa2 100644 --- a/src/tools/register-gcp.ts +++ b/src/tools/register-gcp.ts @@ -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, @@ -14,12 +15,7 @@ export function registerGcpTools( 'list_projects', { title: 'List GCP Projects', - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, + 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: { @@ -40,12 +36,7 @@ export function registerGcpTools( 'get_cross_project_costs', { title: 'Cross-Project Cost Summary', - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, + 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: { diff --git a/src/tools/register-shared.ts b/src/tools/register-shared.ts index 663ab6d..6e9ac06 100644 --- a/src/tools/register-shared.ts +++ b/src/tools/register-shared.ts @@ -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( @@ -31,12 +33,7 @@ export function registerSharedTools( 'get_cost_summary', { title: 'Cloud Cost Summary', - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, + 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: { @@ -59,12 +56,7 @@ export function registerSharedTools( 'detect_anomalies', { title: 'Detect Cost Anomalies', - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, + 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: { @@ -86,12 +78,7 @@ export function registerSharedTools( 'list_recommendations', { title: 'Cost Optimization Recommendations', - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, + 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: { @@ -109,12 +96,7 @@ export function registerSharedTools( 'get_cost_forecast', { title: 'Cost Forecast', - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, + 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: { @@ -132,12 +114,7 @@ export function registerSharedTools( 'check_budgets', { title: 'Budget Status', - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, + 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: { @@ -151,12 +128,7 @@ export function registerSharedTools( 'compare_periods', { title: 'Compare Cost Periods', - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, + annotations: TOOL_ANNOTATIONS_READ_ONLY, description: 'Compare costs between two date ranges, showing per-service absolute and percentage changes.', inputSchema: { @@ -178,12 +150,7 @@ export function registerSharedTools( 'top_spending_resources', { title: 'Top Spending Resources', - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, + 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: { @@ -205,12 +172,7 @@ export function registerSharedTools( 'get_cost_by_tag', { title: 'Cost by Tag', - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, + 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: { @@ -235,12 +197,7 @@ export function registerSharedTools( 'find_idle_resources', { title: 'Find Idle Resources', - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, + 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: { @@ -254,12 +211,7 @@ export function registerSharedTools( 'find_untagged_resources', { title: 'Find Untagged Resources', - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, + 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: { @@ -273,12 +225,7 @@ export function registerSharedTools( 'get_current_date', { title: 'Current Date', - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, + annotations: TOOL_ANNOTATIONS_LOCAL, description: "Returns today's date and the start/end of current and previous months in YYYY-MM-DD format", inputSchema: {},