bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 16쪽 중 6쪽
he connection between your node and the peer it's directly connected to, which is standard TLS.
#### Layer 2: End-to-End Payload Encryption (Yggdrasil)
Separately, Yggdrasil encrypts all traffic between source and destination using keys derived from both nodes' ed25519 keypairs. This is end-to-end so if *Node A* sends a message to *Node C* and it routes through *Node B*, *Node B* sees only ciphertext it cannot decrypt. 
```
Node A ──[TLS]──► Bootstrap Node ──[TLS]──► Node B
Layer 1 Layer 1
Node A ═══════════[E2E Encrypted]═══════════► Node B
Layer 2
(bootstrap can't read this)
```
Both of these matter because in a mesh network, your traffic may pass through nodes you don't control. *Layer 1* protects the link whereas *Layer 2* protects the payload across the entire path, regardless of how many hops it takes.
{% hint style="info" %}
So even though a different node could 'see' that there is communication happening, it can't figure out what is being communicated. 
{% endhint %}
### Security & Privacy
What routing nodes can and cannot see is important to understanding security and privacy of anything connected to a node, coming from a node, being received *by* a node, etc. 
#### What Nodes Can & Cannot See
* Routing nodes *can* see:
* That your node exists and your IP address (you connected to them directly).
* That two nodes are communicating, including the **\[1]** source and **\[2]** destination public keys.
* When communication happens, how frequently, and the approximate message sizes. 
* Routing nodes *cannot* see:
* Message content. They route encrypted bytes with no ability to decrypt.
* **\[1]** What your application does, **\[2]** what services you expose, or **\[3]** what commands are being sent.
* Anything inside the encrypted payload: application-level metadata like JSON fields, headers, or protocol details.
{% hint style="info" %}
'Routing nodes' also includes *bootstrap peers.* 
{% endhint %}
***
| **Information** | **Sender** | **Receiver** | **Routing Nodes** |
| -------------------------- | ---------- | ------------ | ----------------------- |
| *Message content* | Yes | Yes | No |
| *Application metadata* | Yes | Yes | No |
| *Who is communicating* | Yes | Yes | Yes (public keys) |
| *When and how often* | Yes | Yes | Yes |
| *Approximate message size* | Yes | Yes | Yes |
| *Your IP address* | — | — | Yes (direct peers only) |
***
#### Limitations
Yggdrasil, unlike Tor, does not use onion routing, meaning there is no traffic obfuscation. Although it protects the content of conversations, anyone who controls the routing nodes can observe the communication patterns, specifically who communicates with whom and when, but they cannot see the actual content of this communication. 
Additionally, your Internet Protocol (IP) address is visible to direct connections. This visibility extends to bootstrap peers and any node you directly connect with, making your real IP address accessible to them.
{% hint style="warning" %}
Security considerations are important when using Yggdrasil. While it employs standard cryptographic methods, such as `ed25519`, TLS 1.3, and the Noise protocol, it has not undergone a formal, independent security audit. Thus, additional caution is advised. 
{% endhint %}
In the event of a key compromise, the consequences are total. If this happens, anyone with access to your `private.pem` file can impersonate your node. Due to this risk, you *must* private key with the same vigilance as an SSH key. 
Moreover, Yggdrasil does not include access control mechanisms. Any node possessing your public key can send messages to you, so it is crucial that your application *independently validate message senders* when necessary.
### Data Flow
This section describes the flow of data during sending and receiving messages within an AXL-powered application.
#### Sending a Message
Your application posts data to `localhost:9002/send` with the destination peer's public key. 
The node dials the remote peer over the gVisor TCP stack, writes a length-prefixed message, and returns: there is response from the remote destination.
#### Receiving a Message
When a remote peer sends your node a message, the multiplexer checks it against registered protocol streams (MCP, A2A). If nothing matches, it goes into an in-memory queue which your application then polls.
#### MCP / A2A (Request-Response)
For structured communication, the node:
1. wraps your JSON-RPC body in a transport envelope
2. sends it to the remote peer
3. waits for a response (30-second timeout)
4. unwraps it, then returns it. 
The remote peer must have the corresponding service running.
### Wire Format
All TCP messages between nodes are length-prefixed: a 4-byte big-endian `uint32` length followed by a payload of XYZ number of bytes.
```
┌──────────────┬─────────────────────────────────┐
│ Length (4B) │ Payload (Length bytes) │
│ big-endian │ │
│ uint32 │ │
└──────────────┴─────────────────────────────────┘
```
{% hint style="info" %}
Max message size defaults to 16 MB which is configurable via `max_message_size`.
{% endhint %}
#### Envelope Routing
The multiplexer determines how to handle each inbound message by inspecting its content:
| **Envelope Pattern** | **Routed To** |
| -------------------------------------- | ------------------------------------------ |
| `{"service": "...", "request": {...}}` | MCP Router |
| `{"a2a": true, "request": {...}}` | A2A Server |
| *Anything else* | Message queue (your app reads via polling) |
The first protocol stream whose discriminator matches 'wins' and any unmatched messages go to the queue.
### Internals
Here you can find a breakdown of the AXL repo and some additional information on the dependencies it requires. 
***
#### Project Layout
```
cmd/node/ # Go entrypoint — main.go (wiring), config.go (settings)
api/ # HTTP handlers — send, recv, topology, mcp, a2a
internal/
tcp/listen/ # Inbound TCP: listener, multiplexer, stream interface
tcp/dial/ # Outbound TCP: peer dialing
mcp/ # MCP stream (envelope parsing, forwarding to router)
a2a/ # A2A stream (envelope parsing, forwarding to server)
integrations/ # Python services: MCP router, A2A server
examples/ # Python examples: tensors, gossipsub, convergecast, A2A
```
***
#### gVisor TCP Stack
AXL uses [gVisor's](https://gvisor.dev/) userspace network stack instead of the OS kernel's TCP/IP. 
For this, there is/are:
* **No TUN device:** No virtual network interface are created.
* **No root privileges:** Everything runs in userspace.
* **No system configuration:** No `sysctl` and no routing table changes.
The stack bridges to Yggdrasil's core, providing standard TCP operations (**\[1]** listen, **\[2]** accept, **\[3]** dial) over the encrypted mesh.
* **Inbound:** A TCP listener on `tcp_port` (default 7000) accepts connections from remote nodes. Each connection delivers a length-prefixed message routed by the multiplexer.
* **Outbound:** `DialPeerConnection` converts a 64-char hex public key to a Yggdrasil IPv6 address and dials it through the gVisor stack.
#### The Stream Interface
The multiplexer routes messages using a `Stream` interface:
```go
type Stream interface {
GetID() string
IsAllowed(data []byte, metadata any) bool
Forward(metadata any, fromPeerId string) ([]byte, error)
}
```
* `IsAllowed` inspects raw bytes and returns `true` if this stream handles the message.
* `Forward` processes the message and returns a response.
{% hint style="info" %}
Streams are checked in order, where the first match wins. If there is no match it is sent to the message queue.
{% endhint %}
MCP and A2A are implemented as streams. To add a new protocol, implement `Stream` and register it in `internal/tcp/listen/listener.go`.
#### Connection Limits
There are some default connection limits (as shown below) but these values can be adjusted. See the [Configuration](/tech/agent-exchange-layer/configuration) page for more information.
| **Setting** | **Default** | **Description** |
| ------------------------ | ----------- | ---------------------------------------- |
| `max_concurrent_conns` | 128 | Max simultaneous inbound TCP connections |
| `conn_read_timeout_secs` | 60 | Read timeout per connection |
| `conn_idle_timeout_secs` | 300 | Idle timeout per connection |
#### Message Queue
Inbound messages that don't match any stream go to an in-memory queue:
* **Unbounded:** Messages accumulate until read.
* **Single-consumer:** Each read dequeues one message. If you need multiple consumers, you'll need to build a fan-out layer that polls `/recv` and distributes to your consumers.
* **Non-persistent:** The queue empties on restart.
* **First-in, First Out (FIFO):** Messages are returned in the order they arrived.
#### Dependencies
These are the dependencies required by AXL. 
| **Dependency** | **Purpose** |
| ----------------------------------------------------------------- | ---------------------- |
| [yggdrasil-go](https://github.com/yggdrasil-network/yggdrasil-go) | Mesh networking core |
| [gVisor](https://gvisor.dev/) | Userspace TCP/IP stack |
| [gologme/log](https://github.com/gologme/log) | Structured logging |
{% hint style="warning" %}
Go module requires `Go 1.25.x` (gVisor has build tag issues with `1.26`). See [Get Started](/tech/agent-exchange-layer/get-started) for more info.
{% endhint %}
#### Running Tests
The repo includes Go tests for the core node (API handlers, TCP transport and protocol streams) and Python tests for the MCP router and A2A server integrations. You don't need any external services. 
```bash
# Go
go test ./...
# Python integrations
cd integrations
pip install -e ".[test]"
pytest
```
# Building Applications & Examples
Shipped examples and step-by-step patterns for building your own services with send/recv, MCP, and A2A, plus patterns for building your own applications on top of it.
## Overview
AXL is a peer-to-peer networking layer that lets you build distributed applications over a mesh of connected nodes. 
{% hint style="info" %}
For more on the internals of AXL, check out [How it Works.](/tech/agent-exchange-layer/how-it-works)
{% endhint %}
It provides low-level messaging primitives along with higher-level protocol support for MCP and A2A, so you can go from simple fire-and-forget communication to fully discoverable agent services with minimal setup.
### Building your Own Application
Building an application using AXL means picking a starting point. In this case, that starting point can be a *building pattern* which makes use of AXL's low-level functionalities in a particular way. 
These patterns include **\[1]** fire-and-forget (using `send`/`recv`), **\[2]** MCP services (`request`/`response`), and **\[3]** A2A (agent-to-agent). 
### Pattern 1: Send/Recv (Fire-and-Forget)
This is the simplest pattern. Your application sends raw bytes and polls for incoming messages.
```python
import requests, json, time
AXL = "http://127.0.0.1:9002"
PEER = "1ee862344fb283395143ac9775150d2e5936efd6e78ed0db83e3f290d3d539ef"
def send(message):
requests.post(f"{AXL}/send",
headers={"X-Destination-Peer-Id": PEER},
data=json.dumps(message))
def recv_loop():
while True:
resp = requests.get(f"{AXL}/recv")
if resp.status_code == 200:
sender = resp.headers.get("X-From-Peer-Id")
print(f"From {sender[:8]}...: {resp.text}")
time.sleep(0.2)
```
* **When to use:** Simple messaging, notifications, data streaming, custom protocols where you control both sides.
* **Limitation:** No built-in acknowledgment. If you need request-response, use MCP/A2A or build correlation over `send`/`recv`.
### Pattern 2: MCP Services (Request-Response)
MCP (Model Context Protocol) gives you structured JSON-RPC request-response. You expose a named service on your node, and other nodes call it remotely.
The requests flow like this:
```
Remote node calls POST /mcp/{your_key}/sentiment
-> Your node receives it
-> Multiplexer sees "service" field → forwards to MCP Router (localhost:9003)
-> Router dispatches to your service (localhost:7100)
-> Your service processes and responds
-> Response flows back to remote node
```
#### Step 1: Write Your Service
You can start by configuring a basic HTTP server:
```python
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/mcp", methods=["POST"])
def handle():
req = request.json
if req.get("method") == "tools/list":
return jsonify({
"jsonrpc": "2.0", "id": req["id"],
"result": {"tools": [{"name": "analyze", "description": "Analyze sentiment"}]}
})
if req.get("method") == "tools/call":
result = do_analysis(req["params"].get("arguments", {}))
return jsonify({
"jsonrpc": "2.0", "id": req["id"],
"result": {"content": [{"type": "text", "text": json.dumps(result)}]}
})
return jsonify({"error": "unknown method"}), 400
app.run(host="127.0.0.1", port=7100)
```
#### Step 2: Start the MCP Router
```bash
cd integrations
pip install -e .
python -m mcp_routing.mcp_router --port 9003
```
#### Step 3: Register your Service w/ Router
```python
requests.post("http://127.0.0.1:9003/register", json={
"service": "sentiment",
"endpoint": "http://127.0.0.1:7100/mcp"
})
```
Don't forget to deregister on shutdown using this command:
`requests.delete("http://127.0.0.1:9003/register/sentiment")`
#### Step 4: Enable MCP (Node Config)
If you run this command, any node on the network can call your service by your public key and service name:
```json
{
"router_addr": "http://127.0.0.1",
"router_port": 9003
}
```
If you want to calling a remote MCP service (from another node), you'd run this:
```bash
# List tools on a remote peer's "sentiment" service
curl -X POST http://127.0.0.1:9002/mcp/{peer_id}/sentiment \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1,"params":{}}'
# Call a specific tool
curl -X POST http://127.0.0.1:9002/mcp/{peer_id}/sentiment \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","id":1,"params":{"name":"analyze","arguments":{"market":"0x3f"}}}'
```
{% hint style="info" %}
Replace `{peer_id}` with the remote node's 64-character hex public key. 
Both nodes must share at least one common peer but they don't need direct connectivity.
{% endhint %}
#### **MCP Router Endpoints**
Use this list of router endpoints.
| **Endpoint** | **Description** |
| ---------------------------- | -------------------------------------------------------------- |
| `POST /route` | Forward a request to a registered service (called by the node) |
| `POST /register` | Register a service: `{"service": "...", "endpoint": "..."}` |
| `DELETE /register/{service}` | Remove a service |
| `GET /services` | List registered services |
| `GET /health` | Health check |
### Pattern 3: A2A (Agent-to-Agent)
A2A wraps your MCP services as [A2A skills](https://github.com/google/A2A), making them discoverable by A2A-compatible agents.
Run this command:
```bash
python -m a2a_serving.a2a_server --port 9004 --router http://127.0.0.1:9003
```
Then add it to your node configuration file:
```json
{
"a2a_addr": "http://127.0.0.1",
"a2a_port": 9004
}
```
The A2A server auto-discovers services from the MCP router and advertises them at `/.well-known/agent.json`. 
Remote nodes can interact with your A2A server like this:
```bash
# Fetch the remote peer's agent card (discover available skills)
curl http://127.0.0.1:9002/a2a/{peer_id}
# Send an A2A request
curl -X POST http://127.0.0.1:9002/a2a/{peer_id} \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "message/send",
"id": 1,
"params": {
"message": {
"role": "user",
"parts": [{"kind": "text", "text": "{\"service\":\"sentiment\",\"request\":{\"jsonrpc\":\"2.0\",\"method\":\"tools/list\",\"id\":1,\"params\":{}}}"}],
"messageId": "msg-001"
}
}
}'
```
The `messageId` is a client-assigned correlation ID. The text part must be a JSON-stringified MCP request matching the format the A2A server expects.
#### A2A Test Client
 A convenience script is included at `examples/python-client/a2a_client.py`:
```bash
# Local mode (talk to your own A2A server)
python examples/python-client/a2a_client.py --service sentiment --method tools/list
# Remote mode (route through the mesh to a remote peer)
python examples/python-client/a2a_client.py \
--remote --peer-id {peer_id} \
--service sentiment --method tools/list
```
### Adding a Custom Protocol
If MCP and A2A don't fit your needs, you can add your own protocol by implementing the `Stream` interface:
```go
type MyStream struct{}
func (s *MyStream) GetID() string { return "my-protocol" }
func (s *MyStream) IsAllowed(data []byte, metadata any) bool {
var envelope map[string]interface{}
if err := json.Unmarshal(data, &envelope); err != nil {
return false
}
_, ok := envelope["my_protocol"]
return ok
}
func (s *MyStream) Forward(metadata any, fromPeerId string) ([]byte, error) {
// Process the message, return a response
return responseBytes, nil
}
```
Register it in `internal/tcp/listen/listener.go` alongside the MCP and A2A streams. Messages matching your discriminator will be routed to your handler instead of the default queue.
### Sharing Your Service
Once running, other nodes need two things: **\[1]** your public key (so other nodes can find and connect to yours) and **\[2]** your service name, so they know what to call. 
You can share your public key and service name however you like. 
> *e.g., "I'm `37227e...` and I run a `sentiment` MCP service."*
### Built-in Examples
There are several example applications that are built into the AXL repository itself, each demonstrating an angle of the technology. You can find them here.
#### 1. Tensor Exchange
Send and receive PyTorch tensors between nodes using msgpack serialization.
> **File:** `examples/python-client/client.py`
**Modes:**
* `recv`: listen for incoming tensors
* `tensor`: send a tensor to a peer
* `bandwidth`: bandwidth test
```bash
pip3 install -r examples/python-client/requirements.txt
# On the receiving node
python3 examples/python-client/client.py recv --port 9002
# On the sending node
python3 examples/python-client/client.py tensor --port 9012 --peer <PEER_KEY>
```
#### 2. Remote MCP Server
Connect two nodes so one can call MCP tools hosted on the other. A2A is not required. The node's `/mcp/` endpoint talks directly to a remote peer's MCP router.
1. **Remote Machine (Sender)**
```bash
./node -config node-config.json
# Start the MCP router
python -m mcp_routing.mcp_router
# Start your MCP service(s) and register them with the router
```
2. **Local Machine (Receiver)**
```bash
./node -config node-config.json
# List tools on the remote peer's "weather" service
curl -X POST http://127.0.0.1:9002/mcp/<remote-public-key>/weather \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1,"params":{}}'
```
Both nodes must be able to reach at least one common peer (configured in `Peers`). They don't need direct connectivity.
#### 3. Remote A2A
Optimize integration by transforming MCP services into A2A skills using the optional A2A extension. 
1. **Remote Machine (Sender):**
```bash
python -m a2a_serving.a2a_server
```
2. **Local Machine (Receiver):**
```bash
python examples/python-client/a2a_client.py \
--remote --peer-id <remote-public-key> \
--service weather --method tools/list
```
The A2A server automatically detects and registers MCP services as skills, making access easy for agents that are already A2A-compatible.
#### 4. GossipSub
GossipSub-style pub/sub message propagation with IHAVE/IWANT lazy forwarding, built on `send`/`recv`.
> **File:** `examples/python-client/gossipsub/gossipsub.py`
#### 5. Convergecast
Tree-based data aggregation using the network's spanning tree. Nodes der프로젝트로 돌아가기