Skip to main content
Glama

Tempo Filler MCP Server

Official
by TRANZACT
Tempo.dib7.91 kB
#!meta {"kernelInfo":{"defaultKernelName":"csharp","items":[{"name":".NET"},{"name":"csharp","languageName":"C#","aliases":["C#","c#"]},{"name":"fsharp","languageName":"F#","aliases":["F#","f#"]},{"name":"html","languageName":"HTML"},{"name":"http","languageName":"HTTP"},{"name":"httpRequest","languageName":"http"},{"name":"javascript","languageName":"JavaScript","aliases":["js"]},{"name":"kql","languageName":"KQL"},{"name":"mermaid","languageName":"Mermaid"},{"name":"pwsh","languageName":"PowerShell","aliases":["powershell"]},{"name":"sql","languageName":"SQL"},{"name":"value"},{"name":"vscode","aliases":["frontend"]}]}} #!csharp using System; using System.Net.Http; using System.Net.Http.Headers; using System.Text.Json; using System.Threading.Tasks; public class JiraIssue { public string Id {get;set;} } private static HttpClient _client = new HttpClient(); private static JsonSerializerOptions _options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, }; // _client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes("username:******************"))}"); _client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", $"Bearer your-personal-access-token"); public async Task<HttpResponseMessage> PostTime(string key, decimal hours, string start, string end) { var getIssueResponse = await _client.GetAsync($"https://jira.company.com/rest/api/latest/issue/{key}"); var getIssueString = await getIssueResponse.Content.ReadAsStringAsync(); var jiraIssue = JsonSerializer.Deserialize<JiraIssue>(getIssueString, _options); var payload = @"{ ""attributes"": {}, ""billableSeconds"": {time}, ""worker"": ""username"", ""started"": ""{start}"", ""timeSpentSeconds"": {time}, ""originTaskId"": ""{issue}"", ""remainingEstimate"": null, ""endDate"": ""{end}"" }" .Replace("{time}", (hours*3600).ToString()) .Replace("{issue}", jiraIssue.Id) .Replace("{start}", $"{start}T00:00:00.000") .Replace("{end}", $"{end}T00:00:00.000"); return await _client.PostAsync("https://jira.company.com/rest/tempo-timesheets/4/worklogs/", new StringContent(payload, Encoding.UTF8, "application/json")); } public async Task<HttpResponseMessage> PostTime(string key, decimal hours, DateTime start, DateTime end) => await PostTime(key, hours, start.ToString("yyyy-MM-dd"), end.ToString("yyyy-MM-dd")); private static string PTO = "PROJ-1001"; private static string ADMINISTRATIVE = "PROJ-1002"; private static string ExampleProject = "PROJ-1234"; public class TempoTime{ public string Key {get;set;} public decimal Hours {get;set;} } public class TempoDay { private int? end; public int Start {get;set;} public int End { get => end ?? Start; set => end = value; } public IEnumerable<TempoTime> Times {get;set;} } public class Tempo { public int Month {get;set;} public IEnumerable<TempoDay> Days {get;set;} } var full = new [] { new TempoTime { Key = ExampleProject, Hours = 8m }, }; display($"Full: {full.Sum(x => x.Hours)}"); var pto = new [] { new TempoTime{Key = PTO, Hours = 8} }; var halfPto = full .Union(pto) .Select(x => new TempoTime { Key = x.Key, Hours = x.Hours / 2}); var fourthPto = full.Select(x => new TempoTime { Key = x.Key, Hours = x.Hours * 0.75m }) .Union(pto.Select(x => new TempoTime { Key = x.Key, Hours = x.Hours * 0.25m })) ; var administrative = new [] { new TempoTime { Key = ADMINISTRATIVE, Hours = 8 } }; var halfAdministrative = full .Union(administrative) .Select(x => new TempoTime { Key = x.Key, Hours = x.Hours / 2}); var halfPTOhalfAdministrative = administrative .Union(pto) .Select(x => new TempoTime { Key = x.Key, Hours = x.Hours / 2}); var overTime = full .Select(x => new TempoTime { Key = x.Key, Hours = x.Hours * 1.5m }); var tempoMonth = new Tempo { Month = 7, Days = new []{ new TempoDay { Start=9, Times=full }, // new TempoDay { Start=2, Times=halfPto }, // new TempoDay { Start=3, Times=pto }, // new TempoDay { Start=25, Times=fourthPto }, // new TempoDay { Start=28, End=30, Times=full }, // new TempoDay { Start=21, End=23, Times=full }, // new TempoDay { Start=24, Times=halfPto }, // new TempoDay { Start=27, End=31, Times=full }, } }; var now = DateTime.Now; var timesToPost = tempoMonth.Days .SelectMany(day => day.Times, (day, time) => new { day, time }) .GroupBy(x => (x.time.Key, x.day.Start, x.day.End), (key, times) => new { key, times = times.Select(x => x.time.Hours) }) .OrderBy(x => x.key) .SelectMany(x => x.times, (x, time) => new { x.key, time }) .Select(x => new { Key = x.key.Key, Hours = x.time, Start = new DateTime(now.Year, tempoMonth.Month, x.key.Start), End = new DateTime(now.Year, tempoMonth.Month, x.key.End) }) .ToList(); timesToPost #!csharp var me = await _client.GetAsync($"https://jira.company.com/rest/api/latest/myself"); var me1 = await me.Content.ReadAsStringAsync(); me1 #!csharp var tasks = timesToPost .Select(x => PostTime(x.Key, x.Hours, x.Start, x.End)) .ToList(); var results = await Task.WhenAll(tasks); var responses = results .Select(x => new { x.StatusCode, Response = x.Content.ReadAsStringAsync().Result }); responses #!csharp // Root myDeserializedClass = JsonConvert.DeserializeObject<List<Root>>(myJsonResponse); public record Issue( string summary, string key ); public record Response( int? billableSeconds, string timeSpent, Issue issue, string started ); var deserializedResponses = responses .Select(r => new { r.StatusCode, Response = JsonSerializer.Deserialize<Response[]>(r.Response, _options) }); deserializedResponses.Take(2) #!csharp var formattedResponses = deserializedResponses .SelectMany(r => r.Response, (x, y) => new {x.StatusCode, y.issue.key, y.issue.summary, y.billableSeconds, y.timeSpent, y.started}); formattedResponses #!csharp var pivotTable = formattedResponses .GroupBy(r => r.key) .Select(g => new { Key = g.Key, Hours = formattedResponses .GroupBy(r => DateTime.Parse(r.started).Date) .OrderBy(d => d.Key) .ToDictionary( d => d.Key, d => g.Where(r => DateTime.Parse(r.started).Date == d.Key) .Sum(x => (x.billableSeconds ?? 0) / 3600.0) ) }) .OrderBy(x => x.Key); var dates = formattedResponses .Select(r => DateTime.Parse(r.started).Date) .Distinct() .OrderBy(d => d); // Calculate daily totals var dailyTotals = dates.ToDictionary( date => date, date => formattedResponses .Where(r => DateTime.Parse(r.started).Date == date) .Sum(x => (x.billableSeconds ?? 0) / 3600.0) ); var result = pivotTable.Select(row => new Dictionary<string, object>() { { "Key", row.Key }, }.Concat(dates.Select(date => new KeyValuePair<string, object>( date.ToString("MM/dd"), row.Hours.GetValueOrDefault(date, 0) ) )).ToDictionary(x => x.Key, x => x.Value)); var html = "<table border='1'><tr><th>Key</th>"; html += string.Join("", dates.Select(d => $"<th>{d:MM/dd}</th>")); html += "</tr>"; foreach (var row in result) { html += "<tr>"; html += $"<td>{row["Key"]}</td>"; foreach (var date in dates) { var value = row[date.ToString("MM/dd")]; html += $"<td>{value:F1}</td>"; } html += "</tr>"; } // Add totals row html += "<tr style='font-weight: bold'><td>Total</td>"; foreach (var date in dates) { html += $"<td>{dailyTotals[date]:F1}</td>"; } html += "</tr>"; html += "</table>"; display(HTML(html));

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/TRANZACT/tempo-filler-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server