ms-graph-search-sdk
2
总安装量
2
周安装量
#67085
全站排名
安装命令
npx skills add https://github.com/pauljsnider/ai-config-guide --skill ms-graph-search-sdk
Agent 安装分布
openclaw
2
gemini-cli
2
claude-code
2
github-copilot
2
codex
2
kimi-cli
2
Skill 文档
Microsoft Graph Search SDK Skill
When asked to search OneDrive, Teams, Outlook, or other Microsoft 365 services, use Python with the requests library for clean, simple API calls.
ð Security-First Design
ONE CRITICAL RULE: Claude NEVER displays your token back to you.
Simple workflow:
- Claude opens Graph Explorer for you
- You paste token in chat (it’s fine)
- Claude uses it but never echoes it back
Step 1: Install Dependencies (Auto)
Check and install if needed:
pip3 install -q requests
Step 2: Get Access Token
Simple 3-Step Process:
-
Claude opens Graph Explorer (automatically)
# macOS: open "https://developer.microsoft.com/en-us/graph/graph-explorer" # Linux: xdg-open "https://developer.microsoft.com/en-us/graph/graph-explorer" # Windows: start "https://developer.microsoft.com/en-us/graph/graph-explorer" -
You get token from browser:
- Sign in with Microsoft account
- Click “Access token” tab
- Copy the token
- Paste it in chat (it’s fine to paste)
-
Claude uses it silently:
- Stores in Python variable
- Makes API calls
- Never displays it back
Token Notes:
- Expires in 69-90 minutes, just get a new one when needed
- Claude will store it for the session
Step 3: Search Using Python
Simple Token Usage
#!/usr/bin/env python3
import requests
# User pastes token in chat, Claude uses it
TOKEN = "user_provided_token_here" # Claude replaces with actual token
# â
CORRECT: Use it silently
headers = {"Authorization": f"Bearer {TOKEN}"}
response = requests.get("https://graph.microsoft.com/v1.0/me", headers=headers)
# â WRONG: NEVER display the token
# print(f"Using token: {TOKEN}") # DON'T DO THIS
Timezone Handling (Central Time)
IMPORTANT: Always convert dates/times to Central Time Zone for display.
from datetime import datetime
from zoneinfo import ZoneInfo
def to_central_time(iso_datetime_str):
"""Convert ISO datetime string to Central Time"""
if not iso_datetime_str or iso_datetime_str == 'Unknown':
return 'Unknown'
# Remove trailing zeros and parse
dt_str = iso_datetime_str.rstrip('0').rstrip('.')
# Parse the datetime (assume UTC if no timezone)
if 'Z' in dt_str or '+' in dt_str or dt_str.endswith('-'):
dt = datetime.fromisoformat(dt_str.replace('Z', '+00:00'))
else:
dt = datetime.fromisoformat(dt_str).replace(tzinfo=ZoneInfo('UTC'))
# Convert to Central Time
central = dt.astimezone(ZoneInfo('America/Chicago'))
# Format nicely
return central.strftime('%Y-%m-%d %I:%M %p CST')
# Example usage:
# start_time = to_central_time("2026-01-06T15:00:00.0000000")
# print(start_time) # "2026-01-06 09:00 AM CST"
Common Operations
All examples below assume:
import os
import requests
from datetime import datetime
from zoneinfo import ZoneInfo
TOKEN = os.getenv('MS_GRAPH_TOKEN')
if not TOKEN:
print("Error: MS_GRAPH_TOKEN not set", file=sys.stderr)
sys.exit(1)
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
Get Current User
response = requests.get("https://graph.microsoft.com/v1.0/me", headers=HEADERS)
user = response.json()
print(f"Name: {user.get('displayName')}")
print(f"Email: {user.get('userPrincipalName')}")
Search OneDrive Files
query = "report"
url = f"https://graph.microsoft.com/v1.0/me/drive/root/search(q='{query}')"
response = requests.get(url, headers=HEADERS)
files = response.json().get('value', [])
for file in files[:10]:
print(f"ð {file['name']}")
print(f" Modified: {to_central_time(file.get('lastModifiedDateTime', 'Unknown'))}")
print(f" Size: {file.get('size', 0):,} bytes")
List OneDrive Files
response = requests.get("https://graph.microsoft.com/v1.0/me/drive/root/children", headers=HEADERS)
files = response.json().get('value', [])
for file in files[:10]:
print(f"ð {file['name']}")
print(f" Type: {file.get('@microsoft.graph.downloadUrl', 'Folder' if 'folder' in file else 'File')}")
Files Shared With Me
response = requests.get("https://graph.microsoft.com/v1.0/me/drive/sharedWithMe", headers=HEADERS)
files = response.json().get('value', [])
for item in files[:10]:
remote_item = item.get('remoteItem', {})
shared_by = item.get('shared', {}).get('sharedBy', {}).get('user', {}).get('displayName', 'Unknown')
print(f"ð¤ {remote_item.get('name', 'Unknown')}")
print(f" Shared by: {shared_by}")
Search Emails
query = "budget"
url = f"https://graph.microsoft.com/v1.0/me/messages?$search=\"{query}\"&$top=10"
response = requests.get(url, headers=HEADERS)
messages = response.json().get('value', [])
for msg in messages:
print(f"âï¸ {msg.get('subject', 'No subject')}")
sender = msg.get('from', {}).get('emailAddress', {}).get('address', 'Unknown')
print(f" From: {sender}")
print(f" Date: {to_central_time(msg.get('receivedDateTime', 'Unknown'))}")
List Inbox Messages
url = "https://graph.microsoft.com/v1.0/me/messages?$top=10&$select=subject,from,receivedDateTime"
response = requests.get(url, headers=HEADERS)
messages = response.json().get('value', [])
for msg in messages:
sender = msg.get('from', {}).get('emailAddress', {}).get('name', 'Unknown')
print(f"ð§ {msg.get('subject', 'No subject')}")
print(f" From: {sender}")
print(f" Date: {to_central_time(msg.get('receivedDateTime', 'Unknown'))}")
List Teams
response = requests.get("https://graph.microsoft.com/v1.0/me/joinedTeams", headers=HEADERS)
teams = response.json().get('value', [])
for team in teams:
print(f"ð¥ {team.get('displayName', 'Unknown')}")
print(f" Description: {team.get('description', 'No description')}")
List Calendar Events
response = requests.get("https://graph.microsoft.com/v1.0/me/calendar/events?$top=10", headers=HEADERS)
events = response.json().get('value', [])
for event in events:
# Get start/end times and convert to Central Time
start = event.get('start', {}).get('dateTime', 'Unknown')
end = event.get('end', {}).get('dateTime', 'Unknown')
print(f"ð
{event.get('subject', 'No subject')}")
print(f" Start: {to_central_time(start)}")
print(f" End: {to_central_time(end)}")
print(f" Location: {event.get('location', {}).get('displayName', 'No location')}")
Unified Search (Multiple Services)
search_body = {
"requests": [{
"entityTypes": ["driveItem", "message"],
"query": {"queryString": "quarterly report"},
"from": 0,
"size": 25
}]
}
response = requests.post(
"https://graph.microsoft.com/v1.0/search/query",
headers={**HEADERS, "Content-Type": "application/json"},
json=search_body
)
results = response.json()
# Parse results
for request_result in results.get('value', []):
for hit_container in request_result.get('hitsContainers', []):
for hit in hit_container.get('hits', []):
resource = hit.get('resource', {})
print(f"ð {resource.get('name') or resource.get('subject', 'Unknown')}")
print(f" Type: {resource.get('@odata.type', 'Unknown')}")
Error Handling Template
def handle_response(response, reopen_browser=False):
"""Handle API response with proper error messages"""
if response.status_code == 401:
print("â Token expired or invalid", file=sys.stderr)
print("", file=sys.stderr)
print("To get a new token:", file=sys.stderr)
print("1. Open https://developer.microsoft.com/en-us/graph/graph-explorer", file=sys.stderr)
print("2. Sign in and click 'Access token' tab", file=sys.stderr)
print("3. Run: export MS_GRAPH_TOKEN=\"<your-token>\"", file=sys.stderr)
if reopen_browser:
import platform
import subprocess
system = platform.system()
url = "https://developer.microsoft.com/en-us/graph/graph-explorer"
try:
if system == "Darwin":
subprocess.run(["open", url])
elif system == "Linux":
subprocess.run(["xdg-open", url])
elif system == "Windows":
subprocess.run(["start", url], shell=True)
print("\nâ
Opened Graph Explorer in browser", file=sys.stderr)
except Exception as e:
print(f"\nâ ï¸ Could not open browser: {e}", file=sys.stderr)
return None
elif response.status_code == 403:
print("â Permission denied. Grant consent in Graph Explorer.", file=sys.stderr)
return None
elif response.status_code == 429:
print("â Rate limited. Wait a moment and try again.", file=sys.stderr)
return None
elif response.status_code == 200:
return response.json()
else:
print(f"â Error {response.status_code}: {response.text}", file=sys.stderr)
return None
# Usage:
# data = handle_response(response, reopen_browser=True)
# if data:
# # Process data
Complete Example Script Template
#!/usr/bin/env python3
"""
Microsoft Graph Search - Simple & Secure
Searches OneDrive and Outlook using Microsoft Graph API.
Security:
- User pastes token in chat (it's fine)
- Claude uses it but never displays it back
"""
import requests
from datetime import datetime
from zoneinfo import ZoneInfo
# User pastes token in chat, Claude replaces this
TOKEN = "user_provided_token_here"
def to_central_time(iso_datetime_str):
"""Convert ISO datetime string to Central Time"""
if not iso_datetime_str or iso_datetime_str == 'Unknown':
return 'Unknown'
dt_str = iso_datetime_str.rstrip('0').rstrip('.')
if 'Z' in dt_str or '+' in dt_str or dt_str.endswith('-'):
dt = datetime.fromisoformat(dt_str.replace('Z', '+00:00'))
else:
dt = datetime.fromisoformat(dt_str).replace(tzinfo=ZoneInfo('UTC'))
central = dt.astimezone(ZoneInfo('America/Chicago'))
return central.strftime('%Y-%m-%d %I:%M %p CST')
def handle_response(response):
"""Handle API response with error checking"""
if response.status_code == 401:
print("â Token expired. Get new token from Graph Explorer.")
return None
elif response.status_code == 403:
print("â Permission denied.")
return None
elif response.status_code == 429:
print("â Rate limited. Wait and retry.")
return None
elif response.status_code == 200:
return response.json()
else:
print(f"â Error {response.status_code}: {response.text}")
return None
def search_onedrive(query):
"""Search OneDrive files"""
headers = {"Authorization": f"Bearer {TOKEN}"}
url = f"https://graph.microsoft.com/v1.0/me/drive/root/search(q='{query}')"
response = requests.get(url, headers=headers)
data = handle_response(response)
if data:
files = data.get('value', [])
print(f"\nð Found {len(files)} files matching '{query}':\n")
for i, file in enumerate(files[:10], 1):
print(f"{i}. {file['name']}")
print(f" Modified: {to_central_time(file.get('lastModifiedDateTime', 'Unknown'))}")
print(f" Size: {file.get('size', 0):,} bytes\n")
def search_emails(query):
"""Search Outlook emails"""
headers = {"Authorization": f"Bearer {TOKEN}"}
url = f"https://graph.microsoft.com/v1.0/me/messages?$search=\"{query}\"&$top=10"
response = requests.get(url, headers=headers)
data = handle_response(response)
if data:
messages = data.get('value', [])
print(f"\nâï¸ Found {len(messages)} emails matching '{query}':\n")
for i, msg in enumerate(messages, 1):
sender = msg.get('from', {}).get('emailAddress', {}).get('address', 'Unknown')
print(f"{i}. {msg.get('subject', 'No subject')}")
print(f" From: {sender}")
print(f" Date: {to_central_time(msg.get('receivedDateTime', 'Unknown'))}\n")
# Run searches
search_onedrive("report")
search_emails("budget")
Output Format
Present results clearly with dates/times in Central Time:
ð OneDrive Search Results for "quarterly report"
Found 3 files:
1. Q4-2024-Report.xlsx
Modified: 2024-12-15 04:30 AM CST
Size: 45,231 bytes
Path: /Documents/Reports
2. Quarterly-Summary.docx
Modified: 2024-12-10 08:22 AM CST
Size: 23,450 bytes
Path: /Documents/Reports
3. Q4-Presentation.pptx
Modified: 2024-12-05 03:15 AM CST
Size: 1,234,567 bytes
Path: /Shared/Team
Common Queries & Workflows
“Find my Excel files in OneDrive”
- Check if
MS_GRAPH_TOKENis set - If not, open Graph Explorer and instruct user
- Run search:
q='.xlsx' - Display results with file names, dates, sizes
“Search my emails for X”
- Check token (reuse if already set)
- Use
$search="X"parameter - Display subject, sender, date in Central Time
“Show files shared with me”
- Use
/me/drive/sharedWithMeendpoint - Parse remoteItem for file details
- Show who shared each file
“What meetings do I have today?”
- Use
/me/calendar/events - Filter by date
- Convert times to Central Time using
to_central_time() - Display subject, time, location
Best Practices
Security
- Always read token from
MS_GRAPH_TOKENenvironment variable - Never pass token as command-line argument
- Never print token to stdout or stderr
- Never log token to files
- Store token in memory only during script execution
API Usage
- Use
$topparameter to limit results (avoid large responses) - Use
$selectto request only needed fields - Handle pagination for large result sets
- Implement exponential backoff for rate limits
Time Handling
- Always convert dates/times to Central Time for display
- Use the
to_central_time()function consistently - Handle missing or invalid datetime strings gracefully
Error Handling
- Check response status codes before parsing JSON
- Provide helpful error messages to stderr
- Re-open browser on 401 errors (expired token)
- Exit with non-zero code on errors
Code Quality
- Use descriptive function names
- Add docstrings to functions
- Pretty-print JSON with
json.dumps(data, indent=2)when debugging - Use f-strings for formatting
Platform Detection for Browser Opening
import platform
import subprocess
def open_graph_explorer():
"""Open Graph Explorer in default browser"""
system = platform.system()
url = "https://developer.microsoft.com/en-us/graph/graph-explorer"
try:
if system == "Darwin": # macOS
subprocess.run(["open", url])
elif system == "Linux":
subprocess.run(["xdg-open", url])
elif system == "Windows":
subprocess.run(["start", url], shell=True)
print("â
Opened Graph Explorer in browser")
except Exception as e:
print(f"â ï¸ Could not open browser: {e}", file=sys.stderr)
print(f"Please open manually: {url}", file=sys.stderr)
Advantages Over curl
- Cleaner code – No complex escaping or quoting
- Better error handling – Native Python exceptions
- Easier parsing – Direct dict access vs JSON strings
- Token security – Claude never displays it back
- Type safety – Python type hints and linting
- Maintainability – Easier to read and modify
Troubleshooting
Token Expired
â Token expired or invalid
Solution:
- Get new token from Graph Explorer (token expires after 69-90 minutes)
- Paste new token in chat
Permission Denied
â Permission denied
Solution:
- In Graph Explorer, grant consent for required scopes
- Check Permissions Reference
Module Not Found
ModuleNotFoundError: No module named 'requests'
Solution:
pip3 install requests
Timezone Issues
ZoneInfoNotFoundError: 'America/Chicago'
Solution:
# Install tzdata
pip3 install tzdata
Resources
Microsoft Graph
| Resource | Link |
|---|---|
| Graph Explorer | https://developer.microsoft.com/en-us/graph/graph-explorer |
| API Reference | https://learn.microsoft.com/en-us/graph/api/overview |
| Permissions Reference | https://learn.microsoft.com/en-us/graph/permissions-reference |
| Quick Start Guide | https://learn.microsoft.com/en-us/graph/tutorials |
Python Libraries
- requests: https://requests.readthedocs.io/
- datetime: https://docs.python.org/3/library/datetime.html
- zoneinfo: https://docs.python.org/3/library/zoneinfo.html
Important Notes
- Simple approach – Uses requests library for direct API calls
- Token flow – You paste token in chat (it’s fine), Claude never displays it back
- Screen protection – Token never echoed or printed
- Token reuse – Claude stores it in memory for the session
- Auto-install – Claude Code can install requests automatically
- Graph Explorer – Uses Graph Explorer for token generation
- Central Time – All dates/times converted to America/Chicago timezone
Last Updated: 2026-01-11
Security Notice: The ONE rule: Claude never displays your token back to you. Paste it in chat, Claude uses it silently.