Skip to main content
Glama
singlebarrel

odatav4-mcp

by singlebarrel
README.md
# odatav4-mcp

An MCP (Model Context Protocol) server for OData v4 endpoints, tuned for
**Microsoft Dataverse / Dynamics 365** but usable with any OData v4 service.

- **Zero-registration auth**: signs in with Entra ID using the well-known
  Microsoft public client (`51f81489-12ee-4a9e-aac9-25749fb2a1de`, the same app
  XrmToolBox and the Dataverse samples use). The default flow opens your
  browser with a localhost redirect (works where Conditional Access blocks
  device-code flow); device-code flow is available for headless/SSH sessions.
  MFA works; tokens are cached on disk so you sign in once, not per session.
- **One config value**: just the endpoint URL.
- **Full OData surface**: metadata discovery, queries ($filter/$select/$expand/
  $apply/$count), paging, CRUD, associations, functions/actions, and a raw
  request escape hatch.

## Install

```bash
git clone <this-repo> odatav4-mcp
cd odatav4-mcp
npm install        # also compiles TypeScript (prepare script)
```

Optional but recommended — sign in once from a terminal so the MCP client never
has to relay sign-in instructions:

```bash
ODATA_URL=https://yourorg.crm.dynamics.com npm run login
# headless / SSH (no local browser):
ODATA_URL=https://yourorg.crm.dynamics.com npm run login -- --devicecode
```

## Configuration

| Env var | Required | Description |
| --- | --- | --- |
| `ODATA_URL` | yes | Service root. A bare Dataverse org URL (`https://yourorg.crm.dynamics.com`) is expanded to `/api/data/v9.2/` automatically. |
| `ODATA_TENANT` | no | Tenant id or domain (default `organizations`). Set this if your account exists in multiple tenants. |
| `ODATA_CLIENT_ID` | no | Override the Entra app id (default: the well-known Dataverse dev-tools client). |
| `ODATA_LOGIN_FLOW` | no | `interactive` (default: opens a browser, localhost redirect) or `devicecode` (enter a code at microsoft.com/devicelogin — for headless/SSH, or if your tenant blocks the browser flow). |
| `ODATA_AUTH` | no | Set to `none` for anonymous OData services (e.g. public test services). |
| `ODATA_CACHE_DIR` | no | Where tokens and $metadata are cached (default `~/.config/odatav4-mcp/<host>`). |

## Hook up to your client

Use the absolute path to `dist/index.js` in all cases.

### GitHub Copilot (VS Code)

Create `.vscode/mcp.json` in your workspace (or add via **MCP: Add Server**):

```json
{
  "servers": {
    "dataverse": {
      "type": "stdio",
      "command": "node",
      "args": ["/path/to/odatav4-mcp/dist/index.js"],
      "env": { "ODATA_URL": "https://yourorg.crm.dynamics.com" }
    }
  }
}
```

### Claude Code

```bash
claude mcp add dataverse --env ODATA_URL=https://yourorg.crm.dynamics.com -- node /path/to/odatav4-mcp/dist/index.js
```

### Codex CLI

In `~/.codex/config.toml`:

```toml
[mcp_servers.dataverse]
command = "node"
args = ["/path/to/odatav4-mcp/dist/index.js"]
env = { ODATA_URL = "https://yourorg.crm.dynamics.com" }
```

## Tools

| Tool | Purpose |
| --- | --- |
| `login`, `auth_status`, `logout` | Device-code sign-in and token management |
| `service_info`, `whoami` | Connection status; Dataverse WhoAmI |
| `list_entity_sets`, `describe_entity`, `list_operations` | Schema discovery from `$metadata` (cached 6h) |
| `query`, `next_page`, `get_record` | Reads with full OData query options and paging |
| `create_record`, `update_record`, `delete_record` | CRUD (update sends `If-Match: *` to prevent accidental upserts) |
| `associate_records`, `disassociate_records` | N:N / collection navigation `$ref` operations |
| `call_function`, `call_action` | OData functions (GET) and actions (POST), bound or unbound — covers Dataverse messages and custom APIs |
| `raw_request` | Any other request, restricted to the configured host |

## Notes

- **Auth flow**: silent token refresh from the on-disk MSAL cache first; if that
  fails, tools return an instruction to run the `login` tool. By default that
  opens your system browser (on WSL it launches the Windows browser via
  `wslview` or `rundll32.exe`) and completes through a localhost redirect; MSAL
  runs the loopback listener automatically. The `login` tool accepts
  `{"flow": "devicecode"}` to switch flows per call. Both flows are attempted
  against the same well-known client id, which has `http://localhost`
  registered as a redirect URI.
- The token cache (`msal-cache.json`) contains refresh tokens; it is written
  with mode 600 into your home config directory. Delete it (or use `logout`) to
  revoke local access.
- Dataverse `$metadata` is large (10 MB+); it is cached on disk for 6 hours.
  Pass `refresh: true` to `list_entity_sets` to force a re-download.