Xyte MCP server

An MCP server that gives an AI assistant typed, validated access to the Xyte platform API over a local stdio transport.

The Xyte MCP server gives an AI assistant typed, validated access to the Xyte platform API. It runs as a subprocess on the developer's own machine, launched by the assistant's MCP host, and talks to the Hub Server directly over HTTPS. There is no hosted component and no third party in the request path.

MCP (Model Context Protocol) is the standard by which an AI host discovers and invokes external tools. This server implements it over a stdio transport.

The server presents the API as three tools rather than 77 hand-written ones. The assistant discovers the right endpoint, reads its exact contract, and calls it with arguments validated against that contract before a request is sent. Arguments are checked against the endpoint spec, so a wrong parameter name produces a precise error rather than an opaque HTTP 4xx.

The package is published as @xyteai/mcp. Source, issues, and releases are at github.com/xyte-io/xyte-mcp.

Requirements

  • Node.js 22 or newer.
  • An AI tool that supports MCP.
  • A Partner Key.

Install and configure

Register the server with your MCP host. In Claude Code:

claude mcp add xyte --scope user -e XYTE_PARTNER_API_KEY=<your-partner-key> -- npx -y @xyteai/mcp

Hosts configured by file expect an equivalent entry:

{
  "mcpServers": {
    "xyte": {
      "command": "npx",
      "args": ["-y", "@xyteai/mcp"],
      "env": {
        "XYTE_PARTNER_API_KEY": "<your-partner-key>"
      }
    }
  }
}

The server reads its configuration from the environment.

VariablePurpose
XYTE_PARTNER_API_KEYPartner Key. Authorizes the 14 partner-scoped endpoints.
XYTE_ORG_API_KEYOrganization API key. Authorizes the 63 organization-scoped endpoints.
XYTE_MCP_READ_ONLYSet to 1 to refuse every mutating endpoint. Writes are permitted by default.
XYTE_HUB_URLOverrides the Hub Server base URL. Defaults to https://hub.xyte.io.
XYTE_ENTRY_URLOverrides the entry base URL. Defaults to https://entry.xyte.io.
XYTE_MCP_TIMEOUT_MSPer-request timeout in milliseconds. Defaults to 15000.

At least one key is required. The server exits at startup with No Xyte API key configured when neither is set.

The last three variables are escape hatches for non-production hubs. The defaults are correct for normal use.

📘

Two setup details worth confirming

Register the server at user scope. Without --scope user, the server is visible only in the directory it was added from.

MCP servers load at session start. An existing session will not see the server until the host is restarted or a new session is opened. Confirm registration with claude mcp list, which reports ✔ Connected.

Credential routing

Both keys may be set at the same time. Every endpoint in the catalog declares an auth scope, and the server selects the matching credential per call. A call to a partner-scoped endpoint uses the Partner Key; a call to an organization-scoped endpoint uses the organization key. An endpoint whose scope has no configured key is rejected before a request is built.

The three tools

The intended sequence is discovery, then contract, then call: xyte_endpoints_listxyte_endpoint_describexyte_api_call.

Endpoint keys are stable identifiers of the form namespace.group.action, such as partner.devices.getDevices. The server rejects an unknown key and returns near-miss suggestions rather than guessing.

xyte_endpoints_list

Discovers endpoints. All arguments are optional and combine as a conjunction; omitting them all returns the full catalog.

{
  "namespace": "partner",
  "group": "devices",
  "readOnly": true
}
FieldTypeDescription
namespace"organization" | "partner"Restricts results to one credential scope.
groupstringRestricts results to one resource group, such as devices or tickets.
method"GET" | "POST" | "PUT" | "PATCH" | "DELETE"Restricts results to one HTTP method.
searchstringCase-insensitive substring match over key, title, and path.
readOnlybooleanWhen true, lists only non-mutating endpoints.

The response carries compact rows plus the unfiltered catalog size, so the caller can tell a narrow filter from an empty catalog.

{
  "count": 7,
  "totalAvailable": 77,
  "endpoints": [
    {
      "key": "partner.devices.getDevices",
      "method": "GET",
      "pathTemplate": "/core/v1/partner/devices",
      "title": "Get Devices",
      "mutating": false
    }
  ]
}

xyte_endpoint_describe

Returns the full contract for one endpoint.

{
  "key": "partner.devices.getTelemetries"
}
FieldTypeDescription
keystringEndpoint key, as returned by xyte_endpoints_list. Required.

The response states the resolved URL, the credential the call will use, and which arguments xyte_api_call will accept.

{
  "key": "partner.devices.getTelemetries",
  "title": "Get Telemetries",
  "method": "GET",
  "pathTemplate": "/core/v1/partner/devices/:device_id/telemetries",
  "url": "https://hub.xyte.io/core/v1/partner/devices/:device_id/telemetries",
  "authScope": "partner",
  "pathParams": ["device_id"],
  "queryParams": [],
  "bodyType": "none",
  "mutating": false,
  "requiresConfirm": false
}

xyte_api_call

Invokes an endpoint.

{
  "key": "partner.tickets.addComment",
  "path": { "ticket_id": "9f3c1e20-...-a71b" },
  "body": { "message": "Replacement unit shipped." }
}
FieldTypeDescription
keystringEndpoint key. Required.
pathobjectPath parameter values. All parameters listed by xyte_endpoint_describe are required.
queryobjectQuery parameters. Only names listed by xyte_endpoint_describe are accepted.
bodyanyJSON request body. Applies only to endpoints whose bodyType is json.
confirmstringRequired for DELETE endpoints. Must equal the endpoint key verbatim.

The response reports the HTTP status and elapsed time alongside the payload.

{
  "key": "partner.tickets.addComment",
  "method": "POST",
  "status": 200,
  "durationMs": 214,
  "data": {}
}

Partner endpoint coverage

The catalog holds 77 endpoints. 14 are partner-scoped and authorized by a Partner Key.

Endpoint keyMethodPath
partner.devices.getDevicesGET/core/v1/partner/devices
partner.devices.getDeviceInfoGET/core/v1/partner/devices/:device_id
partner.devices.getCommandsGET/core/v1/partner/devices/:device_id/commands
partner.devices.getConfigurationGET/core/v1/partner/devices/:device_id/config
partner.devices.getTelemetriesGET/core/v1/partner/devices/:device_id/telemetries
partner.devices.getStateHistoryGET/core/v1/partner/devices/:device_id/history
partner.devices.getStateHistoryMultiDevicesGET/core/v1/partner/devices/histories
partner.devices.deleteDeviceDELETE/core/v1/partner/devices/:device_id
partner.organizations.createOrganizationPOST/core/v1/partner/organizations
partner.tickets.getTicketsGET/core/v1/partner/tickets
partner.tickets.getTicketGET/core/v1/partner/tickets/:ticket_id
partner.tickets.updateTicketPUT/core/v1/partner/tickets/:ticket_id
partner.tickets.addCommentPOST/core/v1/partner/tickets/:ticket_id/message
partner.tickets.closeTicketPOST/core/v1/partner/tickets/:ticket_id/resolved

These wrap the Partner Core API, which remains the authoritative contract for request and response bodies.

The remaining 63 endpoints are organization-scoped — Devices, Spaces, Incidents, Tickets, Commands, Groups, Users, plus assets, notes, and edges — and require XYTE_ORG_API_KEY. A Partner Key does not authorize them.

The catalog is generated from the Bruno collection that the published API reference is built from, and is committed to the package. It is refreshed as a maintainer step at release time, so a newly added endpoint appears once a new version ships.

Write policy

Reads and writes are both permitted by default.

  • GET, HEAD, POST, PUT, and PATCH are allowed.
  • DELETE additionally requires confirm set to the endpoint key verbatim. This holds whether or not writes are otherwise restricted — an irreversible call is the one that cannot be walked back.
  • XYTE_MCP_READ_ONLY=1 restricts the server to GET and HEAD. Anything else is refused before a request is built, and the refusal tells the model that only an operator can lift the restriction.

The MCP annotations on xyte_api_callreadOnlyHint and destructiveHint — follow the live configuration, so a host can prompt for what the server will actually do.

The threat model is worth stating plainly. The caller is a model, and the read tools return content a third party can influence: Device names, Ticket bodies, notes. Nothing prevents a model from treating text it has just read as an instruction. The server tells the model outright that fleet content is untrusted and that mutating intent must come from the operator, but that is a mitigation rather than a guarantee. Run read-only when an agent works unattended, or through content you do not control:

claude mcp add xyte-ro --scope user -e XYTE_PARTNER_API_KEY=<your-partner-key> -e XYTE_MCP_READ_ONLY=1 -- npx -y @xyteai/mcp

Both registrations can coexist — a read-only server for exploration and a writable one for sessions where changes should land.

API keys are never echoed back. Output is filtered by field name and by literal secret value.

📘

Upgrading from 0.1.x

Version 0.1.x was read-only by default and used XYTE_MCP_ALLOW_WRITES=1 to permit writes. That variable is still honoured and keeps its original meaning whenever it is present, so a server pinned with XYTE_MCP_ALLOW_WRITES=0 stays shut across the upgrade. Where the two disagree, the more restrictive one wins. New configurations should use XYTE_MCP_READ_ONLY.

The Device API is not exposed

The catalog covers operator-scoped endpoints only. The Device API is deliberately excluded.

The reason is authentication, not coverage. A Device API call authenticates as a Device, using the device access token issued at registration — see Device authentication. The MCP server authenticates as an operator, holding a Partner Key or an organization key. A Device credential is not an operator credential, and the server has no mechanism to hold one per Device.

Register, send Telemetry, and report state through the Device API directly from the Device or its agent. The node-js Agent Example Project is a working reference.

Related