| name | org-report |
| description | Generate an executive organization brief (leadership chain, direct reports, extended teams, and vendor partners) for a target person. Produces a Word + PDF report from a directory MCP source (WorkIQ is the reference implementation) and displays it in a Copilot canvas. Triggers on "generate org report for X", "build org brief for X", "make an org report for X", "org profile document for X". |
Org Report
Generate a polished executive organization brief for a target person. The report includes:
- Cover page with target, generation timestamp, FTE / vendor counts, byline, methodology, and disclosure
- Full management chain (up to the top)
- Team index (color-coded)
- Individual profiles: chain, target, each team's FTEs, individual contributors
- Appendix: all contractors and vendors grouped by team
Output is a Word .docx, a .pdf rendered from it, and a live Copilot canvas view.
When to use
Trigger on any of:
- "Generate an org report for {Person}"
- "Build an org brief for {Person}"
- "Make an executive org report for {Person}"
- "Create an org profile document for {Person}"
- "Produce an organization brief for {Person}"
If the user asks for a report on themselves, use their profile as the target.
Prerequisites (verify before starting)
- Directory MCP — a Model Context Protocol server that exposes org-tree queries and person profiles. This plugin was built against Microsoft's WorkIQ MCP (
workiq-ask tool); any MCP that answers equivalent queries about a corporate directory and communication signals will work with minor prompt adjustments in Phases 2–4.
- Python 3.11+ with
python-docx and docx2pdf installed:
pip install python-docx docx2pdf
- Microsoft Word (Windows/macOS) — required by
docx2pdf. On Linux, swap in LibreOffice via a headless converter and pass --pdf-out '' to skip in-script PDF.
- Canvas extension — the
org-directory extension should be installed at
~/.copilot/extensions/org-directory/. This plugin ships a copy in
extensions/org-directory/; copy it once, then extensions_reload.
- Configuration (optional) — cover byline, methodology, disclosure, base font, and vendor-detection rules can be customized via
config.json. See § Configuration below. Without a config file the plugin uses tenant-neutral defaults.
If any prerequisite is missing, stop and tell the user what to install.
Workflow
Phase 1 — Confirm the target and reset state
Ask the user to confirm the target (UPN, email, or full name). Then create a
fresh state file at ~/.copilot/extensions/org-directory/artifacts/directory.json:
{ "orgChart": null, "profiles": {}, "updatedAt": null }
Phase 2 — Resolve the org structure via the directory MCP
Use the MCP's directory query (e.g., WorkIQ workiq-ask, or workiq-fetch on /users/... where available) to build the tree:
- Target profile: get
displayName, jobTitle, department, email, officeLocation.
- Management chain: walk up via
/users/{id}/manager until you reach the top
(e.g., the CEO). Save as managementChain (ordered top → target's manager).
- Direct reports: recursively fetch
/users/{id}/directReports for the target
and every descendant. Attach as nested reports: [...].
Persist to directory.json:
{
"orgChart": {
"managementChain": [ { "displayName": "...", "jobTitle": "..." }, ... ],
"target": { "displayName": "...", "jobTitle": "...", "email": "...", "department": "..." },
"directReports": [
{ "displayName": "...", "jobTitle": "...", "email": "...",
"reports": [ { ...
Vendor detection: in tenants that use the First Last (Company Name) display-name
convention (Microsoft is one example), contractors are identified by that parenthetical
plus an email prefix. Both signals are configurable — see vendor_detection in
scripts/config.example.json. Without configured rules the plugin treats all directory
entries as FTEs.
Phase 3 — Enrich every person's profile
For every person in the tree (target + all descendants), call the directory MCP in
parallel batches of 10–15 with this exact prompt:
For {Full Name} ({title, if known}, in {target}'s org), produce a concise
professional profile grounded in accessible communication signals (email,
meetings, chat, documents). Return ONLY this JSON in a ```json fence:
{
"displayName": "{Full Name}",
"jobTitle": "...",
"department": "...",
"email": "...",
"summary": "2-3 sentence executive summary of their role and focus areas",
"recentWork": ["3-6 recent projects or themes"],
"topics": ["4-8 short tags describing what they work on"],
"expertise": ["3-6 specific skills or domains"],
"collaborators": ["6-8 people they work most closely with"],
"signalDensity": "high | medium | low | none",
"signalCounts": { "emails": 0, "meetings": 0, "chats": 0, "docs": 0 },
"signalCountsWindow": "e.g. Jul-Aug 2026",
"officeLocation": "..."
}
About the new optional fields:
signalDensity — coarse indicator of how much verifiable signal grounded this
profile. Use high when 30+ signals, medium for 10-30, low for 3-10,
none for < 3. Renderer displays a colored dot next to the name.
signalCounts — approximate counts per channel over the last 60 days. The
renderer prints "Grounded in N emails · N meetings · N chats · N docs
({signalCountsWindow})" as an attribution footer. Skip channels with 0.
signalCountsWindow — the human-readable date range the counts cover.
Rules:
Phase 4 — (Optional) Validate collaborators
The first-pass collaborator list often includes people from unrelated meetings.
For any profile whose collaborators look off (mostly outside the org, unfamiliar
names), do a second pass with a narrower prompt:
Who are the 6-8 people {Name} ({title/company}, in {target}'s org) works most
closely with day-to-day? Return only this exact JSON in a ```json fence:
{"person": "{Name}", "collaborators": ["Name1", "Name2", ...]}
Same batch pattern (10–15 parallel). Merge overwrites the previous
collaborators list and clears any collaboratorsNote. Accept ~5% empties.
Phase 4.2 — (Optional) Deep dive for the target and direct reports
For the target and each of their direct reports, run a second MCP pass with a
longer prompt to fill the deepDive object. This costs an extra ~10-25 MCP
calls per report but adds a dedicated "DEEP DIVE" section to the highest-signal
profiles.
For {Full Name} ({title}, in {target}'s org), summarize the leadership shape
of their work in the last 6 months, grounded strictly in accessible
communication signals. Return ONLY this JSON in a ```json fence:
{
"displayName": "{Full Name}",
"deepDive": {
"strategicBets": ["3-5 concrete initiatives or bets they are driving"],
"recentDecisions": ["3-5 explicit decisions or trade-offs they have made"],
"crossTeamDependencies": ["3-5 dependencies on other teams or partners"]
}
}
Same parallel-batch rules as Phase 3. Merge by displayName.lower().strip() —
deepDive overwrites any prior deepDive object. Skip anyone not in
target + directReports — depth beyond that is noisy and expensive.
Phase 4.5 — (Optional) Fetch profile photos
If your application layer supplies a photo fetcher (e.g., the Microsoft Graph
fetcher at OrgReports/scripts/fetch_photos.py),
run it now to populate the photo cache. The renderer picks photos up
automatically if they land at <photos.directory>/<email>.jpg (see
§ Configuration).
python scripts/fetch_photos.py
Photos are optional — skip this phase and the report renders name-only cards.
Phase 5 — Generate the DOCX + PDF
Run the export script. It reads directory.json, writes the DOCX and PDF, and
copies them into the canvas extension's artifacts folder. If a config.json
lives in the current working directory, cover copy and vendor rules load from
it; otherwise built-in tenant-neutral defaults apply.
python ~/.copilot/installed-plugins/alex-mall/org-report/scripts/export_org.py
# Explicit config path:
python ~/.copilot/installed-plugins/alex-mall/org-report/scripts/export_org.py --config config.json
If that path does not resolve, the plugin was installed by a route other than
the Alex ACT Mall. Locate the installed scripts/export_org.py under
~/.copilot/installed-plugins/ and use that path instead of guessing.
Optional flags:
--state <path> — override the state file (default: canvas artifacts)
--docx-out <path> — override the DOCX output path
--pdf-out <path> — override the PDF output path (set to empty string to skip PDF)
--config <path> — explicit config file path (default: ./config.json, then user scope, then built-in defaults)
Phase 6 — Refresh the canvas
extensions_reload
open_canvas({ canvasId: "org-directory", instanceId: "org-report-1" })
The canvas embeds the fresh PDF with a "Download PDF" button.
Phase 7 — Report completion
Tell the user:
- Target name and how many FTE + vendor profiles were enriched
- Where the DOCX lives (path)
- That the canvas is open and refreshable
Editing the cover copy
Cover byline, methodology paragraph, and disclosure paragraph are all
configurable via config.json. See § Configuration below. The default
disclosure covers three things every exec brief of this kind should say:
- Data-access limits — information barriers, sensitivity labels, DLP policies
- AI accuracy — summaries may contain errors, verify before acting
- Confidentiality — organizational confidential personnel info, do not
redistribute
Configuration
export_org.py loads its cover copy, byline, base font, prose labels, vendor
rules, and photo settings from config.json. Discovery order:
--config PATH (explicit CLI flag)
./config.json in the current working directory
~/.copilot/extensions/org-directory/config.json (user-level default)
- Built-in tenant-neutral defaults
Any subset of keys is valid; missing keys fall back to defaults. See
scripts/config.example.json for the full schema and a starting template.
Available config sections
| Section | Purpose |
|---|
byline | Cover byline (author + org). Empty → paragraphs skipped. |
cover_copy | Methodology + disclosure paragraphs. |
typography | Base font family. |
vendor_detection | Email prefixes + display-name paren excludes. |
labels | Prose strings the renderer uses (e.g., "no profile available"). |
photos | Profile photo directory, lookup key, and rendered size. |
tenure | Enable/disable the "N years " line under contact. |
signal_density | Colored dot per profile keyed by signalDensity. |
deep_dive | Whether to render the "DEEP DIVE" section when deepDive is populated. |
attribution | Whether to print the "Grounded in N emails · ..." footer. |
team_stats | Extra TOTAL/DEPTH columns on the team index table. |
cross_team | Cross-team collaboration table (top N pairs by shared collaborators). |
Profile photos
Set photos.directory to a folder of image files named <email>.jpg (or
.jpeg / .png). The renderer looks up each entry's email (with .upn
fallback), then their normalized displayName. Missing photos are silently
skipped. The plugin does not fetch photos itself — pair it with an
application-layer fetcher (see OrgReports' scripts/fetch_photos.py for the
Microsoft Graph reference implementation).
For the Microsoft-tenant configuration used by the OrgReports
application, see that repo's config.json.
Known limits
- MCP latency: directory queries can take 10–60 s each; some questions run
minutes. Always batch in parallel.
- MCP flakiness: occasional
No MCP client found / timeouts. Retry the
specific failed call once.
- Empty profiles: ~5–10% of contractors have no surfaceable signals. Mark
with
collaboratorsNote and move on.
- Vendor name format: relies on the
First Last (Company) directory
convention. Configure vendor_detection.email_prefixes and
display_name_paren_excludes in config.json for your tenant.
- Directory vs contacts:
/users/{id} is the corporate directory;
/me/contacts is personal contacts. IDs are not interchangeable.
- Binary content: most directory MCPs don't yet expose files or profile
photos as bytes — only structured metadata.
docx2pdf on Windows: requires Microsoft Word. On macOS/Linux, substitute
LibreOffice or a headless converter and pass --pdf-out ''.