mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
feat(mcp): add Microsoft 365 (Graph) server to the MCP catalog (#43099)
* feat(mcp): add Microsoft 365 (Graph) server to the MCP catalog * fix(mcp): signpost the self-hosted Microsoft 365 URL and pin the catalog entry in tests * test(mcp): pin both shipped copies of a catalog icon to the same bytes --------- Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
This commit is contained in:
parent
d729f975aa
commit
b9e71e990a
4 changed files with 47 additions and 1 deletions
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-label="Microsoft 365"><rect x="1" y="1" width="10.5" height="10.5" fill="#F25022"/><rect x="12.5" y="1" width="10.5" height="10.5" fill="#7FBA00"/><rect x="1" y="12.5" width="10.5" height="10.5" fill="#00A4EF"/><rect x="12.5" y="12.5" width="10.5" height="10.5" fill="#FFB900"/></svg>
|
||||
|
After Width: | Height: | Size: 361 B |
|
|
@ -217,6 +217,17 @@
|
|||
{"name": "GOOGLE_CLIENT_SECRET", "description": "Google OAuth Client Secret", "secret": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "microsoft_365",
|
||||
"title": "Microsoft 365 (Graph)",
|
||||
"description": "Outlook mail and calendar, OneDrive and SharePoint files, and Teams through Microsoft Graph, with each user's own Entra ID sign-in. Self-hosted: run ms-365-mcp-server next to the proxy and point the URL at it",
|
||||
"icon_url": "/ui/assets/logos/microsoft_365.svg",
|
||||
"category": "Productivity",
|
||||
"registry_url": "https://registry.modelcontextprotocol.io/servers/io.github.Softeria%2Fms-365-mcp-server",
|
||||
"transport": "http",
|
||||
"url": "http://localhost:3000/mcp",
|
||||
"env_vars": []
|
||||
},
|
||||
{
|
||||
"name": "obsidian",
|
||||
"title": "Obsidian",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import json
|
||||
import os
|
||||
from typing import Final
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -95,10 +96,42 @@ class TestMCPRegistryFile:
|
|||
with open(registry_path, "r") as f:
|
||||
data = json.load(f)
|
||||
names = {s["name"] for s in data["servers"]}
|
||||
expected = {"github", "slack", "postgresql", "snowflake", "atlassian"}
|
||||
expected = {"github", "slack", "postgresql", "snowflake", "atlassian", "microsoft_365"}
|
||||
missing = expected - names
|
||||
assert not missing, f"Missing well-known servers: {missing}"
|
||||
|
||||
def test_microsoft_365_is_a_self_hosted_streamable_http_server(self, registry_path):
|
||||
"""The Graph server runs next to the proxy in org mode, so the entry must be streamable HTTP at /mcp."""
|
||||
with open(registry_path, "r") as f:
|
||||
data = json.load(f)
|
||||
entry: Final = next(s for s in data["servers"] if s["name"] == "microsoft_365")
|
||||
assert entry["transport"] == "http"
|
||||
assert entry["url"].endswith("/mcp")
|
||||
assert entry["category"] == "Productivity"
|
||||
assert "ms-365-mcp-server" in entry["registry_url"]
|
||||
|
||||
def test_bundled_icons_exist(self, registry_path):
|
||||
"""An icon served from the proxy's own assets ships twice, as the built copy the wheel packages and as
|
||||
the dashboard source copy every Docker image rebuilds from. Both must exist and match or a card goes blank."""
|
||||
with open(registry_path, "r") as f:
|
||||
data = json.load(f)
|
||||
proxy_dir: Final = os.path.dirname(registry_path)
|
||||
built_logos_dir: Final = os.path.join(proxy_dir, "_experimental", "out", "assets", "logos")
|
||||
source_logos_dir: Final = os.path.join(
|
||||
proxy_dir, "..", "..", "ui", "litellm-dashboard", "public", "assets", "logos"
|
||||
)
|
||||
bundled: Final = [s for s in data["servers"] if s.get("icon_url", "").startswith("/ui/assets/logos/")]
|
||||
assert bundled, "at least one registry entry ships its own icon"
|
||||
for server in bundled:
|
||||
file_name: Final = os.path.basename(server["icon_url"])
|
||||
built: Final = os.path.join(built_logos_dir, file_name)
|
||||
source: Final = os.path.join(source_logos_dir, file_name)
|
||||
assert os.path.isfile(built), f"{server['name']}: {server['icon_url']} missing from the built dashboard"
|
||||
assert os.path.isfile(source), f"{server['name']}: {server['icon_url']} missing from the dashboard source"
|
||||
with open(built, "rb") as built_file, open(source, "rb") as source_file:
|
||||
same_bytes: Final = built_file.read() == source_file.read()
|
||||
assert same_bytes, f"{server['name']}: built and source copies of {file_name} differ"
|
||||
|
||||
def test_env_vars_structure(self, registry_path):
|
||||
with open(registry_path, "r") as f:
|
||||
data = json.load(f)
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-label="Microsoft 365"><rect x="1" y="1" width="10.5" height="10.5" fill="#F25022"/><rect x="12.5" y="1" width="10.5" height="10.5" fill="#7FBA00"/><rect x="1" y="12.5" width="10.5" height="10.5" fill="#00A4EF"/><rect x="12.5" y="12.5" width="10.5" height="10.5" fill="#FFB900"/></svg>
|
||||
|
After Width: | Height: | Size: 361 B |
Loading…
Add table
Reference in a new issue