Native Microsoft 365 Agents (A365) channel for OpenClaw with integrated Graph API tools.
Alpha: This project is under active development. APIs and configuration may change between releases.
# Create a GitHub Personal Access Token with `read:packages` scope
# Then login:
echo YOUR_GITHUB_TOKEN | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin
Download the example and fill in your credentials:
curl -O https://raw.githubusercontent.com/SidU/openclaw-a365/main/.env.example
mv .env.example .env
# Edit .env with your credentials
Required environment variables:
| Variable | Description |
|---|---|
ANTHROPIC_API_KEY |
Anthropic API key (or use another provider) |
A365_APP_ID |
Agentic App ID |
A365_APP_PASSWORD |
Agentic App Password |
A365_TENANT_ID |
Azure AD Tenant ID |
AA_INSTANCE_ID |
Autonomous Agent Instance ID |
AGENT_IDENTITY |
Agent UPN (e.g., agent@contoso.com) |
OWNER |
Owner UPN (e.g., user@contoso.com) |
OWNER_AAD_ID |
Owner's AAD Object ID |
docker run --cap-add=NET_ADMIN --env-file .env -p 3978:3978 ghcr.io/sidu/openclaw-a365:latest
Or run in background:
docker run -d --name openclaw-a365 --cap-add=NET_ADMIN --env-file .env -p 3978:3978 ghcr.io/sidu/openclaw-a365:latest
Note:
--cap-add=NET_ADMINis required for network policy enforcement. If you're usingNETWORK_MODE=unrestricted(default), you can omit it.
Point your A365 agent to https://your-host:3978/api/messages
Alternative: Build from source
If you prefer to build locally instead of using the pre-built image:
git clone https://github.com/SidU/openclaw-a365.git
cd openclaw-a365
cp .env.example .env
# Edit .env with your credentials
docker-compose up -d
Configure which LLM model to use via environment variables:
# Primary model (default: anthropic/claude-opus-4-6)
OPENCLAW_MODEL=anthropic/claude-sonnet-4-20250514
# Fallback models (comma-separated, tried in order if primary fails)
OPENCLAW_FALLBACK_MODELS=openai/gpt-4o,openrouter/anthropic/claude-3-haiku
Supported providers:
- Anthropic: anthropic/claude-opus-4-6, anthropic/claude-sonnet-4-20250514
- OpenAI: openai/gpt-4o, openai/gpt-4-turbo
- OpenRouter: openrouter/anthropic/claude-3.5-sonnet, etc.
- Azure OpenAI: azure/gpt-4o (requires AZURE_OPENAI_* config)
Control what external network resources the agent can access. Since LLM agents can execute code that makes network requests, you may want to restrict outbound access.
| Mode | Description |
|---|---|
unrestricted |
No restrictions - agent can access any URL (default) |
restricted |
Only essential domains: Microsoft (auth, Graph) + your configured LLM provider |
allowlist |
Essential domains + custom domains you specify |
# In your .env file:
# Option 1: No restrictions (default)
NETWORK_MODE=unrestricted
# Option 2: Locked down to essentials only
NETWORK_MODE=restricted
# Option 3: Essentials + specific domains
NETWORK_MODE=allowlist
NETWORK_ALLOWLIST=api.weather.gov,api.github.com,your-api.internal.com
In restricted and allowlist modes, these domains are always allowed:
| Domain | Purpose |
|---|---|
login.microsoftonline.com |
Azure AD authentication |
graph.microsoft.com |
Microsoft Graph API (calendar, email) |
smba.trafficmanager.net |
Bot Framework message delivery |
*.botframework.com |
Bot Framework services |
| Your LLM provider | Auto-detected from API keys (Anthropic, OpenAI, etc.) |
Network policy is enforced via iptables at the container level, which catches all outbound traffic - including shell commands, curl, wget, or any code the LLM might execute. At container startup:
allowlist mode, your custom domains are also resolvedFuture: Dynamic approval flow where the agent can request access to new domains and the owner can approve/deny via Teams notification (Phase 2).
When using restricted or allowlist mode, the container needs the NET_ADMIN capability:
# With docker run:
docker run --cap-add=NET_ADMIN --env-file .env -p 3978:3978 ghcr.io/sidu/openclaw-a365:latest
# docker-compose.yml already includes this capability
Note: In
unrestrictedmode (default),NET_ADMINis not required.
Learn more: Microsoft Agent 365 Identity Documentation
A key design principle of A365 agents is that agents have their own user identity in the tenant (e.g., agent@contoso.com). This is fundamentally different from traditional "on behalf of user" OAuth flows. Microsoft calls this an "Agentic User" - a specialized identity that functions as a full member of your Microsoft 365 organization.
| Traditional Delegated Access | Agentic Identity |
|---|---|
| Agent acts as the user | Agent acts as itself |
| Access to everything user can access | Access only to explicitly shared resources |
| User must be online to refresh tokens | Agent operates autonomously 24/7 |
| Audit logs show "user did X via app" | Audit logs show "agent@contoso.com did X" |
This model treats the agent as a trusted assistant with its own identity, rather than a service wearing the user's credentials.
The A365 channel uses Federated Identity Credentials (FIC) via the Agentic Blueprint to authenticate as the agent's identity.
sequenceDiagram
participant Agent as OpenClaw Agent
participant Cache as Token Cache
(in-memory)
participant CB as External Token
Callback Service
participant AAD as Entra ID
(login.microsoftonline.com)
participant Graph as Microsoft Graph API
Agent->>Cache: Check token for username|scope
alt Cache hit (expires > now + 5min)
Cache-->>Agent: Cached access token
else Cache miss or expired
alt Token Callback configured
Agent->>CB: POST {username, scope}
+ Bearer callbackToken
CB-->>Agent: {access_token, expires_at}
Note over Agent,CB: If callback fails,
falls through to T1/T2
end
rect rgb(240, 248, 255)
Note over Agent,AAD: T1/T2/User FIC Flow (Federated Identity Credentials)
Agent->>AAD: Step 1 — T1 Token
grant_type=client_credentials
client_id=blueprintClientAppId
client_secret=blueprintClientSecret
scope=api://AzureAdTokenExchange/.default
fmi_path=aaInstanceId
AAD-->>Agent: T1 access_token
Agent->>AAD: Step 2 — T2 Token
grant_type=client_credentials
client_id=aaInstanceId
client_assertion_type=jwt-bearer
client_assertion=T1 token
scope=api://AzureAdTokenExchange/.default
AAD-->>Agent: T2 access_token
Agent->>AAD: Step 3 — Agent Token
grant_type=user_fic
client_id=aaInstanceId
client_assertion=T1 token
username=agent@contoso.com
user_federated_identity_credential=T2 token
scope=https://graph.microsoft.com/.default
AAD-->>Agent: User FIC access_token + expires_in
end
Agent->>Cache: Store token (key: username|scope)
end
Agent->>Graph: API call with Bearer token
Graph-->>Agent: Response
The agent authenticates using its own identity (AGENT_IDENTITY), then accesses resources that have been shared with it (e.g., the owner's calendar).
The Agentic credentials (A365_APP_ID, A365_APP_PASSWORD) are used for both:
1. A365 message authentication
2. Graph API token acquisition (T1/T2 flow for agent identity)
The following tools are available to the LLM when Graph API is configured:
| Tool | Description |
|---|---|
get_calendar_events |
Get calendar events for a date range |
create_calendar_event |
Create a new calendar event |
update_calendar_event |
Update an existing event |
delete_calendar_event |
Delete a calendar event |
find_meeting_times |
Find available times for all attendees |
send_email |
Send an email via Microsoft Graph |
get_user_info |
Get user profile information |
| Variable | Description |
|---|---|
A365_APP_ID |
Agentic App ID |
A365_APP_PASSWORD |
Agentic App Password |
A365_TENANT_ID |
Azure AD Tenant ID |
AA_INSTANCE_ID |
Autonomous Agent Instance ID (for FIC) |
AGENT_IDENTITY |
Agent service account UPN |
OWNER |
Owner's email address |
OWNER_AAD_ID |
Owner's AAD Object ID |
| Variable | Default | Description |
|---|---|---|
OPENCLAW_MODEL |
anthropic/claude-opus-4-6 |
Primary LLM model |
OPENCLAW_FALLBACK_MODELS |
- | Comma-separated fallback models |
BUSINESS_HOURS_START |
08:00 |
Business hours start |
BUSINESS_HOURS_END |
18:00 |
Business hours end |
TIMEZONE |
America/Los_Angeles |
Timezone |
DM_POLICY |
pairing |
DM policy: open, pairing, closed |
| Variable | Description |
|---|---|
ANTHROPIC_API_KEY |
Anthropic API key |
OPENAI_API_KEY |
OpenAI API key |
OPENROUTER_API_KEY |
OpenRouter API key |
AZURE_OPENAI_API_KEY |
Azure OpenAI API key |
AZURE_OPENAI_ENDPOINT |
Azure OpenAI endpoint URL |
| Property | Description |
|---|---|
AGENT_IDENTITY |
The agent's own user account in the tenant (e.g., agent@contoso.com). This is a real Entra ID user that resources are shared with. |
OWNER |
Email of the person this agent supports (the "principal"). The owner should share their calendar/resources with AGENT_IDENTITY. |
OWNER_AAD_ID |
AAD Object ID of the owner (for role detection) |
For the agent to access the owner's calendar, the owner must share it:
agent@contoso.com (the AGENT_IDENTITY)This explicit sharing model ensures the agent only accesses what the owner has consciously granted.
When the owner interacts with the agent, they get UserRole: Owner. Others get UserRole: Requester.
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────┐
│ Microsoft Teams │───▶│ A365 Service │───▶│ OpenClaw A365 │
│ Outlook/Email │ │ │ │ ┌───────────────┐ │
└─────────────────┘ └─────────────────┘ │ │ Claude/GPT │ │
│ │ │ │
┌─────────────────────────────────────│────│ Graph Tools │ │
│ │ └───────────────┘ │
▼ └─────────────────────────┘
┌─────────┐
│ Graph │ ◄── Agent authenticates as its own identity
│ API │ (accesses resources shared with it)
└─────────┘
MIT
$ claude mcp add openclaw-a365 \
-- python -m otcore.mcp_server <graph>