microsoft/teams.net
Publicmirrored fromhttps://github.com/microsoft/teams.netAvailable
core/samples/McpServer/GraphClient.cs
67lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | using System.Net.Http.Headers; |
| 5 | using System.Net.Http.Json; |
| 6 | using System.Text.Json.Serialization; |
| 7 | using Azure.Core; |
| 8 | using Azure.Identity; |
| 9 | |
| 10 | namespace McpServer; |
| 11 | |
| 12 | // App-only Microsoft Graph client. Reuses the bot's AzureAd:TenantId / ClientId / |
| 13 | // ClientCredentials[0]:ClientSecret to acquire a token for graph.microsoft.com, |
| 14 | // then calls /users with $search. Requires User.ReadBasic.All (Application) consent. |
| 15 | public sealed class GraphClient |
| 16 | { |
| 17 | private static readonly TokenRequestContext TokenContext = new(["https://graph.microsoft.com/.default"]); |
| 18 | private readonly TokenCredential _credential; |
| 19 | private readonly HttpClient _http; |
| 20 | |
| 21 | public GraphClient(IConfiguration config, HttpClient http) |
| 22 | { |
| 23 | string tenantId = config["AzureAd:TenantId"] |
| 24 | ?? throw new InvalidOperationException("AzureAd:TenantId is not configured."); |
| 25 | string clientId = config["AzureAd:ClientId"] |
| 26 | ?? throw new InvalidOperationException("AzureAd:ClientId is not configured."); |
| 27 | string clientSecret = config["AzureAd:ClientCredentials:0:ClientSecret"] |
| 28 | ?? throw new InvalidOperationException("AzureAd:ClientCredentials:0:ClientSecret is not configured."); |
| 29 | |
| 30 | _credential = new ClientSecretCredential(tenantId, clientId, clientSecret); |
| 31 | _http = http; |
| 32 | } |
| 33 | |
| 34 | public async Task<IReadOnlyList<UserMatch>> SearchUsersAsync( |
| 35 | string query, int top, CancellationToken cancellationToken) |
| 36 | { |
| 37 | AccessToken token = await _credential.GetTokenAsync(TokenContext, cancellationToken); |
| 38 | |
| 39 | string search = $"\"displayName:{query}\" OR \"userPrincipalName:{query}\""; |
| 40 | string url = "https://graph.microsoft.com/v1.0/users" |
| 41 | + $"?$search={Uri.EscapeDataString(search)}" |
| 42 | + "&$select=id,displayName,userPrincipalName" |
| 43 | + $"&$top={top}"; |
| 44 | |
| 45 | using HttpRequestMessage req = new(HttpMethod.Get, url); |
| 46 | req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Token); |
| 47 | req.Headers.Add("ConsistencyLevel", "eventual"); |
| 48 | |
| 49 | using HttpResponseMessage resp = await _http.SendAsync(req, cancellationToken); |
| 50 | resp.EnsureSuccessStatusCode(); |
| 51 | |
| 52 | GraphUsersResponse? body = await resp.Content.ReadFromJsonAsync<GraphUsersResponse>( |
| 53 | cancellationToken: cancellationToken); |
| 54 | |
| 55 | return body?.Value |
| 56 | .Select(u => new UserMatch(u.Id, u.DisplayName, u.UserPrincipalName)) |
| 57 | .ToArray() ?? []; |
| 58 | } |
| 59 | |
| 60 | private sealed record GraphUser( |
| 61 | [property: JsonPropertyName("id")] string Id, |
| 62 | [property: JsonPropertyName("displayName")] string? DisplayName, |
| 63 | [property: JsonPropertyName("userPrincipalName")] string? UserPrincipalName); |
| 64 | |
| 65 | private sealed record GraphUsersResponse( |
| 66 | [property: JsonPropertyName("value")] List<GraphUser> Value); |
| 67 | } |
| 68 | |