182 lines
7.0 KiB
C#
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; }
|
|
}
|