@toolrelay/cli 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +6 -0
- package/README.md +113 -309
- package/package.json +3 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Copyright (c) 2024-2026 ToolRelay, Inc. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software is proprietary and confidential. Unauthorized copying, distribution,
|
|
4
|
+
modification, or use of this software, via any medium, is strictly prohibited.
|
|
5
|
+
|
|
6
|
+
Use of this software is governed by the terms of service at https://toolrelay.io/terms.
|
package/README.md
CHANGED
|
@@ -1,30 +1,60 @@
|
|
|
1
1
|
# @toolrelay/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Turn any REST API into an MCP server that AI agents can use — in one command.
|
|
4
|
+
|
|
5
|
+
Define your API endpoints in a JSON config, and the CLI gives you a local [Model Context Protocol](https://modelcontextprotocol.io) server that Claude, GPT, and other AI agents can connect to instantly. No SDK integration, no code changes, no infrastructure to manage.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
AI agents need tools. Your API already exists. The gap between the two is boilerplate: building an MCP server, handling auth, mapping parameters, debugging what the agent actually sent. This CLI closes that gap.
|
|
10
|
+
|
|
11
|
+
- **Zero code** — describe your API in JSON, get a working MCP server
|
|
12
|
+
- **Works with any REST API** — GET, POST, PUT, DELETE with path, query, body, and header parameters
|
|
13
|
+
- **Built-in auth** — static tokens, API keys, OAuth 2.0 (with PKCE), custom headers
|
|
14
|
+
- **Audit traces** — every tool call logs the full request/response cycle so you can see exactly what happened
|
|
15
|
+
- **Session viewer** — browse past sessions in a local web UI, like Playwright's HTML reporter for API calls
|
|
16
|
+
- **Environment variable interpolation** — `${VAR}` syntax keeps secrets out of committed config files
|
|
17
|
+
- **Deploy when ready** — one command to go from local testing to a hosted MCP endpoint at [toolrelay.io](https://toolrelay.io)
|
|
4
18
|
|
|
5
19
|
## Quick start
|
|
6
20
|
|
|
7
21
|
```bash
|
|
8
|
-
# Interactive wizard — walks you through app and tools
|
|
9
22
|
npx @toolrelay/cli init
|
|
23
|
+
```
|
|
10
24
|
|
|
11
|
-
|
|
12
|
-
npx @toolrelay/cli init --yes
|
|
25
|
+
The wizard walks you through your API — name, base URL, auth, and endpoints. It generates a `toolrelay.json` config file.
|
|
13
26
|
|
|
14
|
-
|
|
15
|
-
npx @toolrelay/cli validate toolrelay.json
|
|
27
|
+
Then start the MCP server:
|
|
16
28
|
|
|
17
|
-
|
|
29
|
+
```bash
|
|
18
30
|
npx @toolrelay/cli serve toolrelay.json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
That's it. Add the printed URL to Claude Desktop's config and your API is available as tools.
|
|
34
|
+
|
|
35
|
+
## How it works
|
|
19
36
|
|
|
20
|
-
# Deploy to ToolRelay
|
|
21
|
-
npx @toolrelay/cli login
|
|
22
|
-
npx @toolrelay/cli publish toolrelay.json
|
|
23
37
|
```
|
|
38
|
+
toolrelay.json
|
|
39
|
+
|
|
|
40
|
+
npx @toolrelay/cli serve toolrelay.json
|
|
41
|
+
|
|
|
42
|
+
Local MCP Server (localhost:8787)
|
|
43
|
+
/ | \
|
|
44
|
+
tools/list tools/call health
|
|
45
|
+
|
|
|
46
|
+
Your REST API backend
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
1. You describe your API in `toolrelay.json` — endpoints, parameters, auth
|
|
50
|
+
2. `serve` starts a local MCP Streamable HTTP server
|
|
51
|
+
3. AI agents connect and discover your tools via `tools/list`
|
|
52
|
+
4. When an agent calls a tool, the CLI maps parameters, injects auth, and proxies the request to your backend
|
|
53
|
+
5. Every call is logged with full audit traces — request, response, timing, errors
|
|
24
54
|
|
|
25
|
-
## Config file
|
|
55
|
+
## Config file
|
|
26
56
|
|
|
27
|
-
The `init` wizard generates this
|
|
57
|
+
The `init` wizard generates this, or create it by hand:
|
|
28
58
|
|
|
29
59
|
```json
|
|
30
60
|
{
|
|
@@ -32,7 +62,7 @@ The `init` wizard generates this file for you, or you can create it manually:
|
|
|
32
62
|
"name": "My API",
|
|
33
63
|
"base_url": "http://localhost:3000",
|
|
34
64
|
"auth_type": "static_token",
|
|
35
|
-
"auth_config": { "token": "
|
|
65
|
+
"auth_config": { "token": "${API_TOKEN}" }
|
|
36
66
|
},
|
|
37
67
|
"tools": [
|
|
38
68
|
{
|
|
@@ -48,125 +78,46 @@ The `init` wizard generates this file for you, or you can create it manually:
|
|
|
48
78
|
}
|
|
49
79
|
```
|
|
50
80
|
|
|
51
|
-
> **Tip:** `description` on both tools and parameters is how AI agents
|
|
52
|
-
|
|
53
|
-
### Environment variable interpolation
|
|
81
|
+
> **Tip:** `description` on both tools and parameters is how AI agents decide which tool to use. Clear descriptions directly improve how well agents interact with your API.
|
|
54
82
|
|
|
55
|
-
|
|
83
|
+
### Environment variables
|
|
56
84
|
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
"app": {
|
|
60
|
-
"base_url": "${API_BASE_URL}",
|
|
61
|
-
"auth_type": "static_token",
|
|
62
|
-
"auth_config": { "token": "${API_TOKEN}" }
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
```
|
|
85
|
+
Use `${VAR_NAME}` in any string field to reference environment variables. Secrets stay out of your config:
|
|
66
86
|
|
|
67
87
|
```bash
|
|
68
|
-
export
|
|
69
|
-
export API_TOKEN=my-secret-key
|
|
88
|
+
export API_TOKEN=sk-my-secret-key
|
|
70
89
|
npx @toolrelay/cli serve toolrelay.json
|
|
71
90
|
```
|
|
72
91
|
|
|
73
|
-
If a referenced variable is missing, the CLI fails with a clear error listing the missing
|
|
92
|
+
If a referenced variable is missing, the CLI fails with a clear error listing the missing names.
|
|
74
93
|
|
|
75
94
|
### Auth types
|
|
76
95
|
|
|
77
|
-
| `auth_type` | `auth_config`
|
|
96
|
+
| `auth_type` | `auth_config` | Description |
|
|
78
97
|
|---|---|---|
|
|
79
98
|
| `none` | — | No authentication |
|
|
80
99
|
| `static_token` | `{ "token": "..." }` | Bearer token in Authorization header |
|
|
81
100
|
| `api_key_relay` | `{ "header_name": "X-API-Key" }` | Consumer API keys relayed to backend |
|
|
82
|
-
| `custom_header` | `{ "headers": { "X-Custom": "value" } }` | Custom
|
|
83
|
-
| `oauth2` | `{ "authorize_url", "token_url",
|
|
101
|
+
| `custom_header` | `{ "headers": { "X-Custom": "value" } }` | Custom header name/value pairs |
|
|
102
|
+
| `oauth2` | `{ "authorize_url", "token_url", ... }` | OAuth 2.0 with PKCE (see [OAuth2 section](#oauth2)) |
|
|
84
103
|
|
|
85
104
|
### Parameter mapping
|
|
86
105
|
|
|
87
|
-
Each parameter in `parameter_mapping`
|
|
106
|
+
Each parameter in `parameter_mapping` tells the CLI how to translate what the agent sends into what your API expects:
|
|
88
107
|
|
|
89
108
|
| Field | Required | Description |
|
|
90
109
|
|---|---|---|
|
|
91
110
|
| `name` | yes | Parameter name (what the AI agent sends) |
|
|
92
111
|
| `type` | yes | `string`, `number`, `boolean`, `object`, or `array` |
|
|
93
112
|
| `required` | yes | Whether the parameter is required |
|
|
94
|
-
| `target` | yes | Where
|
|
113
|
+
| `target` | yes | Where it goes: `path`, `query`, `body`, or `header` |
|
|
95
114
|
| `backend_key` | no | Backend field name if different from `name` |
|
|
96
|
-
| `
|
|
97
|
-
| `description` | no |
|
|
98
|
-
|
|
99
|
-
#### Path parameters
|
|
100
|
-
|
|
101
|
-
Path parameters replace `{placeholder}` segments in the `endpoint_path`. The parameter `name` (or `backend_key` if set) must match the placeholder name.
|
|
102
|
-
|
|
103
|
-
```json
|
|
104
|
-
{
|
|
105
|
-
"name": "create-order",
|
|
106
|
-
"http_method": "POST",
|
|
107
|
-
"endpoint_path": "/api/customers/{customer_id}/orders",
|
|
108
|
-
"parameter_mapping": [
|
|
109
|
-
{ "name": "customer_id", "type": "string", "required": true, "target": "path" }
|
|
110
|
-
]
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Agent sends `{ "customer_id": "cust_42" }` → request goes to `/api/customers/cust_42/orders`.
|
|
115
|
-
|
|
116
|
-
#### Query parameters
|
|
117
|
-
|
|
118
|
-
Query parameters are appended as `?key=value` to the URL. Use `backend_key` when the backend expects a different name than what the agent sends, and `default` for optional filters.
|
|
115
|
+
| `default_value` | no | Default when not provided |
|
|
116
|
+
| `description` | no | Shown to AI agents — always include this |
|
|
119
117
|
|
|
120
|
-
|
|
121
|
-
{
|
|
122
|
-
"name": "search_products",
|
|
123
|
-
"http_method": "GET",
|
|
124
|
-
"endpoint_path": "/api/products",
|
|
125
|
-
"parameter_mapping": [
|
|
126
|
-
{ "name": "query", "type": "string", "required": true, "target": "query", "backend_key": "q" },
|
|
127
|
-
{ "name": "category", "type": "string", "required": false, "target": "query" },
|
|
128
|
-
{ "name": "page", "type": "number", "required": false, "target": "query", "default": 1 },
|
|
129
|
-
{ "name": "limit", "type": "number", "required": false, "target": "query", "default": 20 }
|
|
130
|
-
]
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
Agent sends `{ "query": "bluetooth speaker", "category": "electronics" }` → request goes to `/api/products?q=bluetooth+speaker&category=electronics&page=1&limit=20`.
|
|
135
|
-
|
|
136
|
-
#### Body parameters
|
|
137
|
-
|
|
138
|
-
Body parameters are sent as JSON in the request body. Use dot-notation in `backend_key` to create nested objects (e.g., `address.city` becomes `{ "address": { "city": "..." } }`).
|
|
139
|
-
|
|
140
|
-
```json
|
|
141
|
-
{
|
|
142
|
-
"name": "create_contact",
|
|
143
|
-
"http_method": "POST",
|
|
144
|
-
"endpoint_path": "/api/contacts",
|
|
145
|
-
"parameter_mapping": [
|
|
146
|
-
{ "name": "name", "type": "string", "required": true, "target": "body" },
|
|
147
|
-
{ "name": "email", "type": "string", "required": true, "target": "body" },
|
|
148
|
-
{ "name": "phone", "type": "string", "required": false, "target": "body" },
|
|
149
|
-
{ "name": "city", "type": "string", "required": false, "target": "body", "backend_key": "address.city" },
|
|
150
|
-
{ "name": "state", "type": "string", "required": false, "target": "body", "backend_key": "address.state" },
|
|
151
|
-
{ "name": "tags", "type": "array", "required": false, "target": "body", "default": [] }
|
|
152
|
-
]
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
Agent sends `{ "name": "Alice", "email": "alice@example.com", "city": "Austin", "state": "TX" }` → request body becomes:
|
|
157
|
-
|
|
158
|
-
```json
|
|
159
|
-
{
|
|
160
|
-
"name": "Alice",
|
|
161
|
-
"email": "alice@example.com",
|
|
162
|
-
"address": { "city": "Austin", "state": "TX" },
|
|
163
|
-
"tags": []
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
#### Mixing targets
|
|
118
|
+
**Path** parameters replace `{placeholder}` in the URL. **Query** parameters are appended as `?key=value`. **Body** parameters are sent as JSON (use dot-notation in `backend_key` for nesting, e.g. `address.city`). **Header** parameters are injected as HTTP headers.
|
|
168
119
|
|
|
169
|
-
A single tool can
|
|
120
|
+
A single tool can mix all four targets:
|
|
170
121
|
|
|
171
122
|
```json
|
|
172
123
|
{
|
|
@@ -175,286 +126,139 @@ A single tool can combine path, query, body, and header parameters:
|
|
|
175
126
|
"endpoint_path": "/api/items/{item_id}",
|
|
176
127
|
"parameter_mapping": [
|
|
177
128
|
{ "name": "item_id", "type": "string", "required": true, "target": "path" },
|
|
178
|
-
{ "name": "dry_run", "type": "boolean", "required": false, "target": "query", "
|
|
129
|
+
{ "name": "dry_run", "type": "boolean", "required": false, "target": "query", "default_value": false },
|
|
179
130
|
{ "name": "title", "type": "string", "required": true, "target": "body" },
|
|
180
|
-
{ "name": "price", "type": "number", "required": true, "target": "body" },
|
|
181
131
|
{ "name": "idempotency_key", "type": "string", "required": false, "target": "header", "backend_key": "Idempotency-Key" }
|
|
182
132
|
]
|
|
183
133
|
}
|
|
184
134
|
```
|
|
185
135
|
|
|
186
|
-
Agent sends `{ "item_id": "abc", "title": "Widget", "price": 9.99, "idempotency_key": "req-1" }` → `PUT /api/items/abc?dry_run=false` with body `{ "title": "Widget", "price": 9.99 }` and header `Idempotency-Key: req-1`.
|
|
187
|
-
|
|
188
136
|
## Commands
|
|
189
137
|
|
|
190
|
-
### `init` — Generate a config file
|
|
191
|
-
|
|
192
|
-
```bash
|
|
193
|
-
npx @toolrelay/cli init # Interactive wizard
|
|
194
|
-
npx @toolrelay/cli init --yes # Starter template (no prompts)
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
Walks you through configuring your app (name, base URL, auth type) and defining tools (endpoints, parameters).
|
|
198
|
-
|
|
199
|
-
### `validate` — Check config
|
|
200
|
-
|
|
201
|
-
```bash
|
|
202
|
-
npx @toolrelay/cli validate toolrelay.json
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
Validates the config without making any HTTP calls. Catches:
|
|
206
|
-
|
|
207
|
-
- **Schema errors**: Missing required fields, invalid enum values, malformed URLs
|
|
208
|
-
- **Path parameter mismatches**: `{placeholder}` in `endpoint_path` without a matching `target: "path"` parameter (and vice versa)
|
|
209
|
-
- **Auth config**: Required fields per auth type (e.g., `token` for `static_token`, `authorize_url` for `oauth2`)
|
|
210
|
-
|
|
211
|
-
Run this in CI to catch config issues before deploying.
|
|
212
|
-
|
|
213
138
|
### `serve` — Local MCP server
|
|
214
139
|
|
|
215
140
|
```bash
|
|
216
141
|
npx @toolrelay/cli serve toolrelay.json
|
|
217
|
-
npx @toolrelay/cli serve toolrelay.json --port
|
|
142
|
+
npx @toolrelay/cli serve toolrelay.json --port 9000 --verbose
|
|
218
143
|
```
|
|
219
144
|
|
|
220
|
-
Starts a local MCP Streamable HTTP server
|
|
145
|
+
Starts a local MCP Streamable HTTP server. On startup it prints a Claude Desktop config snippet you can copy directly.
|
|
221
146
|
|
|
222
147
|
| Option | Description |
|
|
223
148
|
|---|---|
|
|
224
149
|
| `--base-url <url>` | Override `base_url` from config |
|
|
225
|
-
| `-p, --port <number>` | Port
|
|
150
|
+
| `-p, --port <number>` | Port (default: 8787) |
|
|
226
151
|
| `-v, --verbose` | Show response headers and extra detail |
|
|
227
152
|
|
|
228
|
-
On startup it prints the Claude Desktop config snippet:
|
|
229
|
-
|
|
230
|
-
```json
|
|
231
|
-
{
|
|
232
|
-
"mcpServers": {
|
|
233
|
-
"my-api": {
|
|
234
|
-
"url": "http://localhost:8787/mcp"
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
The server implements the full MCP Streamable HTTP protocol (`initialize`, `tools/list`, `tools/call`, `ping`, SSE, session management).
|
|
241
|
-
|
|
242
153
|
**Endpoints:**
|
|
243
154
|
|
|
244
155
|
| Endpoint | Description |
|
|
245
156
|
|---|---|
|
|
246
|
-
| `POST /mcp` | MCP JSON-RPC
|
|
247
|
-
| `GET /mcp` | SSE stream (with `Mcp-Session-Id`
|
|
248
|
-
| `GET /health` | Health check
|
|
157
|
+
| `POST /mcp` | MCP JSON-RPC (tools/list, tools/call, etc.) |
|
|
158
|
+
| `GET /mcp` | SSE stream (with `Mcp-Session-Id`) |
|
|
159
|
+
| `GET /health` | Health check + OAuth status |
|
|
249
160
|
| `GET /timeline` | Call timeline as JSON |
|
|
250
|
-
| `GET /oauth/start` | Trigger OAuth flow (OAuth2 apps
|
|
251
|
-
| `GET /oauth/status` | Check OAuth state
|
|
252
|
-
|
|
253
|
-
**Call timeline:** Every tool invocation is tracked with sequence numbers, timestamps, arguments, status, latency, and the gap between calls (how long the AI spent "thinking"). On shutdown (Ctrl+C), a formatted timeline and audit summary are printed.
|
|
254
|
-
|
|
255
|
-
**Session persistence:** Sessions are saved to `.toolrelay/sessions/` on shutdown. Browse them with `toolrelay ui`.
|
|
256
|
-
|
|
257
|
-
### `ui` — Session viewer
|
|
258
|
-
|
|
259
|
-
```bash
|
|
260
|
-
npx @toolrelay/cli ui
|
|
261
|
-
npx @toolrelay/cli ui --port 8788 --no-open
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
Opens a local web UI to browse past `serve` sessions. Inspect call timelines, drill into individual tool calls to see parameter resolution, request/response bodies, diagnostics, and errors.
|
|
161
|
+
| `GET /oauth/start` | Trigger OAuth flow (OAuth2 apps) |
|
|
162
|
+
| `GET /oauth/status` | Check OAuth state |
|
|
265
163
|
|
|
266
|
-
|
|
267
|
-
|---|---|
|
|
268
|
-
| `-p, --port <number>` | Port to listen on (default: 8788) |
|
|
269
|
-
| `--no-open` | Don't auto-open the browser |
|
|
164
|
+
Every tool call is tracked with sequence number, timing, arguments, status, and the gap between calls. On Ctrl+C, a formatted timeline and summary are printed. Sessions are saved to `.toolrelay/sessions/` — browse them with `toolrelay ui`.
|
|
270
165
|
|
|
271
|
-
### `
|
|
166
|
+
### `init` — Generate a config
|
|
272
167
|
|
|
273
168
|
```bash
|
|
274
|
-
npx @toolrelay/cli
|
|
169
|
+
npx @toolrelay/cli init # Interactive wizard
|
|
170
|
+
npx @toolrelay/cli init --yes # Starter template
|
|
275
171
|
```
|
|
276
172
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
For CI/CD pipelines, set the `TOOLRELAY_DEPLOY_TOKEN` environment variable instead — it takes priority over the credentials file.
|
|
280
|
-
|
|
281
|
-
### `logout` — Clear saved credentials
|
|
173
|
+
### `validate` — Check config
|
|
282
174
|
|
|
283
175
|
```bash
|
|
284
|
-
npx @toolrelay/cli
|
|
176
|
+
npx @toolrelay/cli validate toolrelay.json
|
|
285
177
|
```
|
|
286
178
|
|
|
287
|
-
|
|
179
|
+
Validates without making HTTP calls. Catches schema errors, path parameter mismatches, and missing auth fields. Run in CI to catch config issues before deploying.
|
|
288
180
|
|
|
289
|
-
### `
|
|
181
|
+
### `ui` — Session viewer
|
|
290
182
|
|
|
291
183
|
```bash
|
|
292
|
-
npx @toolrelay/cli
|
|
184
|
+
npx @toolrelay/cli ui
|
|
293
185
|
```
|
|
294
186
|
|
|
295
|
-
|
|
187
|
+
Opens a local web UI to browse past `serve` sessions. Drill into individual tool calls to see parameter resolution, request/response bodies, diagnostics, and errors.
|
|
296
188
|
|
|
297
|
-
### `publish` — Deploy to
|
|
189
|
+
### `publish` — Deploy to production
|
|
298
190
|
|
|
299
191
|
```bash
|
|
192
|
+
npx @toolrelay/cli login
|
|
300
193
|
npx @toolrelay/cli publish toolrelay.json
|
|
301
194
|
npx @toolrelay/cli publish toolrelay.json --dry-run
|
|
302
|
-
npx @toolrelay/cli publish toolrelay.json --prune
|
|
303
195
|
```
|
|
304
196
|
|
|
305
|
-
Deploys your
|
|
306
|
-
|
|
307
|
-
| Option | Description |
|
|
308
|
-
|---|---|
|
|
309
|
-
| `--dry-run` | Show what would change without making any API calls |
|
|
310
|
-
| `--prune` | Delete remote tools that are no longer in the config file |
|
|
311
|
-
|
|
312
|
-
**How it works:**
|
|
197
|
+
Deploys your config to [ToolRelay](https://toolrelay.io). The command is idempotent — creates on first run, updates only what changed after that.
|
|
313
198
|
|
|
314
|
-
|
|
315
|
-
2. Authenticates using `TOOLRELAY_DEPLOY_TOKEN` (env var) or `~/.toolrelay/credentials.json` (from `login`)
|
|
316
|
-
3. Matches your local app to a remote app by slug (derived from `app.name`)
|
|
317
|
-
4. Creates the app if it doesn't exist, or updates it if the config changed
|
|
318
|
-
5. Syncs tools — creates new ones, updates changed ones, and optionally prunes removed ones
|
|
319
|
-
6. Auto-publishes the app if it hasn't been published yet
|
|
199
|
+
What you get after publishing:
|
|
320
200
|
|
|
321
|
-
**
|
|
322
|
-
|
|
323
|
-
-
|
|
324
|
-
-
|
|
325
|
-
-
|
|
326
|
-
|
|
327
|
-
**Tier limits** are enforced by the API. If your plan doesn't support the number of apps or tools you're deploying, the command will fail with a clear error message.
|
|
328
|
-
|
|
329
|
-
#### Pre-publish checks
|
|
330
|
-
|
|
331
|
-
Before making any API calls, `publish` validates your config to catch common mistakes:
|
|
332
|
-
|
|
333
|
-
| Check | Severity | Description |
|
|
334
|
-
|---|---|---|
|
|
335
|
-
| Localhost / private IP | Error | `base_url` cannot point to `localhost`, `127.0.0.1`, `192.168.*`, etc. |
|
|
336
|
-
| HTTP base URL | Warning | Production APIs should use HTTPS |
|
|
337
|
-
| Missing tool descriptions | Warning | AI agents rely on descriptions to pick the right tool |
|
|
338
|
-
| OAuth2 required fields | Error | `authorize_url` and `token_url` must be set (`client_id`/`client_secret` are optional for public client PKCE) |
|
|
339
|
-
| OAuth2 localhost URLs | Error | OAuth URLs must be publicly reachable |
|
|
340
|
-
| Missing static token | Error | `static_token` auth requires a `token` in `auth_config` |
|
|
341
|
-
| Duplicate tool names | Error | Each tool must have a unique name |
|
|
342
|
-
|
|
343
|
-
Errors block the deploy. Warnings are printed but don't prevent publishing.
|
|
344
|
-
|
|
345
|
-
**Authentication:**
|
|
201
|
+
- **Hosted MCP endpoint** at `https://proxy.toolrelay.io/mcp/<your-slug>`
|
|
202
|
+
- **Consumer portal** for API key self-service
|
|
203
|
+
- **Auto-generated docs** — OpenAPI spec, SKILL.md, landing page
|
|
204
|
+
- **Usage analytics** — call volume, latency, error rates per tool
|
|
205
|
+
- **Rate limiting and caching** — per-consumer, per-tier controls
|
|
346
206
|
|
|
347
|
-
|
|
|
207
|
+
| Option | Description |
|
|
348
208
|
|---|---|
|
|
349
|
-
| `
|
|
350
|
-
| `
|
|
209
|
+
| `--dry-run` | Show what would change without making API calls |
|
|
210
|
+
| `--prune` | Delete remote tools no longer in config |
|
|
351
211
|
|
|
352
|
-
|
|
212
|
+
For CI/CD, use `TOOLRELAY_DEPLOY_TOKEN` instead of `toolrelay login`. Generate one at [toolrelay.io/dashboard/settings](https://toolrelay.io/dashboard/settings).
|
|
353
213
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
| Variable | Description |
|
|
357
|
-
|---|---|
|
|
358
|
-
| `TOOLRELAY_DEPLOY_TOKEN` | Deploy token for authentication (takes priority over credentials file) |
|
|
359
|
-
| `TOOLRELAY_API_URL` | Override the API endpoint (default: `https://api.toolrelay.io`) |
|
|
214
|
+
## OAuth2
|
|
360
215
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
When your app uses `auth_type: "oauth2"`, the `serve` command runs a full browser-based OAuth2 authorization code flow locally.
|
|
216
|
+
When `auth_type` is `"oauth2"`, the `serve` command handles the full browser-based authorization code flow locally:
|
|
364
217
|
|
|
365
218
|
```json
|
|
366
219
|
{
|
|
367
|
-
"
|
|
368
|
-
"
|
|
369
|
-
"
|
|
370
|
-
"
|
|
371
|
-
"
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
"client_id": "${OAUTH_CLIENT_ID}",
|
|
375
|
-
"client_secret": "${OAUTH_CLIENT_SECRET}",
|
|
376
|
-
"scopes": "read write"
|
|
377
|
-
}
|
|
378
|
-
},
|
|
379
|
-
"tools": [...]
|
|
220
|
+
"auth_config": {
|
|
221
|
+
"authorize_url": "https://provider.com/oauth/authorize",
|
|
222
|
+
"token_url": "https://provider.com/oauth/token",
|
|
223
|
+
"client_id": "${OAUTH_CLIENT_ID}",
|
|
224
|
+
"client_secret": "${OAUTH_CLIENT_SECRET}",
|
|
225
|
+
"scopes": "read write"
|
|
226
|
+
}
|
|
380
227
|
}
|
|
381
228
|
```
|
|
382
229
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
1. On startup, a temporary HTTP server starts on a random port for the OAuth callback
|
|
386
|
-
2. Your browser opens to the upstream provider's authorize URL (with PKCE by default)
|
|
387
|
-
3. After you authorize, the provider redirects to `http://localhost:<port>/callback`
|
|
388
|
-
4. The CLI exchanges the authorization code for access/refresh tokens
|
|
389
|
-
5. Tokens are stored in memory — every tool call gets `Authorization: Bearer <token>` injected automatically
|
|
390
|
-
6. If the access token expires and a refresh token is available, it's refreshed automatically
|
|
230
|
+
On startup, a browser window opens for authorization. After you approve, tokens are stored in memory and injected into every tool call automatically. Refresh is handled transparently.
|
|
391
231
|
|
|
392
|
-
|
|
232
|
+
`client_id` and `client_secret` are optional — public client PKCE flows work without them.
|
|
393
233
|
|
|
394
|
-
|
|
234
|
+
Register `http://localhost` (any port) as a redirect URI in your OAuth provider.
|
|
395
235
|
|
|
396
|
-
###
|
|
236
|
+
### PKCE
|
|
397
237
|
|
|
398
|
-
PKCE
|
|
399
|
-
|
|
400
|
-
```json
|
|
401
|
-
{
|
|
402
|
-
"app": {
|
|
403
|
-
"name": "Cognito API",
|
|
404
|
-
"base_url": "https://api.example.com",
|
|
405
|
-
"auth_type": "oauth2",
|
|
406
|
-
"auth_config": {
|
|
407
|
-
"authorize_url": "https://mypool.auth.us-east-1.amazoncognito.com/oauth2/authorize",
|
|
408
|
-
"token_url": "https://mypool.auth.us-east-1.amazoncognito.com/oauth2/token",
|
|
409
|
-
"client_id": "${COGNITO_CLIENT_ID}",
|
|
410
|
-
"client_secret": "${COGNITO_CLIENT_SECRET}",
|
|
411
|
-
"scopes": "openid profile",
|
|
412
|
-
"use_pkce": false
|
|
413
|
-
}
|
|
414
|
-
},
|
|
415
|
-
"tools": [...]
|
|
416
|
-
}
|
|
417
|
-
```
|
|
238
|
+
PKCE is enabled by default. Set `"use_pkce": false` for providers that reject it (e.g., AWS Cognito confidential clients):
|
|
418
239
|
|
|
419
|
-
| Provider | PKCE
|
|
240
|
+
| Provider | PKCE | Recommendation |
|
|
420
241
|
|---|---|---|
|
|
421
|
-
| Auth0 | Yes | Leave default
|
|
422
|
-
| Supabase | Yes (default since 2024) | Leave default |
|
|
423
|
-
| Okta | Yes | Leave default |
|
|
424
|
-
| Google | Yes | Leave default |
|
|
242
|
+
| Auth0, Okta, Google, Supabase | Yes | Leave default |
|
|
425
243
|
| AWS Cognito (public client) | Yes | Leave default |
|
|
426
|
-
| AWS Cognito (confidential client) | May reject
|
|
427
|
-
| Custom OAuth servers | Varies | Try default first; set `false` if you get errors about unknown parameters |
|
|
428
|
-
|
|
429
|
-
## Local to production
|
|
244
|
+
| AWS Cognito (confidential client) | May reject | Set `"use_pkce": false` |
|
|
430
245
|
|
|
431
|
-
|
|
246
|
+
## Workflow
|
|
432
247
|
|
|
433
248
|
```
|
|
434
|
-
1. toolrelay init → Generate toolrelay.json
|
|
435
|
-
2. toolrelay serve → Test locally with Claude Desktop
|
|
249
|
+
1. npx @toolrelay/cli init → Generate toolrelay.json
|
|
250
|
+
2. npx @toolrelay/cli serve → Test locally with Claude Desktop
|
|
436
251
|
3. (iterate on config)
|
|
437
|
-
4. toolrelay
|
|
438
|
-
5. toolrelay publish → Deploy to production
|
|
252
|
+
4. npx @toolrelay/cli publish → Deploy to production
|
|
439
253
|
```
|
|
440
254
|
|
|
441
|
-
Once published, your app gets:
|
|
442
|
-
|
|
443
|
-
- **Hosted MCP endpoint** (`/mcp/<slug>`) — any AI agent can connect
|
|
444
|
-
- **Consumer portal** (`/portal/<slug>`) — API key self-service for developers
|
|
445
|
-
- **Auto-generated docs** — OpenAPI spec, SKILL.md, landing page
|
|
446
|
-
- **Usage analytics** — call volume, latency, error rates per tool
|
|
447
|
-
- **Rate limiting and caching** — per-consumer, per-tier controls
|
|
448
|
-
|
|
449
|
-
Sign up at [toolrelay.io](https://toolrelay.io) to get started.
|
|
450
|
-
|
|
451
255
|
### File locations
|
|
452
256
|
|
|
453
257
|
| Path | Purpose |
|
|
454
258
|
|---|---|
|
|
455
|
-
| `toolrelay.json` |
|
|
456
|
-
| `.toolrelay/sessions/` | Local
|
|
457
|
-
| `~/.toolrelay/credentials.json` | Login credentials (
|
|
259
|
+
| `toolrelay.json` | App + tool config (committed to git) |
|
|
260
|
+
| `.toolrelay/sessions/` | Local session logs (auto-gitignored) |
|
|
261
|
+
| `~/.toolrelay/credentials.json` | Login credentials (mode `0600`) |
|
|
458
262
|
|
|
459
263
|
## License
|
|
460
264
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toolrelay/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI for ToolRelay — validate configs, serve local MCP servers, and deploy to production with audit traces",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
-
"README.md"
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
13
14
|
],
|
|
14
15
|
"keywords": [
|
|
15
16
|
"toolrelay",
|