diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json index 9be87d1..1798318 100644 --- a/.github/plugin/marketplace.json +++ b/.github/plugin/marketplace.json @@ -2,7 +2,7 @@ "name": "dataverse-skills", "metadata": { "description": "Official Dataverse plugin marketplace for agent-guided development", - "version": "1.6.0" + "version": "1.7.0" }, "owner": { "name": "Microsoft", @@ -13,7 +13,7 @@ "name": "dataverse", "description": "Microsoft Dataverse plugin for coding agents — powering CRUD, bulk data operations, advanced queries, schema lifecycle, and environment management through intelligent skills that unify MCP, CLI, and SDK workflows.", "source": "./.github/plugins/dataverse", - "version": "1.6.0", + "version": "1.7.0", "homepage": "https://github.com/microsoft/Dataverse-skills" } ] diff --git a/.github/plugins/dataverse/.claude-plugin/plugin.json b/.github/plugins/dataverse/.claude-plugin/plugin.json index 955fb22..4c94ce0 100644 --- a/.github/plugins/dataverse/.claude-plugin/plugin.json +++ b/.github/plugins/dataverse/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "dataverse", "description": "Microsoft Dataverse plugin for coding agents — powering CRUD, bulk data operations, advanced queries, schema lifecycle, and environment management through intelligent skills that unify MCP, CLI, and SDK workflows.", - "version": "1.6.0", + "version": "1.7.0", "author": { "name": "Microsoft", "url": "https://www.microsoft.com" diff --git a/.github/plugins/dataverse/.codex-plugin/plugin.json b/.github/plugins/dataverse/.codex-plugin/plugin.json index 7d3be0c..4684c2d 100644 --- a/.github/plugins/dataverse/.codex-plugin/plugin.json +++ b/.github/plugins/dataverse/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "dataverse", "description": "Microsoft Dataverse plugin for coding agents — powering CRUD, bulk data operations, advanced queries, schema lifecycle, and environment management through intelligent skills that unify MCP, CLI, and SDK workflows.", - "version": "1.6.0", + "version": "1.7.0", "author": { "name": "Microsoft", "url": "https://www.microsoft.com" diff --git a/.github/plugins/dataverse/.cursor-plugin/plugin.json b/.github/plugins/dataverse/.cursor-plugin/plugin.json index 028dbf9..f76eac2 100644 --- a/.github/plugins/dataverse/.cursor-plugin/plugin.json +++ b/.github/plugins/dataverse/.cursor-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "dataverse", "displayName": "Microsoft Dataverse", "description": "Microsoft Dataverse plugin for coding agents — powering CRUD, bulk data operations, advanced queries, schema lifecycle, and environment management through intelligent skills that unify MCP, CLI, and SDK workflows.", - "version": "1.6.0", + "version": "1.7.0", "author": { "name": "Microsoft", "url": "https://www.microsoft.com" diff --git a/.github/plugins/dataverse/.github/plugin/plugin.json b/.github/plugins/dataverse/.github/plugin/plugin.json index 73bfac0..52aca52 100644 --- a/.github/plugins/dataverse/.github/plugin/plugin.json +++ b/.github/plugins/dataverse/.github/plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "dataverse", "description": "Microsoft Dataverse plugin for coding agents — powering CRUD, bulk data operations, advanced queries, schema lifecycle, and environment management through intelligent skills that unify MCP, CLI, and SDK workflows.", - "version": "1.6.0", + "version": "1.7.0", "author": { "name": "Microsoft", "url": "https://www.microsoft.com" diff --git a/.github/plugins/dataverse/scripts/auth.py b/.github/plugins/dataverse/scripts/auth.py index 0bb5829..fe58e39 100644 --- a/.github/plugins/dataverse/scripts/auth.py +++ b/.github/plugins/dataverse/scripts/auth.py @@ -330,7 +330,7 @@ def get_token(scope=None): "unknown", }) _ALLOWED_AGENTS = frozenset({ - "claude-code", "copilot", "cursor", "codex", "unknown", + "claude-code", "copilot", "cursor", "codex", "opencode", "unknown", }) # Strict format: key=value pairs, semicolon-separated. No spaces, no PII. _CONTEXT_RE = re.compile( diff --git a/.github/plugins/dataverse/scripts/install-opencode.py b/.github/plugins/dataverse/scripts/install-opencode.py new file mode 100644 index 0000000..e6c9a55 --- /dev/null +++ b/.github/plugins/dataverse/scripts/install-opencode.py @@ -0,0 +1,140 @@ +""" +install-opencode.py — Install Dataverse skills into an opencode-compatible location. + +opencode has no plugin marketplace for skill bundles (unlike Claude Code, GitHub +Copilot, Cursor, and Codex, which install this plugin via their own marketplace +commands). opencode instead auto-discovers skills from these locations: + + - .claude/skills//SKILL.md (project, Claude Code compatibility layer) + - ~/.claude/skills//SKILL.md (global, Claude Code compatibility layer) + +This script copies the plugin's skill folders (and scripts/auth.py) from this +repo into one of those locations inside a target project, so opencode picks +them up automatically on its next session. It is idempotent — re-running it +only overwrites files whose content actually changed. + +Usage: + # From a clone of microsoft/Dataverse-skills, targeting the current directory: + python .github/plugins/dataverse/scripts/install-opencode.py --target /path/to/your/project + + # Install into the global location instead (available in all projects): + python .github/plugins/dataverse/scripts/install-opencode.py --global + + # Default target is the current working directory: + python .github/plugins/dataverse/scripts/install-opencode.py + +After running, restart (or start a new) opencode session in the target +project — skills are discovered at session startup. +""" + +import argparse +import filecmp +import shutil +import sys +from pathlib import Path + +_PLUGIN_ROOT = Path(__file__).resolve().parent.parent +_SKILLS_SRC = _PLUGIN_ROOT / "skills" +_AUTH_SRC = _PLUGIN_ROOT / "scripts" / "auth.py" + + +def _copy_tree_if_changed(src: Path, dst: Path) -> list[str]: + """Copy src into dst recursively, skipping files whose content is unchanged. + + Returns a list of human-readable change descriptions (created/updated). + """ + changes = [] + dst.mkdir(parents=True, exist_ok=True) + for item in sorted(src.iterdir()): + target = dst / item.name + if item.is_dir(): + changes.extend(_copy_tree_if_changed(item, target)) + continue + if target.exists() and filecmp.cmp(item, target, shallow=False): + continue + action = "updated" if target.exists() else "created" + shutil.copy2(item, target) + changes.append(f"{action}: {target}") + return changes + + +def install_skills(dest_skills_dir: Path) -> list[str]: + """Copy every skills/dv-* folder into dest_skills_dir. Returns change log.""" + changes = [] + for skill_dir in sorted(_SKILLS_SRC.glob("dv-*")): + if not skill_dir.is_dir(): + continue + changes.extend(_copy_tree_if_changed(skill_dir, dest_skills_dir / skill_dir.name)) + return changes + + +def install_auth_script(target: Path) -> list[str]: + """Copy scripts/auth.py into /scripts/auth.py if missing or changed.""" + if not _AUTH_SRC.exists(): + return [] + dest_dir = target / "scripts" + dest_dir.mkdir(parents=True, exist_ok=True) + dest = dest_dir / "auth.py" + if dest.exists() and filecmp.cmp(_AUTH_SRC, dest, shallow=False): + return [] + action = "updated" if dest.exists() else "created" + shutil.copy2(_AUTH_SRC, dest) + return [f"{action}: {dest}"] + + +def main(): + parser = argparse.ArgumentParser( + description="Install Dataverse skills into an opencode-compatible .claude/skills/ location." + ) + parser.add_argument( + "--target", + default=".", + help="Target project directory (default: current directory). Ignored with --global.", + ) + parser.add_argument( + "--global", + dest="use_global", + action="store_true", + help="Install into ~/.claude/skills/ instead of a project's .claude/skills/.", + ) + args = parser.parse_args() + + if not _SKILLS_SRC.exists(): + print(f"ERROR: skills source directory not found: {_SKILLS_SRC}", file=sys.stderr) + sys.exit(2) + + if args.use_global: + target = Path.home() + dest_skills_dir = target / ".claude" / "skills" + else: + target = Path(args.target).resolve() + dest_skills_dir = target / ".claude" / "skills" + + print(f"Installing Dataverse skills into: {dest_skills_dir}") + skill_changes = install_skills(dest_skills_dir) + + auth_changes = [] + if not args.use_global: + auth_changes = install_auth_script(target) + + all_changes = skill_changes + auth_changes + if not all_changes: + print("Nothing to do — all files already up to date.") + else: + for line in all_changes: + print(f" {line}") + print(f"\n{len(all_changes)} file(s) installed/updated.") + + print( + "\nDone. Restart (or start a new) opencode session in the target project — " + "skills are discovered at session startup via .claude/skills/." + ) + if not args.use_global: + print( + "Next: describe what you want in plain English (e.g. \"Connect to Dataverse\") " + "and the dv-connect skill will handle MCP registration for opencode." + ) + + +if __name__ == "__main__": + main() diff --git a/.github/plugins/dataverse/skills/dv-connect/SKILL.md b/.github/plugins/dataverse/skills/dv-connect/SKILL.md index d7d6ece..7e0c36f 100644 --- a/.github/plugins/dataverse/skills/dv-connect/SKILL.md +++ b/.github/plugins/dataverse/skills/dv-connect/SKILL.md @@ -20,7 +20,7 @@ Before touching anything, check whether this workspace is already connected to a Run these checks in order. If **all four pass**, skip straight to Step 7 (final verification) and stop there. 1. **`.env` is present and complete** — file exists at the workspace root and contains non-empty values for `DATAVERSE_URL`, `TENANT_ID`, and `MCP_CLIENT_ID` -2. **MCP is registered** — `.mcp.json` (Claude Code) or the equivalent Copilot / Cursor config file has a `dataverse-*` server entry pointing at the `DATAVERSE_URL` from `.env` +2. **MCP is registered** — `.mcp.json` (Claude Code) or the equivalent Copilot / Cursor / Codex / opencode config file has a `dataverse-*` server entry pointing at the `DATAVERSE_URL` from `.env` 3. **Both auth surfaces match `.env`** — `dataverse auth who` shows a profile whose `Environment Url` matches `DATAVERSE_URL`, AND `pac org who` against a PAC profile for the same URL succeeds. (DV CLI auth covers Connect / Data / Query / Metadata / MCP / Python; PAC auth covers `dv-solution` and `dv-admin`. Both are front-loaded at connect time so neither prompts later.) 4. **Python SDK is importable and current** — `python -c "from PowerPlatform.Dataverse.client import DataverseClient; import pandas; from importlib.metadata import version; v=version('PowerPlatform-Dataverse-Client'); assert v>='0.1.0b9', f'SDK {v} is outdated, need >=0.1.0b9'"` exits 0 @@ -151,20 +151,18 @@ Present authentication options: Write `.env` directly — do not instruct the user to create it: -Detect the current tool (Claude or Copilot) from context and set `MCP_CLIENT_ID` automatically: -- Claude (CLI or VSCode extension): `0c412cc3-0dd6-449b-987f-05b053db9457` -- GitHub Copilot: `aebc6443-996d-45c2-90f0-388ff96faa56` +Detect the current tool from context and set `MCP_CLIENT_ID` automatically — see [mcp-configuration.md](references/mcp-configuration.md) Step 0 for the full agent → client-ID mapping. Also set plugin attribution variables for User-Agent tagging. **Fill in the two literals below from your own context** — you (the agent) loaded this plugin, so you already know both values: - `PLUGIN_VERSION` — the `version` field of your loaded plugin manifest (e.g. `"1.5.0"`). At runtime, `auth.py` re-reads this from the live manifest via host env vars; this `.env` entry is a fallback for offline cases. -- `AGENT` — your host identity, one of: `claude-code`, `copilot`, `cursor`, `codex`, or `unknown`. Must match an entry in `_ALLOWED_AGENTS` in `auth.py` — if you don't recognize your host, use `unknown`. +- `AGENT` — your host identity, one of: `claude-code`, `copilot`, `cursor`, `codex`, `opencode`, or `unknown`. Must match an entry in `_ALLOWED_AGENTS` in `auth.py` — if you don't recognize your host, use `unknown`. ```python # Substitute these two literals from your loaded plugin context. # Do NOT leave the angle-bracket placeholders — replace with real values. plugin_version = "" -agent_host = "" +agent_host = "" with open(".env", "w") as f: f.write(f"DATAVERSE_URL={dataverse_url}\n") @@ -244,12 +242,12 @@ All three must resolve the same user/environment. They prove the DV CLI cache, t ## Step 6: Configure MCP server **Skip this step** if MCP is already configured: -- `.mcp.json` or `~/.copilot/mcp-config.json` or `~/.cursor/mcp.json` or `~/.codex/config.toml` contains a Dataverse server entry +- `.mcp.json` or `~/.copilot/mcp-config.json` or `~/.cursor/mcp.json` or `~/.codex/config.toml` or `opencode.json` / `~/.config/opencode/opencode.json` contains a Dataverse server entry - `claude mcp list` shows a `dataverse-*` server registered If MCP is not configured, follow [mcp-configuration.md](references/mcp-configuration.md): -1. Detect which tool the user is running (Copilot, Claude, Cursor, or Codex) from context +1. Detect which tool the user is running (Copilot, Claude, Cursor, Codex, or opencode) from context 2. Set `MCP_CLIENT_ID` based on tool choice 3. Get environment URL from `.env` 4. Default to GA endpoint (`/api/mcp`) @@ -262,26 +260,7 @@ If MCP is not configured, follow [mcp-configuration.md](references/mcp-configura DATAVERSE_OPERATION_CONTEXT=app=dataverse-skills/{DATAVERSE_PLUGIN_VERSION};skill=mcp-direct;agent={DATAVERSE_PLUGIN_AGENT} ``` -For Claude Code (`claude mcp add -t stdio`), pass it via `-e DATAVERSE_OPERATION_CONTEXT=...`. For Copilot/Cursor JSON configs, add it to the `"env"` object in the stdio server entry; for Codex, add it to its `[mcp_servers..env]` table. - -**Important:** MCP configuration requires an editor/CLI restart. - -**For Copilot:** Write the JSON config, then: -> ✅ Dataverse MCP server configured. **Restart your editor** for changes to take effect. - -**For Claude:** Run the `claude mcp add` command, then warn the user about the auth popup that will appear on next launch: -> ✅ Dataverse MCP server registered. Restart Claude Code to enable MCP tools. -> Remember to **use `claude --continue` to resume the session** without losing context. -> -> **On restart, a browser window will open** asking you to sign in to your Dataverse environment. This is the MCP proxy authenticating on your behalf — sign in with the same account you used for `dataverse auth create` (or your active DV CLI profile, e.g., `{username}`). This only happens once; the token is cached for future sessions, and `dataverse auth create` populates the same cache so the popup is skipped if you've already run it. - -**For Cursor:** Write the JSON config, then: -> ✅ Dataverse MCP server `dataverse-{orgid}` configured in `~/.cursor/mcp.json`. **Reload the Cursor window** (Ctrl+Shift+P → "Developer: Reload Window") for the new MCP server to appear under Settings → Tools & MCPs. -> -> On first use, the `npx @microsoft/dataverse` proxy starts a device-code sign-in in your browser. Sign in with the same account you used for `dataverse auth create`; the token is cached in your OS credential store for future sessions. If you've already run `dataverse auth create`, the proxy reuses that cache silently — no device code. - -**For Codex:** Write the TOML config to `~/.codex/config.toml`. Codex loads MCP tools only at startup, so don't claim they're callable until the user restarts. Tell the user: -> ✅ Dataverse MCP server `dataverse-{orgid}` configured in `~/.codex/config.toml`. **Restart Codex** (CLI) or reload the Codex IDE to load the MCP tools. +**Important:** MCP configuration requires an editor/CLI restart (or opencode session restart) for every host. Register the server and deliver the exact per-host confirmation message from [mcp-configuration.md](references/mcp-configuration.md) Step 8 — do not improvise the wording, it carries host-specific restart and auth-popup guidance. --- @@ -354,6 +333,4 @@ After verifying MCP works, tell the user: ## Supported Agents -This plugin's skill files are natively loaded by both **GitHub Copilot CLI** and **Claude Code CLI** when installed as a plugin. No manual context-loading is needed — both agents discover and invoke skills automatically. - -The PAC CLI commands, Python scripts, and XML templates work identically in both environments. +This plugin's skills are natively discovered by GitHub Copilot, Claude Code, Cursor, and Codex when installed as a plugin, and by opencode via its built-in `.claude/skills` compatibility layer (see the README for the opencode install path). No manual context-loading is needed. PAC CLI commands, Python scripts, and XML templates work identically across all five. diff --git a/.github/plugins/dataverse/skills/dv-connect/references/mcp-configuration.md b/.github/plugins/dataverse/skills/dv-connect/references/mcp-configuration.md index 01c8739..ad3c9cd 100644 --- a/.github/plugins/dataverse/skills/dv-connect/references/mcp-configuration.md +++ b/.github/plugins/dataverse/skills/dv-connect/references/mcp-configuration.md @@ -1,6 +1,6 @@ # MCP Server Configuration Reference -Detailed instructions for configuring the Dataverse MCP server for GitHub Copilot, Claude Code, Cursor, or Codex. +Detailed instructions for configuring the Dataverse MCP server for GitHub Copilot, Claude Code, Cursor, Codex, or opencode. The environment URL should already be known from the `dv-connect` flow (stored in `DATAVERSE_URL` in `.env`). If it's not set, go back to Step 2 of the `dv-connect` skill to discover and select the environment first. @@ -10,7 +10,7 @@ The parameters for the MCP server should be determined from context or environme ## 0. Determine which tool to configure -Determine whether to configure MCP for GitHub Copilot, Claude Code, Cursor, or Codex: +Determine whether to configure MCP for GitHub Copilot, Claude Code, Cursor, Codex, or opencode: - If explicitly mentioned in prompt, use that. - Otherwise, determine which tool the user is running from the context. - Only if choosing based on the context is impossible, ask the user: @@ -20,12 +20,13 @@ Determine whether to configure MCP for GitHub Copilot, Claude Code, Cursor, or C > 2. **Claude** > 3. **Cursor** > 4. **Codex** +> 5. **opencode** -Based on the result, set the `TOOL_TYPE` variable to `copilot`, `claude`, `cursor`, or `codex`. Store this for use in all subsequent steps. +Based on the result, set the `TOOL_TYPE` variable to `copilot`, `claude`, `cursor`, `codex`, or `opencode`. Store this for use in all subsequent steps. Set the `MCP_CLIENT_ID` variable in `.env` based on the tool choice: - If `copilot`: `MCP_CLIENT_ID` = `aebc6443-996d-45c2-90f0-388ff96faa56` -- If `claude`, `cursor`, or `codex`: `MCP_CLIENT_ID` = `0c412cc3-0dd6-449b-987f-05b053db9457` (all use the `@microsoft/dataverse` npx stdio proxy, which authenticates as the Dataverse CLI app) +- If `claude`, `cursor`, `codex`, or `opencode`: `MCP_CLIENT_ID` = `0c412cc3-0dd6-449b-987f-05b053db9457` (all use the `@microsoft/dataverse` npx stdio proxy, which authenticates as the Dataverse CLI app) - If `claude` and the VSCode extension is used: set it to the same value as `CLIENT_ID` if already set, otherwise offer to create a new app registration following the auth setup in the `dv-connect` skill. --- @@ -84,6 +85,16 @@ Based on the scope, set the `CONFIG_PATH` variable: Store this path for use in steps 2 and 5. +**If TOOL_TYPE is `opencode`:** + +opencode reads MCP server definitions from its own JSON config, not from any other tool's config file. The options are: +1. **Project** (default, available only in this project) — `opencode.json` (relative to the current working directory) +2. **Global** (available in all projects) — `~/.config/opencode/opencode.json` (use the user's home directory) + +Based on the scope, set the `CONFIG_PATH` variable accordingly. + +Store this path for use in steps 2 and 5. + --- ## 2. Check already-configured MCP servers @@ -133,6 +144,12 @@ Read the `config.toml` file at `CONFIG_PATH` (determined in step 1) to check for If the environment URL from `.env` is already in `CONFIGURED_URLS`, the MCP server is **already configured**. Confirm with the user whether they want to re-register it before proceeding. If not, skip to the end. +**If TOOL_TYPE is `opencode`:** + +Read the JSON configuration file at `CONFIG_PATH` (determined in step 1) to check for already-configured servers. The file is opencode's own config schema — look under the top-level `mcp` key; each entry is `{ "type": "local", "command": [...], ... }`. Extract the environment URL from each entry's `command` array (the URL is the argument that follows `"mcp"`, e.g. in `["npx", "-y", "@microsoft/dataverse@latest", "mcp", "https://org.crm.dynamics.com"]`). Store the URLs as `CONFIGURED_URLS`. If the file doesn't exist or has no `mcp` key, treat `CONFIGURED_URLS` as empty (`[]`). This step must never block the skill. + +If the environment URL from `.env` is already in `CONFIGURED_URLS`, the MCP server is **already configured**. Confirm with the user whether they want to re-register it before proceeding. If not, skip to the end. + --- ## 3. Determine the environment URL @@ -436,6 +453,49 @@ Where: - If `[mcp_servers.{SERVER_NAME}]` already exists, replace that table (and its `.env` sub-table) with the new values; otherwise append the new tables - After writing, ask the user to **restart Codex** for the new MCP server to load +**If TOOL_TYPE is `opencode`:** + +Update the JSON configuration file at `CONFIG_PATH` (determined in step 1) to add the new server. opencode's config schema defines MCP servers under the top-level `mcp` key, using `type: "local"` for a stdio subprocess (same proxy pattern as Cursor/Codex) — never `type: "remote"` with a direct `url`, since the Dataverse MCP HTTP endpoint requires the npx proxy to handle authentication. + +**Generate a unique server name** from the `USER_URL`: +1. Extract the subdomain (organization identifier) from the URL + - Example: `https://orgbc9a965c.crm10.dynamics.com` → `orgbc9a965c` +2. Use lowercase format: `dataverse-{orgid}` + - Example: `dataverse-orgbc9a965c` + +This is the `SERVER_NAME`. + +**Update the configuration file:** + +1. Read the existing configuration file at `CONFIG_PATH`, or create a new empty config if it doesn't exist: + ```json + {} + ``` + +2. Add or update the server entry under `mcp`: + ```json + { + "mcp": { + "{SERVER_NAME}": { + "type": "local", + "command": ["npx", "-y", "@microsoft/dataverse@latest", "mcp", "{USER_URL}"], + "environment": { + "DATAVERSE_OPERATION_CONTEXT": "app=dataverse-skills/{DATAVERSE_PLUGIN_VERSION};skill=mcp-direct;agent=opencode" + } + } + } + } + ``` + + Append `"--preview"` to the `command` array if the user chose the Preview endpoint in step 4. + +3. Write the updated configuration back to `CONFIG_PATH` with proper JSON formatting (2-space indentation). + +**Important notes:** +- Do NOT overwrite other top-level keys or sibling `mcp.*` entries — preserve the existing structure +- If `SERVER_NAME` already exists, update it with the new `command`/`environment` +- After writing, ask the user to **restart the opencode session** (exit and relaunch `opencode`, or start a new session) for the new MCP server to load + --- ## 6. Ensure tenant-level admin consent (one-time per tenant) @@ -443,7 +503,7 @@ Where: The MCP client app registration must be granted admin consent on the Azure AD tenant. This is a **one-time** action per tenant — once done, it applies to all Dataverse environments in that tenant. It **requires an Azure AD Global Admin or Privileged Role Admin**. List out the parameters chosen in previous steps: -- Tool type (Copilot, Claude, Cursor, or Codex) from step 0 +- Tool type (Copilot, Claude, Cursor, Codex, or opencode) from step 0 - Scope from step 1 - Environment URL from step 3 - Endpoint (GA or Preview) from step 4 @@ -572,6 +632,24 @@ Tell the user: Pause and give the user a chance to restart Codex before proceeding. +**If TOOL_TYPE is `opencode`:** + +Tell the user: + +> ✅ Dataverse MCP server `{SERVER_NAME}` written to `{CONFIG_PATH}`. +> +> **IMPORTANT: opencode loads MCP servers at session startup.** **Restart the opencode session** (exit and relaunch `opencode`, or start a new session) for the Dataverse tools to appear — the current session cannot call them yet. +> +> After restart, you will be able to: +> - List all tables in your Dataverse environment +> - Query records from any table +> - Create, update, or delete records +> - Explore your schema and relationships + +**Do not claim the Dataverse MCP tools are callable in the current session.** They become available only after a restart. If the user asks you to run an MCP query before restarting, explain that the tools load on restart — do **not** spin up a separate `npx @microsoft/dataverse mcp` stdio proxy as a workaround, and if the user explicitly required MCP, do **not** silently fall back to the SDK or Web API. Surface the restart requirement instead. + +Pause and give the user a chance to restart the opencode session before proceeding. + --- ## 9. Troubleshooting @@ -609,3 +687,9 @@ If something goes wrong, help the user check: - If `--validate` returns **403 Forbidden** on the GA endpoint, the client isn't allowlisted yet — run `dataverse mcp allow {MCP_CLIENT_ID}` (Step 7, Method A), then re-validate. - Confirm the server entry exists: look for `[mcp_servers.{SERVER_NAME}]` in `~/.codex/config.toml` (global) or `.codex/config.toml` (project). - If `npx` can't be found when Codex launches the server, ensure Node.js 18+ is on PATH; on Windows the proxy command may need `cmd /c` wrapping (see Step 5). +- **If TOOL_TYPE is `opencode`:** + - The Dataverse MCP tools load only when opencode starts a session, after `opencode.json` (or `~/.config/opencode/opencode.json`) is written — the session that wrote the config cannot see them yet. Do not treat their absence in the current session as a failure. + - Confirm the server entry exists: look for `mcp.{SERVER_NAME}` in the file at `CONFIG_PATH`. Verify with `opencode mcp list` after restarting. + - If `npx` can't be found when opencode launches the server, ensure Node.js 18+ is on PATH. + - If `--validate` returns **403 Forbidden** on the GA endpoint, the client isn't allowlisted yet — run `dataverse mcp allow {MCP_CLIENT_ID}` (Step 7, Method A), then re-validate. + - opencode also reads project skills from `.claude/skills/` automatically — if `dv-*` skills aren't showing up, confirm they were installed via `scripts/install-opencode.py` (see the README's opencode install section) rather than assuming a marketplace install happened. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc701ef..704021a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -106,6 +106,16 @@ In the **Codex app**, do the same through **Plugins → Add marketplace**: set * Codex discovers the plugin via the repo-root `.agents/plugins/marketplace.json` (its native marketplace path; it falls back to `.claude-plugin/marketplace.json` if that's absent) and loads it through the native `.github/plugins/dataverse/.codex-plugin/plugin.json` manifest. Like Copilot, Codex caches the plugin at install time, so run `codex plugin marketplace upgrade dataverse-skills` after local edits to refresh. +### Testing with opencode + +opencode has no plugin marketplace for skill bundles, so there's no install/uninstall cycle to mirror here. Instead, use the installer script to copy the skill folders into your local clone's `.claude/skills/` (or a scratch test project): + +```bash +python .github/plugins/dataverse/scripts/install-opencode.py --target +``` + +The script is idempotent and only rewrites files whose content changed, so re-run it after every local edit to refresh the copy, then restart your opencode session to pick up the changes (opencode discovers skills at session startup, not live). + ## Legal This project is licensed under the [MIT License](LICENSE). diff --git a/README.md b/README.md index e1e8928..133adf7 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,23 @@ codex plugin marketplace upgrade dataverse-skills The listing is published at [cursor.com/marketplace/microsoft-dataverse](https://cursor.com/marketplace/microsoft-dataverse). +### opencode + +opencode doesn't have a marketplace for skill bundles (its own `plugin` concept is for npm-based JS/TS hooks, not skills). opencode does, however, natively auto-discover skills placed under `.claude/skills//SKILL.md` — its built-in Claude Code compatibility layer. Use the installer script from a clone of this repo to copy the skills there: + +```bash +git clone https://github.com/microsoft/Dataverse-skills.git +cd Dataverse-skills + +# Install into a project (defaults to the current directory) +python .github/plugins/dataverse/scripts/install-opencode.py --target /path/to/your/project + +# Or install globally (available in every project) +python .github/plugins/dataverse/scripts/install-opencode.py --global +``` + +The script is idempotent — re-run it after pulling updates to refresh your copy. Restart (or start a new) opencode session afterward; skills are discovered at session startup. + ## Verify the install After installation, ask your agent: