Skip to main content
Glama
NilsTv8

TeamViewer MCP Server

by NilsTv8
README.md
# TeamViewer MCP Server (no-DCR)

A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that exposes the [TeamViewer Web API](https://webapi.teamviewer.com/api/v1/docs) as tools for AI assistants — specifically for MCP clients that **don't** support Dynamic Client Registration (RFC 7591), such as Microsoft Copilot Studio / Power Platform custom connectors.

The server is an OAuth 2.0 **authorization server + resource server** that brokers access to TeamViewer. It never hands MCP clients a real TeamViewer token: it issues its own opaque, audience-bound tokens, holds TeamViewer credentials encrypted server-side, and resolves a live TeamViewer token internally on every tool call.

```
MCP client (Copilot Studio)
    ↕ OAuth via /authorize, /token (this server)
MCP server (this repo)
    ↕ OAuth via account.teamviewer.com + webapi.teamviewer.com
TeamViewer
```

---

## Two deployment variants

This project ships as two parallel repos, because the target MCP clients speak OAuth client registration differently:

| Variant | Repo | Use for |
|---|---|---|
| DCR | `NilsTv8/TV_ONE_MCP_Anthropic` | Clients that support Dynamic Client Registration (e.g. Claude.ai connectors) |
| **No-DCR (this repo)** | `NilsTv8/TV_ONE_MCP_Anthropic_noDCR` | Clients that don't (e.g. Microsoft Copilot Studio / Power Platform) |

This repo accepts **any** `client_id` (no registration step), but still strictly validates `redirect_uri` — either an exact match against `MCP_ALLOWED_REDIRECT_URIS`, or a trusted domain (and its subdomains) via `MCP_ALLOWED_REDIRECT_HOSTS`. At least one of the two must be set, or every `/authorize` request is rejected (fail closed).

---

## Requirements

- [Node.js](https://nodejs.org) 22 or later
- A TeamViewer account and an OAuth2 app registered in the [TeamViewer Management Console](https://login.teamviewer.com/nav#app/myapps) (Integrations → Apps)
- A publicly reachable HTTPS URL for local testing (TeamViewer's OAuth redirect requires one) — e.g. [Tailscale Funnel](https://tailscale.com/kb/1223/funnel) or [ngrok](https://ngrok.com)

---

## Setup

### 1. Register a TeamViewer OAuth app

1. Go to **[login.teamviewer.com → Integrations → Apps](https://login.teamviewer.com/nav#app/myapps)** and sign in.
2. Create an app and set its **Callback URL** to `{TEAMVIEWER_MCP_URL}/callback` (the public URL this server will run at, plus `/callback` — see Step 3).
3. Copy the **Client ID** and **Client Secret**.

### 2. Install and build

```bash
git clone https://github.com/NilsTv8/TV_ONE_MCP_Anthropic_noDCR.git
cd TV_ONE_MCP_Anthropic_noDCR
npm install
npm run build
```

### 3. Configure environment variables

| Variable | Required | Description |
|---|---|---|
| `TEAMVIEWER_CLIENT_ID` | Yes | TV OAuth app client ID (from Step 1) |
| `TEAMVIEWER_CLIENT_SECRET` | Yes | TV OAuth app client secret |
| `MCP_ALLOWED_REDIRECT_URIS` | No\* | Comma-separated list of exact, trusted MCP-client callback URLs |
| `MCP_ALLOWED_REDIRECT_HOSTS` | No\* | Comma-separated list of trusted domains — matches that host exactly or any subdomain of it (e.g. `consent.azure-apim.net` covers Copilot Studio's `global.consent.azure-apim.net`, `unitedstates.consent.azure-apim.net`, etc.) |
| `TEAMVIEWER_MCP_URL` | Yes | Public base URL of this server (no trailing slash) |
| `TEAMVIEWER_CALLBACK_URL` | No | OAuth callback URL — defaults to `{TEAMVIEWER_MCP_URL}/callback` |
| `PORT` | No | HTTP port (default `3000`) |

\* At least one of `MCP_ALLOWED_REDIRECT_URIS` / `MCP_ALLOWED_REDIRECT_HOSTS` must be set.

No encryption-key variable is needed — TeamViewer tokens are encrypted server-side with a key generated fresh in memory on every boot (see [Security design](#security-design) below).

### 4. Run

```bash
TEAMVIEWER_CLIENT_ID=<id> \
TEAMVIEWER_CLIENT_SECRET=<secret> \
MCP_ALLOWED_REDIRECT_HOSTS=consent.azure-apim.net \
TEAMVIEWER_MCP_URL=https://<your-public-domain> \
PORT=3000 node dist/index.js
```

### 5. Connect Copilot Studio (or another non-DCR client)

Create a custom connector with OAuth 2.0 "Generic" authentication:

| Field | Value |
|---|---|
| Authorization URL | `https://<your-public-domain>/authorize` |
| Token URL | `https://<your-public-domain>/token` |
| Refresh URL | same as Token URL |
| Client ID / secret | from Step 1 |
| Scope | see [Available scopes](#available-scopes) below |

Power Platform generates its own redirect URL per connector after you save it (`https://<region>.consent.azure-apim.net/redirect/<connector-id>`) — that's exactly what `MCP_ALLOWED_REDIRECT_HOSTS=consent.azure-apim.net` is for; you don't need to copy the generated URL anywhere.

---

## OAuth endpoints (auto-served)

| Endpoint | Purpose |
|---|---|
| `GET /.well-known/oauth-protected-resource` (and `/mcp` suffix) | RFC 9728 protected resource metadata |
| `GET /.well-known/oauth-authorization-server` | RFC 8414 authorization server metadata |
| `GET /authorize` | Starts the OAuth flow → redirects to TeamViewer |
| `POST /token` | Exchanges an authorization code or refresh token |
| `POST /revoke` | Revokes a token |
| `GET /callback` | TeamViewer redirects here after the user logs in |

(`POST /register` / Dynamic Client Registration is intentionally not offered in this variant — see the DCR repo instead.)

---

## Available tools

27 action-based tools (each multi-action tool takes a single `action` parameter plus action-specific fields):

| Group | Tools |
|---|---|
| Account & Company | `tv_account`, `tv_company` |
| Users & Access Management | `tv_users`, `tv_deactivate_user_tfa`, `tv_get_user_effective_permissions`, `tv_get_user_roles`, `tv_respond_to_join_company_request`, `tv_user_roles`, `tv_user_role_assignments`, `tv_user_groups`, `tv_user_group_members` |
| Contacts | `tv_contacts` |
| Devices & Device Groups | `tv_device_groups`, `tv_managed_devices`, `tv_managed_device_managers`, `tv_managed_groups`, `tv_managed_group_managers` |
| Policies | `tv_teamviewer_policies`, `tv_monitoring_policies`, `tv_patch_policies` |
| Monitoring | `tv_monitoring` |
| Sessions & Remote Control | `tv_sessions`, `tv_connect_device` |
| Reports & Event Logs | `tv_connection_reports`, `tv_list_device_reports`, `tv_get_event_logs` |
| Tokens | `tv_tokens` (permanent, non-expiring TeamViewer API tokens — separate from this server's own OAuth tokens) |

Each tool's `description` (visible via `tools/list`) documents its exact `action` values and parameters.

### Available scopes

Requested during the OAuth flow; map to TeamViewer's own API permission scopes:

`UserInfo.View` · `Computers.View` · `Computers.Edit` · `Computers.Delete` · `Groups.View` · `Groups.Create` · `Groups.Edit` · `Groups.Delete` · `Contacts.View` · `Contacts.Create` · `Contacts.Edit` · `Contacts.Delete` · `Partners.View` · `Sessions.ManualCreation`

---

## Docker

```bash
docker build -t teamviewer-mcp-nodcr .
docker run -p 3000:3000 \
  -e TEAMVIEWER_CLIENT_ID=<id> \
  -e TEAMVIEWER_CLIENT_SECRET=<secret> \
  -e MCP_ALLOWED_REDIRECT_HOSTS=consent.azure-apim.net \
  -e TEAMVIEWER_MCP_URL=https://<your-public-domain> \
  teamviewer-mcp-nodcr
```

The image is a pinned, multi-stage, non-root build (compiles with dev dependencies in a `build` stage, ships only `dist/` and production dependencies in the `runtime` stage) with a container `HEALTHCHECK` against `/.well-known/oauth-authorization-server`.

## Production

Hosted on Azure App Service with this Docker image. Azure terminates TLS; the server always runs plain HTTP internally. Deploys automatically on every push to `master`.

---

## Security design

- **No TeamViewer token pass-through** — this server issues its own opaque, audience-bound tokens; TeamViewer access/refresh tokens are held server-side, AES-256-GCM encrypted, and only ever resolved internally right before a WebAPI call.
- **Refresh-token rotation with reuse detection** — replaying a consumed refresh token revokes the whole session.
- **`redirect_uri` allow-list, not a rubber stamp** — since any `client_id` is accepted, the allow-list (`MCP_ALLOWED_REDIRECT_URIS` / `MCP_ALLOWED_REDIRECT_HOSTS`) is the only check on where an authorization code can be sent; it's enforced so a mismatch gets a direct (non-redirecting) rejection, never a redirect to an unvalidated target.
- **No raw upstream error text in tool results** — a failed TeamViewer API call is logged in full server-side; only the HTTP status is surfaced to the calling tool/LLM.

See `CLAUDE.md` in this repo for full architectural detail, local dev setup, and environment specifics.

---

## License

MIT