add-workiq-tools

Adds WorkIQ MCP tool servers to an existing .NET AgentFramework, Node.js, or Python agent using the A365 CLI. Runs a365 develop list-available to show the…

npx skills add https://github.com/microsoft/agent365-skills --skill add-workiq-tools

Add WorkIQ Tools (A365 CLI + SDK)

Trigger phrases — any of these will activate this skill automatically:

  • "add workiq tools to this agent"
  • "add work intelligence tools"
  • "give this agent access to m365 data"
  • "give my agent access to email and calendar"
  • "add sharepoint access to this agent"
  • "add work iq mail to this agent"
  • "add work iq calendar to this agent"
  • "let this agent read emails and calendar events"
  • "wire up workiq MCP servers"

Overview

This skill adds WorkIQ MCP tool servers to an existing A365 agent using the A365 CLI.

WorkIQ tools give your agent pre-built access to M365 work data via MCP. The capability categories below describe what each catalog server does — but always pull the exact CLI argument names from a365 develop list-available. V2 catalog names look like mcp_MailTools, mcp_CalendarTools, etc., and they evolve over time.

  • Mail — Read, send, and manage email
  • Calendar — Read/create events, check availability
  • Teams — Read channel messages, list teams
  • SharePoint — Search documents, read files, list sites
  • OneDrive — Manage OneDrive files
  • Word — Read and write Word documents
  • User/Presence — Get user profile and presence
  • Copilot — Chat with Microsoft 365 Copilot
  • Dataverse and Dynamics 365 — CRUD and domain actions

How it works:

  1. a365 develop list-available — shows the catalog of available MCP servers
  2. a365 develop add-mcp-servers — adds selected servers to ToolingManifest.json
  3. Agent code is wired to load those tools at runtime via GetMcpToolsAsync
  4. Permissions are applied separately by a developer or Global Administrator

All changes are additive and idempotent — rerunning is safe.


Phase 0A — Workspace Triage and Detection Cache

Step 1 — Triage the workspace

Run in parallel:

  • Glob **/*.csproj, package.json, requirements.txt, pyproject.toml, src/**/*.ts, **/*.cs, **/*.pyhasProjectFiles.
  • Read .a365-workspace-detection.local.jsoncacheState (fresh if detectedAt < 60 min, stale if older, missing if absent).

Decide:

cacheStatehasProjectFilesAction
freshContinue to Step 2 below.
missing / stalefalseHard stop with a useful message: "WorkIQ tools wire MCP servers into an existing agent — there's no agent code in this workspace yet. Run /agent365:make-ai-teammate (recommended — WorkIQ requires an AI Teammate or OBO agent) first to scaffold and register the agent, then come back here." Do not proceed.
missing / staletrueTell the user: "Found existing agent code but no fresh Agent 365 registration. I'll run a365-setup now to register it and write the detection cache, then continue here automatically." Read ${CLAUDE_PLUGIN_ROOT}/skills/a365-setup/SKILL.md and follow it to completion, then continue to Step 2.

Step 2 — Load from cache

🛑 STOP — .a365-workspace-detection.local.json MUST exist before this step. Read the file path .a365-workspace-detection.local.json in the working directory. If it does not exist, you arrived at Step 2 by skipping Step 1's triage routing. Do NOT proceed. Do NOT invent default cache values. Do NOT run any further phase (no a365 develop command, no file edits). Instead:

  1. Tell the user verbatim: "I skipped the Step 1 triage and the detection cache wasn't written. Running a365-setup now to fix that, then I'll return here."
  2. Read ${CLAUDE_PLUGIN_ROOT}/skills/a365-setup/SKILL.md and follow it to completion.
  3. Re-verify the file now exists, then continue below.

The stop hook (validate-add-workiq-tools.js) will fail the session at end if the cache file is missing — this guard exists so the model halts immediately rather than wiring half-detected MCP servers.

Load from cache: agentStack, programmingLanguage, usesTeamsOrCopilot, agentType, authMode (if previously stored).

Present the loaded values in one message and wait for confirmation:

Here's what we detected about your agent:
  • Stack:    {agentStack}
  • Language: {programmingLanguage}

Reply **yes** to confirm, or describe any corrections.

Phase 0B — Agent Type and Authentication Mode

Read ${CLAUDE_PLUGIN_ROOT}/shared/agent-detection.md — section "Agent Type and Auth Mode Detection" — and follow it exactly.

If agentType and authMode are already present in the detection cache (from a prior skill run in this session OR pre-populated by a parent skill like make-ai-teammate), the confirmation behavior depends on agentType:

  • agentType = "ai-teammate" — skip the confirmation prompt entirely. The AI Teammate identity model is unambiguous (authMode = agentic-user, no obo/s2s decision exists), so a confirm prompt adds friction without catching drift. Proceed silently.
  • agentType = "system-agent" — confirm the cached values with the user before proceeding, since the obo/s2s choice is meaningful and a stale value would silently route to the wrong token path.

Read authMode case-insensitively (S2S = s2s, OBO = obo); always write back the canonical lowercase value.

Store agentType (ai-teammate or system-agent) and authMode (obo, s2s, or agentic-user).

Update .a365-workspace-detection.local.json — merge agentType and authMode into the existing cache file, preserving all other fields. Use the Write tool to write the merged object back.

If authMode = s2s, stop immediately and exit:

❌  WorkIQ tools are not available for S2S agents.
    WorkIQ requires a delegated user token (OBO) at runtime — S2S client credentials
    cannot be used for WorkIQ API calls.

    To use WorkIQ, switch your agent to On-Behalf-Of mode (`obo`)
    and re-run this skill.

Do not proceed to Phase 0C or any further phases. Mark all tasks cancelled and end the session.

Framework support guard

WorkIQ extension adapters are only published by Microsoft for a subset of frameworks per language. Verified against Agent365-{dotnet,python,nodejs} on 2026-05-21. If the cache's agentStack falls on the ❌ row for the agent's programmingLanguage, hard-stop and end the session.

programmingLanguageagentStackStatus
DotNetAgent Framework, Semantic Kernel✅ Supported (verified samples)
DotNetAzure AI Foundry✅ Package exists; best-effort wiring (no published sample)
NodeJSLangChain, OpenAI, Claude✅ Supported (verified samples)
NodeJSSemantic Kernel, Google ADK❌ Hard-stop — no Microsoft package
PythonAgent Framework, OpenAI, Google ADK✅ Supported (verified samples)
PythonSemantic Kernel, Azure AI Foundry✅ Package exists; best-effort wiring (no published sample)
PythonLangChain❌ Hard-stop — no package, no sample
PythonClaude, CrewAI❌ Hard-stop in this skill — samples ship a local DIY scaffold (~165–600 lines), out of scope here

When hard-stopping, show this message (substitute the values and list the supported stacks for that language):

❌  WorkIQ tools do not have a Microsoft-published adapter for ({programmingLanguage}, {agentStack}).

    Supported in {programmingLanguage}:
      <list the ✅ agentStacks from the matrix above for that language>

    Options:
      1. Switch the agent to a supported framework via /agent365:make-ai-teammate.
      2. (Advanced) Author your own MCP wrapper modeled on the Claude SDK sample's DIY scaffold:
         https://github.com/microsoft/Agent365-Samples/blob/main/python/claude/sample-agent/mcp_tool_registration_service.py
         — out of scope for this skill.

Mark all tasks cancelled and end the session. Do not proceed to Phase 0C.


Phase 0C — Create and Display Task List

Show this checklist to the user BEFORE running Phase 1. Use whichever mechanism the runtime supports — the user must see the list before any CLI command or code edit happens, and items must be updated as work progresses:

  • Claude Code: call TaskCreate for each item below; it's already in allowed-tools and renders natively as a checklist with status icons. Use TaskUpdate to mark in_progress / completed.
  • VS Code Copilot Chat / GitHub Copilot CLI: allowed-tools is ignored — emit a markdown checklist directly in chat (- [ ] Detect agent type…) and edit the list to flip items to - [x] as each phase completes.

Either way: exactly one task in_progress at a time; complete it before moving on.

TaskCreate: "Detect agent type and check prerequisites"
TaskCreate: "Show available WorkIQ tools catalog"
TaskCreate: "Add WorkIQ MCP servers via CLI"
TaskCreate: "Wire MCP tool service in agent code"
TaskCreate: "Offer Word @mention handler (if applicable)"
TaskCreate: "Guide permissions handoff"
TaskCreate: "Set up dev token for testing"
TaskCreate: "Validate build"

Phase 1 — Detect Agent Type and Check Prerequisites

Mark task in progress: "Detect agent type and check prerequisites"

1.1 Detect agent type

  1. Read ${CLAUDE_PLUGIN_ROOT}/shared/agent-detection.md for detection heuristics.

  2. Run detection:

    • Glob **/*.csproj + Grep AgentApplication in **/*.cs → .NET AgentFramework
    • Glob **/package.json + .ts/.js files present → Node.js
    • Glob **/*.py or requirements.txt / pyproject.toml → Python
  3. Load reference patterns:

    • If .NET: Read ${CLAUDE_PLUGIN_ROOT}/skills/add-workiq-tools/references/dotnet-workiq.md
    • If Node.js: Read ${CLAUDE_PLUGIN_ROOT}/skills/add-workiq-tools/references/nodejs-workiq.md
    • If Python: Read ${CLAUDE_PLUGIN_ROOT}/skills/add-workiq-tools/references/python-workiq.md

1.2 Check prerequisites

Run both checks in one step:

a365 --version; a365 develop list-configured 2>/dev/null || echo "a365 CLI not found — will install"

If a365 is missing:

dotnet tool install -g Microsoft.Agents.A365.DevTools.Cli
a365 --version; a365 develop list-configured

Report the current state to the user — which servers are already in ToolingManifest.json.

1.3 Check for AGENTIC_APP_ID

Following agent-detection.md AGENTIC_APP_ID detection order:

  • Check .env / .env.example for AGENTIC_APP_ID=
  • Check appsettings.json for AgenticAppId
  • Check a365.generated.config.json for agentBlueprintId

If not found, note this — user will need to run a365 setup at some point. Do not block.

Mark task complete: "Detect agent type and check prerequisites"


Phase 2 — Show Available WorkIQ Tools Catalog

Mark task in progress: "Show available WorkIQ tools catalog"

2.1 List available servers

a365 develop list-available

Note: a365 develop list-available does not require a365.config.json — it reads the environment from A365_ENVIRONMENT env var (defaults to prod). The output now includes a Version column showing V1 or V2 for each server.

Show the output to the user. The catalog includes WorkIQ servers (mail, calendar, Teams, SharePoint, OneDrive, Word, user/presence, Copilot) and Dataverse/Dynamics 365.

2.2 Ask which tools to add

If the user provided specific tool names as the skill argument, use those and skip the question.

Otherwise, parse the a365 develop list-available output to extract the server names, then present them as numbered options. Also check a365 develop list-configured output (from Phase 1.2) to mark already-installed servers so the developer can see what's new vs already present.

AskUserQuestion:
  question: |
    Which WorkIQ tool servers would you like to add?
    (Servers already in ToolingManifest.json are marked ✅)

    <list every server name from a365 develop list-available output, numbered>
    <N+1>. All of the above
    <N+2>. Let me type specific names
  options: <dynamically built from CLI output — one entry per server name>

For each option: if the server name appears in the a365 develop list-configured output, append (✅ already configured) to the label. Include it in the list anyway — user may want to re-add or upgrade version.

Mark task complete: "Show available WorkIQ tools catalog"


Phase 3 — Add WorkIQ MCP Servers via CLI

Mark task in progress: "Add WorkIQ MCP servers via CLI"

3.1 Add selected servers

Run a365 develop add-mcp-servers with the selected server names. Run the command once with all selected names space-separated:

# Substitute the exact mcpServerName values from your `list-available` output.
# Example shown using the current V2 catalog names — yours may differ if the catalog evolved.
a365 develop add-mcp-servers "mcp_MailTools" "mcp_CalendarTools"

# If running from a different directory, use --project-path:
a365 develop add-mcp-servers "mcp_MailTools" "mcp_CalendarTools" --project-path "<project_dir>"

(Adjust the server names to match whichever servers the user selected from the live catalog. The CLI does case-insensitive trim-comparison, but the names must otherwise match the catalog's mcpServerName exactly.)

This command creates ToolingManifest.json if it does not exist, or adds the selected servers to it if it does.

⚠️ This command only writes ToolingManifest.json — it does NOT grant permissions. Permissions are handled separately in Phase 5.

3.2 Verify the manifest was updated

a365 develop list-configured

Confirm each selected server now appears in the output. The Version column shows V1 or V2 based on the server's scope pattern.

If a server was already configured, that is expected — the CLI is idempotent.

Mark task complete: "Add WorkIQ MCP servers via CLI"


Phase 4 — Wire MCP Tool Service in Agent Code

Mark task in progress: "Wire MCP tool service in agent code"

The wiring pattern depends on both programmingLanguage and agentStack from the detection cache (loaded in Phase 0A Step 2; framework support guard in Phase 0B has already hard-stopped any unsupported pair). Pick the branch from the routing table:

programmingLanguageagentStackBranch
DotNetAgent Framework§4.1 — .NET Agent Framework
DotNetSemantic Kernel§4.2 — .NET Semantic Kernel
DotNetAzure AI Foundry§4.3 — .NET Azure AI Foundry (best-effort; no published sample)
NodeJSLangChain§4.4 — Node.js LangChain
NodeJSOpenAI§4.5 — Node.js OpenAI
NodeJSClaude§4.6 — Node.js Claude SDK
PythonAgent Framework§4.7 — Python Agent Framework
PythonOpenAI§4.8 — Python OpenAI
PythonGoogle ADK§4.9 — Python Google ADK
PythonSemantic Kernel§4.10 — Python Semantic Kernel (best-effort; no published sample)
PythonAzure AI Foundry§4.11 — Python Azure AI Foundry (best-effort; no published sample)

Detailed code patterns for each branch live in the language-specific reference docs:

  • .NET: ${CLAUDE_PLUGIN_ROOT}/skills/add-workiq-tools/references/dotnet-workiq.md
  • Node.js: ${CLAUDE_PLUGIN_ROOT}/skills/add-workiq-tools/references/nodejs-workiq.md
  • Python: ${CLAUDE_PLUGIN_ROOT}/skills/add-workiq-tools/references/python-workiq.md

For every branch:

  1. Mark new code with // A365 WorkIQ — added by add-workiq-tools skill (.NET / Node.js) or # A365 WorkIQ — added by add-workiq-tools skill (Python). For best-effort branches use … best-effort wiring (verify against SDK source before production) instead.
  2. Grep for the framework's wiring symbol (GetMcpToolsAsync / AddToolServersToAgentAsync / addToolServersToAgent / add_tool_servers_to_agent / McpToolRegistrationService) before editing — skip the wiring step if already present.

⚠️ Preserve-observability rule (applies to ALL §4.x branches that edit the message-handler file)

instrument-observability writes anchors into the same files §4.x will touch — for .NET / Python the WorkIQ call goes into the same method body that the observability skill wraps with BaggageBuilder + InvokeAgentScope (.NET OnMessageAsync, Python process_user_message); for Node.js Claude SDK both skills edit src/client.ts. A naïve Edit with a too-broad old_string will silently delete the observability wrapping. Before any Edit call inside §4.x, follow this checklist:

  1. Grep the target file for the observability anchor symbols:

    • .NET (AgentApplication subclass): BaggageBuilder, InvokeAgentScope, InferenceScope, Agent365ObservabilityContext
    • Node.js (src/agent.ts and src/client.ts): BaggageBuilder, BaggageBuilderUtils, InvokeAgentScope, InferenceScope, AgenticTokenCacheInstance, preloadObservabilityToken
    • Python (agent.py): BaggageBuilder, populate_baggage, InvokeAgentScope, with builder.build(), AgenticTokenCache
  2. If any of those symbols are present, scope your Edit old_string as narrowly as possible — anchor on the single statement immediately before/after the new line, never a multi-statement block, never the method signature alone, never the full method body. Examples:

    • ✅ Good: anchor on the var response = await chatClient.GetResponseAsync(...) line and insert GetMcpToolsAsync immediately above it.
    • ❌ Bad: anchor on protected override async Task OnMessageAsync(...) plus the entire body — the replacement will obliterate the using var baggageScope = ... and using var invokeScope = ... blocks observability put there.
  3. After the Edit completes, re-grep the file for the same observability anchors. If any disappeared, the edit clobbered observability — revert the edit and re-apply with a narrower anchor. Do not proceed to the next file.

  4. If instrument-observability has NOT run yet in this project (no anchor symbols anywhere), wire WorkIQ normally — there is nothing to preserve. The composite has_obs signal in the parent skill (make-ai-teammate Phase 0A.3) is what tells you which case you're in; the cache reflects it.

This rule is enforced by validate-add-workiq-tools.js at session end: if has_obs = true was in the cache at session start, the validator re-checks for the observability anchors and fails the session if they were removed.


§4.1 .NET Agent Framework

  1. Grep Microsoft.Agents.A365.Tooling in **/*.csproj. If missing:
    dotnet add package Microsoft.Agents.A365.Tooling
    dotnet add package Microsoft.Agents.A365.Tooling.Extensions.AgentFramework
    
  2. Read dotnet-workiq.md — sections "Program.cs — Service Registration" and "Agent Class — GetMcpToolsAsync (Agent Framework)".
  3. Edit Program.cs: add the two-line AddSingleton<IMcpToolRegistrationService, ...> + AddSingleton<IMcpToolServerConfigurationService, ...> form (matches the verified Agent365-Samples AF sample). builder.Services.AddMcpServices() exists as a one-liner alternative but registers both as Scoped — the AF sample uses Singleton lifetimes to match AgentApplication's singleton agent host and avoid captive-dependency issues. Skip if already present.
  4. Edit the AgentApplication subclass: add the GetMcpToolsAsync call inside OnMessageAsync (Agent Framework's per-turn handler) — not OnMessageActivityAsync (older docs in this repo had that wrong; the verified sample uses OnMessageAsync).

§4.2 .NET Semantic Kernel

SK API differs from AF. SK uses AddToolServersToAgentAsync (mutates Kernel, void return), called during agent initialization — not per-message. Do not copy the AF pattern.

  1. Install:
    dotnet add package Microsoft.Agents.A365.Tooling
    dotnet add package Microsoft.Agents.A365.Tooling.Extensions.SemanticKernel
    
  2. Read dotnet-workiq.md — section "Agent Class — AddToolServersToAgentAsync (Semantic Kernel)".
  3. Edit the agent-initialization code: call AddToolServersToAgentAsync(kernel, userAuthorization, authHandlerName, turnContext, bearerToken?) after the Kernel is built and before the first run.

§4.3 .NET Azure AI Foundry (BEST-EFFORT — no published sample)

Tell the user verbatim: "Microsoft publishes the Microsoft.Agents.A365.Tooling.Extensions.AzureAIFoundry package but no sample exists for it. Skill installs the package and stops — wire the call manually after reading the SDK source."

  1. Install:
    dotnet add package Microsoft.Agents.A365.Tooling.Extensions.AzureAIFoundry
    
  2. Direct the user to the SDK source: https://github.com/microsoft/Agent365-dotnet/tree/main/src/Tooling/Extensions/AzureAIFoundry
  3. Do not generate wiring code. Mark this branch complete with a best-effort note in the final summary.

§4.4 Node.js LangChain

  1. Grep agents-a365-tooling in **/package.json. If missing:
    npm install @microsoft/agents-a365-tooling @microsoft/agents-a365-tooling-extensions-langchain
    
  2. Read nodejs-workiq.md — section "LangChain — Wiring (VERIFIED)".
  3. Edit src/client.ts (or wherever the getClient factory lives). Add module-level toolService = new McpToolRegistrationService() singleton and the per-turn call inside getClient. Capture the return value — LangChain rebuilds the agent because createAgent's tools are immutable.
  4. After this branch finishes, fall through to Phase 4.5 — its two gates decide whether the Word @mention offer fires or no-ops. Phase 4.5 is not optional; do not jump to Phase 5 from here.

§4.5 Node.js OpenAI

  1. Install:
    npm install @microsoft/agents-a365-tooling @microsoft/agents-a365-tooling-extensions-openai
    
  2. Read nodejs-workiq.md — section "OpenAI Agents SDK — Wiring (VERIFIED)".
  3. Edit the getClient factory. OpenAI extension mutates agent.mcpServers in place — do not assign the return to a new variable (that's the LangChain pattern). Sample uses variable name agent, not personalizedAgent.

§4.6 Node.js Claude SDK

  1. Install:
    npm install @microsoft/agents-a365-tooling @microsoft/agents-a365-tooling-extensions-claude
    
  2. Read nodejs-workiq.md — section "Claude SDK — Wiring (VERIFIED)".
  3. Edit the getClient factory. First arg to addToolServersToAgent is the Options object from @anthropic-ai/claude-agent-sdk, not an Agent. Mutates options.allowedTools and options.mcpServers; return type is Promise<void>.

§4.7 Python Agent Framework

  1. Grep microsoft-agents-a365-tooling in requirements.txt / pyproject.toml. If missing:

    pip3 install microsoft-agents-a365-tooling microsoft-agents-a365-tooling-extensions-agentframework 2>/dev/null \
      || pip install microsoft-agents-a365-tooling microsoft-agents-a365-tooling-extensions-agentframework
    

    Edit requirements.txt or [project.dependencies] in pyproject.toml to add both package names — pip install alone does NOT update either file, and the validator requires their presence.

    Package suffix is agentframework (single word, no internal dash). …-agent-framework is not a valid PyPI name and will fail pip install.

  2. Read python-workiq.md — section "Python Agent Framework — Wiring (VERIFIED)".

  3. Edit agent.py to add the tool_service singleton, mcp_servers_initialized flag, the setup_mcp_servers method, and the call from process_user_message. AF kwarg is turn_context=, not context=. initial_tools=[] is required (positional, no default).

§4.8 Python OpenAI

  1. Install:
    pip3 install microsoft-agents-a365-tooling microsoft-agents-a365-tooling-extensions-openai 2>/dev/null \
      || pip install microsoft-agents-a365-tooling microsoft-agents-a365-tooling-extensions-openai
    
    Edit requirements.txt / pyproject.toml to add both package names.
  2. Read python-workiq.md — section "Python OpenAI Agents SDK — Wiring (VERIFIED)".
  3. Edit agent.py with the sample's 3-priority ladder (USE_AGENTIC_AUTH → bearer-token → handler-only). Kwarg is context= (not turn_context=). No agentic_app_id, no initial_tools (OpenAI extension doesn't require it).

§4.9 Python Google ADK

  1. Install:
    pip3 install microsoft-agents-a365-tooling microsoft-agents-a365-tooling-extensions-googleadk 2>/dev/null \
      || pip install microsoft-agents-a365-tooling microsoft-agents-a365-tooling-extensions-googleadk
    
    Edit requirements.txt / pyproject.toml.
  2. Read python-workiq.md — section "Python Google ADK — Wiring (VERIFIED, with sample-vs-PyPI divergence)".
  3. Decide between two paths (the published ADK sample diverges from the PyPI extension's signature):
    • Path A — PyPI extension (default, smaller scope): import McpToolRegistrationService from microsoft_agents_a365.tooling.extensions.googleadk.services.mcp_tool_registration_service and call add_tool_servers_to_agent(agent=..., auth=..., auth_handler_name=..., context=turn_context, auth_token=...). Do NOT pass agentic_app_id — the PyPI extension's signature has no such kwarg and will TypeError. Wrap in asyncio.wait_for(timeout=10.0).
    • Path B — DIY scaffold (matches sample exactly): ask the user "Do you want the sample's exact behavior (AGENTIC_APP_ID env-var override + ~165-line local file)? Or the simpler PyPI-extension path?" — if they pick Path B, copy mcp_tool_registration_service.py from Agent365-Samples/python/google-adk/sample-agent/ into the project and import locally.
  4. Pre-skip the call if neither bearer token nor auth handler is available (Playground scenario).

§4.10 Python Semantic Kernel (BEST-EFFORT — no published sample)

Tell the user verbatim: "No Microsoft sample exists for Python Semantic Kernel. Wiring shape is inferred from the SDK signature — verify against the SDK source before production deployment."

  1. Install:
    pip3 install microsoft-agents-a365-tooling microsoft-agents-a365-tooling-extensions-semantickernel 2>/dev/null \
      || pip install microsoft-agents-a365-tooling microsoft-agents-a365-tooling-extensions-semantickernel
    
    Edit requirements.txt / pyproject.toml.
  2. Read python-workiq.md — section "Python Semantic Kernel — Wiring (BEST-EFFORT — no published sample)".
  3. Edit the agent code with the best-effort call shown there. Mark every new line with # A365 WorkIQ — best-effort wiring (verify against SDK source before production).

§4.11 Python Azure AI Foundry (BEST-EFFORT — no published sample)

Tell the user verbatim: "No Microsoft sample exists for Python Azure AI Foundry. The exact import path is not independently verified. Skill installs the package and stops — wire the call manually after reading the SDK source."

  1. Install:
    pip3 install microsoft-agents-a365-tooling microsoft-agents-a365-tooling-extensions-azureaifoundry 2>/dev/null \
      || pip install microsoft-agents-a365-tooling microsoft-agents-a365-tooling-extensions-azureaifoundry
    
    Edit requirements.txt / pyproject.toml.
  2. Direct the user to the SDK source: https://github.com/microsoft/Agent365-python/tree/main/libraries/microsoft-agents-a365-tooling-extensions-azureaifoundry
  3. Do not generate wiring code. Mark this branch complete with a best-effort note.

Mark task complete: "Wire MCP tool service in agent code"


Phase 4.5 — Offer Word @mention handler (gated)

Mark task in progress: "Offer Word @mention handler (if applicable)"

Always enter this phase after Phase 4 completes — regardless of which §4.x branch ran. The two gates below decide whether the offer fires or the phase no-ops; the decision belongs to the gates, not to the LLM's intuition. Do not skip ahead to Phase 5 without running BOTH gates — the gate tables below each specify the exact task-completion note to write when they fail.

Read programmingLanguage and agentStack from .a365-workspace-detection.local.json case-insensitively — accept nodejs / NodeJS / Node.js and langchain / LangChain as equivalent. If agentStack is the combined string "Node.js LangChain", treat it as LangChain.

Gate 1 — Framework check. Read programmingLanguage and agentStack from .a365-workspace-detection.local.json:

programmingLanguageagentStackApply?
NodeJSLangChain✅ continue to Gate 2
anything elseanything else❌ skip phase — mark task complete with note: "N/A — only Node.js LangChain has a verified proactive @mention pattern in this repo today."

Gate 2 — mcp_WordServer presence. The user only sees the offer if WordServer was actually added. Read ToolingManifest.json (or whichever path Phase 3 wrote) and check:

grep -i '"mcpServerName":\s*"mcp_WordServer"' ToolingManifest.json
  • Match found → continue to the offer below.
  • No match → skip phase — mark task complete with note: "N/A — mcp_WordServer was not in the selected servers."

Both gates passed — ask via AskUserQuestion:

"You added mcp_WordServer. Do you want this AI Teammate to also notify and reply when someone @mentions it on a Word comment? It will read the document, post a reply on the same comment thread (not a new top-level comment), and DM the user in Teams with the reply text. The pattern is best-effort — no Microsoft Node.js sample is published yet, but the underlying SDK APIs are verified."

Options: Yes — wire @mention handling / No — Word read/write only

On Yes:

⚠️ Preserve existing observability wiring. If instrument-observability already ran, the message handler (handleAgentMessageActivity) is wrapped in an outer baggageScope.run(...) (the canonical pattern from Phase 5.5 of that skill). The new case NotificationType.WpxComment branch lives inside handleAgentNotificationActivity — a structurally separate handler — so it does NOT need to share the baggage scope. Do NOT modify or remove the existing baggageScope.run wrapping in handleAgentMessageActivity while adding the @mention code; removing it would silently break observability for regular messages.

  1. Read ${CLAUDE_PLUGIN_ROOT}/skills/add-workiq-tools/references/nodejs-workiq.md — section "Optional: Word @mention notification handling (LangChain — BEST-EFFORT)".
  2. Edit src/agent.ts:
    • Pre-check the AgentApplication super(...) call for proactive: {}. Add it if missing — required for proactive Teams DMs.
    • Add the per-user conversation index (userKeyToConversationId: Map<string, string> + userKeysFor helper).
    • Add the trackConversationForProactive private method. Call it from BOTH the message handler AND the installationUpdate(add) handler.
    • Add the case NotificationType.WpxComment branch + handleWpxCommentNotification method.
  3. Edit src/client.ts (optional but recommended): wire LangGraph MemorySaver checkpointer keyed on conversation.id so multi-turn @mention threads keep tool-call history.
  4. Mark all generated lines with: // A365 WorkIQ — best-effort wiring (verify against SDK source before production)
  5. Tell the user the gotchas verbatim:
    • "The document URL is on activity.attachments[*].contentUrl, NOT on wpxCommentNotification."
    • "Proactive DM requires the user to have spoken to the bot at least once (or had it installed). If userKeyToConversationId.get(...) returns undefined on first @mention, the agent surfaces a 'DM me once to enable Word notifications' message instead of failing silently."
    • "Tell the LLM explicitly to use the reply tool — without that instruction, models default to AddComment which creates a new top-level thread."

On No:

  1. Tell the user verbatim: "Skipped. Word tools still work for direct user prompts; only the proactive @mention path is omitted. You can re-enable later by re-running /agent365:add-workiq-tools — the skill is idempotent."
  2. Record the decision in the cache so the stop-hook validator honours it instead of failing the session at end:
    • Read .a365-workspace-detection.local.json, merge { "wordMentionDeclined": true } into it, Write it back.
    • Without this marker, validate-add-workiq-tools.js will treat the missing WpxComment / proactive / userKeyToConversationId symbols as evidence of a silent skip (Phase 4.5 silently bypassed) and hard-fail the session. The marker is the machine-readable signal that "user declined" is the legitimate completion state.

Mark task complete: "Offer Word @mention handler (if applicable)"


Phase 5 — Guide Permissions Handoff

Mark task in progress: "Guide permissions handoff"

Adding servers to ToolingManifest.json does not automatically grant permissions. Determine which path applies:

Path A — Blueprint does NOT exist yet (a365 setup not yet run)

Tell the user:

Permissions will be applied automatically when you run a365 setup all. The setup process reads ToolingManifest.json and grants OAuth2 permissions for all configured MCP servers as part of blueprint creation.

Run: a365 setup all

Path B — Blueprint already exists (a365.generated.config.json present)

Check if a365.generated.config.json exists and has a non-empty agentBlueprintId.

If yes, tell the user:

⚠️ Global Administrator action required.

Your blueprint already exists. MCP permissions must be granted separately.

Step 1 — Share the updated manifest with your Global Administrator:

  • Share the file: ToolingManifest.json (just updated by the CLI)
  • Or share the whole project folder path

Step 2 — Admin runs (from the project directory where a365.config.json lives):

a365 setup permissions mcp  # grants OAuth2 grants for all servers in manifest (handles V1 + V2 mixed manifests)

MCP V1/V2 migration: If migrating from V1 to V2 servers and the admin wants to remove legacy shared-audience scopes:

a365 setup permissions mcp --remove-legacy-scopes --dry-run  # preview what would be removed
a365 setup permissions mcp --remove-legacy-scopes            # apply

Step 3 — After admin confirms permissions are granted, continue testing.

Reference: https://learn.microsoft.com/en-us/microsoft-agent-365/developer/reference/cli/develop

Mark task complete: "Guide permissions handoff"


Phase 6 — Set Up Dev Token for Testing

Mark task in progress: "Set up dev token for testing"

6.1 Get a bearer token for local development

a365 develop get-token

This opens a browser for interactive authentication and returns a token for the MCP resource.

For per-server tokens (V2 pattern):

a365 develop get-token --resource mcp -o raw

6.2 Set the token in environment

For .NET — set in Properties/launchSettings.json (or appsettings.Development.json):

{
  "profiles": {
    "WorkIQ Dev": {
      "commandName": "Project",
      "environmentVariables": {
        "SKIP_TOOLING_ON_ERRORS": "true",
        "BEARER_TOKEN_<SERVERNAME>": "<token-from-get-token>"
      }
    }
  }
}

For Node.js — add to .env:

BEARER_TOKEN=<token-from-get-token>
SKIP_TOOLING_ON_ERRORS=true

For Python — add to .env:

BEARER_TOKEN=<token-from-get-token>
SKIP_TOOLING_ON_ERRORS=true
ENV=development

Tell the user: tokens expire — re-run a365 develop get-token to refresh.

Mark task complete: "Set up dev token for testing"


Phase 7 — Validate Build

Mark task in progress: "Validate build"

For .NET AgentFramework

dotnet build

For Node.js

npm install
npm run build || npm run compile || echo "No build script — skipping compile check"

For Python

pip3 install -r requirements.txt 2>/dev/null || pip install -r requirements.txt || pip install .
python3 -c "from microsoft_agents_a365.tooling.extensions.agent_framework import McpToolRegistrationService; print('WorkIQ imports OK')" 2>/dev/null || python -c "from microsoft_agents_a365.tooling.extensions.agent_framework import McpToolRegistrationService; print('WorkIQ imports OK')"

Adjust the import path to match the installed framework extension (e.g. .langchain, .openai).

If build fails, present error output with suggested fixes. Do not revert changes.

Mark task complete: "Validate build"


Phase 8 — Final Summary

  1. TaskList — Show all completed tasks.

  2. Ask:

WorkIQ tools are wired. Want to run a quick local test now?
  1. Yes — run the test-local skill
  2. No  — show me the summary and I'll test later

If yes, invoke the test-local skill.

  1. Present summary:
✅ WorkIQ tools added!

**Agent type:** [.NET AgentFramework | Node.js | Python]
**MCP servers added:** [list of server names from a365 develop list-configured]
**ToolingManifest.json:** updated ✅
**Agent code wired:** GetMcpToolsAsync ✅

**Permissions status:**
[Path A: Will be applied by a365 setup all]
  OR
[Path B: ⚠️ Global Administrator must run a365 setup permissions mcp]

**Dev testing:**
  1. Get a token: a365 develop get-token
  2. Set BEARER_TOKEN (or per-server BEARER_TOKEN_<SERVER>) in your .env / launchSettings.json
  3. Set SKIP_TOOLING_ON_ERRORS=true for dev
  4. Start your agent and test a WorkIQ prompt (e.g. "List my Teams channels")

MCP CLI Commands Reference

All commands the skill uses — show this table to the user on request.

CommandWhat it doesWho
a365 develop list-availableFull WorkIQ server catalog with V1/V2 labelsDeveloper
a365 develop add-mcp-servers "mcp_MailTools" "mcp_CalendarTools"Writes selected servers to ToolingManifest.json — no permissions yet. Names must match exact mcpServerName from list-available.Developer
a365 develop list-configuredShows servers currently in ToolingManifest.jsonDeveloper
a365 develop get-tokenBrowser auth → bearer token for local testingDeveloper
a365 develop get-token --resource mcp -o rawRaw token string (pipe to clipboard or .env)Developer
a365 setup permissions mcpGrants OAuth2 delegated scopes for all servers in manifestGlobal Admin
a365 setup permissions mcp --remove-legacy-scopes --dry-runPreview removal of V1 shared-audience scopes (V1→V2 migration)Global Admin
a365 setup permissions mcp --remove-legacy-scopesApply V1 scope removalGlobal Admin
a365 develop add-permissionsGrant permissions for a custom client app (not blueprint)Developer (Application.ReadWrite.All)
a365 setup allProvision blueprint AND grant MCP permissions in one stepDeveloper
a365 develop-mcpManage Dataverse-hosted MCP servers (separate command)Developer

MCP Permissions by Server

All WorkIQ servers require delegated (OBO) permissions — this is why authMode = s2s blocks WorkIQ entirely. The agent code wires the unified scope Tools.ListInvoke.All; the Graph scopes below are granted at the Entra app level by a365 setup permissions mcp.

The OAuth2 scopes that a365 setup permissions mcp grants are fetched from the live catalog — see a365 develop list-available to inspect what each server requires. We don't reproduce the scope mapping here because the catalog can evolve; the CLI's grant step uses live data, not this doc.

agentic-user path: The Agentic User identity (AI Teammate) satisfies the "signed-in user" requirement — a365 setup all --aiteammate provisions the Agentic User and grants all delegated scopes to it. WorkIQ calls are made on behalf of the Agentic User, not the human caller.


Permissions Workflow

Developer                                  Global Administrator
─────────────────────────────────          ──────────────────────────────────────
1. a365 develop list-available
   (browse catalog)

2. a365 develop add-mcp-servers
   "mcp_MailTools" "mcp_CalendarTools"   (exact names from list-available)
   → writes ToolingManifest.json
   → NO permissions granted yet

3a. No blueprint yet:
    a365 setup all
    → provisions blueprint
    → grants MCP permissions ✅

3b. Blueprint already exists:
    Share ToolingManifest.json ───────→   a365 setup permissions mcp
    with admin                             (grants OAuth2 delegated scopes)
                                           → permissions granted ✅

4. a365 develop get-token
   set BEARER_TOKEN in .env
   set SKIP_TOOLING_ON_ERRORS=true
   → test locally ✅

Error Handling

SituationAction
a365 CLI not installedInstall with dotnet tool install -g Microsoft.Agents.A365.DevTools.Cli
a365 develop list-available failsCheck a365 CLI authentication; run a365 auth login
Need to manage MCP servers in DataverseUse a365 develop-mcp (not a365 develop) — separate command for Dataverse-hosted MCP server management
Server name not found in catalogShow user the list-available output and ask to re-select
add-mcp-servers failsRun a365 develop list-available again to verify exact server name spelling
Tooling package install failsCheck NuGet/npm/pip registry access; verify runtime is installed
Build fails after wiringDo not revert; show error and offer to debug
403 from WorkIQ at runtimeGA has not run a365 setup permissions mcp — share ToolingManifest.json with admin
Token errors at runtimeRun a365 develop get-token; set env vars; enable SKIP_TOOLING_ON_ERRORS=true

Idempotency

On subsequent runs:

  • a365 develop list-configured will show already-added servers — skip re-adding them
  • Skip tooling package install if already in .csproj / package.json
  • Skip GetMcpToolsAsync wiring if already present (detect by grep)
  • Always revalidate the build

References

  • Agent Detection: ${CLAUDE_PLUGIN_ROOT}/shared/agent-detection.md
  • .NET Patterns: ${CLAUDE_PLUGIN_ROOT}/skills/add-workiq-tools/references/dotnet-workiq.md
  • Node.js Patterns: ${CLAUDE_PLUGIN_ROOT}/skills/add-workiq-tools/references/nodejs-workiq.md
  • Python Patterns: ${CLAUDE_PLUGIN_ROOT}/skills/add-workiq-tools/references/python-workiq.md
  • CLI Reference: https://learn.microsoft.com/en-us/microsoft-agent-365/developer/reference/cli/develop