This commit is contained in:
Даниил Грабарь
2026-09-13 10:06:21 +03:00
parent 910afce714
commit 80c87682ea
30 changed files with 1155 additions and 220 deletions
@@ -16,6 +16,7 @@ web:
enabled: true
host: 0.0.0.0
port: 8007
token: "replace-with-a-long-secret-token"
endpoints: pkg://iiwa_config/config/api_endpoints.yaml
joint_limits: pkg://iiwa_config/config/moveit/joint_limits.yaml
~~~
@@ -28,7 +29,65 @@ After starting the stack with **cobot run**, the server is available at **http:/
There is no separate health-check endpoint. If Swagger UI opens, the HTTP server is running. Readiness of ROS components is checked when a specific endpoint is called.
By default, the server listens on all network interfaces and does not use authentication. Do not expose port 8007 to an untrusted network. For local access, set **host: 127.0.0.1**. For remote access, restrict the network with firewall rules or a VPN.
When `host` is not `localhost`, `127.0.0.1`, or another loopback address, the
`token` field is required; otherwise the server exits during startup. When
`token` is set, Bearer authentication applies to every REST route and to the
MCP route `/mcp/mcp`, including with a local `host`.
The `/docs`, `/redoc`, and `/openapi.json` resources are available without a
header so the browser can load Swagger UI. This does not expose control
commands. Open `/docs`, click **Authorize**, paste the `web.token` value without
the word `Bearer`, and confirm. Swagger adds the header to API requests.
The token is stored in **cobot-setting.yaml**. Do not add it to documentation,
scripts, or public repositories. If it is exposed, replace it and restart the
stack. For remote access, also restrict port 8007 with a firewall or VPN.
### REST and MCP authentication
Every request to a protected REST route or MCP must include the following header
when `web.token` is set:
~~~ http
Authorization: Bearer <web.token value>
~~~
Client setup examples:
=== "curl"
~~~ bash
HOST=http://localhost:8007
API_TOKEN='copy web.token from cobot-setting.yaml'
AUTH_HEADER="Authorization: Bearer ${API_TOKEN}"
curl -sS -H "${AUTH_HEADER}" $HOST/robot/joint_states
~~~
=== "Python"
~~~ python
import httpx
HOST = "http://localhost:8007"
API_TOKEN = "copy web.token from cobot-setting.yaml"
HEADERS = {"Authorization": f"Bearer {API_TOKEN}"}
response = httpx.get(f"{HOST}/robot/joint_states", headers=HEADERS, timeout=10)
response.raise_for_status()
~~~
=== "MATLAB"
~~~ matlab
HOST = 'http://localhost:8007';
API_TOKEN = 'copy web.token from cobot-setting.yaml';
readOpts = weboptions('Timeout', 10, ...
'HeaderFields', {'Authorization', ['Bearer ' API_TOKEN]});
jointState = webread([HOST '/robot/joint_states'], readOpts);
~~~
Pass the same header when connecting an MCP client to
`http://<host>:8007/mcp/mcp`. If the client supports custom HTTP headers, set
`Authorization: Bearer <web.token value>` in its connection settings.
## Preparing the examples
@@ -648,4 +707,4 @@ When troubleshooting, proceed from simple checks to more complex ones:
3. Make sure that the complete stack is running: `controller_manager`, MoveIt, and `iiwa_motion_server`.
4. After starting a sequence, inspect **/sequences/logs**. After publishing a trajectory, inspect **/trajectory/logs**.
The MCP server runs in the same process but provides a separate interface at **http://server-address:8007/mcp/mcp**. For ordinary HTTP integrations, use the endpoints documented on this page.
The MCP server runs in the same process but provides a separate interface at **http://server-address:8007/mcp/mcp**. It uses the same Bearer token; pass the `Authorization` header when connecting an MCP client. For ordinary HTTP integrations, use the endpoints documented on this page.
@@ -16,6 +16,7 @@ web:
enabled: true
host: 0.0.0.0
port: 8007
token: "замените-на-длинный-секретный-токен"
endpoints: pkg://iiwa_config/config/api_endpoints.yaml
joint_limits: pkg://iiwa_config/config/moveit/joint_limits.yaml
~~~
@@ -28,7 +29,65 @@ web:
Отдельного health-check в сервере нет. Если открывается Swagger UI, HTTP-сервер запущен. Готовность ROS-компонентов проверяется при обращении к конкретному маршруту.
По умолчанию сервер слушает все сетевые интерфейсы и не использует аутентификацию. Не публикуйте порт 8007 в недоверенную сеть. Для локальной работы укажите **host: 127.0.0.1**, а для удалённого доступа ограничьте сеть правилами firewall или VPN.
Если `host` отличается от `localhost`, `127.0.0.1` или другого loopback-адреса,
поле `token` обязательно: без него сервер завершит запуск с ошибкой. Если
`token` заполнен, Bearer-аутентификация применяется ко всем REST-маршрутам и
к MCP-маршруту `/mcp/mcp`, в том числе при локальном `host`.
Страницы `/docs`, `/redoc` и схема `/openapi.json` доступны без заголовка,
чтобы браузер мог загрузить Swagger UI. Это не открывает команды управления.
Откройте `/docs`, нажмите **Authorize**, вставьте значение `web.token` без
слова `Bearer` и подтвердите. Swagger сам добавит нужный заголовок к запросам.
Токен хранится в **cobot-setting.yaml**. Не добавляйте его в документацию,
скрипты или публичные репозитории; после утечки замените значение и перезапустите
стек. Для удалённого доступа дополнительно ограничьте порт 8007 firewall или VPN.
### Аутентификация REST и MCP
Каждый запрос к защищённому REST-маршруту или MCP при заполненном `web.token`
должен содержать:
~~~ http
Authorization: Bearer <значение-web.token>
~~~
Примеры подготовки клиентов:
=== "curl"
~~~ bash
HOST=http://localhost:8007
API_TOKEN='скопируйте значение web.token из cobot-setting.yaml'
AUTH_HEADER="Authorization: Bearer ${API_TOKEN}"
curl -sS -H "${AUTH_HEADER}" $HOST/robot/joint_states
~~~
=== "Python"
~~~ python
import httpx
HOST = "http://localhost:8007"
API_TOKEN = "скопируйте значение web.token из cobot-setting.yaml"
HEADERS = {"Authorization": f"Bearer {API_TOKEN}"}
response = httpx.get(f"{HOST}/robot/joint_states", headers=HEADERS, timeout=10)
response.raise_for_status()
~~~
=== "MATLAB"
~~~ matlab
HOST = 'http://localhost:8007';
API_TOKEN = 'скопируйте значение web.token из cobot-setting.yaml';
readOpts = weboptions('Timeout', 10, ...
'HeaderFields', {'Authorization', ['Bearer ' API_TOKEN]});
jointState = webread([HOST '/robot/joint_states'], readOpts);
~~~
Тот же заголовок передаётся MCP-клиенту при подключении к
`http://<host>:8007/mcp/mcp`. Если клиент поддерживает пользовательские HTTP
заголовки, укажите `Authorization: Bearer <значение-web.token>` в его настройках.
## Подготовка к примерам
@@ -96,7 +155,7 @@ GET **/robot/joint_states** возвращает последнее сообще
=== "Python"
~~~ python
response = httpx.get(f"{HOST}/robot/joint_states", timeout=T_READ)
response = httpx.get(f"{HOST}/robot/joint_states", headers=HEADERS, timeout=T_READ)
response.raise_for_status()
state = response.json()
print(dict(zip(state["name"], state["position"])))
@@ -139,7 +198,7 @@ GET **/robot/pose** вычисляет прямую кинематику чер
=== "Python"
~~~ python
response = httpx.get(f"{HOST}/robot/pose", timeout=T_READ)
response = httpx.get(f"{HOST}/robot/pose", headers=HEADERS, timeout=T_READ)
response.raise_for_status()
pose = response.json()
print(pose["position"])
@@ -172,7 +231,7 @@ GET **/robot/positions** читает положения group_state из SRDF.
=== "Python"
~~~ python
response = httpx.get(f"{HOST}/robot/positions", timeout=T_READ)
response = httpx.get(f"{HOST}/robot/positions", headers=HEADERS, timeout=T_READ)
response.raise_for_status()
for position in response.json():
print(position["name"], "—", position["description"])
@@ -223,6 +282,7 @@ POST **/robot/move/named** перемещает манипулятор в пол
~~~ python
response = httpx.post(
f"{HOST}/robot/move/named",
headers=HEADERS,
json={"name": "home", "speed": 0.1, "accel_scale": 0.0},
timeout=T_MOVE,
)
@@ -274,7 +334,7 @@ POST **/robot/move/pose** принимает положение TCP в метр
"a": 0.0, "b": 3.14159, "c": 0.0,
"speed": 0.1, "planner": "ptp", "frame_id": "",
}
response = httpx.post(f"{HOST}/robot/move/pose", json=target, timeout=T_MOVE)
response = httpx.post(f"{HOST}/robot/move/pose", headers=HEADERS, json=target, timeout=T_MOVE)
response.raise_for_status()
print(response.json())
~~~
@@ -319,6 +379,7 @@ POST **/robot/move/joints** принимает ровно семь углов в
~~~ python
response = httpx.post(
f"{HOST}/robot/move/joints",
headers=HEADERS,
json={
"joints": [0.0, 0.5, 0.0, -1.57, 0.0, 1.57, 0.0],
"speed": 0.1,
@@ -382,7 +443,7 @@ POST **/trajectory/send** принимает одну или несколько
],
"validate_limits": True,
}
response = httpx.post(f"{HOST}/trajectory/send", json=trajectory, timeout=T_READ)
response = httpx.post(f"{HOST}/trajectory/send", headers=HEADERS, json=trajectory, timeout=T_READ)
response.raise_for_status()
print(response.json())
~~~
@@ -430,6 +491,7 @@ joint1,joint2,joint3,joint4,joint5,joint6,joint7,t
with open("trajectory.csv", "rb") as csv_file:
response = httpx.post(
f"{HOST}/trajectory/send_csv",
headers=HEADERS,
params={"separator": ",", "validate_limits": True},
files={"file": ("trajectory.csv", csv_file, "text/csv")},
timeout=T_READ,
@@ -468,7 +530,7 @@ GET **/trajectory/logs?n=50** возвращает до 300 последних
=== "Python"
~~~ python
response = httpx.get(f"{HOST}/trajectory/logs", params={"n": 20}, timeout=T_READ)
response = httpx.get(f"{HOST}/trajectory/logs", headers=HEADERS, params={"n": 20}, timeout=T_READ)
response.raise_for_status()
for line in response.json()["lines"]:
print(line)
@@ -538,6 +600,7 @@ POST **/sequences/start** запускает отдельный процесс m
with open("motion_sequence_config.json", "rb") as config:
response = httpx.post(
f"{HOST}/sequences/start",
headers=HEADERS,
files={"config": ("motion_sequence_config.json", config, "application/json")},
data={"n_iterations": "3", "delay_between_iterations": "5.0"},
timeout=T_READ,
@@ -577,11 +640,11 @@ POST **/sequences/start** запускает отдельный процесс m
=== "Python"
~~~ python
status = httpx.get(f"{HOST}/sequences/status", timeout=T_READ)
status = httpx.get(f"{HOST}/sequences/status", headers=HEADERS, timeout=T_READ)
status.raise_for_status()
print(status.json())
logs = httpx.get(f"{HOST}/sequences/logs", params={"n": 50}, timeout=T_READ)
logs = httpx.get(f"{HOST}/sequences/logs", headers=HEADERS, params={"n": 50}, timeout=T_READ)
logs.raise_for_status()
for line in logs.json()["lines"]:
print(line)
@@ -617,7 +680,7 @@ POST **/stop** останавливает запущенный runner, публ
=== "Python"
~~~ python
response = httpx.post(f"{HOST}/stop", timeout=T_READ)
response = httpx.post(f"{HOST}/stop", headers=HEADERS, timeout=T_READ)
response.raise_for_status()
print(response.json())
~~~
@@ -648,4 +711,4 @@ POST **/stop** останавливает запущенный runner, публ
3. Убедитесь, что стек запущен полностью: controller_manager, MoveIt и iiwa_motion_server.
4. После запуска последовательности посмотрите **/sequences/logs**; после публикации траектории — **/trajectory/logs**.
MCP-сервер работает в том же процессе, но это отдельный интерфейс: его адрес — **http://адрес-сервера:8007/mcp/mcp**. Для обычных HTTP-интеграций используйте маршруты из этой страницы.
MCP-сервер работает в том же процессе, но это отдельный интерфейс: его адрес — **http://адрес-сервера:8007/mcp/mcp**. Он использует тот же Bearer-токен; передайте заголовок `Authorization` при подключении MCP-клиента. Для обычных HTTP-интеграций используйте маршруты из этой страницы.