Skip to content
Merged
30 changes: 25 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
# Changelog

## [Unreleased]
## [0.2.0] - 2026-06-24

### Added

- CEP-22: oversized payload transfer for chunking MCP messages that exceed the NIP-44 single-event size limit (~65 KB), using a transport-agnostic framing engine (start/accept/chunk/end/abort frames, SHA-256 digest verification, and out-of-order reassembly), enabled by default and negotiated through the `support_oversized_transfer` capability tag so servers only fragment to clients that advertise support (#88, #89, #91)
- CEP-22: progress-aware request timeouts and an in-flight transfer watchdog, providing per-chunk idle-timeout reset, a max-total transfer cap, and receiver-side reaping of stalled transfers, opt-in via `call_tool_with_options` and `progress_aware_options` (#92)
- CEP-17: multi-stage relay resolution with server identity parsing, relay list (NIP-65) fetching, and `fetch_events`, plus transport integration that resolves a server's preferred relays before connecting (#82, #83)
- CEP-6: expanded server announcements with full `InitializeResult` parsing in `ServerAnnouncement`, auto-publishing on `start()`, relay list publishing, and a tool and resource schema mapping table (#77, #78, #79, #81)
- CEP-23: optional server profile metadata published as a NIP-01 kind 0 event, via a new `ProfileMetadata` type, so clients see a human-friendly identity (#77, #79)
- CEP-41: open-ended streaming - a server tool emits ordered chunks back to a
client while a request is in flight via `call_tool_stream`; the client
consumes them as an async `Stream`; the stream supplements the final
JSON-RPC response rather than replacing it, negotiated through the
`support_open_stream` capability tag (#97, #98)
- CI: MSRV and feature-matrix checks (#75)
- `examples/python/`: runnable Python examples using the UniFFI binding — an
offline install sanity check, server/tool discovery (mirrors `discovery.rs`),
and a client `tools/list` caller (mirrors `proxy.rs`).

### Changed

- Upgraded `rmcp` from 0.16.0 to 1.8 to gain progress-aware request timeouts (#86)
- Raised the minimum supported Rust version (MSRV) from 1.70 to 1.88
- Added `sha2` and `hex` dependencies for CEP-22 payload digests
- Enabled the `missing_docs` lint, closed rustdoc coverage gaps, and added SDK documentation links and a CEP-22 oversized-transfer guide (#67, #73)
- Bumped `nostr-sdk` from `0.43` to `0.44` (pulls core `nostr` `0.44.3`). No source
changes were required: the breaking removals in the unreleased 0.45 line
(`NostrSigner`, `TagKind`, `EventBuilder::sign_with_keys`, `TagStandard`)
Expand All @@ -16,11 +37,10 @@
`.github/workflows/ffi.yml` to install `uniffi-bindgen-cli` at tag `v0.31.2`
and invoke it as `uniffi-bindgen-cli` (renamed from `uniffi-bindgen` in 0.30).

### Added
### Fixed

- `examples/python/`: runnable Python examples using the UniFFI binding — an
offline install sanity check, server/tool discovery (mirrors `discovery.rs`),
and a client `tools/list` caller (mirrors `proxy.rs`).
- `MockRelayPool` live broadcast now respects per-subscription filters instead of echoing every event to every subscriber (#90)
- Made the oversized-transfer e2e timing tests deterministic with virtual paused time and the relay config hermetic, removing CI flakiness and a 30 s real-network discovery hang (#93, #94)

## [0.1.1] - 2026-05-08

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "contextvm-sdk"
version = "0.1.1"
version = "0.2.0"
edition = "2021"
rust-version = "1.88"
description = "Rust SDK for the ContextVM protocol — MCP over Nostr"
Expand Down
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# contextvm-sdk

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Rust](https://img.shields.io/badge/rust-1.70%2B-orange.svg)](https://www.rust-lang.org)
[![Rust](https://img.shields.io/badge/rust-1.88%2B-orange.svg)](https://www.rust-lang.org)

Rust SDK for the [ContextVM protocol](https://contextvm.org) — **MCP over Nostr**.

Expand Down Expand Up @@ -58,11 +58,11 @@ Add to your `Cargo.toml`:
contextvm-sdk = { git = "https://github.com/ContextVM/rs-sdk" }
```

Or clone and use as a path dependency:
Or pin a published release from crates.io:

```toml
[dependencies]
contextvm-sdk = { path = "../rust-contextvm-sdk" }
contextvm-sdk = "0.2.0"
```

## Quick Start
Expand Down Expand Up @@ -168,13 +168,17 @@ async fn main() -> contextvm_sdk::Result<()> {

The in-repo Rust SDK guides live in [`docs/README.md`](docs/README.md):

- For most users, the main pattern is: build an `rmcp` server or client, then attach [`NostrServerTransport`](src/transport/server/mod.rs:87) or [`NostrClientTransport`](src/transport/client/mod.rs:69).
- For most users, the main pattern is: build an `rmcp` server or client, then attach [`NostrServerTransport`](src/transport/server/mod.rs) or [`NostrClientTransport`](src/transport/client/mod.rs).

- [`docs/overview.md`](docs/overview.md)
- [`docs/server-transport.md`](docs/server-transport.md)
- [`docs/client-transport.md`](docs/client-transport.md)
- [`docs/open-stream.md`](docs/open-stream.md)
- [`docs/discovery.md`](docs/discovery.md)
- [`docs/encryption.md`](docs/encryption.md)
- [`docs/transport-modes.md`](docs/transport-modes.md)
- [`docs/stateless.md`](docs/stateless.md)
- [`docs/oversized-transfer.md`](docs/oversized-transfer.md)
- [`docs/rmcp.md`](docs/rmcp.md)
- [`docs/transports.md`](docs/transports.md)
- [`docs/gateway.md`](docs/gateway.md)
Expand Down Expand Up @@ -212,12 +216,21 @@ it adds no event kind — frames ride inside `notifications/progress` messages).
See [docs/oversized-transfer.md](docs/oversized-transfer.md) for the timeout
model and tuning.

Open-ended streaming (CEP-41) lets a server tool emit an ordered sequence of
chunks back to the client while a request is in flight. The client consumes them
as an async `Stream` via `call_tool_stream`. Unlike CEP-22, the stream supplements
the final JSON-RPC response rather than replacing it. Disabled by default; opt in
with `with_open_stream(OpenStreamConfig::enabled())`. See
[docs/open-stream.md](docs/open-stream.md) for the writer and client APIs and the
keepalive timer model.

### Server Transport Config

| Field | Default | Description |
|--------------------------|-----------------------|------------------------------------------|
| `relay_urls` | `["wss://relay.damus.io"]` | Nostr relays to connect to |
| `encryption_mode` | `Optional` | Encryption policy |
| `gift_wrap_mode` | `Optional` | Gift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059) |
| `server_info` | `None` | Server metadata for announcements |
| `is_announced_server` | `false` | Auto-publish announcements on start (CEP-6) |
| `allowed_public_keys` | `[]` (allow all) | Client pubkey allowlist (hex) |
Expand All @@ -228,6 +241,7 @@ model and tuning.
| `publish_relay_list` | `true` | Whether to publish kind 10002 relay list metadata |
| `profile_metadata` | `None` | Profile metadata for kind 0 publication (CEP-23) |
| `oversized_transfer` | enabled | CEP-22 oversized payload transfer config ([guide](docs/oversized-transfer.md)) |
| `open_stream` | disabled | CEP-41 open-stream config; opt-in ([guide](docs/open-stream.md)) |

### Client Transport Config

Expand All @@ -236,11 +250,13 @@ model and tuning.
| `relay_urls` | `[]` | Nostr relays to connect to (empty = use relay resolution) |
| `server_pubkey` | (required) | Target server's public key (hex, npub, or nprofile) |
| `encryption_mode` | `Optional` | Encryption policy |
| `gift_wrap_mode` | `Optional` | Gift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059) |
| `is_stateless` | `false` | Emulate initialize locally |
| `timeout` | `30s` | Response timeout |
| `discovery_relay_urls` | `None` (bootstrap relays) | Relays for CEP-17 kind 10002 discovery |
| `fallback_operational_relay_urls` | `None` | Relays probed in parallel with CEP-17 discovery |
| `oversized_transfer` | enabled | CEP-22 oversized payload transfer config ([guide](docs/oversized-transfer.md)) |
| `open_stream` | disabled | CEP-41 open-stream config; opt-in ([guide](docs/open-stream.md)) |

When `relay_urls` is empty, `start()` runs automatic relay resolution: configured relays > nprofile hints > CEP-17 kind 10002 discovery > fallback probing > bootstrap defaults.

Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ For most native Rust applications, the primary entry points are `NostrServerTran
- Stateless mode guide: client-side initialize emulation and when to use it
- Discovery guide: public discovery helpers and event kinds
- Oversized transfer guide: CEP-22 fragmentation, the three-timer model, and progress-aware request options
- Open-stream guide: CEP-41 streaming responses, the writer and `call_tool_stream` APIs, and the keepalive timer model

### Bridging existing MCP applications

Expand Down
31 changes: 21 additions & 10 deletions docs/client-transport.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,14 @@ async fn main() -> anyhow::Result<()> {
}

let result = client
.call_tool(CallToolRequestParams {
name: "echo".into(),
arguments: serde_json::from_value(serde_json::json!({
"message": "hello from native contextvm client"
}))
.ok(),
meta: None,
task: None,
})
.call_tool(
CallToolRequestParams::new("echo").with_arguments(
serde_json::from_value(serde_json::json!({
"message": "hello from native contextvm client"
}))
.unwrap(),
),
)
.await?;

println!("Echo result: {}", first_text(&result));
Expand Down Expand Up @@ -151,11 +150,23 @@ Start with these fields in `NostrClientTransportConfig`:
- `server_pubkey`: the target server's public key (hex, npub, or nprofile with relay hints)
- `encryption_mode`: whether plaintext is allowed
- `gift_wrap_mode`: whether to use persistent or ephemeral wrapping
- `open_stream`: CEP-41 open-stream settings; disabled by default, opt in with `with_open_stream(OpenStreamConfig::enabled())`
- `is_stateless`: whether initialize is emulated locally for stateless workflows
- `timeout`: how long request correlation waits for a response
- `discovery_relay_urls`: bootstrap relays for CEP-17 kind 10002 relay-list discovery (defaults to `DEFAULT_BOOTSTRAP_RELAY_URLS`)
- `fallback_operational_relay_urls`: non-authoritative relays probed in parallel with CEP-17 discovery

## Open-ended streaming (CEP-41)

For tools that stream output while a call is in flight, use `call_tool_stream`
with a `ClientOpenStreamHandle`. Capture the handle from the transport with
`transport.open_stream_handle()` before `serve()` consumes the transport, then
call `call_tool_stream(peer, &handle, params)` to receive a `ToolStreamCall`
whose `stream` yields chunks and whose `result` resolves to the final
`CallToolResult`. Open-stream is disabled by default; enable it with
`with_open_stream(OpenStreamConfig::enabled())`. See
[open-stream.md](open-stream.md).

## When to use this instead of the proxy

Use this page's approach when you are writing a new Rust MCP client that should speak ContextVM natively.
Expand All @@ -168,4 +179,4 @@ Use the proxy guide when you want a simpler message-oriented bridge and do not w
- The initialize request is sent automatically as part of the running client startup sequence.
- Stateless initialization behavior is covered by the conformance tests.
- Capability learning and gift-wrap handling happen inside the client transport implementation.
- When `relay_urls` is empty, `start()` runs 6-stage relay resolution before connecting: configured relays > nprofile hints > CEP-17 kind 10002 discovery > fallback probing > bootstrap defaults. Callers can set `server_pubkey` to an nprofile and omit `relay_urls` entirely.
- When `relay_urls` is empty, `start()` runs 6-stage relay resolution before connecting: configured relays > nprofile hints > CEP-17 kind 10002 discovery > fallback probing > sequential fallback > bootstrap defaults. Callers can set `server_pubkey` to an nprofile and omit `relay_urls` entirely.
Loading
Loading