# Edgevana Compute Agent

> Hourly GPU instances for agents over MCP and A2A. 13 tools. Every launch shows a preview and needs the user’s approval before anything is charged.

Agents can launch hourly GPU offers only. Bare-metal and CPU servers can be searched and priced, not launched.

## Files for agents

- [Agent Card](https://edgevana.com/.well-known/agent-card.json): A2A Agent Card, JSON
- [agents.md](https://edgevana.com/agents.md): This reference as Markdown
- [llms.txt](https://edgevana.com/llms.txt): Index of the files for language models
- [API reference](https://edgevana.com/docs/api): The developer docs, for people
- [API keys](https://nodes.edgevana.com/profile/api-keys): Create an edgv_ key (sign-in)

## Endpoints

| interface | URL | protocol |
|--|--|--|
| Agent Card | https://edgevana.com/.well-known/agent-card.json | A2A Agent Card, JSON, no auth |
| MCP | https://mcp.edgevana.com/mcp | MCP 2025-03-26 to 2025-11-25, Streamable HTTP |
| A2A JSON-RPC | https://mcp.edgevana.com/a2a/jsonrpc | A2A v0.3, JSON-RPC 2.0 (preferred transport) |
| A2A REST | https://mcp.edgevana.com/a2a/rest | A2A v0.3, HTTP+JSON |

- **Auth:** X-API-Key: edgv_... or Authorization: Bearer edgv_...
- **API keys:** https://nodes.edgevana.com/profile/api-keys (keys start with edgv_)
- **Transport:** Streamable HTTP
- **Versions:** MCP 2025-03-26 to 2025-11-25, A2A v0.3
- **Agent Card:** Edgevana Edge AI Infrastructure, version 1.13.0, protocol 0.3.0

## Approval before anything is charged

launch_instance, request_reserved_instance and terminate_instance run in two calls. The first call, without confirm, returns a preview and changes nothing. Show the preview to the user. Only after the user approves, send the same arguments again with confirm: true.

For launch_instance the preview returns the hourly price and an approval object, plus the deposit due today when the quote succeeds. The confirm call sends the approval object unchanged and a unique idempotency_key. If the offer was just taken, the preview may offer an equivalent one (same GPU, possibly another site) in substituted: show substituted.note, and confirm with substituted.offer_id and substituted.site. If the approved offer is taken by the time you confirm, an equivalent offer at the same site may be launched instead, never above the approved hourly price or deposit; a higher price is refused. The approved deposit is the ceiling. If approval.deposit_due_cents is null, run the preview again: a confirm without a deposit quote is refused.

request_reserved_instance sends a request to Edgevana sales: nothing is ordered or charged. terminate_instance destroys hourly GPU instances immediately, and that cannot be undone.

## Tools (13)

These are the MCP names. A2A supports the same actions except request_reserved_instance; there, compare_to_cloud is compare_cloud and get_platform_info is platform_info.

| tool | access | params |
|--|--|--|
| search_servers | Read only | server_type, query, region, site, launchable_only, max_price_hourly, min_vram_gb, min_gpus, min_direct_ports, min_reliability, min_ram_gb, min_cores, sort, detail_sku, offset, limit |
| get_server_specs | Read only | sku |
| get_locations | Read only | country, city |
| check_availability | Read only | site, server_type, sku, limit |
| get_pricing | Read only | server_type, item_id, search, include_locations, limit |
| compare_to_cloud | Read only | workload_type, cloud_provider |
| get_platform_info | Read only | topic |
| launch_instance | Preview, then confirm (spends money) | offer_id (required), site (required), ssh_key, custom_name, disk_gb, ports, onstart, payment_source_id, coupon_ids, confirm, approved (required when confirm: true), idempotency_key (required when confirm: true) |
| request_reserved_instance | Preview, then confirm (no charge) | offer_id (required), site (required), email (required), first_name (required), last_name, quantity, term_months, notes, confirm |
| list_instances | Read only | limit, offset |
| get_instance | Read only | instance_id (required) |
| update_instance | Changes settings, no charge | instance_id (required), ssh_key, cluster, deployment_type, custom_name |
| terminate_instance | Preview, then confirm (destructive) | instance_id (required), when, cancel_reason, confirm |

### search_servers

Search GPU and bare-metal offers. Filter by price, VRAM, GPU count and site. GPU offer summaries include launch_status, host_disk_gb and disk_options (the root disk sizes you can choose at launch).

- **Access:** Read only
- **Group:** Discovery and pricing

| parameter | type | required | default |
|--|--|--|--|
| server_type | gpu \| non_gpu \| all | no | all |
| query | string | no |  |
| region | string | no |  |
| site | string | no |  |
| launchable_only | boolean | no |  |
| max_price_hourly | number | no |  |
| min_vram_gb | integer | no |  |
| min_gpus | integer | no |  |
| min_direct_ports | integer (host.direct_port_count; SSH takes one) | no |  |
| min_reliability | number (0 to 1, host.reliability) | no |  |
| min_ram_gb | number | no |  |
| min_cores | number | no |  |
| sort | price \| price_desc \| vram \| vram_desc | no |  |
| detail_sku | string | no |  |
| offset | integer | no | 0 |
| limit | integer (max 50) | no | 20 |

```json
{
  "name": "search_servers",
  "arguments": {
    "server_type": "gpu",
    "launchable_only": true,
    "max_price_hourly": 3,
    "min_vram_gb": 80,
    "limit": 5
  }
}
```

### get_server_specs

Hardware specs for server SKUs. Give a sku for full details.

- **Access:** Read only
- **Group:** Discovery and pricing

| parameter | type | required | default |
|--|--|--|--|
| sku | string | no |  |

```json
{
  "name": "get_server_specs",
  "arguments": {
    "sku": "<sku from search_servers>"
  }
}
```

### get_locations

Edgevana data center locations, filtered by country or city.

- **Access:** Read only
- **Group:** Discovery and pricing

| parameter | type | required | default |
|--|--|--|--|
| country | string | no |  |
| city | string | no |  |

```json
{
  "name": "get_locations",
  "arguments": {
    "country": "United States"
  }
}
```

### check_availability

Live stock for GPU and bare-metal servers by site, type or SKU.

- **Access:** Read only
- **Group:** Discovery and pricing

| parameter | type | required | default |
|--|--|--|--|
| site | string | no |  |
| server_type | gpu \| non_gpu \| all | no | all |
| sku | string | no |  |
| limit | number | no | 30 |

```json
{
  "name": "check_availability",
  "arguments": {
    "server_type": "gpu",
    "site": "<site>",
    "limit": 10
  }
}
```

### get_pricing

Prices in USD for GPU and bare-metal servers, with available count. Bare-metal results include estimated_monthly; GPU results include offer_kind and launch_status.

- **Access:** Read only
- **Group:** Discovery and pricing

| parameter | type | required | default |
|--|--|--|--|
| server_type | gpu \| non_gpu \| all | no | all |
| item_id | string | no |  |
| search | string | no |  |
| include_locations | boolean | no | false |
| limit | number (max 50) | no | 20 |

```json
{
  "name": "get_pricing",
  "arguments": {
    "server_type": "gpu",
    "search": "H100",
    "include_locations": true
  }
}
```

### compare_to_cloud

Compare Edgevana to AWS, GCP and Azure for a workload type.

- **Access:** Read only
- **Group:** Discovery and pricing

| parameter | type | required | default |
|--|--|--|--|
| workload_type | ai_inference \| ai_training \| real_time_voice \| gaming \| streaming \| hft \| general | no | general |
| cloud_provider | aws \| gcp \| azure \| all | no | all |

```json
{
  "name": "compare_to_cloud",
  "arguments": {
    "workload_type": "ai_training",
    "cloud_provider": "aws"
  }
}
```

### get_platform_info

Edgevana platform, capabilities and architecture.

- **Access:** Read only
- **Group:** Discovery and pricing

| parameter | type | required | default |
|--|--|--|--|
| topic | overview \| compute \| edgeview \| edgelink \| pricing \| security \| ai_infrastructure | no | overview |

```json
{
  "name": "get_platform_info",
  "arguments": {
    "topic": "compute"
  }
}
```

### launch_instance

Launch an hourly GPU instance from an offer. The first call returns a preview with the price, including any chosen disk, and the deposit when the quote succeeds. It also lists any requested ports. A second call with confirm: true launches. With ports, the instance is ready only after every port is mapped. A host that cannot map them fails with ERROR_PORTS_UNMAPPED after the port checks time out, and the service tries to destroy the instance. ERROR_PORTS_UNMAPPED does not confirm destruction. Check the instance (connection.status) before another launch.

- **Access:** Preview, then confirm (spends money)
- **Group:** Instance lifecycle

| parameter | type | required | default |
|--|--|--|--|
| offer_id | string | required |  |
| site | string (one of the offer’s hourly_sites) | required |  |
| ssh_key | string | no |  |
| custom_name | string | no |  |
| disk_gb | integer (within the offer’s disk_options; set at launch only) | no |  |
| ports | string[] (e.g. "7946/tcp"; up to 16, 1-65535, not 22) | no |  |
| onstart | string (shell script, at most 4096 bytes; runs at every start; no secrets) | no |  |
| payment_source_id | string | no |  |
| coupon_ids | string[] | no |  |
| confirm | boolean | no | false |
| approved | { hourly_cents: integer > 0, deposit_due_cents: integer >= 0 } | required when confirm: true |  |
| idempotency_key | string (8-128 characters) | required when confirm: true |  |

```json
{
  "name": "launch_instance",
  "arguments": {
    "offer_id": "<offer_id>",
    "site": "<one of its hourly_sites>",
    "ssh_key": "ssh-ed25519 AAAA…"
  }
}
```

### request_reserved_instance

Send a request for a reserved GPU offer to Edgevana sales. Nothing is ordered or charged.

- **Access:** Preview, then confirm (no charge)
- **Group:** Instance lifecycle

| parameter | type | required | default |
|--|--|--|--|
| offer_id | string | required |  |
| site | string | required |  |
| email | string | required |  |
| first_name | string | required |  |
| last_name | string | no |  |
| quantity | integer | no |  |
| term_months | integer | no |  |
| notes | string | no |  |
| confirm | boolean | no | false |

```json
{
  "name": "request_reserved_instance",
  "arguments": {
    "offer_id": "<offer_id>",
    "site": "<site>",
    "email": "you@company.com",
    "first_name": "Ada",
    "quantity": 8,
    "term_months": 12
  }
}
```

### list_instances

List the instances on your account, each with its connection object.

- **Access:** Read only
- **Group:** Instance lifecycle

| parameter | type | required | default |
|--|--|--|--|
| limit | number | no | 20 |
| offset | string | no |  |

```json
{
  "name": "list_instances",
  "arguments": {
    "limit": 20
  }
}
```

### get_instance

Status, specs, site and billing for one instance. connection holds status (ready, provisioning, failed, destroying or destroyed), public_ip (string or null), ssh (null or { host, port, user }) and ports [{ internal, proto, external, ip }].

- **Access:** Read only
- **Group:** Instance lifecycle

| parameter | type | required | default |
|--|--|--|--|
| instance_id | string | required |  |

```json
{
  "name": "get_instance",
  "arguments": {
    "instance_id": "<instance_id>"
  }
}
```

### update_instance

Change an instance’s SSH key, cluster, deployment type or name.

- **Access:** Changes settings, no charge
- **Group:** Instance lifecycle

| parameter | type | required | default |
|--|--|--|--|
| instance_id | string | required |  |
| ssh_key | string | no |  |
| cluster | string | no |  |
| deployment_type | string | no |  |
| custom_name | string | no |  |

```json
{
  "name": "update_instance",
  "arguments": {
    "instance_id": "<instance_id>",
    "custom_name": "training-01"
  }
}
```

### terminate_instance

Destroy an instance. Preview first, then confirm. Hourly GPU instances are always destroyed immediately (deprovisioned now; cannot be undone). Other instances can only be destroyed with when: Immediately for now; EndOfTerm is refused.

- **Access:** Preview, then confirm (destructive)
- **Group:** Instance lifecycle

| parameter | type | required | default |
|--|--|--|--|
| instance_id | string | required |  |
| when | Immediately (EndOfTerm is refused for now) | no |  |
| cancel_reason | string | no |  |
| confirm | boolean | no | false |

```json
{
  "name": "terminate_instance",
  "arguments": {
    "instance_id": "<instance_id>"
  }
}
```

## Lifecycle (5 steps)

Values in angle brackets come from earlier calls; the approved object is an example, send the one from your preview.

### 1. Search

Pick an offer with launch_status "available". Note its offer_id and one of its hourly_sites.

```json
{
  "name": "search_servers",
  "arguments": {
    "server_type": "gpu",
    "query": "H100",
    "launchable_only": true,
    "sort": "price"
  }
}
```

### 2. Preview launch

Returns the hourly price and an approval object, plus the deposit due today when the quote succeeds. Nothing is created. Show it to the user. If the deposit could not be quoted (approval.deposit_due_cents is null), run the preview again: a confirm without a deposit quote is refused.

```json
{
  "name": "launch_instance",
  "arguments": {
    "offer_id": "<offer_id>",
    "site": "<one of the offer’s hourly_sites>",
    "ssh_key": "ssh-ed25519 AAAA..."
  }
}
```

### 3. Confirm launch

After the user approves: same args (or the substituted offer_id and site), confirm: true, the approval object unchanged, and a unique idempotency_key. If the approved offer is taken by the time you confirm, an equivalent offer at the same site may be launched instead, never above the approved hourly price or deposit; a higher price is refused.

```json
{
  "name": "launch_instance",
  "arguments": {
    "offer_id": "<offer_id>",
    "site": "<one of the offer’s hourly_sites>",
    "ssh_key": "ssh-ed25519 AAAA...",
    "confirm": true,
    "approved": {
      "hourly_cents": 89,
      "deposit_due_cents": 2500
    },
    "idempotency_key": "00000000-0000-4000-8000-000000000001"
  }
}
```

### 4. Monitor

Check connection.status: ready, provisioning, failed, destroying or destroyed. destroying means the instance is cancelled but not yet confirmed gone. When ready, connection.ports lists mapped ports. connection.public_ip and connection.ssh can be null even when ready. Check connection.ssh before you read its host, port and user. When status is not ready, public_ip and ssh are null, and ports is [].

```json
{
  "name": "get_instance",
  "arguments": {
    "instance_id": "<instance_id>"
  }
}
```

### 5. Destroy

Call without confirm for a preview, then again with confirm: true. Hourly GPU instances are destroyed immediately; EndOfTerm is refused for them.

```json
{
  "name": "terminate_instance",
  "arguments": {
    "instance_id": "<instance_id>",
    "confirm": true
  }
}
```

A completed retry with the same idempotency_key and arguments returns the first result while the server keeps its record. That record resets when the server restarts. After an interrupted or failed launch, check list_instances before you retry.

## Offers

GPU offer summaries returned by search_servers include launch_status and launch_note:

- **available:** launch with launch_instance
- **coming_soon:** not open for instant launch yet
- **request:** reserved offer; send a no-charge request with request_reserved_instance over MCP
- **sold_out:** no stock right now

## Billing and deposits

Hourly GPU takes a non-refundable deposit when the instance is created. The amount depends on the hourly price, and the launch preview shows it (deposit.due_today). Credit already on the account counts towards it, and the deposit comes back as account credit against usage. Without an ssh_key the instance is billed but not provisioned.

## Inbound ports and the connection object

launch_instance takes optional ports, such as ["7946/tcp", "7946/udp"]: up to 16, from 1 to 65535, not 22 (SSH is always mapped). The preview lists the normalized ports. The instance is ready only after every port is mapped.

A host that cannot map them fails with ERROR_PORTS_UNMAPPED after the port checks time out, and the service tries to destroy the instance. ERROR_PORTS_UNMAPPED does not confirm destruction. Check the instance (connection.status) before another launch. No hourly usage is billed before the instance is ready; the deposit stays as account credit.

Only hosts with direct ports can map ports, and SSH takes one of them: GPU offers report host.direct_port_count and host.reliability, so for N ports search with min_direct_ports N + 1 (and min_reliability, for example 0.99). The launch is refused before any spend when the host is known to have too few direct ports.

get_instance and list_instances return a connection object for each instance:

```json
{
  "status": "ready",
  "public_ip": "203.0.113.7",
  "ssh": {
    "host": "203.0.113.7",
    "port": 40022,
    "user": "root"
  },
  "ports": [
    {
      "internal": 7946,
      "proto": "tcp",
      "external": 41234,
      "ip": "203.0.113.7"
    }
  ],
  "destroyed_at": null
}
```

destroyed_at is the confirmed destroy time after a cancel. It is null before that, after a failed launch is cleaned up, or when the time is unknown, and servers before 1.10.0 omit it. subscription.cancelled_at is the billing term start, not the destroy time.

## Startup script (onstart)

launch_instance takes an optional onstart: a shell script of at most 4096 bytes (UTF-8, no NUL) that runs as root when the instance starts, and again on every restart, so make it safe to run twice. Instances run ubuntu:22.04 with the NVIDIA driver, and no CUDA libraries or Python; install what the workload needs in the script, and start long-running services in the background (setsid or nohup). The script is stored in plain text with the order: never put secrets in it. The preview and instance responses show only its bytes and sha256. The instance is ready when SSH accepts connections, not when the script finishes, so have the script delete a marker file first, write it as its last step, and check for it.

## Limits

- **Results per call:** search_servers and get_pricing: limit up to 50, default 20. check_availability: default 30.
- **Inbound ports:** Up to 16 per instance, from 1 to 65535, not 22.
- **Startup script:** onstart: at most 4096 bytes, UTF-8, no NUL.
- **Root disk:** disk_gb within the offer’s disk_options, set at launch only; it cannot be resized later.
- **Idempotency key:** 8 to 128 characters, unique per launch.
- **Deposit:** Set by the hourly price; the launch preview shows the amount.
- **Request rate limits:** Not published.

## Connect

### Claude Code

Run in your terminal, then claude mcp list to confirm.

```bash
claude mcp add --transport http edgevana https://mcp.edgevana.com/mcp --header "X-API-Key: YOUR_EDGV_KEY"
```

### Codex

Add to ~/.codex/config.toml, and export EDGEVANA_API_KEY=edgv_… before starting Codex.

```toml
[mcp_servers.edgevana]
url = "https://mcp.edgevana.com/mcp"
env_http_headers = { "X-API-Key" = "EDGEVANA_API_KEY" }
```

### Cursor

Paste into Settings → MCP, or ~/.cursor/mcp.json.

```json
{
  "mcpServers": {
    "edgevana": {
      "url": "https://mcp.edgevana.com/mcp",
      "headers": {
        "X-API-Key": "<your-edgv_...-key>"
      }
    }
  }
}
```

### Claude Desktop

Paste into Edit config, or claude_desktop_config.json.

```json
{
  "mcpServers": {
    "edgevana": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote@0.15.2",
        "https://mcp.edgevana.com/mcp",
        "--header",
        "X-API-Key:${EDGEVANA_API_KEY}"
      ],
      "env": {
        "EDGEVANA_API_KEY": "<your-edgv_...-key>"
      }
    }
  }
}
```

### A2A

Send a task message to the JSON-RPC endpoint.

```bash
curl -X POST https://mcp.edgevana.com/a2a/jsonrpc \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_EDGV_API_KEY" \
  -d '{"jsonrpc":"2.0","method":"message/send","id":1,"params":{"message":{"kind":"message","messageId":"msg-1","role":"user","parts":[{"kind":"text","text":"Find GPU servers"}]}}}'
```

### Agent Card

Read the card, no key needed.

```bash
curl https://edgevana.com/.well-known/agent-card.json
```

A command-line tool is coming soon. Until then, connect over MCP or A2A as above.
