diff --git a/PepApi.sln b/PepApi.sln index 1beb7d2..66559be 100644 --- a/PepApi.sln +++ b/PepApi.sln @@ -1,12 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.14.36518.9 d17.14 +VisualStudioVersion = 17.14.36518.9 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepApi.Core", "PepApi.Core\PepApi.Core.csproj", "{6761DEB6-14DD-4930-B1BB-BEC16A278E73}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepLib.Core", "PepLib.Core\PepLib.Core.csproj", "{3E03F837-2CD5-4681-BED9-7179DF766DF6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepMcp", "PepMcp\PepMcp.csproj", "{28899B7B-2185-4141-B348-E227DFB355E9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,6 +43,18 @@ Global {3E03F837-2CD5-4681-BED9-7179DF766DF6}.Release|x64.Build.0 = Release|Any CPU {3E03F837-2CD5-4681-BED9-7179DF766DF6}.Release|x86.ActiveCfg = Release|Any CPU {3E03F837-2CD5-4681-BED9-7179DF766DF6}.Release|x86.Build.0 = Release|Any CPU + {28899B7B-2185-4141-B348-E227DFB355E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28899B7B-2185-4141-B348-E227DFB355E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28899B7B-2185-4141-B348-E227DFB355E9}.Debug|x64.ActiveCfg = Debug|Any CPU + {28899B7B-2185-4141-B348-E227DFB355E9}.Debug|x64.Build.0 = Debug|Any CPU + {28899B7B-2185-4141-B348-E227DFB355E9}.Debug|x86.ActiveCfg = Debug|Any CPU + {28899B7B-2185-4141-B348-E227DFB355E9}.Debug|x86.Build.0 = Debug|Any CPU + {28899B7B-2185-4141-B348-E227DFB355E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28899B7B-2185-4141-B348-E227DFB355E9}.Release|Any CPU.Build.0 = Release|Any CPU + {28899B7B-2185-4141-B348-E227DFB355E9}.Release|x64.ActiveCfg = Release|Any CPU + {28899B7B-2185-4141-B348-E227DFB355E9}.Release|x64.Build.0 = Release|Any CPU + {28899B7B-2185-4141-B348-E227DFB355E9}.Release|x86.ActiveCfg = Release|Any CPU + {28899B7B-2185-4141-B348-E227DFB355E9}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PepMcp/PepMcp.csproj b/PepMcp/PepMcp.csproj new file mode 100644 index 0000000..ca79672 --- /dev/null +++ b/PepMcp/PepMcp.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/PepMcp/PepTools.cs b/PepMcp/PepTools.cs new file mode 100644 index 0000000..1c74bb1 --- /dev/null +++ b/PepMcp/PepTools.cs @@ -0,0 +1,403 @@ +using ModelContextProtocol.Server; +using System.ComponentModel; +using System.Text.Json; + +[McpServerToolType] +public class PepTools +{ + private static readonly HttpClient _httpClient = new() + { + BaseAddress = new Uri("http://localhost:8085") + }; + + private static readonly JsonSerializerOptions _jsonOptions = new() + { + PropertyNameCaseInsensitive = true, + WriteIndented = true + }; + + [McpServerTool, Description("Get a list of nesting programs for a specific year. Returns program name, status, customer, material info, and dates.")] + public static async Task GetNests( + [Description("The year to get nests for (e.g., 2024, 2025)")] int year, + [Description("Filter by customer name")] string? customer = null, + [Description("Filter by status: ToBeCut, Cut, Cancelled, OnHold, or All")] string? status = null, + [Description("Maximum number of results to return")] int? limit = null) + { + try + { + var queryParams = new List(); + if (!string.IsNullOrWhiteSpace(customer)) + queryParams.Add($"customer={Uri.EscapeDataString(customer)}"); + if (!string.IsNullOrWhiteSpace(status)) + queryParams.Add($"status={Uri.EscapeDataString(status)}"); + if (limit.HasValue) + queryParams.Add($"limit={limit.Value}"); + + var query = queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""; + var response = await _httpClient.GetAsync($"/nests/{year}{query}"); + + if (!response.IsSuccessStatusCode) + return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"; + + var json = await response.Content.ReadAsStringAsync(); + return json; + } + catch (Exception ex) + { + return $"Error calling PEP API: {ex.Message}"; + } + } + + [McpServerTool, Description("Get detailed information about a specific nesting program including parts, plates, and material details.")] + public static async Task GetNestDetails( + [Description("The name of the nest program (e.g., 'N12345')")] string nestName, + [Description("Optional year to narrow down the search")] int? year = null) + { + try + { + var url = year.HasValue + ? $"/nests/{year.Value}/{Uri.EscapeDataString(nestName)}" + : $"/nests/{Uri.EscapeDataString(nestName)}"; + + var response = await _httpClient.GetAsync(url); + + if (!response.IsSuccessStatusCode) + return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"; + + var json = await response.Content.ReadAsStringAsync(); + return json; + } + catch (Exception ex) + { + return $"Error calling PEP API: {ex.Message}"; + } + } + + [McpServerTool, Description("Get the plates used in a specific nesting program with material, size, thickness, and quantity information.")] + public static async Task GetNestPlates( + [Description("The name of the nest program (e.g., 'N12345')")] string nestName, + [Description("Optional year to narrow down the search")] int? year = null) + { + try + { + var url = year.HasValue + ? $"/nests/{year.Value}/{Uri.EscapeDataString(nestName)}/plates" + : $"/nests/{Uri.EscapeDataString(nestName)}/plates"; + + var response = await _httpClient.GetAsync(url); + + if (!response.IsSuccessStatusCode) + return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"; + + var json = await response.Content.ReadAsStringAsync(); + return json; + } + catch (Exception ex) + { + return $"Error calling PEP API: {ex.Message}"; + } + } + + [McpServerTool, Description("Search for parts across nesting programs by part name. Returns which programs contain the part and quantities.")] + public static async Task SearchParts( + [Description("The part name or partial name to search for")] string searchTerm, + [Description("Optional year to limit the search")] int? year = null, + [Description("Optional customer name to filter results")] string? customer = null, + [Description("Maximum number of results to return (default 100)")] int limit = 100) + { + try + { + var queryParams = new List + { + $"search={Uri.EscapeDataString(searchTerm)}", + $"limit={limit}" + }; + + if (year.HasValue) + queryParams.Add($"year={year.Value}"); + if (!string.IsNullOrWhiteSpace(customer)) + queryParams.Add($"customer={Uri.EscapeDataString(customer)}"); + + var query = "?" + string.Join("&", queryParams); + var response = await _httpClient.GetAsync($"/nests/parts/search{query}"); + + if (!response.IsSuccessStatusCode) + return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"; + + var json = await response.Content.ReadAsStringAsync(); + return json; + } + catch (Exception ex) + { + return $"Error calling PEP API: {ex.Message}"; + } + } + + [McpServerTool, Description("Get material usage analytics showing consumption by material type, grade, and thickness over a date range.")] + public static async Task GetMaterialUsage( + [Description("Start date for the analysis period (yyyy-MM-dd)")] string? startDate = null, + [Description("End date for the analysis period (yyyy-MM-dd)")] string? endDate = null, + [Description("Group results by 'month' for monthly breakdown, or omit for totals")] string? groupBy = null, + [Description("Only include programs that have been cut (default true)")] bool cutOnly = true) + { + try + { + var queryParams = new List(); + if (!string.IsNullOrWhiteSpace(startDate)) + queryParams.Add($"startDate={Uri.EscapeDataString(startDate)}"); + if (!string.IsNullOrWhiteSpace(endDate)) + queryParams.Add($"endDate={Uri.EscapeDataString(endDate)}"); + if (!string.IsNullOrWhiteSpace(groupBy)) + queryParams.Add($"groupBy={Uri.EscapeDataString(groupBy)}"); + queryParams.Add($"cutOnly={cutOnly.ToString().ToLower()}"); + + var query = "?" + string.Join("&", queryParams); + var response = await _httpClient.GetAsync($"/analytics/material-usage{query}"); + + if (!response.IsSuccessStatusCode) + return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"; + + var json = await response.Content.ReadAsStringAsync(); + return json; + } + catch (Exception ex) + { + return $"Error calling PEP API: {ex.Message}"; + } + } + + [McpServerTool, Description("Get the most commonly used plate sizes with count, total weight, and area statistics.")] + public static async Task GetPlateSizes( + [Description("Filter by material number")] int? materialNumber = null, + [Description("Filter by material grade")] string? grade = null, + [Description("Start date for the analysis period (yyyy-MM-dd)")] string? startDate = null, + [Description("End date for the analysis period (yyyy-MM-dd)")] string? endDate = null, + [Description("Only include programs that have been cut (default true)")] bool cutOnly = true) + { + try + { + var queryParams = new List(); + if (materialNumber.HasValue) + queryParams.Add($"materialNumber={materialNumber.Value}"); + if (!string.IsNullOrWhiteSpace(grade)) + queryParams.Add($"grade={Uri.EscapeDataString(grade)}"); + if (!string.IsNullOrWhiteSpace(startDate)) + queryParams.Add($"startDate={Uri.EscapeDataString(startDate)}"); + if (!string.IsNullOrWhiteSpace(endDate)) + queryParams.Add($"endDate={Uri.EscapeDataString(endDate)}"); + queryParams.Add($"cutOnly={cutOnly.ToString().ToLower()}"); + + var query = "?" + string.Join("&", queryParams); + var response = await _httpClient.GetAsync($"/analytics/plate-sizes{query}"); + + if (!response.IsSuccessStatusCode) + return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"; + + var json = await response.Content.ReadAsStringAsync(); + return json; + } + catch (Exception ex) + { + return $"Error calling PEP API: {ex.Message}"; + } + } + + [McpServerTool, Description("Get material consumption breakdown by thickness showing nest count, plate count, and weight distribution.")] + public static async Task GetThicknessBreakdown( + [Description("Filter by year")] int? year = null, + [Description("Start date for the analysis period (yyyy-MM-dd)")] string? startDate = null, + [Description("End date for the analysis period (yyyy-MM-dd)")] string? endDate = null, + [Description("Only include programs that have been cut (default true)")] bool cutOnly = true) + { + try + { + var queryParams = new List(); + if (year.HasValue) + queryParams.Add($"year={year.Value}"); + if (!string.IsNullOrWhiteSpace(startDate)) + queryParams.Add($"startDate={Uri.EscapeDataString(startDate)}"); + if (!string.IsNullOrWhiteSpace(endDate)) + queryParams.Add($"endDate={Uri.EscapeDataString(endDate)}"); + queryParams.Add($"cutOnly={cutOnly.ToString().ToLower()}"); + + var query = "?" + string.Join("&", queryParams); + var response = await _httpClient.GetAsync($"/analytics/thickness-breakdown{query}"); + + if (!response.IsSuccessStatusCode) + return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"; + + var json = await response.Content.ReadAsStringAsync(); + return json; + } + catch (Exception ex) + { + return $"Error calling PEP API: {ex.Message}"; + } + } + + [McpServerTool, Description("Get material usage breakdown by customer showing which customers use the most material.")] + public static async Task GetCustomerUsage( + [Description("Filter by specific customer ID or name")] string? customerId = null, + [Description("Number of months to analyze (default 12)")] int months = 12, + [Description("Only include programs that have been cut (default true)")] bool cutOnly = true) + { + try + { + var queryParams = new List + { + $"months={months}", + $"cutOnly={cutOnly.ToString().ToLower()}" + }; + + if (!string.IsNullOrWhiteSpace(customerId)) + queryParams.Add($"customerId={Uri.EscapeDataString(customerId)}"); + + var query = "?" + string.Join("&", queryParams); + var response = await _httpClient.GetAsync($"/analytics/customer-usage{query}"); + + if (!response.IsSuccessStatusCode) + return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"; + + var json = await response.Content.ReadAsStringAsync(); + return json; + } + catch (Exception ex) + { + return $"Error calling PEP API: {ex.Message}"; + } + } + + [McpServerTool, Description("Get stock recommendations based on historical material usage patterns.")] + public static async Task GetStockRecommendations( + [Description("Number of months of history to analyze (default 6)")] int months = 6, + [Description("Multiplier for suggested stock levels (default 1.5)")] double stockMultiplier = 1.5, + [Description("Filter by customer ID or name")] string? customerId = null, + [Description("Only include programs that have been cut (default true)")] bool cutOnly = true) + { + try + { + var queryParams = new List + { + $"months={months}", + $"stockMultiplier={stockMultiplier}", + $"cutOnly={cutOnly.ToString().ToLower()}" + }; + + if (!string.IsNullOrWhiteSpace(customerId)) + queryParams.Add($"customerId={Uri.EscapeDataString(customerId)}"); + + var query = "?" + string.Join("&", queryParams); + var response = await _httpClient.GetAsync($"/analytics/stock-recommendations{query}"); + + if (!response.IsSuccessStatusCode) + return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"; + + var json = await response.Content.ReadAsStringAsync(); + return json; + } + catch (Exception ex) + { + return $"Error calling PEP API: {ex.Message}"; + } + } + + [McpServerTool, Description("Get the top materials by usage volume.")] + public static async Task GetTopMaterials( + [Description("Number of top materials to return (default 10)")] int count = 10, + [Description("Number of months to analyze (default 12)")] int months = 12, + [Description("Only include programs that have been cut (default true)")] bool cutOnly = true) + { + try + { + var queryParams = new List + { + $"count={count}", + $"months={months}", + $"cutOnly={cutOnly.ToString().ToLower()}" + }; + + var query = "?" + string.Join("&", queryParams); + var response = await _httpClient.GetAsync($"/analytics/top-materials{query}"); + + if (!response.IsSuccessStatusCode) + return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"; + + var json = await response.Content.ReadAsStringAsync(); + return json; + } + catch (Exception ex) + { + return $"Error calling PEP API: {ex.Message}"; + } + } + + [McpServerTool, Description("Get materials that need to be ordered for programs with 'To Be Cut' status. Shows plate sizes and quantities needed.")] + public static async Task GetMaterialsToOrder( + [Description("Filter by customer ID or name")] string? customerId = null, + [Description("Filter by year (defaults to current year)")] int? year = null, + [Description("Filter by programmer name")] string? programmedBy = null) + { + try + { + var queryParams = new List(); + if (!string.IsNullOrWhiteSpace(customerId)) + queryParams.Add($"customerId={Uri.EscapeDataString(customerId)}"); + if (year.HasValue) + queryParams.Add($"year={year.Value}"); + if (!string.IsNullOrWhiteSpace(programmedBy)) + queryParams.Add($"programmedBy={Uri.EscapeDataString(programmedBy)}"); + + var query = queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""; + var response = await _httpClient.GetAsync($"/analytics/materials-to-order{query}"); + + if (!response.IsSuccessStatusCode) + return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"; + + var json = await response.Content.ReadAsStringAsync(); + return json; + } + catch (Exception ex) + { + return $"Error calling PEP API: {ex.Message}"; + } + } + + [McpServerTool, Description("Get a list of all available materials in the system with grade, thickness, and density information.")] + public static async Task GetMaterials() + { + try + { + var response = await _httpClient.GetAsync("/materials"); + + if (!response.IsSuccessStatusCode) + return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"; + + var json = await response.Content.ReadAsStringAsync(); + return json; + } + catch (Exception ex) + { + return $"Error calling PEP API: {ex.Message}"; + } + } + + [McpServerTool, Description("Get details for a specific material by material number.")] + public static async Task GetMaterial( + [Description("The material number to look up")] int materialNumber) + { + try + { + var response = await _httpClient.GetAsync($"/materials/{materialNumber}"); + + if (!response.IsSuccessStatusCode) + return $"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"; + + var json = await response.Content.ReadAsStringAsync(); + return json; + } + catch (Exception ex) + { + return $"Error calling PEP API: {ex.Message}"; + } + } +} diff --git a/PepMcp/Program.cs b/PepMcp/Program.cs new file mode 100644 index 0000000..a58a053 --- /dev/null +++ b/PepMcp/Program.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +var builder = Host.CreateApplicationBuilder(args); +builder.Services + .AddMcpServer() + .WithStdioServerTransport() + .WithTools(); + +var app = builder.Build(); +await app.RunAsync();