A shared file extension is not evidence of portability. A round trip is: start with a plugin you did not build for Microsoft 365, import it, record every adaptation, export it, and run the result somewhere else. Work IQ Developer Tools now gives that experiment two explicit boundaries, wiqd plugin import and wiqd plugin export.
This is not the existing Agents Toolkit Agent Plugin story retold with a new command. That workflow explains how to bring an Agent Plugin into a Microsoft project. This one asks a harder question: after Microsoft 365 packaging, authentication, and behavior enter the picture, what can still leave as an Agent Plugin without lying about equivalence?
WIQD’s plugin command tree is alpha. The commands and findings below match the current documentation; use wiqd plugin import --help and wiqd plugin export --help as the authority for the version installed on your machine.
The Source Package, and Why It Is Safe to Touch
Start from the external agentplugins/agent-plugins-example, the project’s copyable reference package, and reduce a fork to the issue-triage fixture below. The upstream sample and this derivative use the MIT License. Keep its LICENSE, attribution, and the SPDX identifier in plugin.json; that makes the fixture legally reusable and keeps its license visible even when a client ignores the standalone file.
issue-triage/
├── LICENSE # MIT License; upstream attribution retained
├── plugin.json # Agent Plugins 1.0.0
├── mcp.json # one Streamable HTTP server
└── skills/
└── triage-issue/
└── SKILL.md # read and write guardrails
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "issue-triage",
"version": "1.0.0",
"description": "Triage repository issues with read-first workflows.",
"license": "MIT"
}
The MCP declaration uses a remote endpoint and contains no credential:
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
"mcpServers": {
"repository": {
"type": "streamable-http",
"url": "https://api.githubcopilot.com/mcp/"
}
}
}
That omission is intentional. Agent Plugins 1.0 defines no portable OAuth or credential-reference fields. Authentication discovery, consent, and storage belong to the client. Putting a bearer token in headers would not make auth portable; it would publish a secret.
The skill keeps the capability contract host-neutral:
---
name: triage-issue
description: Inspect an issue, identify likely ownership, and propose labels.
---
Read the issue, its comments, and repository metadata before proposing changes.
Separate evidence from inference and link the evidence used.
For write operations:
- Show the exact label, assignee, or comment first.
- Ask for explicit confirmation.
- Make no write if confirmation is absent or ambiguous.
Pre-import trust review
Do this before running any tool that interprets a foreign package:
| Check | Finding in this sample | Decision |
|---|---|---|
| Ownership and license | agentplugins/agent-plugins-example derivative, MIT | Reuse permitted; retain LICENSE and attribution |
| Package paths | Four files, no symlinks, every path under issue-triage/ | Accept |
| External references | One HTTPS MCP origin | Allow-list the exact origin; reject redirects to another origin |
| Secrets | No tokens, cookies, .env files, or literal auth headers | Accept; scan again before export |
| Executable hooks | None | Accept |
| MCP process | Remote Streamable HTTP, not stdio | No local executable is launched |
| Authentication | Endpoint requires client-managed GitHub authorization | Must be mapped per host |
| Write behavior | Skill requires preview and confirmation | Preserve and test in every host |
Reading plugin.json is not the whole review. Inspect scripts, symlinks, extension directories, local MCP commands, environment values, and every outbound origin. A plugin is a directory, not a trustworthy object merely because its schema validates.
Import Is a Conversion Report, Not a Certificate
Import into a new sibling directory so the source remains immutable:
wiqd plugin import \
--path ./issue-triage \
--output ./issue-triage-wiqd \
--privacy-url https://example.com/privacy \
--terms-url https://example.com/terms \
--default-auth-type OAuthPluginVault \
--json
The privacy and terms URLs are Microsoft 365 package requirements, not facts inferred from the Agent Plugin. The auth type is also an explicit adaptation: the Agent Plugins specification says the client owns auth, while Microsoft 365 needs a concrete package declaration.
That last line matters. In the current alpha, an imported project has a read-only lifecycle: show, static validate, and export. It cannot yet package, provision, or share. Do not conceal that gap by presenting a successful import as a successful Microsoft 365 deployment.
What mapped, and what did not
| Source artifact | WIQD / Microsoft 365 artifact | Portability judgment |
|---|---|---|
plugin.json name and description | App manifest plus wiqd.plugin.json | Meaning maps; package shape does not |
skills/triage-issue/SKILL.md | appPackage/skills/triage-issue/SKILL.md | Portable content |
mcp.json URL and transport | agentConnectors entry | Portable endpoint intent; different schema |
| Client-managed authorization | OAuthPluginVault plus tenant setup | Host-specific |
| MIT identifier and file | Package metadata / retained source file | Preserve explicitly and verify on export |
| Write confirmation wording | Skill text plus host confirmation UX | Text is portable; enforcement and UI are not |
| Privacy, terms, app ID, icons | Microsoft 365 app package | Host-specific |
Validate, Repair, and Refuse Unsafe Input
First run the offline check and inspect the files yourself. WIQD static validation covers the declarative-agent surface; it does not deeply validate a skill-only or connector-only plugin. A clean static result is therefore not proof that SKILL.md is good or that the MCP server works.
wiqd plugin show --path ./issue-triage-wiqd --json
wiqd plugin validate --path ./issue-triage-wiqd --json
The focused repairs are semantic, not cosmetic:
- Keep the skill description specific enough for discovery: issue inspection, ownership, and labels.
- Keep
OAuthPluginVaultas package metadata, but document that it cannot represent another client’s credential store. - Replace machine-specific values with environment placeholders only in WIQD deploy files. Never export resolved secrets.
- Retain the MIT license and upstream attribution.
- Preserve the read-before-write and confirmation contract verbatim.
Then validate the endpoint separately: resolve its origin, perform the MCP handshake through an authenticated test client, inventory the advertised tools, and compare those names with the skill. A 401 before authorization proves reachability, not validity; a successful initialize plus tools/list proves the server contract you actually depend on.
Refusal paths belong in the evidence
Make two disposable copies and break them on purpose. The first removes a closing brace from plugin.json. The second adds a declarative-agent reference such as ../outside/instructions.md.
Current WIQD import and export deliberately stop on both cases. That is the correct boundary: a partial conversion is more dangerous than a loud failure because its report can look complete while a capability disappeared.
Unsupported client directories, hooks, commands, and rules require a different disposition. They are not automatically malicious, but Agent Plugins 1.0 standardizes skills and MCP servers, not those host-specific behaviors. Inventory them, retain them only in their owning extension namespace, and mark them not exercised in clients that do not implement that namespace.
Test the Microsoft 365 Overlay Honestly
Because imported projects cannot currently provision, create a clean deployable WIQD plugin and apply only the reviewed mappings as a Microsoft 365 overlay. Keep the imported directory untouched as the conversion baseline.
wiqd plugin create --name issue-triage-m365 --output ./issue-triage-m365
cd ./issue-triage-m365
wiqd plugin add skill --name "Triage Issue"
wiqd plugin add connector \
--name "Repository" \
--description "Reads and updates repository issues after confirmation" \
--url https://api.githubcopilot.com/mcp/
Copy the reviewed skill text, add Microsoft 365 package metadata and auth, then validate, provision to a development environment, and package. This is not a lossless import. It is the platform-specific overlay made visible.
Use a test repository and a least-privilege test identity. Run the same read and write scenarios in Microsoft 365 Copilot:
The pass conditions are observable: the read prompt makes no write call; the write prompt previews exact arguments; cancellation makes no call; confirmation makes one call; an auth failure is reported as failure, never as success.
Export, Then Compare Meaning
Export the reviewed WIQD project back to the portable default:
wiqd plugin export \
--path ./issue-triage-wiqd \
--output ./issue-triage-exported \
--format open-plugin \
--json
Do not start with diff -r. Start with assertions:
plugin.name equal
plugin.license equal
skills[].name equal
skills[].description equal
skills[].instructions semantically equal
mcp.servers[].type equal
mcp.servers[].url equal after URL normalization
literal credentials absent in source and export
Microsoft 365 package metadata intentionally host-specific
A byte diff still helps explain normalization, but it cannot tell whether an auth model changed or a confirmation rule lost force.
Run the Export in a Second Client
Install issue-triage-exported/ in a second Agent Plugins-compatible client. The comparison below is the test record to capture with a clean GitHub Copilot CLI profile rather than the Microsoft 365 development environment. Authorize the same test identity, point both clients at the same test repository, and do not mark a row passed until you repeat the identical prompt and inspect its tool trace.
| Identical scenario | Microsoft 365 Copilot | GitHub Copilot CLI | Portability result |
|---|---|---|---|
| Discover “triage issue” | Skill surfaced through packaged plugin | Skill discovered from skills/ | Portable intent, different discovery UX |
| Summarize issue 42 | Remote MCP read succeeds | Remote MCP read succeeds | Portable |
| Endpoint needs auth | Tenant OAuth/plugin-vault flow | Client-owned GitHub sign-in | Same endpoint, non-portable auth |
| Propose three labels | Skill requires preview | Skill requires preview | Portable instruction |
| Apply labels | Host confirmation plus skill guardrail | CLI confirmation plus skill guardrail | Outcome portable, UX host-specific |
| Cancel write | No tool call | No tool call | Portable outcome |
| Interpret unsupported hook | Not loaded | Not loaded | Correctly non-portable |
This table is more useful than “works in both.” It separates the shared capability contract from discovery, consent, and confirmation behavior supplied by each host.
The Portability Matrix
| Concern | Round-trips | Needs an overlay | Cannot be claimed equivalent |
|---|---|---|---|
| Plugin name, description, license | ✓ | ||
SKILL.md workflow and guardrail text | ✓ | ||
| Remote MCP URL and transport | ✓ | ||
Local stdio MCP command | Format can | ✓ runtime/install policy | Process trust and availability |
| Environment placeholders | Format can | ✓ host injection | Ambient environment |
| OAuth discovery and credential storage | ✓ | Consent UX and token handling | |
| Microsoft 365 app ID, icons, privacy, terms | ✓ | ||
| Declarative agent behavior | ✓ | Agent/orchestrator interpretation | |
| Hooks, commands, rules, client extensions | ✓ owning client | Other clients | |
| Write confirmation | Skill wording | ✓ host enforcement | Confirmation UI |
The maintenance rule follows directly. Keep one source when the durable contract is skill text plus MCP location, with thin, reviewed host overlays for auth and packaging. Split sources when hosts require different tool semantics, different safety rules, or behavior-changing instructions. Do not hide those differences behind generated files.
The round trip did not prove that Microsoft 365 Copilot and GitHub Copilot CLI are equivalent. It proved something narrower and more defensible: the issue-triage skill and MCP endpoint survived, while packaging, auth, discovery, and confirmation remained host responsibilities. That is what portable means when the evidence, rather than the extension, gets the final word.
The Value You Just Unlocked
- Portability you can demonstrate: A round trip and second-client test replace assumptions based on a shared file extension.
- A durable capability core: Skill instructions and MCP endpoint intent stay reusable while thin host overlays handle packaging and authentication.
- Adaptations reviewers can see: Import and export reports separate copied, mapped, generated, unresolved, and omitted artifacts.
- Safety rules that survive the trip: Read-before-write behavior, exact previews, and confirmation requirements remain explicit and testable in each host.
- Trust boundaries that stay honest: Client-owned credentials, consent, discovery, and confirmation UX are never mislabeled as portable behavior.
- A maintenance rule for divergence: Teams know when one shared source is enough and when different tool semantics or guardrails require separate sources.
The real unlock is not one package that behaves identically everywhere. It is a portable core with every host-specific promise named, reviewed, and tested instead of hidden behind conversion.
Resources
Have questions or want to share what you're building? Connect with me on LinkedIn or check out more on The Manifest.