>_ The Manifest

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?

📝 Note

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:

CheckFinding in this sampleDecision
Ownership and licenseagentplugins/agent-plugins-example derivative, MITReuse permitted; retain LICENSE and attribution
Package pathsFour files, no symlinks, every path under issue-triage/Accept
External referencesOne HTTPS MCP originAllow-list the exact origin; reject redirects to another origin
SecretsNo tokens, cookies, .env files, or literal auth headersAccept; scan again before export
Executable hooksNoneAccept
MCP processRemote Streamable HTTP, not stdioNo local executable is launched
AuthenticationEndpoint requires client-managed GitHub authorizationMust be mapped per host
Write behaviorSkill requires preview and confirmationPreserve and test in every host
⚠️ Warning

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.

GitHub Copilot

Import this Agent Plugin into a clean directory. Change nothing in the source, return the structured report, and separate copied, mapped, generated, and unresolved artifacts.

Import completed with adaptations.

ResultSourceWIQD project
Copiedskills/triage-issue/SKILL.mdappPackage/skills/triage-issue/SKILL.md
Mappedmcp.json#repositorymanifest.json#agentConnectors
GeneratedAgent Plugin metadataTeams/Microsoft 365 app manifest and wiqd.plugin.json
SuppliedNo portable equivalentprivacy URL, terms URL, package identity
UnresolvedClient-managed authOAuthPluginVault; tenant configuration still required

Source files were not modified. Static validation can run. Packaging, provisioning, and sharing cannot run from this imported project yet because import does not scaffold m365agents.local.yml or env/.env.local.

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 artifactWIQD / Microsoft 365 artifactPortability judgment
plugin.json name and descriptionApp manifest plus wiqd.plugin.jsonMeaning maps; package shape does not
skills/triage-issue/SKILL.mdappPackage/skills/triage-issue/SKILL.mdPortable content
mcp.json URL and transportagentConnectors entryPortable endpoint intent; different schema
Client-managed authorizationOAuthPluginVault plus tenant setupHost-specific
MIT identifier and filePackage metadata / retained source filePreserve explicitly and verify on export
Write confirmation wordingSkill text plus host confirmation UXText is portable; enforcement and UI are not
Privacy, terms, app ID, iconsMicrosoft 365 app packageHost-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:

  1. Keep the skill description specific enough for discovery: issue inspection, ownership, and labels.
  2. Keep OAuthPluginVault as package metadata, but document that it cannot represent another client’s credential store.
  3. Replace machine-specific values with environment placeholders only in WIQD deploy files. Never export resolved secrets.
  4. Retain the MIT license and upstream attribution.
  5. 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.

GitHub Copilot

Run import against both negative fixtures. Do not repair or partially import either package.

Both imports refused; no destination project was created.

FixtureResultWhy
corrupt-manifest/RefusedSource plugin.json could not be parsed
escaping-reference/RefusedDeclarative-agent reference resolves outside the plugin package

Import did not silently drop the unreadable agent, and it did not copy the external file.

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:

Issue Triage (Microsoft 365 development environment)

Summarize issue 42, identify likely ownership, and propose labels. Do not change anything.

Issue 42 reports a timeout in the webhook delivery path. The stack trace and two maintainer comments point to the integrations team. Proposed labels: bug, webhooks, and needs-reproduction. No changes were made.

Apply those labels.

I am ready to add bug, webhooks, and needs-reproduction to issue 42. Confirm that exact write?

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
GitHub Copilot

Export to an Agent Plugin, then report retained, normalized, and omitted data. Do not call byte equality portability.

Export completed.

  • Retained: plugin identity, description, MIT license metadata, triage-issue skill content, repository MCP endpoint and Streamable HTTP intent.
  • Normalized: directory layout and generated JSON formatting.
  • Round-trip metadata: privacy, terms, and package values needed for a later WIQD re-import.
  • Not portable behavior: OAuthPluginVault, tenant consent, Microsoft 365 app identity, icons, deployment files, and confirmation UI.
  • Not exported as portable components: unsupported host hooks, commands, agents, and rules.

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 scenarioMicrosoft 365 CopilotGitHub Copilot CLIPortability result
Discover “triage issue”Skill surfaced through packaged pluginSkill discovered from skills/Portable intent, different discovery UX
Summarize issue 42Remote MCP read succeedsRemote MCP read succeedsPortable
Endpoint needs authTenant OAuth/plugin-vault flowClient-owned GitHub sign-inSame endpoint, non-portable auth
Propose three labelsSkill requires previewSkill requires previewPortable instruction
Apply labelsHost confirmation plus skill guardrailCLI confirmation plus skill guardrailOutcome portable, UX host-specific
Cancel writeNo tool callNo tool callPortable outcome
Interpret unsupported hookNot loadedNot loadedCorrectly 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

ConcernRound-tripsNeeds an overlayCannot be claimed equivalent
Plugin name, description, license
SKILL.md workflow and guardrail text
Remote MCP URL and transport
Local stdio MCP commandFormat can✓ runtime/install policyProcess trust and availability
Environment placeholdersFormat can✓ host injectionAmbient environment
OAuth discovery and credential storageConsent UX and token handling
Microsoft 365 app ID, icons, privacy, terms
Declarative agent behaviorAgent/orchestrator interpretation
Hooks, commands, rules, client extensions✓ owning clientOther clients
Write confirmationSkill wording✓ host enforcementConfirmation 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.