> ## Content Index
> Fetch the complete content index at: https://blog.borodutch.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# I gave my AI agent a Tugport key that sees one project: Tugport's MCP server, tested
- URL: https://blog.borodutch.com/tugport-mcp-server-scoped-agent-key/
- Published: 2026-11-08T16:00:00.000Z
- Updated: 2026-11-08T16:00:00.000Z
- Description: Tugport 0.1.31 runs as an MCP server over stdio with 32 tools. I tested it with the key my own agent uses, which sees one project, can't read secrets and can't delete anything.
- Author: Nikita Kolmogorov
- Tags: Tugport, AI agents, MCP, #radiant

Tugport 0.1.31 can run as an MCP server over stdio: an agent starts `tugport mcp` as a command, and it gets 32 tools for projects, deploys, logs, env vars, domains, backups and the audit log, forwarded to your panel with the agent's own API key. I tested it on 8 November 2026 against the panel that runs most of my sites, using the key my own AI agent works with every day, and what stood out is how little that key can see: one project, with the operator role, and nothing else.

## What `tugport mcp` does

MCP clients such as Claude Code and Codex either connect to a URL or start a command and talk to it over standard input and output. Tugport already had the URL kind, a `POST /mcp` endpoint on the panel. Version 0.1.31, released on 29 October, added the command kind ([commit 6af9168](https://git.borodutch.com/backmeupplz/tugport/commit/6af9168a156ba913eeec3c6ac2c2643650b32824?ref=blog.borodutch.com)). The diff is about thirty lines in `src/cli.rs`: it reads one JSON-RPC message per line, posts each to the panel's `/mcp` with the saved bearer key, and writes the reply back as one line. A message that is only a notification gets a 202 and no output, which is what the MCP stdio transport expects.

You point it at a panel the same way as the rest of the CLI, with `tugport login https://panel.example.com --token tpk_...`, or with the `TUGPORT_URL` and `TUGPORT_TOKEN` environment variables inside the client's config:

```
{ "mcpServers": { "tugport": { "command": "tugport", "args": ["mcp"],
  "env": { "TUGPORT_URL": "https://panel.example.com", "TUGPORT_TOKEN": "tpk_..." } } } }
```

The release binary is a single file and needs nothing else on the machine running the agent, so the agent does not have to live on the server it deploys to.

## What I ran, and what came back

First the part that needs no panel at all. I downloaded `tugport-linux-x86_64` from the v0.1.31 release, started `tugport mcp` with no login, and sent it the three messages a client sends at startup: `initialize`, the `initialized` notification and `tools/list`. It answered with protocol version 2025-06-18, server name `tugport`, version 0.1.31 and a list of 32 tools. Asked to actually call one (`list_projects`), it replied with a plain message that it is not connected to a panel and how to log in, flagged as an error. That offline mode is deliberate: the commit says it exists so MCP directories can inspect the server without credentials.

Then the part with a panel. I sent the same `tools/list` and a few tool calls straight to `https://admin.borodutch.com/mcp` with the agent's key, since that is the endpoint the stdio command forwards to. The panel reported version 0.1.31 (commit 6af9168) and Docker 29.9.2\. I have not run the stdio command against the panel itself; I read what it forwards in the source.

- `list_projects` returned exactly one project, `marketing`, with my agent's role as `operator`, seven services of which six were running and one stopped.
- `get_service` on `marketing.kuma-test`, the one service my agent uses for tested guides, returned its domain, port 3001, the image `louislam/uptime-kuma:2` and the last deploy with its actor (`radiant`) and trigger (`agent`).
- `list_env` returned `"values_visible": false`: this key sees variable names, never values, because it has not been granted `secrets:read`.
- `get_project` for `default` and for `borodutch` both answered `project not found`. Those projects may or may not exist on my panel; the point is that to this key they look the same as projects that do not exist, so an agent cannot even learn their names.

The key's identity from `/api/me` says the same thing in one line: kind `agent`, no all-projects role, no grants, a single project role.

## The 32 tools, grouped

The list is wider than I expected from a deploy tool. By what they touch:

- **Projects and services:** `list_projects`, `get_project`, `create_project`, `create_service`, `get_service`, `update_service`, `delete_service`, `list_services`.
- **Deploys:** `deploy` (waits for the result unless told not to), `list_deploys`, `get_deploy` with the full log, `cancel_deploy`, `rollback`.
- **Running services:** `restart`, `stop`, `start`, `get_logs`, `exec`, `list_env`, `set_env`, `add_domain`, `remove_domain`.
- **Servers and metrics:** `list_servers`, `server_status`, `metrics`.
- **Databases and backups:** `list_replicas`, `add_replica`, `promote_replica`, `list_backups`, `run_backup`, `backup_run`.
- **Audit:** `audit_log`, who did what, newest first.

Putting `delete_service` and `exec` in front of an agent sounds alarming until you remember that the tool list is the same for everyone and the permissions are not. The roles from the API reference apply to every tool call: a viewer reads status, logs and variable names, a deployer deploys, restarts and rolls back, an operator changes settings and creates services, and an admin deletes things. Deleting also needs the separate `destroy` grant, and seeing variable values needs `secrets:read`. My agent has the operator role and neither grant, which is why it can create, deploy and stop its test service but cannot delete one, and why I do not have to trust it not to try.

## Why I prefer a key that sees one project

The usual way to give an agent access to a server is a shell, which is everything or nothing. With a scoped Tugport key the agent sees a single project, can only do what its role allows, and every call it makes lands in the audit log under its own name, with the key that made it. If it misbehaves I revoke that one key with `DELETE /api/keys/{id}` and nothing else changes. If I want to hand a second agent the same tools for a different project, I run `tugport agent <name> --project <project>:<role>` on the panel, which prints the key once, and optionally add `--expires-days` so it stops working by itself.

The same setup works from any client that speaks MCP, so the instructions for one agent are the instructions for all of them. An agent that starts commands gets `tugport mcp`; one that connects to URLs gets `/mcp`; a script gets the REST API; and all three are held to the same roles.

## What I did not test

I did not call `delete_service`, `exec`, `add_replica` or any backup tool, because they change production and my agent's job on this panel does not include them. I also did not measure latency: every call I made went over the internet and through a proxy that holds the key, so any number I put here would describe that path, not Tugport.

## Try it

Install Tugport on a VPS with `curl -fsSL https://tugport.com/install | sh`, open the one-time link it prints to create the owner account, create an agent user for one project, and give its key to your client. The [API reference](https://tugport.com/docs/api/?ref=blog.borodutch.com) lists every endpoint and the MCP section, and the [repository](https://github.com/backmeupplz/tugport?ref=blog.borodutch.com) has the source for everything above. Tugport is young (0.1.x), so expect rough edges and tell me about them.

*Tested on 8 November 2026 with Tugport 0.1.31 (linux x86\_64 client and the panel at 0.1.31, commit 6af9168).*