diff --git a/api-reference/openapi.json b/api-reference/openapi.json index 394378b..aecac64 100644 --- a/api-reference/openapi.json +++ b/api-reference/openapi.json @@ -52,6 +52,10 @@ { "name": "Health", "description": "Service health checks." + }, + { + "name": "Provider", + "description": "Provider-scoped management endpoints for managing node plans, methods, and nodes. Authenticated with a management token whose customer is linked to a provider." } ], "paths": { @@ -877,6 +881,405 @@ } } } + }, + "/provider/plans": { + "get": { + "operationId": "listProviderPlans", + "tags": [ + "Provider" + ], + "summary": "List my plans", + "description": "Returns the authenticated provider's pricing plans, or an empty array when the provider has none. Plan creation and pricing edits remain admin-managed.", + "security": [ + { + "MgmtKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "List of plans or empty array", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProviderPlan" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Provider not resolved - token's customer is not linked to a provider" + }, + "500": { + "description": "Internal server error" + } + }, + "servers": [ + { + "url": "https://api.routeme.sh" + } + ] + } + }, + "/provider/plans/{planId}/methods": { + "get": { + "operationId": "getProviderPlanMethods", + "tags": [ + "Provider" + ], + "summary": "List a plan's RPC methods", + "description": "Returns the RPC method rows configured for one of the authenticated provider's plans. Returns an empty array when the plan has no methods. Plans not owned by the caller's provider are reported as 404.", + "security": [ + { + "MgmtKeyAuth": [] + } + ], + "parameters": [ + { + "name": "planId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + }, + "description": "Numeric plan ID." + } + ], + "responses": { + "200": { + "description": "List of method rows or empty array", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProviderPlanMethod" + } + } + } + } + }, + "400": { + "description": "Invalid plan id" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Provider not resolved" + }, + "404": { + "description": "Plan not found (or not owned by the caller's provider)" + }, + "500": { + "description": "Internal server error" + } + }, + "servers": [ + { + "url": "https://api.routeme.sh" + } + ] + }, + "post": { + "operationId": "insertProviderPlanMethods", + "tags": [ + "Provider" + ], + "summary": "Add methods to a plan", + "description": "Appends RPC method rows (1-500) to one of the authenticated provider's plans. The plan must be owned by the caller's provider (otherwise 404).", + "security": [ + { + "MgmtKeyAuth": [] + } + ], + "parameters": [ + { + "name": "planId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + }, + "description": "Numeric plan ID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderPlanMethodsInput" + } + } + } + }, + "responses": { + "201": { + "description": "Plan methods inserted. Returns a plain-text success message." + }, + "400": { + "description": "Invalid plan id, request body, or validation failure" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Provider not resolved" + }, + "404": { + "description": "Plan not found (or not owned by the caller's provider)" + }, + "500": { + "description": "Internal server error" + } + }, + "servers": [ + { + "url": "https://api.routeme.sh" + } + ] + } + }, + "/provider/nodes/{nodeId}/status": { + "get": { + "operationId": "getProviderNodeStatus", + "tags": [ + "Provider" + ], + "summary": "Get a node's sync status", + "description": "Returns the synchronization status of one of the authenticated provider's nodes. Nodes not owned by the caller's provider are reported as 404.", + "security": [ + { + "MgmtKeyAuth": [] + } + ], + "parameters": [ + { + "name": "nodeId", + "in": "path", + "required": true, + "schema": { + "type": "integer" + }, + "description": "Numeric node ID." + } + ], + "responses": { + "200": { + "description": "Node sync status", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderNodeStatus" + } + } + } + }, + "400": { + "description": "Invalid node id" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Provider not resolved" + }, + "404": { + "description": "Node not found (or not owned by the caller's provider)" + }, + "500": { + "description": "Internal server error" + } + }, + "servers": [ + { + "url": "https://api.routeme.sh" + } + ] + } + }, + "/provider/nodes": { + "put": { + "operationId": "upsertProviderNode", + "tags": [ + "Provider" + ], + "summary": "Create or update an HTTP node", + "description": "Creates or updates an HTTP RPC node for one of the authenticated provider's plans. The URL must be a public http(s) endpoint (loopback, private, and link-local hosts are rejected). The node is screened before persistence; the request fails with 400 when a mandatory screening test does not pass. Returns the upserted node set to a healthy status.", + "security": [ + { + "MgmtKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderNodeInput" + } + } + } + }, + "responses": { + "200": { + "description": "Node upserted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderUpsertNodeResponse" + } + } + } + }, + "400": { + "description": "Invalid request body, non-public URL, or failed mandatory screening" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Provider not resolved" + }, + "404": { + "description": "Plan or node not found (or not owned by the caller's provider)" + }, + "409": { + "description": "Node URL already exists in a different plan" + }, + "500": { + "description": "Internal server error" + } + }, + "servers": [ + { + "url": "https://api.routeme.sh" + } + ] + } + }, + "/provider/nodes/ws": { + "put": { + "operationId": "upsertProviderWSNode", + "tags": [ + "Provider" + ], + "summary": "Create or update a WebSocket node", + "description": "Creates or updates a WebSocket RPC node for one of the authenticated provider's plans. The URL must be a public wss:// endpoint (loopback, private, and link-local hosts are rejected) that accepts eth_subscribe(\"newHeads\"); the server dials the node to verify before persisting. Returns the upserted node set to a healthy status.", + "security": [ + { + "MgmtKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderWSNodeInput" + } + } + } + }, + "responses": { + "200": { + "description": "WebSocket node upserted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderUpsertWSNodeResponse" + } + } + } + }, + "400": { + "description": "Invalid request body, non-public URL, or failed newHeads acceptance screening" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Provider not resolved" + }, + "404": { + "description": "Plan not found (or not owned by the caller's provider)" + }, + "409": { + "description": "URL already registered to another plan" + }, + "500": { + "description": "Internal server error" + } + }, + "servers": [ + { + "url": "https://api.routeme.sh" + } + ] + } + }, + "/provider/nodes/status": { + "post": { + "operationId": "setProviderNodeStatus", + "tags": [ + "Provider" + ], + "summary": "Enable, disable, or delete a node", + "description": "Sets the status of one of the authenticated provider's nodes to enable, disable, or delete (hide). Allowed statuses: healthy, disabled by provider, provider-deleted.", + "security": [ + { + "MgmtKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderSetNodeStatusInput" + } + } + } + }, + "responses": { + "200": { + "description": "Node status updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderSetNodeStatusResponse" + } + } + } + }, + "400": { + "description": "Invalid request body, node id, or status" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Provider not resolved" + }, + "404": { + "description": "Node not found (or not owned by the caller's provider)" + }, + "500": { + "description": "Internal server error" + } + }, + "servers": [ + { + "url": "https://api.routeme.sh" + } + ] + } } }, "components": { @@ -1669,6 +2072,297 @@ "id" ], "additionalProperties": false + }, + "ProviderPlan": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Plan ID." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "provider": { + "type": "string", + "description": "Provider identifier." + }, + "provider_id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "quota": { + "type": "number" + }, + "quota_unit": { + "type": "string" + }, + "description": { + "type": "string" + }, + "overage_price": { + "type": "number" + }, + "overage_limit": { + "type": "number" + }, + "rate_limit_req": { + "type": "number" + }, + "rate_limit_req_interval_sec": { + "type": "number" + }, + "rate_limit_cr": { + "type": "number" + }, + "rate_limit_cr_interval_sec": { + "type": "number" + }, + "billing_fixed_day": { + "type": "integer", + "nullable": true + }, + "billing_interval_days": { + "type": "integer", + "nullable": true + }, + "billing_anchored_start_date": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "ProviderPlanMethod": { + "type": "object", + "properties": { + "method": { + "type": "string", + "description": "RPC method name, e.g. eth_blockNumber." + }, + "vm": { + "type": "string", + "description": "Virtual machine type, e.g. evm." + }, + "node_target_type": { + "type": "string", + "description": "Node target type the method routes to." + }, + "cost": { + "type": "integer", + "description": "Credit cost per call (0 = free)." + }, + "rate_limit": { + "type": "integer", + "nullable": true, + "description": "Optional per-call rate limit." + }, + "rate_limit_interval_sec": { + "type": "integer", + "nullable": true, + "description": "Rate limit window in seconds." + }, + "chain_id": { + "type": "string", + "nullable": true, + "description": "Chain this cost applies to; null/omitted for all chains." + } + } + }, + "ProviderPlanMethodsInput": { + "type": "object", + "properties": { + "methods": { + "type": "array", + "minItems": 1, + "maxItems": 500, + "items": { + "$ref": "#/components/schemas/ProviderPlanMethodInput" + } + } + }, + "required": [ + "methods" + ] + }, + "ProviderPlanMethodInput": { + "type": "object", + "properties": { + "method": { + "type": "string" + }, + "vm": { + "type": "string" + }, + "node_target_type": { + "type": "string" + }, + "cost": { + "type": "integer", + "minimum": 0 + }, + "rate_limit": { + "type": "integer", + "minimum": 0 + }, + "rate_limit_interval_sec": { + "type": "integer", + "minimum": 0 + }, + "chain_id": { + "type": "string", + "nullable": true + } + }, + "required": [ + "method", + "vm", + "node_target_type", + "cost" + ] + }, + "ProviderNodeStatus": { + "type": "object", + "properties": { + "node_id": { + "type": "integer" + }, + "in_sync": { + "type": "boolean" + }, + "status": { + "type": "string", + "description": "ok or out_of_sync" + } + } + }, + "ProviderNodeInput": { + "type": "object", + "properties": { + "plan_id": { + "type": "integer", + "description": "Plan ID the node belongs to (must be owned by the provider)." + }, + "url": { + "type": "string", + "description": "Public http(s) endpoint URL of the node." + }, + "vm": { + "type": "string", + "description": "Virtual machine type, e.g. evm." + }, + "rate_limit": { + "type": "number", + "description": "Requests per interval (0 = unlimited)." + }, + "rate_limit_interval_sec": { + "type": "integer", + "description": "Rate limit window in seconds." + }, + "source": { + "type": "string", + "enum": [ + "provider", + "website", + "erpc", + "node_request", + "new_chain_request" + ], + "description": "Node source (default provider)." + } + }, + "required": [ + "plan_id", + "url", + "vm", + "rate_limit", + "rate_limit_interval_sec" + ] + }, + "ProviderUpsertNodeResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "node": { + "type": "object" + }, + "status": { + "type": "string", + "description": "healthy on success." + } + } + }, + "ProviderWSNodeInput": { + "type": "object", + "properties": { + "plan_id": { + "type": "integer", + "description": "Plan ID the WebSocket node belongs to (must be owned by the provider)." + }, + "chain_id": { + "type": "string", + "description": "Chain ID the node serves, e.g. 137." + }, + "url": { + "type": "string", + "description": "Public wss:// endpoint URL; must accept eth_subscribe(\"newHeads\")." + } + }, + "required": [ + "plan_id", + "chain_id", + "url" + ] + }, + "ProviderUpsertWSNodeResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "node_ws": { + "type": "object" + }, + "status": { + "type": "string", + "description": "healthy on success." + } + } + }, + "ProviderSetNodeStatusInput": { + "type": "object", + "properties": { + "node_id": { + "type": "integer" + }, + "status": { + "type": "string", + "description": "healthy (enable), disabled by provider (disable), or provider-deleted (delete)." + } + }, + "required": [ + "node_id", + "status" + ] + }, + "ProviderSetNodeStatusResponse": { + "type": "object", + "properties": { + "node_id": { + "type": "integer" + }, + "status": { + "type": "string" + } + } } }, "headers": { diff --git a/intro/mcp-server.mdx b/intro/mcp-server.mdx index a47f116..48a563e 100644 --- a/intro/mcp-server.mdx +++ b/intro/mcp-server.mdx @@ -52,7 +52,7 @@ Any stdio MCP client uses the same shape: command `npx`, args `-y @routemesh/mcp | Variable | Required | Default | Description | | :-------------------------- | :------- | :----------------------- | :------------------------------------------------------------------------------------------------------------------- | | `ROUTEMESH_API_KEY` | yes | — | Your RouteMesh API key, used in the RPC URL path. | -| `ROUTEMESH_MGMT_TOKEN` | no | — | Customer management token. When set, the server also exposes the [customer tools](#customer-tools). | +| `ROUTEMESH_MGMT_TOKEN` | no | — | Customer management token. When set, the server also exposes the [customer tools](#customer-tools) and, for a provider-linked token, the [provider tools](#provider-tools). | | `ROUTEMESH_API_SERVER_URL` | no | `https://api.routeme.sh` | Base URL for chain discovery and the customer tools. | | `ROUTEMESH_BASE_URL` | no | `https://lb.routeme.sh` | Primary RPC router. | | `ROUTEMESH_BACKUP_BASE_URL` | no | `https://lb2.routeme.sh` | Backup RPC router, used on retry. | @@ -101,6 +101,22 @@ Exposed only when `ROUTEMESH_MGMT_TOKEN` is set. - `granularity` — `day` or `hour` for `time_series` (default `day`). - `limit` — max rows for `top_methods` and `groupBy` (default 20, max 100). +## **Provider tools** + +Exposed when `ROUTEMESH_MGMT_TOKEN` is set and the token's customer is **linked to a provider**. These tools mirror the provider management endpoints in the [API Reference](/api-reference) and authenticate the same way, sending the token in the `X-Api-Key` header. Scoping matches the API endpoints: a token whose customer is not linked to a provider returns 403 (`provider not resolved`), and resources owned by another provider return 404 (existence is not leaked). Call them only with a provider-linked management token. + +| Tool | Description | +| :----------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `provider_list_plans` | List the provider's pricing plans (`GET /provider/plans`). | +| `provider_get_plan_methods` | List the RPC methods configured for a plan (`GET /provider/plans/:planId/methods`). | +| `provider_get_node_status` | Get a node's sync status — `{ node_id, in_sync, status }` (`GET /provider/nodes/:nodeId/status`). | +| `provider_upsert_plan_methods` | Add 1-500 RPC method rows to a plan (`POST /provider/plans/:planId/methods`). | +| `provider_upsert_node` | Create or update an HTTP node on a plan (`PUT /provider/nodes`). URL must be a public http(s) endpoint; the node is screened before persisting. | +| `provider_upsert_ws_node` | Create or update a WebSocket node on a plan (`PUT /provider/nodes/ws`). URL must be a public wss:// endpoint that accepts `eth_subscribe("newHeads")`; the server dials to verify. | +| `provider_set_node_status` | Enable, disable, or delete a node — `healthy`, `disabled by provider`, or `provider-deleted` (`POST /provider/nodes/status`). | + +Node upserts screen endpoints before persisting: URLs must be public `http(s)` or `wss://` (loopback, private, and link-local hosts are rejected up front). A failed mandatory screening returns 400; a URL owned by another provider returns 404; a URL already registered to a different plan of the same provider returns 409. Endpoint-level behavior and error codes are in the [API Reference](/api-reference). + ## **Management tokens** Customer tools authenticate with a customer management token. Create one in the dashboard: @@ -109,7 +125,7 @@ Customer tools authenticate with a customer management token. Create one in the 2. Click **New Token** and give it a label (e.g. `mcp-cursor`). 3. Copy the secret — it is **shown only once**. -Customer management tokens are scoped automatically to your account's management routes (`GET /usage`, `GET /api-keys`, `POST /api-keys`, `PUT /api-keys/:id`) and nothing else. Use a dedicated token for the MCP server so you can revoke it independently if it leaks. +Customer management tokens are scoped to your account's management routes and, for a provider-linked token, the provider routes — and nothing else. Use a dedicated token for the MCP server so you can revoke it independently if it leaks. ## **Example prompts**