Skip to content
This repository was archived by the owner on Apr 4, 2026. It is now read-only.

Commit 276bf1b

Browse files
Merge pull request #84 from marthendalnunes/feat/mcp-resources
feat: add mcp resources
2 parents f8e68a2 + df4d121 commit 276bf1b

8 files changed

Lines changed: 750 additions & 74 deletions

File tree

Cargo.lock

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ coins-bip39 = "0.12.0"
8484
uuid = { version = "1.11.0", features = ["v4", "serde"] }
8585
chrono = { version = "0.4.39", features = ["serde"] }
8686
url = "2.3.1"
87+
percent-encoding = "2.3"
8788
rand = "0.9.1"
8889

8990
# HTTP client
@@ -93,7 +94,7 @@ reqwest = { version = "0.12.10", features = ["stream"] }
9394
cadence = "1.5.0"
9495

9596
# MCP
96-
rmcp = { version = "0.12.0", features = ["server", "client", "transport-streamable-http-server", "transport-child-process", "transport-io"] }
97+
rmcp = { version = "0.14.0", features = ["server", "client", "transport-streamable-http-server", "transport-child-process", "transport-io"] }
9798

9899
[build-dependencies]
99100
tonic-prost-build = "0.14.1"

docs/mcp.md

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ The Model Context Protocol (MCP) is a specification that allows AI assistants to
88

99
## Implementation
1010

11-
Waypoint uses [rmcp](https://github.com/modelcontextprotocol/rust-sdk) version **0.10.0**, the official Rust SDK for the Model Context Protocol. The implementation leverages the latest macro-based API patterns and the new **Streamable HTTP** transport (MCP spec version 2025-03-26):
11+
Waypoint uses [rmcp](https://github.com/modelcontextprotocol/rust-sdk) version **0.14.0**, the official Rust SDK for the Model Context Protocol. The implementation leverages the latest macro-based API patterns and the new **Streamable HTTP** transport (MCP spec version 2025-03-26):
1212

1313
- **`#[tool_router]`**: Generates tool routing logic for service implementations
1414
- **`#[prompt_router]`**: Generates prompt routing logic for service implementations
@@ -85,6 +85,61 @@ The Waypoint MCP integration provides the following tools to AI assistants:
8585

8686
> **Note**: This implementation provides access to Farcaster user data, casts, reactions, links, and verifications via a comprehensive interface. It allows accessing users by FID or username and supports exploring the social graph through follow relationships.
8787
88+
## Resources
89+
90+
Waypoint also exposes MCP resources that mirror tool outputs. These are read-only, JSON resources that can be fetched via `resources/read` with a `waypoint://` URI.
91+
92+
### URI Design (RFC 3986 / RFC 6570)
93+
94+
Resource URIs follow [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) (URI syntax) and [RFC 6570](https://www.rfc-editor.org/rfc/rfc6570) (URI Template) standards:
95+
96+
- **Path segments** identify resource type and simple identifiers (FID, hash)
97+
- **Query parameters** (`?url=...`) are used for complex values like URLs
98+
99+
This design ensures proper URL encoding, avoids ambiguity, and follows web standards.
100+
101+
### Resource Templates
102+
103+
#### Users
104+
- `waypoint://users/{fid}`: User profile by FID
105+
- `waypoint://users/by-username/{username}`: User profile by username
106+
107+
#### Verifications
108+
- `waypoint://verifications/{fid}`: Verified addresses for a FID
109+
110+
#### Casts
111+
- `waypoint://casts/{fid}/{hash}`: Specific cast by author FID + hash
112+
- `waypoint://casts/by-fid/{fid}`: Recent casts by FID
113+
- `waypoint://casts/by-mention/{fid}`: Casts mentioning a FID
114+
- `waypoint://casts/by-parent/{fid}/{hash}`: Replies to a parent cast
115+
- `waypoint://casts/by-parent-url{?url}`: Replies to a parent URL (RFC 6570 query expansion)
116+
117+
#### Conversations
118+
- `waypoint://conversations/{fid}/{hash}`: Full conversation thread for a cast (includes replies, participants, parent context, quoted casts)
119+
120+
#### Reactions
121+
- `waypoint://reactions/by-fid/{fid}`: Reactions by FID
122+
- `waypoint://reactions/by-target-cast/{fid}/{hash}`: Reactions to a target cast
123+
- `waypoint://reactions/by-target-url{?url}`: Reactions to a target URL (RFC 6570 query expansion)
124+
125+
#### Links
126+
- `waypoint://links/by-fid/{fid}`: Links by FID (defaults to `follow`)
127+
- `waypoint://links/by-target/{fid}`: Links to a target FID (defaults to `follow`)
128+
- `waypoint://links/compact-state/{fid}`: Link compact state by FID
129+
130+
### URL Query Parameters
131+
132+
For resources that accept URLs (e.g., `by-parent-url`, `by-target-url`), pass the URL as a query parameter:
133+
134+
```
135+
waypoint://casts/by-parent-url?url=https%3A%2F%2Fexample.com%2Fpost%2F123
136+
waypoint://reactions/by-target-url?url=https%3A%2F%2Fwarpcast.com%2F~%2Fchannel%2Ftest
137+
```
138+
139+
The URL value should be percent-encoded per RFC 3986.
140+
141+
List-style resources use the same default limit as tools (10), unless the tool supports explicit limits.
142+
88143
### User Tools
89144

90145
#### Get User by FID
@@ -778,23 +833,29 @@ AI assistants can connect to Waypoint's MCP service in several ways:
778833

779834
### HTTP Mode Connection
780835

781-
Connect to the MCP service using the SSE endpoint:
836+
Connect to the MCP service using the Streamable HTTP endpoint:
837+
838+
```
839+
http://waypoint-host:8000/mcp
840+
```
841+
842+
For raw HTTP clients, initialize a session first and include an Accept header that supports both JSON and event streams:
782843

783844
```
784-
http://waypoint-host:8000/sse
845+
Accept: application/json, text/event-stream
785846
```
786847

787848
Example connection using the MCP client library:
788849
```javascript
789850
import { createClient } from "@modelcontextprotocol/client";
790851

791852
const client = createClient({
792-
url: "http://waypoint-host:8000/sse"
853+
url: "http://waypoint-host:8000/mcp"
793854
});
794855

795856
// List available tools
796857
const toolList = await client.listTools();
797-
console.log(toolList); // Will show get_user_by_fid, get_verifications, get_casts_by_user tools
858+
console.log(toolList); // Will show get_user_by_fid, get_verifications_by_fid, get_casts_by_fid tools
798859

799860
// Get user profile data
800861
const userData = await client.callTool({
@@ -805,7 +866,7 @@ console.log(userData);
805866

806867
// Get recent casts from a user
807868
const casts = await client.callTool({
808-
name: "get_casts_by_user",
869+
name: "get_casts_by_fid",
809870
input: { fid: 12345, limit: 5 }
810871
});
811872
console.log(casts);
@@ -816,7 +877,7 @@ console.log(casts);
816877

817878
When using Docker Compose, the MCP service is already configured and exposed:
818879

819-
- MCP Service: `http://localhost:8000/sse`
880+
- MCP Service: `http://localhost:8000/mcp`
820881

821882
The service is enabled by default and will automatically start with Waypoint.
822883

src/commands/mcp/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ async fn serve_mcp(matches: &ArgMatches) -> Result<()> {
7171
// Configure the Streamable HTTP server
7272
let server_config = StreamableHttpServerConfig {
7373
sse_keep_alive: Some(std::time::Duration::from_secs(15)),
74+
sse_retry: None,
7475
stateful_mode: true,
7576
cancellation_token: cancellation_token.clone(),
7677
};

src/services/mcp/base.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ impl MooCow {
101101
}
102102
}
103103

104-
fn _create_resource_text(&self, uri: &str, name: &str) -> Resource {
104+
fn create_resource_text(uri: &str, name: &str) -> Resource {
105105
RawResource::new(uri, name.to_string()).no_annotation()
106106
}
107107

@@ -179,7 +179,7 @@ impl MooCow {
179179
impl ServerHandler for MooCow {
180180
fn get_info(&self) -> ServerInfo {
181181
ServerInfo {
182-
protocol_version: ProtocolVersion::V_2024_11_05,
182+
protocol_version: super::handlers::MCP_PROTOCOL_VERSION,
183183
capabilities: ServerCapabilities::builder()
184184
.enable_prompts()
185185
.enable_resources()
@@ -192,13 +192,13 @@ impl ServerHandler for MooCow {
192192

193193
async fn list_resources(
194194
&self,
195-
_request: Option<PaginatedRequestParam>,
195+
_request: Option<PaginatedRequestParams>,
196196
_: RequestContext<RoleServer>,
197197
) -> Result<ListResourcesResult, McpError> {
198198
Ok(ListResourcesResult {
199199
resources: vec![
200-
self._create_resource_text("str:///cow/info", "cow-info"),
201-
self._create_resource_text("memo://moo-facts", "moo-facts"),
200+
Self::create_resource_text("str:///cow/info", "cow-info"),
201+
Self::create_resource_text("memo://moo-facts", "moo-facts"),
202202
],
203203
next_cursor: None,
204204
meta: None,
@@ -207,7 +207,7 @@ impl ServerHandler for MooCow {
207207

208208
async fn read_resource(
209209
&self,
210-
ReadResourceRequestParam { uri }: ReadResourceRequestParam,
210+
ReadResourceRequestParams { uri, .. }: ReadResourceRequestParams,
211211
_: RequestContext<RoleServer>,
212212
) -> Result<ReadResourceResult, McpError> {
213213
match uri.as_str() {
@@ -230,7 +230,7 @@ impl ServerHandler for MooCow {
230230

231231
async fn list_resource_templates(
232232
&self,
233-
_request: Option<PaginatedRequestParam>,
233+
_request: Option<PaginatedRequestParams>,
234234
_: RequestContext<RoleServer>,
235235
) -> Result<ListResourceTemplatesResult, McpError> {
236236
Ok(ListResourceTemplatesResult {
@@ -340,6 +340,7 @@ impl Service for McpService {
340340
// Configure the Streamable HTTP server
341341
let server_config = StreamableHttpServerConfig {
342342
sse_keep_alive: Some(std::time::Duration::from_secs(15)),
343+
sse_retry: None,
343344
stateful_mode: true,
344345
cancellation_token: ct_for_shutdown.clone(),
345346
};

src/services/mcp/handlers/casts.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ where
100100
tracing::info!("MCP: Fetching cast with FID: {} and hash: {}", fid, hash_hex);
101101

102102
// Convert hex hash to bytes
103-
let hash_bytes = match hex::decode(hash_hex.trim_start_matches("0x")) {
103+
let hash_bytes = match super::utils::parse_hash_bytes(hash_hex) {
104104
Ok(bytes) => bytes,
105-
Err(_) => return format!("Invalid hash format: {}", hash_hex),
105+
Err(message) => return message,
106106
};
107107

108108
// Use the data context to fetch the cast
@@ -177,9 +177,9 @@ where
177177
);
178178

179179
// Convert hex hash to bytes
180-
let parent_hash_bytes = match hex::decode(parent_hash_hex.trim_start_matches("0x")) {
180+
let parent_hash_bytes = match super::utils::parse_hash_bytes(parent_hash_hex) {
181181
Ok(bytes) => bytes,
182-
Err(_) => return format!("Invalid hash format: {}", parent_hash_hex),
182+
Err(message) => return message,
183183
};
184184

185185
// Use the data context to fetch replies
@@ -328,9 +328,9 @@ where
328328
tracing::info!("MCP: Fetching conversation for cast hash: {}", cast_hash);
329329

330330
// Convert hex hash to bytes
331-
let hash_bytes = match hex::decode(cast_hash.trim_start_matches("0x")) {
331+
let hash_bytes = match super::utils::parse_hash_bytes(cast_hash) {
332332
Ok(bytes) => bytes,
333-
Err(e) => return format!("Invalid cast hash: {}", e),
333+
Err(message) => return format!("Invalid cast hash: {}", message),
334334
};
335335

336336
// Fetch the root cast
@@ -659,13 +659,13 @@ where
659659

660660
// The hash is stored in the Message's id field
661661
// This is from the Message.hash property in the protobuf
662-
let parent_hash = match hex::decode(_parent_cast.id.value().trim_start_matches("0x")) {
662+
let parent_hash = match super::utils::parse_hash_bytes(_parent_cast.id.value()) {
663663
Ok(hash_bytes) => hash_bytes,
664-
Err(e) => {
664+
Err(message) => {
665665
tracing::error!(
666666
"Failed to decode cast hash from ID: {} - {}",
667667
_parent_cast.id.value(),
668-
e
668+
message
669669
);
670670
// If we can't decode the hash, use the raw bytes as fallback
671671
// This is a last resort and may not produce correct results

0 commit comments

Comments
 (0)