Documentation
¶
Overview ¶
Package tools implements the individual MCP tool handlers exposed by the CUDly MCP server (mcp/server.go), plus the shared validation and purchase harness they all build on.
Index ¶
- Constants
- func BuildInputSchema[T any](overrides map[string]FieldOverride) (*jsonschema.Schema, error)
- func CredentialScope(explicit string, envVars ...string) string
- func ResolveDryRunConfirm(dryRun, confirm *bool) (effectiveDryRun, effectiveConfirm bool)
- func ValidatePlatform(s string) (ec2types.RIProductDescription, error)
- type AZConfig
- type ActionEntry
- type ArcheraOffer
- type CacheEngine
- type Descriptor
- type FieldOverride
- type LookbackPeriod
- type PaymentOption
- type PurchaseRequest
- type PurchaseResponse
- type Registration
- func NewAWSEC2RIPurchaseTool() Registration
- func NewAWSElastiCacheRIPurchaseTool() Registration
- func NewAWSMemoryDBRIPurchaseTool() Registration
- func NewAWSOpenSearchRIPurchaseTool() Registration
- func NewAWSRDSRIPurchaseTool() Registration
- func NewAWSRedshiftRIPurchaseTool() Registration
- func NewAWSSavingsPlansPurchaseTool() Registration
- func NewAzureComputeRIPurchaseTool() Registration
- func NewGCPComputeEngineCUDPurchaseTool() Registration
- func NewListCommitmentActions(descriptors []Descriptor) Registration
- func NewSearchRecommendationsTool() Registration
- type ResolveClientFunc
- type SPType
- type Scope
- type Tenancy
- type TermYears
Constants ¶
const EnvEnableRealPurchases = "CUDLY_MCP_ENABLE_REAL_PURCHASES"
EnvEnableRealPurchases is the operator-side authorization control gating every real (non-preview) purchase this server executes. The model-supplied confirm flag (decidePurchaseMode) only proves the model asked to spend money; it says nothing about whether the operator running this MCP server process wants it able to. mcp/README.md previously documented confirm as "a guardrail against an accidental call, not an authorization control", which left a prompt-injected or simply hallucinating model, given ambient production credentials, able to execute a real purchase from a single tool call. This env var closes that gap: it must be explicitly set before ExecutePurchase will call into a provider for a real purchase, regardless of confirm. See realPurchasesEnabled for the fail-closed matching rule.
Variables ¶
This section is empty.
Functions ¶
func BuildInputSchema ¶
func BuildInputSchema[T any](overrides map[string]FieldOverride) (*jsonschema.Schema, error)
BuildInputSchema infers the JSON Schema for T via jsonschema.For, then applies the given per-field overrides by JSON field name. It returns an error -- rather than silently skipping -- when an override names a field that does not exist on T, so a typo in the override map is caught at server-startup / test time instead of quietly shipping an unconstrained schema for a money-affecting field.
func CredentialScope ¶
CredentialScope resolves the account/subscription/project identifier that bounds where a purchase lands: the caller-supplied override when present, otherwise the first non-empty value among the ambient environment variables the matching provider factory itself consults (e.g. AZURE_SUBSCRIPTION_ID, providers/azure/provider.go's resolveDefaultSubscription).
It returns "" when neither is set, which is correct rather than an error: a provider that resolves its account purely from ambient credentials (single visible Azure subscription, AWS STS identity, ambient GCP project) is unambiguous for the life of the server process, so there is no second scope for a token to collide with.
Normalization here is limited to trimming, because it is the only rule that is safe for all three providers. Case is deliberately NOT folded: AWS named profiles are case-sensitive section names in ~/.aws/config, so lower-casing one could name a profile that does not exist or a different one that does. Azure subscription IDs ARE case-insensitive and do need folding -- see azureCredentialScope in azure_compute_ri.go, which wraps this.
func ResolveDryRunConfirm ¶
ResolveDryRunConfirm applies the dry_run=true / confirm=false defaults to a tool's optional flags. Go's zero value for bool cannot distinguish "caller omitted the field" from "caller explicitly set it false", which is why both arrive as *bool; this is the single place that resolves them to concrete booleans.
Shared by every purchase tool rather than reimplemented per tool. This is the gate that decides whether real money moves, so seven hand-copied versions of it were seven chances for one to drift: a copy that defaulted dryRun to false would turn an unconfirmed preview into a live purchase, and nothing but review would catch it.
func ValidatePlatform ¶
func ValidatePlatform(s string) (ec2types.RIProductDescription, error)
ValidatePlatform returns the AWS SDK's own ec2types.RIProductDescription enum member for s, or an explicit error when s does not match one of the four values that DescribeReservedInstancesOfferings accepts as ProductDescription (providers/aws/services/ec2/client.go:419). Reusing the SDK's own enum constants -- rather than inventing a "linux"/"windows" vocabulary -- means an outbound offering lookup can never carry a bare string literal that drifts from what the SDK actually recognizes (feedback_sdk_enum_string_literals).
Types ¶
type AZConfig ¶
type AZConfig string
AZConfig is the RDS deployment topology (single-AZ vs multi-AZ), which carries a different price and offering catalog per providers/aws/services/rds/client.go:314-322.
func ValidateAZConfig ¶
ValidateAZConfig returns the typed AZConfig for s, or an explicit error when s is not single-az or multi-az. RDS's own client refuses to guess this value (see the comment at providers/aws/services/rds/client.go:306-322), so the MCP boundary must not default it either.
type ActionEntry ¶
type ActionEntry struct {
Name string `json:"name"`
Provider string `json:"provider,omitempty"`
Product string `json:"product,omitempty"`
Action string `json:"action,omitempty"`
Description string `json:"description"`
RealPurchaseEnabled bool `json:"real_purchase_enabled"`
ExamplePrompts []string `json:"example_prompts,omitempty"`
}
ActionEntry is one catalog entry returned by cudly_list_commitment_actions, reshaping a Descriptor for JSON output.
type ArcheraOffer ¶
type ArcheraOffer struct {
// Pitch is what the coverage does and why it is relevant right now.
Pitch string `json:"pitch"`
// SignupURL is the Archera signup link carrying CUDly attribution.
SignupURL string `json:"signup_url"`
// EnrollmentWindowDays is how long from THIS purchase the buyer has to
// enroll it. Surfaced as a number so a client can compute the deadline
// from effective_date rather than parsing it out of prose.
EnrollmentWindowDays int `json:"enrollment_window_days"`
// NonGatingDisclosure states the offer is optional and CUDly works
// without it.
NonGatingDisclosure string `json:"non_gating_disclosure"`
// SponsorshipDisclosure states the financial relationship behind the
// recommendation.
SponsorshipDisclosure string `json:"sponsorship_disclosure"`
}
ArcheraOffer is the post-purchase Archera underutilization-insurance offer.
Both disclosure fields are non-optional parts of the payload, not decoration: CUDly commits to surfacing the sponsorship AND the fact that it works fully without Archera everywhere the signup link appears (the CLI's printArcheraPitch does the same after a real purchase). An MCP client renders this JSON through a model, so shipping the link without the disclosures alongside it would let the model present a sponsored recommendation as a neutral one.
type CacheEngine ¶
type CacheEngine string
CacheEngine is the ElastiCache engine dimension (common.CacheDetails.Engine).
const ( CacheEngineRedis CacheEngine = "redis" CacheEngineMemcached CacheEngine = "memcached" )
func ValidateCacheEngine ¶
func ValidateCacheEngine(s string) (CacheEngine, error)
ValidateCacheEngine returns the typed CacheEngine for s, or an explicit error when s is neither redis nor memcached.
type Descriptor ¶
type Descriptor struct {
// Name is the MCP tool name, e.g. "cudly_aws_ec2_ri_purchase".
Name string
// Provider is "aws", "azure", "gcp", or "" for provider-agnostic
// meta-tools (cudly_list_commitment_actions, cudly_search_recommendations).
Provider string
// Product is the service the tool acts on, e.g. "ec2", "rds", "compute".
Product string
// Action is what the tool does, e.g. "ri_purchase", "cud_purchase", "search".
Action string
// Description is the tool's full MCP description, shared verbatim with
// the live mcp.Tool registration so the two can never disagree.
Description string
// RealPurchaseEnabled reports whether this tool can execute a real,
// money-spending purchase today (dry_run=false, confirm=true). false for
// read-only tools and for tools shipped dry-run-only pending a
// prerequisite fix (see the Azure/GCP tool comments).
RealPurchaseEnabled bool
// ExamplePrompts are 2-3 natural-language prompts that would plausibly
// invoke this tool, surfaced by cudly_list_commitment_actions so a
// session that doesn't know the tool name yet can find it.
ExamplePrompts []string
}
Descriptor is the source-of-truth metadata for one MCP tool. Every tool file builds one and both mcp/server.go (to know what to register) and cudly_list_commitment_actions (to know what to advertise) read it, so the live tool set and the discoverability catalog can never drift apart -- there is exactly one place each tool's name/description/example prompts are written.
func ListCommitmentActionsDescriptor ¶
func ListCommitmentActionsDescriptor() Descriptor
ListCommitmentActionsDescriptor returns the static Descriptor for cudly_list_commitment_actions itself. It is exported so mcp/server.go can include this tool in its own catalog: the descriptors slice passed to NewListCommitmentActions must be assembled (and thus known) before the tool exists, so its own entry can't come from calling Descriptor() on an already-constructed instance the way every other tool's entry does.
type FieldOverride ¶
type FieldOverride struct {
// Enum, when non-empty, restricts the property to these exact values.
Enum []any
// Default, when non-nil, is recorded on the schema as the property's
// documented default so a caller inspecting the tool (or an MCP client
// that surfaces schema defaults in its UI) can see it without reading
// the tool description prose. It does NOT, by itself, cause the value to
// be applied when the caller omits the field -- each tool's handler
// applies its own default explicitly (see the dry_run/confirm pattern in
// purchase.go) so "omitted" is never silently confused with "false".
Default any
}
FieldOverride declares JSON Schema refinements -- an explicit enum membership and/or a documented default -- for one property of an otherwise auto-inferred schema. Centralizing this in one helper (BuildInputSchema) means every tool declares its enum/default once, next to its Go struct, instead of re-implementing schema post-processing per tool.
type LookbackPeriod ¶
type LookbackPeriod string
LookbackPeriod is the cost/usage lookback window backing a cudly_search_recommendations call. Values match the enum search_recommendations.go's Register advertises in the tool's JSON schema (BuildInputSchema's "lookback_period" FieldOverride); re-validated here so a caller invoking the tool directly -- bypassing MCP schema enforcement -- cannot pass an unsupported window through to the provider.
const ( LookbackPeriod7Days LookbackPeriod = "7d" LookbackPeriod30Days LookbackPeriod = "30d" LookbackPeriod60Days LookbackPeriod = "60d" )
func ValidateLookbackPeriod ¶
func ValidateLookbackPeriod(s string) (LookbackPeriod, error)
ValidateLookbackPeriod returns the typed LookbackPeriod for s, or an explicit error when s is non-empty and not one of the three supported windows. An empty s is valid: lookback_period is optional (json:"...,omitempty"), meaning "let the provider apply its own default".
type PaymentOption ¶
type PaymentOption string
PaymentOption is the AWS/Azure/GCP-agnostic reserved-capacity payment schedule. It is validated at the MCP tool boundary before being copied onto common.Recommendation.PaymentOption (which stays a bare string there for backward compatibility with existing CSV/DB rows -- see pkg/common/types.go) so a caller can never smuggle an unrecognized payment term into a purchase.
const ( PaymentOptionAllUpfront PaymentOption = "all-upfront" PaymentOptionPartialUpfront PaymentOption = "partial-upfront" PaymentOptionNoUpfront PaymentOption = "no-upfront" )
func ValidatePaymentOption ¶
func ValidatePaymentOption(s string) (PaymentOption, error)
ValidatePaymentOption returns the typed PaymentOption for s, or an explicit error when s is not one of the three allowed values. There is no default: an empty or unknown payment option is always an error, never silently coerced to a fallback term (feedback_no_silent_fallbacks).
type PurchaseRequest ¶
type PurchaseRequest struct {
Region string
Recommendation common.Recommendation
DryRun bool
Confirm bool
ResolveClient ResolveClientFunc
// Nonce is optional. When non-empty, this call is treated as a
// DISTINCT purchase from an otherwise-identical one (authorizes a
// deliberate repeat, e.g. "buy 3 more RIs" on top of an earlier "buy 3
// RIs" with the same parameters). When empty (the default), identical
// purchases dedupe so retries never double-buy. See idempotencyKeyFor.
Nonce string
// CredentialScope identifies WHERE the purchase lands: the AWS profile,
// Azure subscription, or GCP project the call is billed to. It is folded
// into the idempotency key so two purchases that are identical in every
// product dimension but target different accounts derive DIFFERENT
// tokens. Populated by each tool via CredentialScope(). See
// idempotencyKeyFor for why omitting it is a double-spend/skipped-spend
// hazard on Azure specifically.
CredentialScope string
}
PurchaseRequest is the provider-agnostic input to ExecutePurchase. Each per-service tool handler builds one after validating its own typed parameters and constructing the common.Recommendation.
type PurchaseResponse ¶
type PurchaseResponse struct {
Success bool `json:"success"`
DryRun bool `json:"dry_run"`
CommitmentID string `json:"commitment_id,omitempty"`
Cost *float64 `json:"cost,omitempty"`
OnDemandCost *float64 `json:"on_demand_cost,omitempty"`
EstimatedSavings *float64 `json:"estimated_savings,omitempty"`
SavingsPercentage *float64 `json:"savings_percentage,omitempty"`
EffectiveDate *string `json:"effective_date,omitempty"`
TermYears int `json:"term_years,omitempty"`
Error string `json:"error,omitempty"`
// Archera is the optional underutilization-insurance offer, populated
// ONLY after a real purchase actually succeeds. See archeraOffer.
Archera *ArcheraOffer `json:"archera,omitempty"`
}
PurchaseResponse is the structured result returned to the MCP caller for both preview and real-purchase outcomes. Error is a string (not the Go error) because it crosses the MCP JSON-RPC boundary as tool output, not a protocol-level error -- ExecutePurchase itself still returns a Go error for gate refusals and provider-call failures.
Cost/OnDemandCost/EstimatedSavings/SavingsPercentage are pointers with omitempty: none of the *FromArgs constructors in this package populate Recommendation's cost fields (they build a fresh Recommendation from the caller's typed args, not from a priced search result), and some provider clients (e.g. AWS EC2 RIs, Savings Plans) never populate PurchaseResult.Cost either. A plain float64 could not distinguish "not known" from "genuinely $0", so every response reported 0 for money fields it never actually priced. A pointer that's nil (and omitted from the JSON payload entirely) when no real value exists lets a caller tell "unknown" apart from "confirmed zero" (feedback_nullable_not_zero).
EffectiveDate is a pointer for the same reason, and it is not a hypothetical concern: `omitempty` on a string only drops "", so a PurchaseResult whose Timestamp was never populated formatted its zero time.Time to the literal "0001-01-01T00:00:00Z" and shipped that as the commitment's start date. That is a fabricated date presented as real, on a field a caller may key billing or renewal reminders off. Nil (and omitted) when the provider reported no timestamp.
func ExecutePurchase ¶
func ExecutePurchase(ctx context.Context, req PurchaseRequest) (*PurchaseResponse, error)
ExecutePurchase runs the shared dry_run/confirm safety gate, then every gate in authorizeRealPurchase (operator opt-in via EnvEnableRealPurchases, and a determinable target account), and for a real purchase that clears them all, resolves the service client and calls PurchaseCommitment with PurchaseSourceMCP and a derived idempotency token. It never calls ResolveClient in preview mode, so a preview makes zero provider/SDK calls; it also never calls ResolveClient when any authorizeRealPurchase gate refuses, so a disabled server -- or one that cannot tell which account a purchase would land in -- makes zero provider/SDK calls either.
type Registration ¶
type Registration interface {
Descriptor() Descriptor
Register(s *mcp.Server) error
}
Registration is implemented by every tool file. Descriptor feeds the catalog; Register performs the live mcp.AddTool (or mcp.Server.AddTool) call that wires the tool's schema and handler onto the server.
func NewAWSEC2RIPurchaseTool ¶
func NewAWSEC2RIPurchaseTool() Registration
NewAWSEC2RIPurchaseTool builds the cudly_aws_ec2_ri_purchase tool.
func NewAWSElastiCacheRIPurchaseTool ¶
func NewAWSElastiCacheRIPurchaseTool() Registration
NewAWSElastiCacheRIPurchaseTool builds the cudly_aws_elasticache_ri_purchase tool.
func NewAWSMemoryDBRIPurchaseTool ¶
func NewAWSMemoryDBRIPurchaseTool() Registration
NewAWSMemoryDBRIPurchaseTool builds cudly_aws_memorydb_ri_purchase.
func NewAWSOpenSearchRIPurchaseTool ¶
func NewAWSOpenSearchRIPurchaseTool() Registration
NewAWSOpenSearchRIPurchaseTool builds cudly_aws_opensearch_ri_purchase.
func NewAWSRDSRIPurchaseTool ¶
func NewAWSRDSRIPurchaseTool() Registration
NewAWSRDSRIPurchaseTool builds the cudly_aws_rds_ri_purchase tool.
func NewAWSRedshiftRIPurchaseTool ¶
func NewAWSRedshiftRIPurchaseTool() Registration
NewAWSRedshiftRIPurchaseTool builds cudly_aws_redshift_ri_purchase.
func NewAWSSavingsPlansPurchaseTool ¶
func NewAWSSavingsPlansPurchaseTool() Registration
NewAWSSavingsPlansPurchaseTool builds the cudly_aws_savingsplans_purchase tool.
func NewAzureComputeRIPurchaseTool ¶
func NewAzureComputeRIPurchaseTool() Registration
NewAzureComputeRIPurchaseTool builds the cudly_azure_compute_ri_purchase tool.
func NewGCPComputeEngineCUDPurchaseTool ¶
func NewGCPComputeEngineCUDPurchaseTool() Registration
NewGCPComputeEngineCUDPurchaseTool builds the cudly_gcp_computeengine_cud_purchase tool.
func NewListCommitmentActions ¶
func NewListCommitmentActions(descriptors []Descriptor) Registration
NewListCommitmentActions builds the cudly_list_commitment_actions tool from descriptors -- the same slice of Descriptor values mcp/server.go collects from every other tool's Descriptor() method, so this catalog is generated from the live registry rather than hand-duplicated in code or docs.
func NewSearchRecommendationsTool ¶
func NewSearchRecommendationsTool() Registration
NewSearchRecommendationsTool builds the cudly_search_recommendations tool.
type ResolveClientFunc ¶
type ResolveClientFunc func(ctx context.Context) (provider.ServiceClient, error)
ResolveClientFunc lazily resolves the provider.ServiceClient that will receive the real PurchaseCommitment call. It is a func, not an already-resolved client, so ExecutePurchase can prove (and tests can assert) that a preview never triggers provider/credential resolution -- only modeExecute invokes it.
type SPType ¶
type SPType string
SPType is the AWS Savings Plans product family (--include-sp-types in the CLI, cmd/main.go:112).
func ValidateSPType ¶
ValidateSPType returns the typed SPType for s, or an explicit error when s is not one of the four AWS Savings Plans product families.
type Scope ¶
type Scope string
Scope is the EC2 RI applicability dimension. Values are the lowercase, hyphenated form that providers/aws/services/ec2/client.go:330-339 (canonicalizeEC2Scope) recognizes and normalizes to the SDK's ec2types.Scope casing ("Region" / "Availability Zone").
func ValidateScope ¶
ValidateScope returns the typed Scope for s, or an explicit error when s is neither region nor availability-zone.
type Tenancy ¶
type Tenancy string
Tenancy is the EC2 RI tenancy dimension. Values match ec2types.Tenancy (providers/aws/services/ec2/client.go:309-318 canonicalizes them further, but "default"/"dedicated" already pass through unchanged).
const ( TenancyDefault Tenancy = Tenancy(ec2types.TenancyDefault) TenancyDedicated Tenancy = Tenancy(ec2types.TenancyDedicated) )
func ValidateTenancy ¶
ValidateTenancy returns the typed Tenancy for s, or an explicit error when s is neither default nor dedicated.
type TermYears ¶
type TermYears int
TermYears is the reserved-capacity commitment length, in years.
func ValidateTermYears ¶
ValidateTermYears returns the typed TermYears for n, or an explicit error when n is not 1 or 3 (the only terms AWS/Azure/GCP reserved-capacity products offer).
func (TermYears) RecommendationTerm ¶
RecommendationTerm renders t in the "1yr"/"3yr" vocabulary that common.Recommendation.Term and the provider clients expect.