옵션
집 Skill 웹 개발 ui-widget-developer

ui-widget-developer

microsoft/skills microsoft/skills

React와 Fluent UI를 사용하여 대화형 위젯이 포함된 Copilot Chat용 MCP 서버를 구축하며, OpenAI Apps SDK 및 MCP Apps Extension 표준을 모두 지원합니다.

...모든 것을 확장하십시오
2
업데이트 된 시간 2026년 9월 18일

Copilot MCP 서버 개발

텔레메트리 태깅

ATK CLI 명령을 실행하기 전에, 모든 CLI 호출이 스킬에서 시작된 것으로 태깅되도록 세션 환경 변수를 설정하십시오:

export ATK_CLI_SKILL=true

세션 시작 시 이 명령을 한 번 실행하십시오. 동일한 터미널에서 이후 실행되는 모든 atk 명령은 이 설정을 상속받게 됩니다.

🔀 경로 선택

진행하기 전에 AskUserQuestion을 사용하여 사용자에게 어떤 경로를 선택할지 물어보세요. M365 Copilot 위젯 렌더링에는 두 가지 모두 지원됩니다. 각각의 장단점을 설명하고 사용자가 선택하도록 하세요:

OAI 앱 (이 스킬) MCP 앱 확장 (modelcontextprotocol/ext-apps)
표준 OpenAI 전용 공식 MCP 표준
다음에서 작동합니다 ChatGPT + M365 Copilot M365 Copilot, ChatGPT, VSCode 등
성숙도 실전 검증을 거쳤으며, 즉시 운영에 투입 가능 새로운 공식 표준, 성장하는 생태계
디자인 OpenAI Apps SDK MCP Apps 프로토콜 (크로스 플랫폼)
선택 시점 기존 OAI 앱에 대한 투자 개방형 표준을 선호하며, 가장 광범위한 클라이언트 지원을 원할 때

질문: "OAI 앱(OpenAI Apps SDK — 실전에서 검증되었으며 ChatGPT 및 M365 Copilot에서 작동)을 만들겠습니까, 아니면 MCP 앱(새로운 공식 표준 — M365 Copilot, ChatGPT, VSCode 등에서 작동)을 만들겠습니까?"

  • OAI 앱 → 아래 내용을 계속 읽어보세요. 이 스킬은 필요한 모든 기능을 다룹니다.
  • MCP 앱modelcontextprotocol/ext-apps 플러그인을 설치한 후(아래 참조), 해당 플러그인에서 적절한 스킬을 사용하세요.

MCP 앱: ext-apps 플러그인 설치

사용자가 MCP 앱을 선택하면 다음 단계를 자동으로 수행하십시오(단순 설명만 제공하고 중단하지 마십시오):

  1. /plugin marketplace add modelcontextprotocol/ext-apps 실행
  2. /plugin install mcp-apps@mcp-apps 실행
  3. 플러그인이 사용 가능한지 확인한 후, 사용자의 의도에 따라 올바른 ext-apps 스킬을 호출합니다

현재 환경에서 플러그인 명령을 사용할 수 없는 경우, 아래의 정확한 명령어를 제공하여 사용자에게 한 번 실행해 달라고 요청한 후, 선택한 ext-apps 스킬을 호출하여 진행하십시오.

참조 명령어:

MCP 앱을 구축하려면 마켓플레이스에서 ext-apps 플러그인을 설치하십시오:

1. /plugin marketplace add modelcontextprotocol/ext-apps
2. /plugin install mcp-apps@mcp-apps

그런 다음 해당 플러그인의 다음 스킬 중 하나를 사용하십시오:
- create-mcp-app      — 대화형 UI가 포함된 새로운 MCP 앱을 처음부터 생성합니다
- add-app-to-server   — 기존 MCP 서버의 도구에 대화형 UI를 추가합니다
- migrate-oai-app     — 기존 OAI 앱을 MCP 앱으로 변환합니다
- convert-web-app     — 웹 앱을 웹 + MCP 하이브리드 앱으로 변환합니다

설치가 완료되면 해당 스킬을 호출하여 진행하십시오.

참고: ext-apps 플러그인은 external modelcontextprotocol/ext-apps 마켓플레이스에 있으며, 이 플러그인 컬렉션의 일부는 아닙니다.

설치 후 핸드오프 매핑:

  • 처음부터 새로운 MCP 앱 생성 → create-mcp-app
  • 기존 MCP 서버에 앱 UI 추가 → add-app-to-server
  • 기존 OAI 앱 마이그레이션 → migrate-oai-app
  • 기존 웹 앱 변환 → convert-web-app

📛 프로젝트 감지 📛

이 기능은 Microsoft 365 Copilot Chat용 OAI 앱 또는 위젯 렌더링이 포함된 MCP 서버를 빌드할 때 트리거됩니다. MCP 서버는 MCP 프로토콜을 지원하는 모든 언어(TypeScript, Python, C# 등)로 작성할 수 있습니다. 에이전트 프로젝트와 MCP 서버는 동일한 저장소, 별도의 폴더, 또는 완전히 다른 프로젝트에 위치할 수 있습니다.

시나리오 라우팅

시작점 필요한 사항 경로
MCP Apps 표준 권장 크로스 플랫폼 위젯 지원 (M365 Copilot, ChatGPT, VSCode 등) modelcontextprotocol/ext-apps를 설치한 후, create-mcp-app 또는 add-app-to-server를 사용하십시오 — 위의 ‘경로 선택’ 참조
처음부터 시작 (에이전트 없음, MCP 서버 없음) 전체 OAI 앱 설정 먼저 declarative-agent-developer에 에이전트 스캐폴딩을 위임한 다음, MCP 서버 및 위젯 설정을 위해 이곳으로 돌아오세요
기존 M365 에이전트, 새로운 MCP 서버 MCP 서버 + 위젯 + mcpPlugin.json 구현 단계부터 시작
기존 MCP 서버, Copilot 위젯 추가 기존 서버에 위젯 지원 추가 Copilot 위젯 프로토콜부터 시작
언어 선택 (TypeScript 이외) 프로토콜 요구 사항 구현해야 할 내용은 Copilot 위젯 프로토콜을 참조하고, 참고 자료로는 MCP 서버 패턴(TypeScript)을 활용하세요

🚨 필수 실행 규칙 🚨

FLUENT UI 적용 (필수): 위젯 구현 시 반드시 React + Fluent UI 컴포넌트를 사용해야 합니다. 위젯 코드를 작성하기 전에, 에이전트는 반드시 다음 문서를 읽고 준수해야 합니다:

  • references/widget-patterns.md
  • references/best-practices.md FLUENT UI 패키지 요구 사항 (필수): 위젯 프로젝트는 구현 전에 Fluent UI 의존성을 포함해야 합니다. 최소한 다음 항목을 위젯 패키지 의존성에 설치하고 유지해야 합니다:
  • @fluentui/react-components
  • react
  • react-dom

이 중 누락된 패키지가 있다면, 위젯 코드 생성을 진행하기 전에 자동으로 설치하십시오.

생성된 위젯에 React 엔트리 파일(예: widgets/src//main.tsx 및 React 컴포넌트 파일)과 @fluentui/react-components의 Fluent 임포트가 포함되어 있지 않은 경우, 작업은 불완전한 것이므로 결과를 반환하기 전에 반드시 수정해야 합니다.

순수 HTML 위젯 금지 (기본값): 최종 위젯 솔루션으로 정적 HTML 템플릿과 인라인 JS를 사용하여 앱 콘텐츠를 직접 구현하지 마십시오. 최소한의 셸 HTML 파일은 빌드된 React 자산을 로딩하기 위한 로더로만 허용됩니다. 순수 HTML 위젯은 사용자가 명시적으로 React가 아닌 프로토타입을 요청한 경우에만 허용됩니다.

백그라운드 프로세스: MCP 서버와 devtunnel은 독립적인 OS 프로세스로 생성되어야 하며, 에이전트의 셸 세션 내에서 실행되어서는 안 됩니다. isBackground: true, mode: "async"Start-Job은 모두 에이전트의 셸 세션 내에서 실행되며, 메시지 사이에서 종료됩니다. 유일하게 안정적인 방법은 분리된 OS 프로세스를 생성하는 것입니다.

Windows — Start-Process -WindowStyle Hidden을 사용하십시오:

# devtunnel 시작
$t = Start-Process -FilePath "devtunnel" `
    -ArgumentList "host","","-a" `
    -WindowStyle Hidden -PassThru `
    -RedirectStandardOutput "tunnel.log" -RedirectStandardError "tunnel-err.log"

# MCP 서버 시작 — cmd.exe /c를 사용하여 작업 디렉터리를 설정하고 PATH를 상속받습니다
$s = Start-Process -FilePath "cmd.exe" `
    -ArgumentList "/c","cd /d  && " `
    -WindowStyle Hidden -PassThru `
    -RedirectStandardOutput "server.log" -RedirectStandardError "server-err.log"

# 나중에 중지할 수 있도록 PID 저장
"$($t.Id),$($s.Id)" | Out-File pids.txt
Write-Host "터널 PID $($t.Id), 서버 PID $($s.Id) 시작됨"

중지 방법: Stop-Process -Id (Get-Content pids.txt).Split(',') 또는 Stop-Process -Id .

Linux/Mac — &와 함께 nohup을 사용하세요:

nohup devtunnel host  > tunnel.log 2>tunnel-err.log &
echo "tunnel:$!" >> pids.txt
nohup  > server.log 2>server-err.log &
echo "server:$!" >> pids.txt

중지하려면: kill $(grep -oP '\d+' pids.txt).

시작 후, 진행하기 전에 로그를 확인하여 두 프로세스가 모두 실행 중인지 확인하십시오:

# Windows
Start-Sleep 3; Get-Content tunnel.log, server.log
# Linux/Mac
sleep 3 && tail tunnel.log server.log

완전 자동화: 사용자에게 명령을 수동으로 실행하라고 절대 요청하지 마십시오. 도구 설치, 인증, 서비스 시작 등 모든 작업을 자동으로 수행하십시오. 사용자에게는 정말로 필요한 경우(예: devtunnel 사용자 로그인-g -d 옵션 사용 시 장치 코드 확인)에만 대화형 입력을 요청하십시오. 도구가 설치되어 있지 않으면 설치하십시오. 서비스를 시작해야 하면 시작하십시오. 사용자는 완전한 자동화를 기대합니다.

경로 선택 (필수 — 코드 작성 전 반드시 수행): 코드를 작성하거나, 명령을 실행하거나, 아키텍처 관련 결정을 내리기 전에 반드시 AskUserQuestion을 사용하여 사용자에게 OAI 앱과 MCP 앱 확장 중 어느 것을 원하는지 물어보아야 합니다.

이 규칙에는 예외가 없습니다. 가장 흔한 실패 사례는 “사용자의 요청만으로도 선택 사항이 명백하므로 물어보는 것은 불필요하다”는 식의 추론입니다. 이러한 추론은 항상 틀린 것이므로, 어떤 경우든 무조건 AskUserQuestion을 호출해야 합니다. 사용자가 “위젯이 포함된 MCP 서버를 구축해 주세요”라고 말하는 것은 이 질문에 대한 답이 아닙니다. 사용자가 이 스킬의 이름을 불러 호출하는 것도 답이 아닙니다. 오직 질문에 대한 명시적인 답변만이 유효합니다. 물어봐야 할 정확한 질문은 위의 ‘경로 선택’ 항목을 참조하십시오.

에이전트 프로비저닝: 재프로비저닝은 에이전트 매니페스트가 변경될 때(예: mcpPlugin.json 도구 정의, MCP 서버 URL, declarativeAgent.json, instruction.txt)에만 필요합니다. MCP 서버 코드 변경(도구 구현, React 위젯 코드, 서버 로직)의 경우 에이전트를 다시 프로비저닝할 필요가 없습니다. 서버를 실행하거나 배포하면 변경 사항이 자동으로 반영됩니다.

프로비저닝이 필요한 경우:

  1. manifest.json의버전을 올립니다 (패치 버전을 증가시킵니다. 예: 1.0.01.0.1)
  2. 에이전트 배포:
    npx -y --package @microsoft/m365agentstoolkit-cli atk provision --env local
    
    

위젯 테스트 링크: MCP 서버가 실행 중일 때 사용자에게 결과를 전달할 때마다, 사용자가 로컬에서 테스트할 수 있도록 모든 위젯에 대한 링크를 반드시 포함해야 합니다. 형식:

🧪 로컬에서 위젯 테스트하기:
- http://localhost:3001/widgets/widget-name.html
- http://localhost:3001/widgets/another-widget.html

mcp-server/widgets/ 디렉터리(또는 이에 상응하는 위젯 폴더)에 있는 모든 .html 파일을 나열하십시오. 이는 사용자가 Copilot에서 테스트하기 전에 위젯 렌더링을 확인하는 데 도움이 됩니다.

완료 시 자동 배포 (필수 — 건너뛰지 마십시오): 코딩이 완료되면 사용자를 기다리지 않고 자동으로 진행하십시오:

  1. 백그라운드에서 MCP 서버 + devtunnel을 시작합니다(위의 ‘백그라운드 프로세스’ 항목 참조).
  2. MCP Inspector를 사용하여 E2E 검증을 실행합니다(아래의 ‘MCP 도구 구성 규칙’ 참조) — 계속하기 전에 오류가 있으면 수정합니다
  3. 필요한 경우 에이전트를 프로비저닝합니다(위의 ‘에이전트 프로비저닝’ 참조)
  4. 다음 형식으로 프로젝트 요약 정보를 출력합니다:
## ✅  — 준비 완료

### 위젯
- [widget-name.html](http://localhost:/widgets/widget-name.html)
- [위젯 이름2.html](http://localhost:/widgets/widget-name2.html)

### 엔드포인트
- MCP 서버: http://localhost:/mcp
- 터널을 통한 MCP: https:///mcp

### Copilot에서 테스트
로컬:      https://m365.cloud.microsoft/chat/?titleId={env/.env.local의 M365_TITLE_ID}
기타 환경: {env/.env.{environment}의 SHARE_LINK}

에이전트 프로젝트 위임: 이 스킬은 선언형 에이전트 프로젝트가 아닌 MCP 서버 및 위젯을 구축합니다. 사용자의 요청에 선언형 에이전트 자체의 생성 또는 구성(스캐폴딩, m365agents.yml, m365agents.local.yml, declarativeAgent.json, 매니페스트 라이프사이클)이 포함된 경우, declarative-agent-developer 스킬로 위임하십시오.

MCP 리소스 등록: 모든 위젯에는 반드시 일치하는 MCP 리소스가 있어야 합니다. 리소스가 없으면 Copilot은 MCP 프로토콜을 통해 위젯 셸을 가져올 수 없으며, 위젯이 렌더링되지 않습니다.

새로운 위젯을 추가할 때마다 다음 체크리스트를 완료하십시오:

  1. widgets/ 디렉터리에 위젯 셸 HTML 파일을 생성하고, widgets/src// 경로에 React 위젯 엔트리를 생성하십시오(widget-patterns.md 참조).
  2. ui://widget/.html URI 상수를 정의하십시오
  3. resources 배열에 다음 내용을 포함한 Resource 항목을 추가하십시오:
    • uri: ui://widget/.html URI
    • mimeType: "text/html+skybridge"
    • _meta: openai/widgetDomainopenai/widgetCSP (환경 변수에서 가져옴)가 포함된 CSP 구성
  4. ☐ 이 URI에 대한 위젯 셸 HTML을 반환하는 resources/read 핸들러를 추가합니다
  5. _meta.openai/outputTemplate가 동일한 ui://widget/.html URI를 가리키도록 도구를 추가합니다
  6. ☐ 초기화 응답에 resources: {} 가 포함되어 있는지 서버 기능을 확인

위젯 셸 및 자산 관련 고려 사항:

  • 권장 방식 (React + Fluent UI): 리소스 HTML은 MCP 서버의 /assets/ 경로에서 제공되는 빌드된 JS/CSS 자산으로 연결되는 최소한의 셸이어야 합니다.
  • 예외적인 경우에만: resources/read를 통한 독립형 HTML은 사용자가 명시적으로 요청한 프로토타입에만 사용됩니다. 기본 및 프로덕션 경로는 React + Fluent UI입니다.

React 빌드 출력을 위한 셸 예시:


  
  

  

자산 URL의 기본 주소로 WIDGET_BASE_URL 또는 MCP_SERVER_URL 환경 변수를 사용하십시오(mcp-server-pattern.md의 "구성 가능한 위젯 기본 URL" 섹션 참조).

전체 리소스 및 자산 제공 패턴에 대해서는 mcp-server-pattern.md를 참조하십시오.

⚠️ MCP 도구 구성 규칙 ⚠️

절대 mcpPlugin.json에 도구 정의를 수동으로 작성하지 마십시오. 항상 MCP Inspector를 사용하여 실행 중인 MCP 서버에서 전체 도구 정의를 가져오십시오.

도구 명명 규칙: 도구 이름은 반드시 ^[A-Za-z0-9_]+$ 패턴(영문자, 숫자, 밑줄만 허용)을 따라야 합니다. 도구 이름에 하이픈(-)을 절대로 사용하지 마십시오. 대신 밑줄을 사용하십시오(예: render_profile, render-profile이 아님).

필수 워크플로:

  1. MCP 서버를 (백그라운드에서)시작합니다.
  2. MCP Inspector를 사용하여 최신 도구 정의를 가져옵니다:
    npx @modelcontextprotocol/[email protected] --cli https://my-mcp-server.example.com --transport http --method tools/list
    
    
  3. 인스펙터에서도구 정의 전체 ( 이름, 설명, inputSchema, _meta, 주석, 제목 포함)를 복사합니다.
  4. mcpPlugin.json 파일의 runtimes[].spec.mcp_tool_description.tools ( RemoteMCPServer 런타임의 spec 객체 내부)에 붙여넣기
  5. devtunnel을 통해E2E 검증을 실행합니다. 각 도구를 호출하고 응답에 structuredContent와 _meta.openai/widgetAccessible: true가 포함되어 있는지 확인합니다:
    npx @modelcontextprotocol/[email protected] --cli https:///mcp --transport http --method tools/call --tool-name 
    
    
    또한 GET https:///health 요청이 {"status":"ok"}를 반환하는지 확인하십시오. 프로비저닝 전에 오류가 있으면 수정하십시오.

MCP Inspector에는 서버의 정확한 도구 스키마가 표시됩니다. 이 정의를 수동으로 작성하거나 수정하지 말고, 전체를 복사하십시오. 이렇게 하면 mcpPlugin.json이 MCP 서버와 항상 동기화 상태를 유지할 수 있습니다.

Microsoft 365 Copilot Chat과 통합되고 풍부한 대화형 위젯을 렌더링하는 MCP 서버를 구축하세요.

아키텍처

M365 Copilot ──▶ mcpPlugin.json ──▶ MCP 서버 ──▶ structuredContent ──▶ React + Fluent UI 위젯
     │              (RemoteMCPServer)    (Streamable HTTP)                  (window.openai.toolOutput)
     │
     └── 기능(People 등)이 MCP 도구에 전달할 데이터를 제공합니다

프로젝트 구조

다음은 예시 프로젝트 구조로, 필수 요건은 아니지만 MCP 서버 및 위젯 개발을 구성하는 데 흔히 사용되는 패턴입니다:

project/
├── appPackage/
│   ├── manifest.json           # 팀 매니페스트 (배포 시 버전 번호 증가)
│   ├── declarativeAgent.json   # 에이전트 구성 + 기능
│   ├── mcpPlugin.json          # _meta를 포함한 도구 정의
│   └── instruction.txt         # 에이전트 동작 지침
├── mcp-server/
│   ├── src/index.ts            # Streamable HTTP를 지원하는 서버
│   ├── widgets/                # 위젯 셸 + React 소스
│   │   ├── my-widget.html      # resources/read에서 반환되는 최소 셸
│   │   └── src/my-widget/      # React + Fluent UI 소스
│   ├── assets/                 # /assets에서 제공되는 빌드된 위젯 번들
│   └── package.json
├── scripts/
│   ├── setup-devtunnel.sh      # Linux/Mac용 devtunnel 설정
│   └── setup-devtunnel.ps1     # Windows용 devtunnel 설정
└── env/.env.local              # MCP_SERVER_URL, MCP_SERVER_DOMAIN

언어 참고: 이는 TypeScript 프로젝트 구조를 보여줍니다. Python의 경우, mcp-server/src/index.ts를 Python 엔트리 포인트(예: server.py)로 대체하십시오. C#의 경우 표준 .NET 프로젝트 구조를 사용하십시오. appPackage/, widgets/, scripts/env/ 디렉터리는 언어에 구애받지 않습니다.

Copilot 위젯 프로토콜

Copilot Chat에서 위젯을 렌더링하려면 MCP 서버가 다음 프로토콜 요구 사항을 구현해야 합니다. 이는 언어와 무관하게 적용됩니다:

  1. 스트리밍 가능한 HTTP 전송 — 세션 관리를 통해 POST, GET, DELETE 요청을 처리하는 /mcp 엔드포인트
  2. CORS 헤더/mcp에 대한 원본 확인을 통해 m365.cloud.microsoft*.m365.cloud.microsoft를 허용하며, 필수 MCP 헤더가 포함되어야 함
  3. 서버 기능initialize 응답에 resources: {}tools: {}를 반드시 선언해야 함
  4. MCP 리소스ui://widget/.html URI, text/html+skybridge MIME 유형 및 CSP _meta를 사용하여 위젯 등록
  5. 도구 응답 형식콘텐츠 (text) + structuredContent (위젯 데이터) + openai/outputTemplate을 포함한 _meta를 반환
  6. 위젯 제공 — 셸 파일의 경우 /widgets/*.html, 빌드된 번들의 경우 /assets/* HTTP 경로를 사용하며, 둘 다 원본 확인(origin-checking) CORS를 적용

전체 프로토콜 세부 정보, JSON 형식 및 기존 MCP 서버에 대한 적용 체크리스트는 references/copilot-widget-protocol.md를 참조하십시오.

구현

MCP 서버 패턴 (TypeScript 참조)

전체 구현 내용은 references/mcp-server-pattern.md를 참조하십시오.

다른 언어의 경우, 해당 언어의 MCP SDK를 사용하여 Copilot 위젯 프로토콜에 설명된 요구 사항을 구현하십시오. SDK 패키지는 ‘언어별 SDK 참조’ 표를 참조하십시오.

핵심 요구 사항:

  • /mcp 경로에서 Streamable HTTP 전송 프로토콜을 노출해야 합니다
  • openai/outputTemplate을 사용하여 structuredContent + _meta 반환
  • HTTP 엔드포인트를 통해 위젯 제공
  • 크로스 오리진 요청에 대한 CORS 처리
  • 부분 데이터에 유연하게 대응하기 (누락된 필드에는 "Unknown"을 기입)

도구 응답 형식:

return {
  content: [{ type: "text", text: "Summary" }],
  structuredContent: { /* 위젯 데이터 */ },
  _meta: { "openai/outputTemplate": "ui://widget/name.html", "openai/widgetAccessible": true }
};

부분 데이터 처리

누락된 필드를 처리하기 위해 항상 입력 데이터를 정규화하십시오:

server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
  const args = request.params.arguments as { title?: string; items?: Partial[] };

  // 데이터 정규화 - 누락된 필드에는 "Unknown"을 채움
  const title = args.title || "Default Title";
  const items = (args.items || []).map(item => ({
    name: item.name || "Unknown",
    value: item.value || "Unknown",
  }));

  // 위젯용 structuredContent 생성
  const structuredContent = { title, items };
  // ...
});

위젯 패턴

전체 예제는 references/widget-patterns.md를 참조하십시오.

핵심 요구 사항:

  • React + Fluent UI 컴포넌트(@fluentui/react-components) 사용
  • 위젯 패키지 의존성에 @fluentui/react-components, react, react-dom이 포함되어 있는지 확인
  • FluentProvider (webLightTheme/webDarkTheme) 및 Fluent 토큰을 사용하여 테마 적용
  • 공유 훅을 통해 데이터에 접근해야 합니다(예: useOpenAiGlobal("toolOutput"))
  • 디버깅용 대체 처리: window.openai를 사용할 수 없을 때 내장된 모의 데이터 사용
  • "Unknown" 값을 적절하게 처리합니다(예: 액션 버튼 숨기기).

플러그인 스키마

mcpPlugin.json 형식에 대해서는 references/plugin-schema.md를 참조하십시오.

핵심 요구 사항:

  • RemoteMCPServer 런타임이 포함된 스키마 v2.4
  • run_for_functions 배열이 도구 이름과 일치해야 함
  • 위젯 바인딩을 위한 도구 정의 내의_meta
  • inputSchema - 유연성을 위해 속성을 선택 사항으로 설정하고, 설명란에 기본값을 명시하십시오

DevTunnels 설정

로컬 테스트 전용. DevTunnels는 사용자의 컴퓨터에서 개발 및 테스트를 위한 것입니다. 에이전트를 더 광범위하게 공유하기 전에, MCP 서버와 위젯 자산을 호스팅 환경(예: Azure App Service, Azure Static Web Apps 또는 다른 호스팅 제공업체)에 배포하고, 이에 따라 에이전트 매니페스트 URL을 업데이트하십시오.

DevTunnels는 안정적인 URL을 위한 명명된 터널을 사용하여 로컬호스트 MCP 서버를 M365 Copilot에 노출합니다. 설정 스크립트, 명령어 참조 및 문제 해결 방법은 references/devtunnels.md를 참조하십시오.

설정 스크립트(npm run tunnel / npm run tunnel:win):

  1. 첫 실행 시 명명된 터널을 생성하거나(또는 기존 터널을 재사용합니다)
  2. 구성된 포트에서 터널 호스팅을 시작합니다
  3. env/.env.local 파일을 MCP_SERVER_URLMCP_SERVER_DOMAIN으로 업데이트합니다(첫 실행 시에만).
  4. 터널 호스팅을 계속합니다

빠른 시작

터미널 1 - MCP 서버 시작:

cd mcp-server
npm install
npm run dev

터미널 2 - DevTunnel 시작:

npm run tunnel
# 또는 Windows의 경우:
npm run tunnel:win

첫 실행 시, 터널이 연결되면 에이전트를 프로비저닝하십시오(‘AGENT PROVISIONING’ 규칙 참조). 이후 실행 시에는 터널 URL이 고정되므로, 에이전트 매니페스트가 변경되지 않는 한 재프로비저닝이 필요하지 않습니다.

개발 워크플로

  1. MCP 서버 시작 (핫 리로드 기능이 포함된 개발 모드):

    • TypeScript: cd mcp-server && npm install && npm run dev
    • Python: cd mcp-server && pip install -r requirements.txt && python server.py
    • C#: cd mcp-server && dotnet run
  2. devtunnel 시작 (첫 실행 시 지정된 이름의 터널을 생성하고, 이후 실행 시 재사용):

    npm run tunnel
    
  3. 프로비저닝 + 테스트 — 이 작업이 필요한 시점은 ‘AGENT PROVISIONING’ 규칙을 참조하십시오; Copilot이 변경 사항을 반영하지 않는 경우 manifest.json의 버전을 업데이트하십시오

모범 사례

자세한 지침은 references/best-practices.md를 참조하십시오.

주요 사항:

  1. 렌더링 도구: 데이터를 입력으로 받아들이고, 내부적으로 데이터를 가져오지 마십시오
  2. 지침: 에이전트에게 먼저 기능을 사용하도록 지시하고, 그 다음 데이터를 MCP 도구로 전달하십시오
  3. 테마: 다크 모드/라이트 모드 지원을 위해 FluentProvider + Fluent 토큰을 사용하십시오
  4. 디버그 모드: 로컬 위젯 테스트를 위해 대체 데이터를 포함하십시오
  5. 부분 데이터: 누락된 필드는 "Unknown" 기본값으로 처리
  6. 작업 버튼: 데이터가 “Unknown”일 때 이메일/채팅 버튼을 숨김
  7. 버전 업데이트: 변경 사항이 Copilot에 반영되지 않을 경우 매니페스트 버전을 올리기
GitHub에서 보기
---
name: ui-widget-developer
description: Build MCP servers for Copilot Chat with interactive widgets using React and Fluent UI, supporting both OpenAI Apps SDK and MCP Apps Extension standards.
---

# Copilot MCP Server Development

## Telemetry Tagging

Before running any `atk` CLI commands, set the session environment variable so all CLI invocations are tagged as skill-initiated:
```bash
export ATK_CLI_SKILL=true
```
Run this once at the start of the session. All subsequent `atk` commands in the same terminal will inherit it.

## 🔀 PATH SELECTION

**Before proceeding, ask the user which path they want to take using AskUserQuestion.** Both are supported for M365 Copilot widget rendering. Present the tradeoffs and let them choose:

| | **OAI Apps** (this skill) | **MCP Apps Extension** (`modelcontextprotocol/ext-apps`) |
|---|---|---|
| **Standard** | OpenAI-specific | Official MCP standard |
| **Works in** | ChatGPT + M365 Copilot | M365 Copilot, ChatGPT, VSCode, and more |
| **Maturity** | Battle-tested, production-ready | New official standard, growing ecosystem |
| **Design** | OpenAI Apps SDK | MCP Apps protocol (cross-platform) |
| **When to choose** | Existing OAI app investment | Prefer the open standard, want broadest client support |

**Ask:** _"Would you like to build an OAI app (OpenAI Apps SDK — battle-tested, works in ChatGPT and M365 Copilot) or an MCP app (new official standard — works in M365 Copilot, ChatGPT, VSCode, and more)?"_

- **OAI apps** → Continue below. This skill covers everything you need.
- **MCP apps** → Install the `modelcontextprotocol/ext-apps` plugin (see below), then use the appropriate skill from that plugin.

### MCP Apps: Install ext-apps Plugin

If the user chooses MCP Apps, do this automatically (do not stop at explanation-only):

1. Run `/plugin marketplace add modelcontextprotocol/ext-apps`
2. Run `/plugin install mcp-apps@mcp-apps`
3. Confirm the plugin is available, then invoke the correct ext-apps skill based on user intent

If plugin commands are unavailable in the current environment, provide the exact commands below and ask the user to run them once, then continue by invoking the selected ext-apps skill.

Reference commands:

```
To build an MCP App, install the ext-apps plugin from the marketplace:

1. /plugin marketplace add modelcontextprotocol/ext-apps
2. /plugin install mcp-apps@mcp-apps

Then use one of these skills from that plugin:
- create-mcp-app      — Scaffold a new MCP App with interactive UI from scratch
- add-app-to-server   — Add interactive UI to an existing MCP server's tools
- migrate-oai-app     — Convert an existing OAI app to use MCP Apps
- convert-web-app     — Turn a web app into a hybrid web + MCP App

After installing, invoke the relevant skill to continue.
```

> **Note:** The ext-apps plugin lives in the external `modelcontextprotocol/ext-apps` marketplace — it is not part of this plugin collection.

**Handoff mapping after install:**
- New MCP app from scratch → `create-mcp-app`
- Add app UI to existing MCP server → `add-app-to-server`
- Migrate existing OAI app → `migrate-oai-app`
- Convert an existing web app → `convert-web-app`

---

## 📛 PROJECT DETECTION 📛

This skill triggers when building MCP servers with OAI app or widget rendering for Microsoft 365 Copilot Chat. The MCP server can be written in any language that supports the MCP protocol (TypeScript, Python, C#, etc.). The agent project and MCP server may live in the same repo, separate folders, or entirely different projects.

## Scenario Routing

| Starting Point | What You Need | Path |
|---------------|---------------|------|
| **Prefer MCP Apps standard** | Cross-platform widget support (M365 Copilot, ChatGPT, VSCode, and more) | Install `modelcontextprotocol/ext-apps`, then use `create-mcp-app` or `add-app-to-server` — see [Path Selection](#-path-selection) above |
| **From scratch** (no agent, no MCP server) | Full OAI app setup | Delegate agent scaffolding to `declarative-agent-developer` first, then return here for MCP server + widgets |
| **Existing M365 agent, new MCP server** | MCP server + widgets + mcpPlugin.json | Start at [Implementation](#implementation) |
| **Existing MCP server, add Copilot widgets** | Widget support added to existing server | Start at [Copilot Widget Protocol](references/copilot-widget-protocol.md#adaptation-checklist-existing-mcp-server) |
| **Language choice** (non-TypeScript) | Protocol requirements | See [Copilot Widget Protocol](references/copilot-widget-protocol.md) for what to implement, [MCP Server Pattern (TypeScript)](references/mcp-server-pattern.md) as a reference |

---

## 🚨 CRITICAL EXECUTION RULES 🚨


**FLUENT UI ENFORCEMENT (REQUIRED):** Widget implementations MUST use React + Fluent UI components. Before writing any widget code, the agent MUST read and follow:
- `references/widget-patterns.md`
- `references/best-practices.md`
**FLUENT UI PACKAGE REQUIREMENT (REQUIRED):** The widget project MUST include Fluent UI dependencies before implementation. At minimum, install and keep these in the widget package dependencies:
- `@fluentui/react-components`
- `react`
- `react-dom`

If any of these packages are missing, install them automatically before continuing with widget code generation.

If the generated widget does not include React entry files (for example `widgets/src/<widget-name>/main.tsx` and a React component file) and Fluent imports from `@fluentui/react-components`, the task is incomplete and MUST be corrected before returning results.

**NO RAW HTML-ONLY WIDGETS (DEFAULT):** Do not implement app content directly with static HTML templates and inline JS as the final widget solution. A minimal shell HTML file is allowed only as a loader for built React assets. Raw/self-contained HTML-only widgets are allowed only when the user explicitly requests a non-React prototype.

**BACKGROUND PROCESSES:** MCP server and devtunnel MUST be spawned as independent OS processes — NOT run inside the agent's shell session. `isBackground: true`, `mode: "async"`, and `Start-Job` all run inside the agent's shell session and will be killed between messages. The only reliable approach is to spawn a detached OS process.

**Windows — use `Start-Process -WindowStyle Hidden`:**
```powershell
# Start devtunnel
$t = Start-Process -FilePath "devtunnel" `
    -ArgumentList "host","<tunnel-name>","-a" `
    -WindowStyle Hidden -PassThru `
    -RedirectStandardOutput "tunnel.log" -RedirectStandardError "tunnel-err.log"

# Start MCP server — use cmd.exe /c to set the working directory and inherit PATH
$s = Start-Process -FilePath "cmd.exe" `
    -ArgumentList "/c","cd /d <abs-path-to-mcp-server> && <start-command>" `
    -WindowStyle Hidden -PassThru `
    -RedirectStandardOutput "server.log" -RedirectStandardError "server-err.log"

# Save PIDs so they can be stopped later
"$($t.Id),$($s.Id)" | Out-File pids.txt
Write-Host "Started tunnel PID $($t.Id), server PID $($s.Id)"
```
To stop: `Stop-Process -Id (Get-Content pids.txt).Split(',')` or `Stop-Process -Id <pid>`.

**Linux/Mac — use `nohup` with `&`:**
```bash
nohup devtunnel host <tunnel-name> > tunnel.log 2>tunnel-err.log &
echo "tunnel:$!" >> pids.txt
nohup <start-command> > server.log 2>server-err.log &
echo "server:$!" >> pids.txt
```
To stop: `kill $(grep -oP '\d+' pids.txt)`.

After starting, tail the logs to confirm both processes are up before proceeding:
```powershell
# Windows
Start-Sleep 3; Get-Content tunnel.log, server.log
```
```bash
# Linux/Mac
sleep 3 && tail tunnel.log server.log
```

**FULL AUTOMATION:** Never tell the user to run commands manually. Install tools, authenticate, start services — do everything automatically. Only ask the user for interactive input that truly requires them (like device code confirmation during `devtunnel user login -g -d`). If a tool isn't installed, install it. If a service needs starting, start it. The user expects full automation.

**PATH SELECTION (REQUIRED — STOP BEFORE ANY CODE):** You MUST use `AskUserQuestion` to ask the user whether they want OAI Apps or MCP Apps Extension before writing any code, running any commands, or making any architectural decisions.

**There is no exception to this rule.** The most common failure mode is reasoning "the user's request makes it obvious, so asking is redundant." This reasoning is always wrong — invoke `AskUserQuestion` regardless. A user saying "build an MCP server with widgets" is NOT an answer to this question. A user invoking this skill by name is NOT an answer. Only an explicit answer to the question counts. See [PATH SELECTION](#-path-selection) above for the exact question to ask.

**AGENT PROVISIONING:** Re-provisioning is only required when the **agent manifest** changes (e.g., mcpPlugin.json tool definitions, MCP server URL, declarativeAgent.json, instruction.txt). MCP server code changes (tool implementations, React widget code, server logic) do **NOT** require re-provisioning the agent — running or deploying the server picks up changes automatically.

When provisioning is needed:
1. **Bump the version** in `manifest.json` (increment the patch version, e.g., `1.0.0` → `1.0.1`)
2. **Deploy the agent:**
   ```bash
   npx -y --package @microsoft/m365agentstoolkit-cli atk provision --env local
   ```

**WIDGET TESTING LINKS:** Every time you return to the user with a result while the MCP server is running, you MUST include links to ALL widgets so they can test them locally. Format:
```
🧪 Test widgets locally:
- http://localhost:3001/widgets/widget-name.html
- http://localhost:3001/widgets/another-widget.html
```
List every `.html` file in the `mcp-server/widgets/` directory (or equivalent widget folder). This helps users verify widget rendering before testing in Copilot.

**AUTO-DEPLOY ON COMPLETION (REQUIRED — DO NOT SKIP):** When coding is complete, proceed automatically without waiting for the user:
1. Start MCP server + devtunnel in the background (per BACKGROUND PROCESSES above)
2. Run E2E verification with MCP Inspector (per MCP TOOL CONFIGURATION RULE below) — fix any failures before continuing
3. Provision the agent if needed (per AGENT PROVISIONING above)
4. Print a project summary in this format:
```
## ✅ <Project Name> — Ready

### Widgets
- [widget-name.html](http://localhost:<PORT>/widgets/widget-name.html)
- [widget-name2.html](http://localhost:<PORT>/widgets/widget-name2.html)

### Endpoints
- MCP server: http://localhost:<PORT>/mcp
- MCP via tunnel: https://<tunnel-url>/mcp

### Test in Copilot
Local:      https://m365.cloud.microsoft/chat/?titleId={M365_TITLE_ID from env/.env.local}
Other envs: {SHARE_LINK from env/.env.{environment}}
```

**AGENT PROJECT DELEGATION:** This skill builds MCP servers and widgets, NOT declarative agent projects. If the user's request involves creating or configuring the declarative agent itself (scaffolding, `m365agents.yml`, `m365agents.local.yml`, `declarativeAgent.json`, manifest lifecycle), delegate to the `declarative-agent-developer` skill.

**MCP RESOURCE REGISTRATION:** Every widget MUST have a matching MCP resource. Without resources, Copilot cannot fetch widget shells through the MCP protocol and widgets will not render.

For each new widget, complete this checklist:
1. ☐ Create a widget shell HTML file in `widgets/` and a React widget entry under `widgets/src/<widget-name>/` (see widget-patterns.md)
2. ☐ Define a `ui://widget/<name>.html` URI constant
3. ☐ Add a `Resource` entry to the `resources` array with:
   - `uri`: the `ui://widget/<name>.html` URI
   - `mimeType`: `"text/html+skybridge"`
   - `_meta`: CSP config with `openai/widgetDomain` and `openai/widgetCSP` (from environment)
4. ☐ Add a handler for `resources/read` that returns the widget shell HTML for this URI
5. ☐ Add the tool with `_meta.openai/outputTemplate` pointing to the same `ui://widget/<name>.html` URI
6. ☐ Verify the server capabilities include `resources: {}` in the initialize response

**Widget shell + asset considerations:**
- **Preferred (React + Fluent UI)**: Resource HTML should be a minimal shell that links to built JS/CSS assets served from the MCP server's `/assets/` route.
- **Exception only**: Self-contained HTML via `resources/read` is for explicit user-requested prototypes only. Default and production path is React + Fluent UI.

Example shell for React build output:
  ```html
  <!doctype html><html><head>
    <script type="module" src="${serverUrl}/assets/my-widget.js"></script>
    <link rel="stylesheet" href="${serverUrl}/assets/my-widget.css">
  </head><body>
    <div id="widget-root"></div>
  </body></html>
  ```
  Use the `WIDGET_BASE_URL` or `MCP_SERVER_URL` environment variable for the asset URL base (see mcp-server-pattern.md "Configurable Widget Base URL" section).

See [mcp-server-pattern.md](references/mcp-server-pattern.md) for the complete resource and asset serving patterns.

---

## ⚠️ MCP TOOL CONFIGURATION RULE ⚠️

**NEVER manually write tool definitions in `mcpPlugin.json`.** Always use MCP Inspector to get the complete tool definitions from the running MCP server.

**TOOL NAMING CONVENTION:** Tool names MUST match the pattern `^[A-Za-z0-9_]+$` (letters, numbers, and underscores only). **NEVER use hyphens (-) in tool names.** Use underscores instead (e.g., `render_profile` not `render-profile`).

**MANDATORY WORKFLOW:**
1. **Start the MCP server** (in background)
2. **Use MCP Inspector** to get the latest tool definitions:
   ```bash
   npx @modelcontextprotocol/[email protected] --cli https://my-mcp-server.example.com --transport http --method tools/list
   ```
3. **Copy the COMPLETE tool definition** from the inspector (including `name`, `description`, `inputSchema`, `_meta`, `annotations`, `title`)
4. **Paste into `mcpPlugin.json`** under `runtimes[].spec.mcp_tool_description.tools` (inside the `RemoteMCPServer` runtime's `spec` object)
5. **Run E2E verification** through the devtunnel — call each tool and confirm the response contains `structuredContent` and `_meta.openai/widgetAccessible: true`:
   ```bash
   npx @modelcontextprotocol/[email protected] --cli https://<tunnel-url>/mcp --transport http --method tools/call --tool-name <tool_name>
   ```
   Also verify `GET https://<tunnel-url>/health` returns `{"status":"ok"}`. Fix any failures before provisioning.

The MCP Inspector shows the exact tool schema from your server. Copy it completely — do not manually write or modify these definitions. This ensures `mcpPlugin.json` stays in sync with the MCP server.

---

Build MCP servers that integrate with Microsoft 365 Copilot Chat and render rich interactive widgets.

## Architecture

```
M365 Copilot ──▶ mcpPlugin.json ──▶ MCP Server ──▶ structuredContent ──▶ React + Fluent UI Widget
     │              (RemoteMCPServer)    (Streamable HTTP)                  (window.openai.toolOutput)
     │
     └── Capabilities (People, etc.) provide data to pass to MCP tools
```

## Project Structure

Example project structure, not a hard requirement but a common pattern for organizing MCP server + widget development:

```
project/
├── appPackage/
│   ├── manifest.json           # Teams manifest (bump version on deploy)
│   ├── declarativeAgent.json   # Agent config + capabilities
│   ├── mcpPlugin.json          # Tool definitions with _meta
│   └── instruction.txt         # Agent behavior instructions
├── mcp-server/
│   ├── src/index.ts            # Server with Streamable HTTP
│   ├── widgets/                # Widget shells + React source
│   │   ├── my-widget.html      # Minimal shell returned by resources/read
│   │   └── src/my-widget/      # React + Fluent UI source
│   ├── assets/                 # Built widget bundles served at /assets
│   └── package.json
├── scripts/
│   ├── setup-devtunnel.sh      # Linux/Mac devtunnel setup
│   └── setup-devtunnel.ps1     # Windows devtunnel setup
└── env/.env.local              # MCP_SERVER_URL, MCP_SERVER_DOMAIN
```

**Language note**: This shows a TypeScript project layout. For Python, replace `mcp-server/src/index.ts` with your Python entry point (e.g., `server.py`). For C#, use a standard .NET project structure. The `appPackage/`, `widgets/`, `scripts/`, and `env/` directories are language-agnostic.

## Copilot Widget Protocol

Your MCP server must implement these protocol requirements to render widgets in Copilot Chat. This applies regardless of language:

1. **Streamable HTTP transport** — `/mcp` endpoint handling POST, GET, DELETE with session management
2. **CORS headers** — Origin-checking on `/mcp` allowing `m365.cloud.microsoft` and `*.m365.cloud.microsoft`, with required MCP headers
3. **Server capabilities** — `initialize` response must declare `resources: {}` and `tools: {}`
4. **MCP resources** — Register widgets with `ui://widget/<name>.html` URIs, `text/html+skybridge` mime type, and CSP `_meta`
5. **Tool response format** — Return `content` (text) + `structuredContent` (widget data) + `_meta` with `openai/outputTemplate`
6. **Widget serving** — HTTP route at `/widgets/*.html` for shell files and `/assets/*` for built bundles, both with origin-checking CORS

For full protocol details, JSON shapes, and an adaptation checklist for existing MCP servers, see [references/copilot-widget-protocol.md](references/copilot-widget-protocol.md).

## Implementation

### MCP Server Pattern (TypeScript Reference)

See [references/mcp-server-pattern.md](references/mcp-server-pattern.md) for complete implementation.

> For other languages, implement the requirements described in [Copilot Widget Protocol](references/copilot-widget-protocol.md) using your language's MCP SDK. See the [Language SDK References](references/copilot-widget-protocol.md#language-sdk-references) table for SDK packages.

Core requirements:
- Expose Streamable HTTP transport on `/mcp`
- Return `structuredContent` + `_meta` with `openai/outputTemplate`
- Serve widgets via HTTP endpoint
- Handle CORS for cross-origin requests
- Handle partial data gracefully (fill in "Unknown" for missing fields)

Tool response format:
```typescript
return {
  content: [{ type: "text", text: "Summary" }],
  structuredContent: { /* widget data */ },
  _meta: { "openai/outputTemplate": "ui://widget/name.html", "openai/widgetAccessible": true }
};
```

### Handling Partial Data

Always normalize input data to handle missing fields:

```typescript
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
  const args = request.params.arguments as { title?: string; items?: Partial<Item>[] };

  // Normalize data - fill in "Unknown" for missing fields
  const title = args.title || "Default Title";
  const items = (args.items || []).map(item => ({
    name: item.name || "Unknown",
    value: item.value || "Unknown",
  }));

  // Build structuredContent for widget
  const structuredContent = { title, items };
  // ...
});
```

### Widget Pattern

See [references/widget-patterns.md](references/widget-patterns.md) for complete examples.

Core requirements:
- Use React + Fluent UI components (`@fluentui/react-components`)
- Ensure widget package dependencies include `@fluentui/react-components`, `react`, and `react-dom`
- Theme with `FluentProvider` (`webLightTheme`/`webDarkTheme`) and Fluent `tokens`
- Access data through shared hooks (e.g., `useOpenAiGlobal("toolOutput")`)
- Debug fallback: embedded mock data when `window.openai` unavailable
- Handle "Unknown" values gracefully (e.g., hide action buttons)

### Plugin Schema

See [references/plugin-schema.md](references/plugin-schema.md) for mcpPlugin.json format.

Core requirements:
- Schema `v2.4` with `RemoteMCPServer` runtime
- `run_for_functions` array matching tool names
- `_meta` in tool definitions for widget binding
- `inputSchema` - make properties optional for flexibility, describe defaults in descriptions

## DevTunnels Setup

> **Local testing only.** DevTunnels are for development and testing on your machine. Before sharing the agent more broadly, deploy both the MCP server and widget assets to a hosted environment (e.g., Azure App Service, Azure Static Web Apps, or another hosting provider) and update the agent manifest URLs accordingly.

DevTunnels expose your localhost MCP server to M365 Copilot using **named tunnels** for stable URLs. See [references/devtunnels.md](references/devtunnels.md) for setup scripts, command reference, and troubleshooting.

The setup script (`npm run tunnel` / `npm run tunnel:win`):
1. Creates a named tunnel on first run (or reuses the existing one)
2. Starts hosting the tunnel on the configured port
3. Updates `env/.env.local` with `MCP_SERVER_URL` and `MCP_SERVER_DOMAIN` (first run only)
4. Continues hosting the tunnel

### Quick Start

**Terminal 1 - Start MCP Server:**
```bash
cd mcp-server
npm install
npm run dev
```

**Terminal 2 - Start DevTunnel:**
```bash
npm run tunnel
# Or on Windows:
npm run tunnel:win
```

On first run, provision the agent once the tunnel is up (see AGENT PROVISIONING rule). On subsequent runs the tunnel URL is stable — no re-provisioning needed unless the agent manifest changes.

## Development Workflow

1. **Start the MCP server** (dev mode with hot reload):
   - TypeScript: `cd mcp-server && npm install && npm run dev`
   - Python: `cd mcp-server && pip install -r requirements.txt && python server.py`
   - C#: `cd mcp-server && dotnet run`

2. **Start the devtunnel** (creates named tunnel on first run, reuses on subsequent runs):
   ```bash
   npm run tunnel
   ```

3. **Provision + test** — see AGENT PROVISIONING rule for when this is needed; bump `version` in manifest.json if Copilot doesn't reflect changes

## Best Practices

See [references/best-practices.md](references/best-practices.md) for detailed guidance.

Key points:
1. **Rendering tools**: Accept data as input, don't fetch internally
2. **Instructions**: Tell agent to use capabilities FIRST, then pass data to MCP tools
3. **Themes**: Use `FluentProvider` + Fluent `tokens` for dark/light support
4. **Debug mode**: Include fallback data for local widget testing
5. **Partial data**: Handle missing fields with "Unknown" defaults
6. **Action buttons**: Hide email/chat buttons when data is "Unknown"
7. **Version bumping**: Bump manifest version when changes aren't reflected in Copilot

모든 파일

0개 파일

ui-widget-developer 설치

스킬 파일을 다운로드하여 .claude/skills/ 디렉터리에 압축을 풀어 주세요.

ZIP 다운로드

저장소를 클론하고 스킬 파일을 프로젝트에 복사하세요.

git clone https://github.com/microsoft/skills/tree/main/.github/plugins/microsoft-365-agents-toolkit/skills/ui-widget-developer # Copy SKILL.md to your .claude/skills/ directory

복사 복사
빠른 설정: 스킬 폴더를 .claude/skills/로 복사하세요. Claude가 해당 스킬을 자동으로 감지하여 사용할 것입니다.
저장소 microsoft/skills

관련 스킬

github-code-search
업데이트 된 시간 2026년 6월 29일
drizzle-orm
업데이트 된 시간 2026년 6월 29일
clickhouse-io
업데이트 된 시간 2026년 6월 29일
prisma-client-api
업데이트 된 시간 2026년 6월 29일
OR