Initial import of FsMcp project, ignore mcp.json, add mcp.example.json
This commit is contained in:
24
.gitignore
vendored
24
.gitignore
vendored
@@ -12,3 +12,27 @@
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
# Build outputs
|
||||
**/bin/
|
||||
**/obj/
|
||||
|
||||
# User-specific files
|
||||
*.user
|
||||
*.suo
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# MCP local credentials/config
|
||||
mcp.json
|
||||
.mcp.json
|
||||
.vscode/mcp.json
|
||||
|
||||
# Rider
|
||||
.idea/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
3
FsMcp.slnx
Normal file
3
FsMcp.slnx
Normal file
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="FsMcp/FsMcp.csproj" />
|
||||
</Solution>
|
||||
22
FsMcp/.mcp/server.json
Normal file
22
FsMcp/.mcp/server.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
|
||||
"description": "<your description here>",
|
||||
"name": "io.github.<your GitHub username here>/<your repo name>",
|
||||
"version": "0.1.0-beta",
|
||||
"packages": [
|
||||
{
|
||||
"registryType": "nuget",
|
||||
"identifier": "<your package ID here>",
|
||||
"version": "0.1.0-beta",
|
||||
"transport": {
|
||||
"type": "stdio"
|
||||
},
|
||||
"packageArguments": [],
|
||||
"environmentVariables": []
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"url": "https://github.com/<your GitHub username here>/<your repo name>",
|
||||
"source": "github"
|
||||
}
|
||||
}
|
||||
9
FsMcp/Endpoints.cs
Normal file
9
FsMcp/Endpoints.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FsMcp;
|
||||
|
||||
public static class Endpoints
|
||||
{
|
||||
public static string UserLogin => $"{ApiBase}/api/login";
|
||||
public static string UserCurrentDetails => $"{ApiBase}/api/users/current/details";
|
||||
|
||||
private static string ApiBase => "https://beta.foodsharing.de";
|
||||
}
|
||||
103
FsMcp/FoodsharingApiClient.cs
Normal file
103
FsMcp/FoodsharingApiClient.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace FsMcp;
|
||||
|
||||
internal sealed class FoodsharingApiClient
|
||||
{
|
||||
private readonly SemaphoreSlim _loginLock = new(1, 1);
|
||||
|
||||
public HttpClient HttpClient { get; } = new(new RequestThrottleHandler(TimeSpan.FromMilliseconds(200)));
|
||||
|
||||
public async Task EnsureLoginAsync()
|
||||
{
|
||||
if (HttpClient.DefaultRequestHeaders.Contains("X-CSRF-Token"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _loginLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (HttpClient.DefaultRequestHeaders.Contains("X-CSRF-Token"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string username = Environment.GetEnvironmentVariable("USERNAME") ?? throw new InvalidOperationException("USERNAME environment variable is not set.");
|
||||
string password = Environment.GetEnvironmentVariable("PASSWORD") ?? throw new InvalidOperationException("PASSWORD environment variable is not set.");
|
||||
|
||||
var payload = new
|
||||
{
|
||||
email = username,
|
||||
password,
|
||||
remember_me = true
|
||||
};
|
||||
|
||||
using var response = await HttpClient.PostAsJsonAsync(Endpoints.UserLogin, payload);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
throw new InvalidOperationException($"Login failed ({(int)response.StatusCode} {response.ReasonPhrase}): {responseBody}");
|
||||
}
|
||||
|
||||
if (!response.Headers.TryGetValues("Set-Cookie", out var setCookieValues))
|
||||
{
|
||||
throw new InvalidOperationException("Set-Cookie headers are missing from login response.");
|
||||
}
|
||||
|
||||
string setCookieHeader = string.Join(";", setCookieValues);
|
||||
string? csrfToken = setCookieHeader
|
||||
.Split(';', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(cookie => cookie.Trim())
|
||||
.FirstOrDefault(cookie => cookie.StartsWith("FS_CSRF_TOKEN="))
|
||||
?.Split('=', 2)[1];
|
||||
|
||||
if (string.IsNullOrWhiteSpace(csrfToken))
|
||||
{
|
||||
throw new InvalidOperationException("CSRF token not found in Set-Cookie header.");
|
||||
}
|
||||
|
||||
HttpClient.DefaultRequestHeaders.Remove("X-CSRF-Token");
|
||||
HttpClient.DefaultRequestHeaders.Add("X-CSRF-Token", csrfToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loginLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RequestThrottleHandler : DelegatingHandler
|
||||
{
|
||||
private readonly TimeSpan _minimumInterval;
|
||||
private readonly SemaphoreSlim _requestGate = new(1, 1);
|
||||
private DateTimeOffset _lastRequestStartedAt = DateTimeOffset.MinValue;
|
||||
|
||||
public RequestThrottleHandler(TimeSpan minimumInterval)
|
||||
: base(new HttpClientHandler())
|
||||
{
|
||||
_minimumInterval = minimumInterval;
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
await _requestGate.WaitAsync(cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
var waitFor = _lastRequestStartedAt + _minimumInterval - DateTimeOffset.UtcNow;
|
||||
if (waitFor > TimeSpan.Zero)
|
||||
{
|
||||
await Task.Delay(waitFor, cancellationToken);
|
||||
}
|
||||
|
||||
_lastRequestStartedAt = DateTimeOffset.UtcNow;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_requestGate.Release();
|
||||
}
|
||||
|
||||
return await base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
40
FsMcp/FsMcp.csproj
Normal file
40
FsMcp/FsMcp.csproj
Normal file
@@ -0,0 +1,40 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RuntimeIdentifiers>win-x64;win-arm64;osx-arm64;linux-x64;linux-arm64;linux-musl-x64</RuntimeIdentifiers>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<!-- Set up the NuGet package to be an MCP server -->
|
||||
<PackAsTool>true</PackAsTool>
|
||||
<PackageType>McpServer</PackageType>
|
||||
|
||||
<!-- Set up the MCP server to be a self-contained application that does not rely on a shared framework -->
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSelfContained>true</PublishSelfContained>
|
||||
|
||||
<!-- Set up the MCP server to be a single file executable -->
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
|
||||
<!-- Set recommended package metadata -->
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageId>SampleMcpServer</PackageId>
|
||||
<PackageVersion>0.1.0-beta</PackageVersion>
|
||||
<PackageTags>AI; MCP; server; stdio</PackageTags>
|
||||
<Description>An MCP server using the MCP C# SDK.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Include additional files for browsing the MCP server. -->
|
||||
<ItemGroup>
|
||||
<None Include=".mcp\server.json" Pack="true" PackagePath="/.mcp/" />
|
||||
<None Include="README.md" Pack="true" PackagePath="/" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
|
||||
<PackageReference Include="ModelContextProtocol" Version="0.7.0-preview.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
19
FsMcp/Program.cs
Normal file
19
FsMcp/Program.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using FsMcp;
|
||||
|
||||
var builder = Host.CreateApplicationBuilder(args);
|
||||
|
||||
// Configure all logs to go to stderr (stdout is used for the MCP protocol messages).
|
||||
builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace);
|
||||
builder.Services.AddSingleton<FoodsharingApiClient>();
|
||||
|
||||
// Add the MCP services: the transport to use (stdio) and the tools to register.
|
||||
builder.Services
|
||||
.AddMcpServer()
|
||||
.WithStdioServerTransport()
|
||||
.WithTools<RandomNumberTools>()
|
||||
.WithTools<CurrentUserTools>();
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
115
FsMcp/README.md
Normal file
115
FsMcp/README.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# MCP Server
|
||||
|
||||
This README was created using the C# MCP server project template.
|
||||
It demonstrates how you can easily create an MCP server using C# and publish it as a NuGet package.
|
||||
|
||||
The MCP server is built as a self-contained application and does not require the .NET runtime to be installed on the target machine.
|
||||
However, since it is self-contained, it must be built for each target platform separately.
|
||||
By default, the template is configured to build for:
|
||||
* `win-x64`
|
||||
* `win-arm64`
|
||||
* `osx-arm64`
|
||||
* `linux-x64`
|
||||
* `linux-arm64`
|
||||
* `linux-musl-x64`
|
||||
|
||||
If your users require more platforms to be supported, update the list of runtime identifiers in the project's `<RuntimeIdentifiers />` element.
|
||||
|
||||
See [aka.ms/nuget/mcp/guide](https://aka.ms/nuget/mcp/guide) for the full guide.
|
||||
|
||||
Please note that this template is currently in an early preview stage. If you have feedback, please take a [brief survey](http://aka.ms/dotnet-mcp-template-survey).
|
||||
|
||||
## Checklist before publishing to NuGet.org
|
||||
|
||||
- Test the MCP server locally using the steps below.
|
||||
- Update the package metadata in the .csproj file, in particular the `<PackageId>`.
|
||||
- Update `.mcp/server.json` to declare your MCP server's inputs.
|
||||
- See [configuring inputs](https://aka.ms/nuget/mcp/guide/configuring-inputs) for more details.
|
||||
- Pack the project using `dotnet pack`.
|
||||
|
||||
The `bin/Release` directory will contain the package file (.nupkg), which can be [published to NuGet.org](https://learn.microsoft.com/nuget/nuget-org/publish-a-package).
|
||||
|
||||
## Developing locally
|
||||
|
||||
To test this MCP server from source code (locally) without using a built MCP server package, you can configure your IDE to run the project directly using `dotnet run`.
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"FsMcp": {
|
||||
"type": "stdio",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"run",
|
||||
"--project",
|
||||
"<PATH TO PROJECT DIRECTORY>"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Refer to the VS Code or Visual Studio documentation for more information on configuring and using MCP servers:
|
||||
|
||||
- [Use MCP servers in VS Code (Preview)](https://code.visualstudio.com/docs/copilot/chat/mcp-servers)
|
||||
- [Use MCP servers in Visual Studio (Preview)](https://learn.microsoft.com/visualstudio/ide/mcp-servers)
|
||||
|
||||
## Testing the MCP Server
|
||||
|
||||
Once configured, you can ask Copilot Chat for a random number, for example, `Give me 3 random numbers`. It should prompt you to use the `get_random_number` tool on the `FsMcp` MCP server and show you the results.
|
||||
|
||||
### Current user profile tool
|
||||
|
||||
This server also exposes `get_current_user_details`.
|
||||
|
||||
- Purpose: Returns full details for the currently authenticated user from `GET /api/users/current/details`.
|
||||
- Auth: Uses `USERNAME` and `PASSWORD` environment variables to log in and then sends the `X-CSRF-Token` header.
|
||||
- Output: Structured profile object including:
|
||||
- identity and account status (`id`, `foodsaver`, `isVerified`, `role`, `isSleeping`)
|
||||
- primary region data (`regionId`, `regionName`) and all memberships (`regions`, `groups`)
|
||||
- profile/contact fields (`firstname`, `lastname`, `email`, `address`, `postcode`, `city`, `landline`, `mobile`, `birthday`, `photo`)
|
||||
- activity/pass data (`stats.weight`, `stats.count`, `lastPassDate`, `lastPassUntilValid`, `lastPassUntilValidInDays`)
|
||||
- permission flags (`permissions.*`)
|
||||
- coordinates (`coordinates.lat`, `coordinates.lon`)
|
||||
|
||||
The tool is strongly typed in `Tools/CurrentUserTools.cs`, where each field is documented with `Description` attributes so MCP clients can surface schema-aware help.
|
||||
|
||||
## Publishing to NuGet.org
|
||||
|
||||
1. Run `dotnet pack -c Release` to create the NuGet package
|
||||
2. Publish to NuGet.org with `dotnet nuget push bin/Release/*.nupkg --api-key <your-api-key> --source https://api.nuget.org/v3/index.json`
|
||||
|
||||
## Using the MCP Server from NuGet.org
|
||||
|
||||
Once the MCP server package is published to NuGet.org, you can configure it in your preferred IDE. Both VS Code and Visual Studio use the `dnx` command to download and install the MCP server package from NuGet.org.
|
||||
|
||||
- **VS Code**: Create a `<WORKSPACE DIRECTORY>/.vscode/mcp.json` file
|
||||
- **Visual Studio**: Create a `<SOLUTION DIRECTORY>\.mcp.json` file
|
||||
|
||||
For both VS Code and Visual Studio, the configuration file uses the following server definition:
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"FsMcp": {
|
||||
"type": "stdio",
|
||||
"command": "dnx",
|
||||
"args": [
|
||||
"<your package ID here>",
|
||||
"--version",
|
||||
"<your package version here>",
|
||||
"--yes"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## More information
|
||||
|
||||
.NET MCP servers use the [ModelContextProtocol](https://www.nuget.org/packages/ModelContextProtocol) C# SDK. For more information about MCP:
|
||||
|
||||
- [Official Documentation](https://modelcontextprotocol.io/)
|
||||
- [Protocol Specification](https://spec.modelcontextprotocol.io/)
|
||||
- [GitHub Organization](https://github.com/modelcontextprotocol)
|
||||
- [MCP C# SDK](https://modelcontextprotocol.github.io/csharp-sdk)
|
||||
263
FsMcp/Tools/CurrentUserTools.cs
Normal file
263
FsMcp/Tools/CurrentUserTools.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
using System.ComponentModel;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using FsMcp;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
internal class CurrentUserTools
|
||||
{
|
||||
private readonly FoodsharingApiClient _apiClient;
|
||||
|
||||
public CurrentUserTools(FoodsharingApiClient apiClient)
|
||||
{
|
||||
_apiClient = apiClient;
|
||||
}
|
||||
|
||||
[McpServerTool]
|
||||
[Description("Returns full profile details for the currently authenticated foodsharing user from /api/users/current/details, including identity, verification, membership regions/groups, permissions, contact data, coordinates, pass status, and activity stats.")]
|
||||
public async Task<CurrentUserDetails> GetCurrentUserDetailsAsync()
|
||||
{
|
||||
await _apiClient.EnsureLoginAsync();
|
||||
|
||||
var userDetails = await _apiClient.HttpClient.GetFromJsonAsync<CurrentUserDetails>(Endpoints.UserCurrentDetails);
|
||||
return userDetails ?? throw new InvalidOperationException("Current user details response was empty.");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record CurrentUserDetails
|
||||
{
|
||||
[Description("Unique user ID.")]
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; init; }
|
||||
|
||||
[Description("True if the account is a foodsaver account.")]
|
||||
[JsonPropertyName("foodsaver")]
|
||||
public bool Foodsaver { get; init; }
|
||||
|
||||
[Description("True if the account is verified.")]
|
||||
[JsonPropertyName("isVerified")]
|
||||
public bool IsVerified { get; init; }
|
||||
|
||||
[Description("Primary region ID of the user.")]
|
||||
[JsonPropertyName("regionId")]
|
||||
public int RegionId { get; init; }
|
||||
|
||||
[Description("True if sleep mode is currently enabled for the user.")]
|
||||
[JsonPropertyName("isSleeping")]
|
||||
public bool IsSleeping { get; init; }
|
||||
|
||||
[Description("Display name of the user's primary region.")]
|
||||
[JsonPropertyName("regionName")]
|
||||
public string RegionName { get; init; } = string.Empty;
|
||||
|
||||
[Description("Public about-me text visible to other users.")]
|
||||
[JsonPropertyName("aboutMePublic")]
|
||||
public string AboutMePublic { get; init; } = string.Empty;
|
||||
|
||||
[Description("Mailbox ID for internal messaging.")]
|
||||
[JsonPropertyName("mailboxId")]
|
||||
public int MailboxId { get; init; }
|
||||
|
||||
[Description("True if an iCal calendar token exists for the user.")]
|
||||
[JsonPropertyName("hasCalendarToken")]
|
||||
public bool HasCalendarToken { get; init; }
|
||||
|
||||
[Description("User first name.")]
|
||||
[JsonPropertyName("firstname")]
|
||||
public string Firstname { get; init; } = string.Empty;
|
||||
|
||||
[Description("User last name.")]
|
||||
[JsonPropertyName("lastname")]
|
||||
public string Lastname { get; init; } = string.Empty;
|
||||
|
||||
[Description("Gender value as provided by API (numeric enum).")]
|
||||
[JsonPropertyName("gender")]
|
||||
public int Gender { get; init; }
|
||||
|
||||
[Description("Path to profile photo resource.")]
|
||||
[JsonPropertyName("photo")]
|
||||
public string Photo { get; init; } = string.Empty;
|
||||
|
||||
[Description("Date/time of last successful pass completion (ISO-8601 timestamp).")]
|
||||
[JsonPropertyName("lastPassDate")]
|
||||
public DateTimeOffset? LastPassDate { get; init; }
|
||||
|
||||
[Description("Date/time until pass validity remains active (ISO-8601 timestamp).")]
|
||||
[JsonPropertyName("lastPassUntilValid")]
|
||||
public DateTimeOffset? LastPassUntilValid { get; init; }
|
||||
|
||||
[Description("Remaining validity duration of the pass in whole days.")]
|
||||
[JsonPropertyName("lastPassUntilValidInDays")]
|
||||
public int? LastPassUntilValidInDays { get; init; }
|
||||
|
||||
[Description("User contribution statistics (weight and count).")]
|
||||
[JsonPropertyName("stats")]
|
||||
public UserStats? Stats { get; init; }
|
||||
|
||||
[Description("Permission flags for profile editing and administrative capabilities.")]
|
||||
[JsonPropertyName("permissions")]
|
||||
public UserPermissions? Permissions { get; init; }
|
||||
|
||||
[Description("True if an active email address is currently configured.")]
|
||||
[JsonPropertyName("hasActiveEmail")]
|
||||
public bool HasActiveEmail { get; init; }
|
||||
|
||||
[Description("Home coordinates of the user profile.")]
|
||||
[JsonPropertyName("coordinates")]
|
||||
public GeoCoordinates? Coordinates { get; init; }
|
||||
|
||||
[Description("Street address.")]
|
||||
[JsonPropertyName("address")]
|
||||
public string Address { get; init; } = string.Empty;
|
||||
|
||||
[Description("City name.")]
|
||||
[JsonPropertyName("city")]
|
||||
public string City { get; init; } = string.Empty;
|
||||
|
||||
[Description("Postal code.")]
|
||||
[JsonPropertyName("postcode")]
|
||||
public string Postcode { get; init; } = string.Empty;
|
||||
|
||||
[Description("Email address.")]
|
||||
[JsonPropertyName("email")]
|
||||
public string Email { get; init; } = string.Empty;
|
||||
|
||||
[Description("Landline phone number.")]
|
||||
[JsonPropertyName("landline")]
|
||||
public string Landline { get; init; } = string.Empty;
|
||||
|
||||
[Description("Mobile phone number.")]
|
||||
[JsonPropertyName("mobile")]
|
||||
public string Mobile { get; init; } = string.Empty;
|
||||
|
||||
[Description("Birthday date/time as provided by API (ISO-8601 timestamp).")]
|
||||
[JsonPropertyName("birthday")]
|
||||
public DateTimeOffset? Birthday { get; init; }
|
||||
|
||||
[Description("Internal about-me text visible to authorized roles.")]
|
||||
[JsonPropertyName("aboutMeIntern")]
|
||||
public string AboutMeIntern { get; init; } = string.Empty;
|
||||
|
||||
[Description("Role identifier as provided by API (numeric enum).")]
|
||||
[JsonPropertyName("role")]
|
||||
public int Role { get; init; }
|
||||
|
||||
[Description("All regions the user is a member of, with classification and responsibility status.")]
|
||||
[JsonPropertyName("regions")]
|
||||
public IReadOnlyList<UserRegionMembership> Regions { get; init; } = [];
|
||||
|
||||
[Description("All groups the user belongs to with responsibility status.")]
|
||||
[JsonPropertyName("groups")]
|
||||
public IReadOnlyList<UserGroupMembership> Groups { get; init; } = [];
|
||||
|
||||
[Description("Optional position or role title text.")]
|
||||
[JsonPropertyName("position")]
|
||||
public string Position { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed record UserStats
|
||||
{
|
||||
[Description("Total saved food weight in kilograms.")]
|
||||
[JsonPropertyName("weight")]
|
||||
public double Weight { get; init; }
|
||||
|
||||
[Description("Total number of counted activities/pickups.")]
|
||||
[JsonPropertyName("count")]
|
||||
public int Count { get; init; }
|
||||
}
|
||||
|
||||
public sealed record UserPermissions
|
||||
{
|
||||
[Description("May edit own user profile.")]
|
||||
[JsonPropertyName("mayEditUserProfile")]
|
||||
public bool MayEditUserProfile { get; init; }
|
||||
|
||||
[Description("May administrate other user profiles.")]
|
||||
[JsonPropertyName("mayAdministrateUserProfile")]
|
||||
public bool MayAdministrateUserProfile { get; init; }
|
||||
|
||||
[Description("May administrate blog posts.")]
|
||||
[JsonPropertyName("administrateBlog")]
|
||||
public bool AdministrateBlog { get; init; }
|
||||
|
||||
[Description("May edit quizzes.")]
|
||||
[JsonPropertyName("editQuiz")]
|
||||
public bool EditQuiz { get; init; }
|
||||
|
||||
[Description("May handle reports.")]
|
||||
[JsonPropertyName("handleReports")]
|
||||
public bool HandleReports { get; init; }
|
||||
|
||||
[Description("May add stores.")]
|
||||
[JsonPropertyName("addStore")]
|
||||
public bool AddStore { get; init; }
|
||||
|
||||
[Description("May edit static content.")]
|
||||
[JsonPropertyName("editContent")]
|
||||
public bool EditContent { get; init; }
|
||||
|
||||
[Description("May administrate newsletter email settings.")]
|
||||
[JsonPropertyName("administrateNewsletterEmail")]
|
||||
public bool AdministrateNewsletterEmail { get; init; }
|
||||
|
||||
[Description("May administrate regions.")]
|
||||
[JsonPropertyName("administrateRegions")]
|
||||
public bool AdministrateRegions { get; init; }
|
||||
|
||||
[Description("May perform global search.")]
|
||||
[JsonPropertyName("maySearchGlobal")]
|
||||
public bool MaySearchGlobal { get; init; }
|
||||
|
||||
[Description("May edit store categories.")]
|
||||
[JsonPropertyName("editStoreCategories")]
|
||||
public bool EditStoreCategories { get; init; }
|
||||
|
||||
[Description("May edit resource categories.")]
|
||||
[JsonPropertyName("editResourceCategories")]
|
||||
public bool EditResourceCategories { get; init; }
|
||||
}
|
||||
|
||||
public sealed record GeoCoordinates
|
||||
{
|
||||
[Description("Latitude in decimal degrees.")]
|
||||
[JsonPropertyName("lat")]
|
||||
public double Lat { get; init; }
|
||||
|
||||
[Description("Longitude in decimal degrees.")]
|
||||
[JsonPropertyName("lon")]
|
||||
public double Lon { get; init; }
|
||||
}
|
||||
|
||||
public sealed record UserRegionMembership
|
||||
{
|
||||
[Description("Region ID.")]
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; init; }
|
||||
|
||||
[Description("Region display name.")]
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[Description("Region classification numeric value from API.")]
|
||||
[JsonPropertyName("classification")]
|
||||
public int Classification { get; init; }
|
||||
|
||||
[Description("True if the user has responsible role in this region.")]
|
||||
[JsonPropertyName("isResponsible")]
|
||||
public bool IsResponsible { get; init; }
|
||||
}
|
||||
|
||||
public sealed record UserGroupMembership
|
||||
{
|
||||
[Description("Group ID.")]
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; init; }
|
||||
|
||||
[Description("Group display name.")]
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
[Description("True if the user has responsible role in this group.")]
|
||||
[JsonPropertyName("isResponsible")]
|
||||
public bool IsResponsible { get; init; }
|
||||
}
|
||||
51
FsMcp/Tools/RandomNumberTools.cs
Normal file
51
FsMcp/Tools/RandomNumberTools.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.ComponentModel;
|
||||
using FsMcp;
|
||||
using ModelContextProtocol.Server;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Sample MCP tools for demonstration purposes.
|
||||
/// These tools can be invoked by MCP clients to perform various operations.
|
||||
/// </summary>
|
||||
internal class RandomNumberTools
|
||||
{
|
||||
private readonly FoodsharingApiClient _apiClient;
|
||||
|
||||
public RandomNumberTools(FoodsharingApiClient apiClient)
|
||||
{
|
||||
_apiClient = apiClient;
|
||||
}
|
||||
|
||||
// Mcp Server tool to get the crsf token for the current session (for demonstration purposes).
|
||||
[McpServerTool]
|
||||
[Description("Gets the CSRF token for the current session.")]
|
||||
public async Task<string> GetCsrfTokenAsync()
|
||||
{
|
||||
await _apiClient.EnsureLoginAsync();
|
||||
return _apiClient.HttpClient.DefaultRequestHeaders.GetValues("X-CSRF-Token").FirstOrDefault() ?? throw new InvalidOperationException("CSRF token not found.");
|
||||
}
|
||||
|
||||
[McpServerTool]
|
||||
[Description("Generates a random number between the specified minimum and maximum values.")]
|
||||
public int GetRandomNumber(
|
||||
[Description("Minimum value (inclusive)")] int min = 0,
|
||||
[Description("Maximum value (exclusive)")] int max = 100)
|
||||
{
|
||||
return Random.Shared.Next(min, max);
|
||||
}
|
||||
|
||||
[McpServerTool]
|
||||
[Description("Generates a random Lorem Ipsum text with the specified number of words.")]
|
||||
public string GetLoremIpsum(int wordCount = 10)
|
||||
{
|
||||
string[] loremIpsumWords = new[]
|
||||
{
|
||||
"Lorem", "ipsum", "dolor", "sit", "amet", "consectetur",
|
||||
"adipiscing", "elit", "sed", "do", "eiusmod", "tempor",
|
||||
"incididunt", "ut", "labore", "et", "dolore", "magna",
|
||||
"aliqua"
|
||||
};
|
||||
|
||||
return string.Join(" ", Enumerable.Range(0, wordCount).Select(_ => loremIpsumWords[Random.Shared.Next(loremIpsumWords.Length)]));
|
||||
}
|
||||
}
|
||||
18070
fsopenapi.json
Normal file
18070
fsopenapi.json
Normal file
File diff suppressed because it is too large
Load Diff
19
mcp.example.json
Normal file
19
mcp.example.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"servers": {
|
||||
"FsMcp": {
|
||||
"type": "stdio",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"run",
|
||||
"--project",
|
||||
"FsMcp/FsMcp.csproj",
|
||||
"-c",
|
||||
"Debug"
|
||||
],
|
||||
"env": {
|
||||
"USERNAME": "your-foodsharing-username",
|
||||
"PASSWORD": "your-foodsharing-password"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user