Awesome-omni-skill typespec-m365-copilot
Guidelines and best practices for building TypeSpec-based declarative agents and API plugins for Microsoft 365 Copilot Triggers on: **/*.tsp
install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/tools/typespec-m365-copilot" ~/.claude/skills/diegosouzapw-awesome-omni-skill-typespec-m365-copilot && rm -rf "$T"
manifest:
skills/tools/typespec-m365-copilot/SKILL.mdsource content
TypeSpec for Microsoft 365 Copilot Development Guidelines
Core Principles
When working with TypeSpec for Microsoft 365 Copilot:
- Type Safety First: Leverage TypeSpec's strong typing for all models and operations
- Declarative Approach: Use decorators to describe intent, not implementation
- Scoped Capabilities: Always scope capabilities to specific resources when possible
- Clear Instructions: Write explicit, detailed agent instructions
- User-Centric: Design for the end-user experience in Microsoft 365 Copilot
File Organization
Standard Structure
project/ ├── appPackage/ │ ├── cards/ # Adaptive Card templates │ │ └── *.json │ ├── .generated/ # Generated manifests (auto-generated) │ └── manifest.json # Teams app manifest ├── src/ │ ├── main.tsp # Agent definition │ └── actions.tsp # API operations (for plugins) ├── m365agents.yml # Agents Toolkit configuration └── package.json
Import Statements
Always include required imports at the top of TypeSpec files:
import "@typespec/http"; import "@typespec/openapi3"; import "@microsoft/typespec-m365-copilot"; using TypeSpec.Http; using TypeSpec.M365.Copilot.Agents; // For agents using TypeSpec.M365.Copilot.Actions; // For API plugins
Agent Development Best Practices
Agent Declaration
@agent({ name: "Role-Based Name", // e.g., "Customer Support Assistant" description: "Clear, concise description under 1,000 characters" })
- Use role-based names that describe what the agent does
- Make descriptions informative but concise
- Avoid generic names like "Helper" or "Bot"
Instructions
@instructions(""" You are a [specific role] specialized in [domain]. Your responsibilities include: - [Key responsibility 1] - [Key responsibility 2] When helping users: - [Behavioral guideline 1] - [Behavioral guideline 2] You should NOT: - [Constraint 1] - [Constraint 2] """)
- Write in second person ("You are...")
- Be specific about the agent's role and expertise
- Define both what to do AND what not to do
- Keep under 8,000 characters
- Use clear, structured formatting
Conversation Starters
@conversationStarter(#{ title: "Action-Oriented Title", // e.g., "Check Status" text: "Specific example query" // e.g., "What's the status of my ticket?" })
- Provide 2-4 diverse starters
- Make each showcase a different capability
- Use action-oriented titles
- Write realistic example queries
Capabilities - Knowledge Sources
Web Search - Scope to specific sites when possible:
op webSearch is AgentCapabilities.WebSearch<Sites = [ { url: "https://learn.microsoft.com" }, { url: "https://docs.microsoft.com" } ]>;
OneDrive and SharePoint - Use URLs or IDs:
op oneDriveAndSharePoint is AgentCapabilities.OneDriveAndSharePoint< ItemsByUrl = [ { url: "https://contoso.sharepoint.com/sites/Engineering" } ] >;
Teams Messages - Specify channels/chats:
op teamsMessages is AgentCapabilities.TeamsMessages<Urls = [ { url: "https://teams.microsoft.com/l/channel/..." } ]>;
Email - Scope to specific folders:
op email is AgentCapabilities.Email< Folders = [ { folderId: "Inbox" }, { folderId: "SentItems" } ], SharedMailbox = "support@contoso.com" // Optional >;
People - No scoping needed:
op people is AgentCapabilities.People;
Copilot Connectors - Specify connection IDs:
op copilotConnectors is AgentCapabilities.GraphConnectors< Connections = [ { connectionId: "your-connector-id" } ] >;
Dataverse - Scope to specific tables:
op dataverse is AgentCapabilities.Dataverse< KnowledgeSources = [ { hostName: "contoso.crm.dynamics.com"; tables: [ { tableName: "account" }, { tableName: "contact" } ]; } ] >;
Capabilities - Productivity Tools
// Python code execution op codeInterpreter is AgentCapabilities.CodeInterpreter; // Image generation op graphicArt is AgentCapabilities.GraphicArt; // Meeting content access op meetings is AgentCapabilities.Meetings; // Specialized AI models op scenarioModels is AgentCapabilities.ScenarioModels< ModelsById = [ { id: "model-id" } ] >;
API Plugin Development Best Practices
Service Definition
@service @actions(#{ nameForHuman: "User-Friendly API Name", descriptionForHuman: "What users will understand", descriptionForModel: "What the model needs to know", contactEmail: "support@company.com", privacyPolicyUrl: "https://company.com/privacy", legalInfoUrl: "https://company.com/terms" }) @server("https://api.example.com", "API Name") @useAuth([AuthType]) // If authentication needed namespace APINamespace { // Operations here }
Operation Definition
@route("/resource/{id}") @get @action @card(#{ dataPath: "$.items", title: "$.title", file: "cards/card.json" }) @capabilities(#{ confirmation: #{ type: "AdaptiveCard", title: "Confirm Action", body: "Confirm with {{ function.parameters.param }}" } }) @reasoning("Consider X when Y") @responding("Present results as Z") op getResource( @path id: string, @query filter?: string ): ResourceResponse;
Models
model Resource { id: string; name: string; description?: string; // Optional fields status: "active" | "inactive"; // Union types for enums @format("date-time") createdAt: utcDateTime; @format("uri") url?: string; } model ResourceList { items: Resource[]; totalCount: int32; nextPage?: string; }
Authentication
API Key
@useAuth(ApiKeyAuth<ApiKeyLocation.header, "X-API-Key">) // Or with reference ID @useAuth(Auth) @authReferenceId("${{ENV_VAR_REFERENCE_ID}}") model Auth is ApiKeyAuth<ApiKeyLocation.header, "X-API-Key">;
OAuth2
@useAuth(OAuth2Auth<[{ type: OAuth2FlowType.authorizationCode; authorizationUrl: "https://auth.example.com/authorize"; tokenUrl: "https://auth.example.com/token"; refreshUrl: "https://auth.example.com/refresh"; scopes: ["read", "write"]; }]>) // Or with reference ID @useAuth(Auth) @authReferenceId("${{OAUTH_REFERENCE_ID}}") model Auth is OAuth2Auth<[...]>;
Naming Conventions
Files
- Agent definitionmain.tsp
- API operationsactions.tsp
- Additional feature files[feature].tsp
- Adaptive Card templatescards/*.json
TypeSpec Elements
- Namespaces: PascalCase (e.g.,
)CustomerSupportAgent - Operations: camelCase (e.g.,
,listProjects
)createTicket - Models: PascalCase (e.g.,
,Project
)TicketResponse - Model Properties: camelCase (e.g.,
,projectId
)createdDate
Common Patterns
Multi-Capability Agent
@agent("Knowledge Worker", "Description") @instructions("...") namespace KnowledgeWorker { op webSearch is AgentCapabilities.WebSearch; op files is AgentCapabilities.OneDriveAndSharePoint; op people is AgentCapabilities.People; }
CRUD API Plugin
namespace ProjectAPI { @route("/projects") @get @action op list(): Project[]; @route("/projects/{id}") @get @action op get(@path id: string): Project; @route("/projects") @post @action @capabilities(#{confirmation: ...}) op create(@body project: CreateProject): Project; @route("/projects/{id}") @patch @action @capabilities(#{confirmation: ...}) op update(@path id: string, @body project: UpdateProject): Project; @route("/projects/{id}") @delete @action @capabilities(#{confirmation: ...}) op delete(@path id: string): void; }
Adaptive Card Data Binding
{ "type": "AdaptiveCard", "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "version": "1.5", "body": [ { "type": "Container", "$data": "${$root}", "items": [ { "type": "TextBlock", "text": "Title: ${if(title, title, 'N/A')}", "wrap": true } ] } ] }
Validation and Testing
Before Provisioning
- Run TypeSpec validation:
or use Agents Toolkitnpm run build - Check all file paths in
decorators exist@card - Verify authentication references match configuration
- Ensure capability scoping is appropriate
- Review instructions for clarity and length
Testing Strategy
- Provision: Deploy to development environment
- Test: Use Microsoft 365 Copilot at https://m365.cloud.microsoft/chat
- Debug: Enable Copilot developer mode for orchestrator insights
- Iterate: Refine based on actual behavior
- Validate: Test all conversation starters and capabilities
Performance Optimization
- Scope Capabilities: Don't grant access to all data if only subset needed
- Limit Operations: Only expose API operations the agent actually uses
- Efficient Models: Keep response models focused on necessary data
- Card Optimization: Use conditional rendering (
) in Adaptive Cards$when - Caching: Design APIs with appropriate caching headers
Security Best Practices
- Authentication: Always use authentication for non-public APIs
- Scoping: Limit capability access to minimum required resources
- Validation: Validate all inputs in API operations
- Secrets: Use environment variables for sensitive data
- References: Use
for production credentials@authReferenceId - Permissions: Request minimum necessary OAuth scopes
Error Handling
model ErrorResponse { error: { code: string; message: string; details?: ErrorDetail[]; }; } model ErrorDetail { field?: string; message: string; }
Documentation
Include comments in TypeSpec for complex operations:
/** * Retrieves project details with associated tasks and team members. * * @param id - Unique project identifier * @param includeArchived - Whether to include archived tasks * @returns Complete project information */ @route("/projects/{id}") @get @action op getProjectDetails( @path id: string, @query includeArchived?: boolean ): ProjectDetails;
Common Pitfalls to Avoid
- ❌ Generic agent names ("Helper Bot")
- ❌ Vague instructions ("Help users with things")
- ❌ No capability scoping (accessing all data)
- ❌ Missing confirmations on destructive operations
- ❌ Overly complex Adaptive Cards
- ❌ Hard-coded credentials in TypeSpec files
- ❌ Missing error response models
- ❌ Inconsistent naming conventions
- ❌ Too many capabilities (use only what's needed)
- ❌ Instructions over 8,000 characters