| name | add-workiq |
| description | Adds Work IQ Copilot MCP by delegating to /add-connector with api-id=shared_a365copilotchatmcp and action mode. Use when users need Microsoft 365 knowledge-grounded Work IQ search/chat. |
| user-invocable | true |
| allowed-tools | Read, AskUserQuestion, Skill |
| model | sonnet |
📋 Shared Instructions: shared-instructions.md — Cross-cutting concerns.
Add Work IQ Copilot MCP (Wrapper)
This skill is a thin wrapper. Use /add-connector as the single implementation path.
Delegation contract
Invoke /add-connector with:
api-id: shared_a365copilotchatmcp
mode: action
Work IQ Integration: MCP Session Pattern
After the connector is added, you'll have access to WorkIQCopilotMCPService. Work IQ uses MCP (Model Context Protocol), a stateful protocol. Use the McpSession wrapper class to manage this properly.
Setup: Create McpSession Wrapper
⚠️ CRITICAL: The McpSession implementation is complex. Copy the production-ready code below exactly. It handles session negotiation, auto-retry on errors, proper JSON-RPC ID sequencing, and response parsing.
Create src/connectors/mcpClient.ts:
import type { IOperationResult } from '@microsoft/managed-apps/data'
import { WorkIQCopilotMCPService } from '../../generated/services/WorkIQCopilotMCPService'
import type { QueryRequest } from '../../generated/models/WorkIQCopilotMCPModel'
export interface JsonRpcRequest {
jsonrpc: '2.0'
id?: string
method: string
params?: Record<string, unknown>
}
export interface JsonRpcResponse {
jsonrpc?: string
id?: string
result?: Record<string, unknown>
error?: { code?: number; message?: string; data?: unknown }
}
type CopilotConversationMessage = {
text?: string
attributions?: Array<{ attributionType?: string; ?: ; ?: }>
}
= {
?: []
}
(): {
(!result. && result.) {
{ : { : result.. } }
}
: = result.
(data == ) {}
( data === ) data
( data === ) {
dataLines = data
.()
.( line.())
.( line.().())
payload = dataLines. ? dataLines.() : data
{
.(payload)
} {
{ : { : data } }
}
}
{ : { : data } }
}
{
nextId =
: |
: |
initialized =
(: <>): | {
container = raw <, >
dataObj =
raw. && raw. === ? (raw. <, >) :
resultObj =
dataObj?. && dataObj. ===
? (dataObj. <, >)
:
: <> = [
dataObj?.[],
dataObj?.,
dataObj?.,
resultObj?.[],
resultObj?.,
resultObj?.,
container[],
container.,
container.,
]
found = candidates.( value === && value. > )
found === ? found :
}
(: ): {
message = (res.?. ?? ).()
message.() || res.?. === -
}
(): {
. =
. =
}
(
: ,
?: <, >,
allowRetry =
): <> {
: = { : , : (.++), method, params }
raw = ( .(
.,
req
)) <>
negotiatedSessionId = .(raw)
(negotiatedSessionId) {
. = negotiatedSessionId
}
parsed = (raw)
(allowRetry && method !== && .(parsed)) {
.()
.()
.(method, params, )
}
parsed
}
(): <> {
res = .(, {
: ,
: {},
: { : , : },
})
. = !res.
res
}
(: ): <{ : ; ?: }> {
(!.) .()
raw = .(, {
: ,
: {
message,
...(. ? { : . } : {}),
},
})
parsed = (raw)
(parsed.) {
. = parsed.
}
{ : parsed., : parsed. }
}
}
(): | {
content = res.?. <{ ?: ; ?: }> |
(!.(content)) {
}
textBlocks = content
.( c. === && c. === )
.( c.!.())
.( value. > )
(textBlocks. === ) {
}
jsonBlock = textBlocks.(
block.() && .(block)
)
(jsonBlock) {
jsonBlock
}
nonMetadata = textBlocks.( !.(block))
nonMetadata ?? textBlocks[]
}
(): { : ; ?: } {
(res.) {
{ : }
}
rawText = (res)
(!rawText) {
{ : res. ? .(res., , ) : }
}
{
inner = .(rawText) {
?:
?:
?:
?:
}
( inner. === ) {
{
convo = .(inner.)
messages = .(convo.) ? convo. : []
attributed = messages.(
.(m.) && m.. >
)
selected = attributed ?? messages[] ?? messages[messages. - ]
replyText = selected?.?.()
(replyText) {
{ : replyText, : inner. }
}
} {
}
}
fallbackText = inner.?.() || inner.?.() || rawText
{ : fallbackText, : inner. }
} {
{ : rawText }
}
}
Usage: Generic Work IQ Integration Pattern
Initialize once per app (typically on component mount or app boot) and reuse for all Work IQ calls:
import { McpSession } from './connectors/mcpClient'
const workIqSession = new McpSession()
export async function queryWorkIQ(userPrompt: string): Promise<string> {
try {
const { text } = await workIqSession.callCopilotChat(userPrompt)
return text
} catch (error) {
const msg = error instanceof Error ? error.message : 'Work IQ query failed'
console.error('Work IQ Error:', msg)
throw error
}
}
Key Patterns:
- ✅ Initialize once, reuse across multiple calls
- ✅ Pass context-specific prompts to
callCopilotChat()
- ✅ Adapt prompts for your specific scenario (meetings, priorities, analysis, etc.)
- ✅ Session automatically reinitializes if "Session not found" error occurs
- ✅ Conversation ID is automatically persisted across calls for multi-turn chats
Prompt Structure
Work IQ responds well to context-rich, structured prompts. Use this pattern and adapt it for your specific scenario:
const prompt = `
You are [role/expert description].
**Context:**
- [Relevant data or background information]
- [Additional context as needed]
**Task:** [Clear, specific instruction]
**Format:** [If you need structured output, specify the format]
- Use markdown with clear section headings (## Section, ## Action Items, etc.)
- Specify limits (word count, number of items, etc.)
`.trim()
const { text } = await workIqSession.callCopilotChat(prompt)
How to adapt this pattern:
- ✅ Customize the role and context for your scenario (e.g., "meeting summarizer", "action item prioritizer", "project analyst")
- ✅ Add domain-specific information from your app
- ✅ Define the exact format you need (structured markdown, JSON, bullets, etc.)
- ✅ Adjust word limits and output expectations for your use case
Examples of adaptable scenarios:
- Meeting summaries with action items
- Prioritized daily action items from emails
- Project risk analysis from documents
- Team performance insights from communications
- Or any other knowledge-grounded analysis task
Response Parsing
Work IQ returns text that you parse based on your use case.
For structured markdown output (when you asked for ## sections):
const { text } = await workIqSession.callCopilotChat(prompt)
function extractSection(text: string, sectionName: string): string[] {
const regex = new RegExp(`##\\s*${sectionName}\\s*([\\s\\S]*?)(?=##|$)`)
const match = text.match(regex)
if (!match) return []
return match[1]
.split('\n')
.filter(line => line.trim().startsWith('-'))
.map(line => line.replace(/^-\s*/, '').trim())
.filter(Boolean)
}
const summary = extractSection(text, 'Summary')
const actionItems = extractSection(text, 'Action Items')
For unstructured output (when format is not critical):
const { text } = await workIqSession.callCopilotChat(prompt)
Adjust parsing based on:
- ✅ The format you specified in the prompt
- ✅ Your response structure (markdown sections, JSON, numbered lists, etc.)
- ✅ Both structured (markdown, JSON) and unstructured (text) outputs
Error Handling & Auto-Recovery
McpSession automatically handles "Session not found" errors by reinitializing. Surface errors appropriately:
try {
const { text } = await workIqSession.callCopilotChat(prompt)
if (text.includes('Error:')) {
throw new Error(text)
}
return text
} catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Query failed'
console.error('Work IQ Error:', errorMsg)
throw error
}
What McpSession handles automatically:
- ✅ Detects "Session not found" (-32001) errors
- ✅ Closes the session and auto-reinitializes
- ✅ Retries the failed request with the new session
- ✅ No manual retry logic needed
You only need to:
- ✅ Wrap calls in try/catch
- ✅ Surface errors to users appropriately
- ✅ Let exceptions propagate for app-level error handling
Implementation Guidance
Step 1: Copy mcpClient.ts
Use the provided McpSession implementation as-is. It handles all MCP protocol complexity.
Step 2: Initialize once
Create the McpSession instance once per app session (in useEffect or on boot).
Step 3: Craft your prompts
For your specific use case, provide context-rich prompts that include:
- Your domain context (meetings, priorities, risks, etc.)
- Specific data from your app
- Expected output format
Step 4: Parse and display
Extract the relevant data from Work IQ's response based on the format you requested.
Common Use Cases
Meeting Summaries:
- Prompt Work IQ with meeting context (date, organizer, attendees, description)
- Request ## Summary and ## Action Items sections
- Parse markdown sections and display in modal
Prioritized Daily Action Items:
- Query Work IQ to extract high-priority tasks from emails/messages
- Request ranked list format with due dates and owners
- Parse and display as prioritized task list
Project Risk Analysis:
- Prompt Work IQ to analyze project communications
- Request structured risk assessment with mitigation recommendations
- Parse JSON and display risk dashboard
Team Performance Insights:
- Query collaboration patterns from Teams/emails
- Request insights on productivity and blockers
- Parse and display performance dashboard
Any Knowledge-Grounded Analysis:
- Adapt the pattern for your domain
- Use Work IQ's access to M365 data (emails, Teams, calendar, documents)
- Return structured or unstructured output as needed
Why This Pattern is Required
Work IQ uses MCP (Model Context Protocol), a stateful protocol that requires:
- Session initialization before first call (handshake to exchange capabilities)
- Session ID tracking across all requests
- Proper JSON-RPC ID sequencing (each request must have a unique numeric ID)
- Multi-turn conversation support with conversation ID persistence
- Complex response parsing (nested JSON-RPC + optional streaming)
- Automatic error recovery on session timeouts
The McpSession class handles all of this. Attempting to bypass it (using random session IDs, hardcoded IDs, or direct API calls) will result in "Session not found" errors and failed integrations.