Files
FsMcp/FsMcp/Tools/ConversationsTools.cs

182 lines
7.0 KiB
C#

using System.ComponentModel;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using FsMcp;
using ModelContextProtocol.Server;
namespace FsMcp.Tools;
internal sealed class ConversationsTools
{
private readonly FoodsharingApiClient _apiClient;
public ConversationsTools(FoodsharingApiClient apiClient)
{
_apiClient = apiClient;
}
[McpServerTool]
[Description(@"Get the list of conversations for the current user from GET /api/conversations.
Conversations with titles beginning with 'Team' are very likely store-chats. Conversations titled
with a first name are very likely private conversations with other users. The conversation title is
not necessarily the same as the store name or user name, though.")]
public async Task<ConversationsResponse> GetConversationsAsync(
[Description("Maximum number of results to return.")] int? limit = null,
[Description("The 0-based index of the first entry that is included.")] int? offset = null)
{
await _apiClient.EnsureLoginAsync();
var response = await _apiClient.HttpClient.GetAsync(Endpoints.Conversations(limit, offset));
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
throw new InvalidOperationException($"API request failed with status {response.StatusCode}: {content}");
}
var result = await response.Content.ReadFromJsonAsync<ConversationsResponse>();
return result ?? new ConversationsResponse();
}
[McpServerTool]
[Description("Get a conversation including some messages from GET /api/conversations/{conversationId}.")]
public async Task<ConversationResponse> GetConversationAsync(
[Description("Conversation ID as positive integer (OpenAPI path pattern: [1-9][0-9]*).")] int conversationId,
[Description("Maximum number of results to return.")] int? limit = null,
[Description("Set to true to mark the conversation as read.")] bool? markAsRead = null)
{
if (conversationId <= 0) throw new ArgumentOutOfRangeException(nameof(conversationId), "conversationId must be positive.");
await _apiClient.EnsureLoginAsync();
var response = await _apiClient.HttpClient.GetAsync(Endpoints.Conversation(conversationId, limit, markAsRead));
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
throw new InvalidOperationException($"API request failed with status {response.StatusCode}: {content}");
}
var result = await response.Content.ReadFromJsonAsync<ConversationResponse>();
return result ?? new ConversationResponse();
}
[McpServerTool]
[Description("Get messages from a conversation from GET /api/conversations/{conversationId}/messages.")]
public async Task<MessageCollection> GetConversationMessagesAsync(
[Description("Conversation ID as positive integer (OpenAPI path pattern: [1-9][0-9]*).")] int conversationId,
[Description("ID of a message to get older messages than this one.")] int? olderThanId = null,
[Description("Maximum number of results to return.")] int? limit = null)
{
if (conversationId <= 0) throw new ArgumentOutOfRangeException(nameof(conversationId), "conversationId must be positive.");
await _apiClient.EnsureLoginAsync();
var response = await _apiClient.HttpClient.GetAsync(Endpoints.ConversationMessages(conversationId, olderThanId, limit));
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
throw new InvalidOperationException($"API request failed with status {response.StatusCode}: {content}");
}
var result = await response.Content.ReadFromJsonAsync<MessageCollection>();
return result ?? new MessageCollection();
}
}
public sealed record ConversationsResponse
{
[Description("List of conversations.")]
[JsonPropertyName("conversations")]
public IReadOnlyList<Conversation> Conversations { get; init; } = Array.Empty<Conversation>();
[Description("List of user profiles referenced in the conversations.")]
[JsonPropertyName("profiles")]
public IReadOnlyList<ConversationProfile> Profiles { get; init; } = Array.Empty<ConversationProfile>();
}
public sealed record ConversationResponse
{
[Description("The conversation object.")]
[JsonPropertyName("conversation")]
public Conversation? Conversation { get; init; }
[Description("List of user profiles referenced in this conversation.")]
[JsonPropertyName("profiles")]
public IReadOnlyList<ConversationProfile> Profiles { get; init; } = Array.Empty<ConversationProfile>();
}
public sealed record MessageCollection
{
[Description("List of messages.")]
[JsonPropertyName("messages")]
public IReadOnlyList<Message> Messages { get; init; } = Array.Empty<Message>();
[Description("List of user profiles referenced in the messages.")]
[JsonPropertyName("profiles")]
public IReadOnlyList<ConversationProfile> Profiles { get; init; } = Array.Empty<ConversationProfile>();
}
public sealed record Conversation
{
[Description("Unique identifier of the conversation.")]
[JsonPropertyName("id")]
public int Id { get; init; }
[Description("Title of the conversation.")]
[JsonPropertyName("title")]
public string? Title { get; init; }
[Description("Store ID associated with the conversation, if any.")]
[JsonPropertyName("storeId")]
public int? StoreId { get; init; }
[Description("Number of unread messages.")]
[JsonPropertyName("unreadMessages")]
public int? UnreadMessages { get; init; }
[Description("The latest message in the conversation.")]
[JsonPropertyName("lastMessage")]
public Message? LastMessage { get; init; }
[Description("Messages in the conversation.")]
[JsonPropertyName("messages")]
public IReadOnlyList<Message> Messages { get; init; } = Array.Empty<Message>();
}
public sealed record Message
{
[Description("Unique identifier of the message.")]
[JsonPropertyName("id")]
public int Id { get; init; }
[Description("Body of the message.")]
[JsonPropertyName("body")]
public string? Body { get; init; }
[Description("Timestamp when the message was sent.")]
[JsonPropertyName("sentAt")]
public DateTimeOffset? SentAt { get; init; }
[Description("ID of the author of the message.")]
[JsonPropertyName("authorId")]
public int? AuthorId { get; init; }
}
public sealed record ConversationProfile
{
[Description("ID of the user.")]
[JsonPropertyName("id")]
public int Id { get; init; }
[Description("Name of the user.")]
[JsonPropertyName("name")]
public string? Name { get; init; }
[Description("Avatar path of the user.")]
[JsonPropertyName("avatar")]
public string? Avatar { get; init; }
[Description("Whether the user is currently using the sleeping hat function.")]
[JsonPropertyName("isSleeping")]
public bool? IsSleeping { get; init; }
}