icarus_canister/
endpoints.rs

1//! Canister endpoints for tool metadata discovery
2//!
3//! In the clean architecture, canisters don't handle MCP protocol.
4//! They only provide metadata for bridge discovery.
5
6use crate::state::STATE;
7use candid::{CandidType, Deserialize, Principal};
8use icarus_core::protocol::{IcarusMetadata, ToolMetadata};
9
10/// Query canister metadata for tool discovery
11pub fn icarus_metadata() -> IcarusMetadata {
12    STATE.with(|s| {
13        let state = s.borrow();
14        if let Some(state) = state.as_ref() {
15            IcarusMetadata {
16                version: state.config.get().version.clone(),
17                canister_id: ic_cdk::api::canister_self(),
18                tools: state
19                    .tools
20                    .iter()
21                    .map(|entry| {
22                        let (name, tool_state) = (entry.key().clone(), entry.value());
23                        ToolMetadata {
24                            name: name.clone(),
25                            candid_method: name.clone(), // Method name matches tool name
26                            is_query: tool_state.is_query,
27                            description: format!("{} tool", name), // TODO: Get from tool definition
28                            parameters: vec![],                    // TODO: Get from tool definition
29                        }
30                    })
31                    .collect(),
32            }
33        } else {
34            IcarusMetadata {
35                version: "1.0.0".to_string(),
36                canister_id: ic_cdk::api::canister_self(),
37                tools: vec![],
38            }
39        }
40    })
41}
42
43/// HTTP request type for canister HTTP gateway
44#[derive(CandidType, Deserialize)]
45pub struct HttpRequest {
46    pub method: String,
47    pub url: String,
48    pub headers: Vec<(String, String)>,
49    pub body: Vec<u8>,
50}
51
52/// HTTP response type for canister HTTP gateway
53#[derive(CandidType)]
54pub struct HttpResponse {
55    pub status_code: u16,
56    pub headers: Vec<(String, String)>,
57    pub body: Vec<u8>,
58}
59
60/// Handle HTTP requests from the IC HTTP gateway
61pub fn http_request(_req: HttpRequest) -> HttpResponse {
62    let metadata = icarus_metadata();
63
64    // Generate HTML showing available tools
65    let tools_html: String = metadata
66        .tools
67        .iter()
68        .map(|tool| {
69            format!(
70                r#"<div class="tool">
71                <strong>{}</strong> ({})
72                <br><small>{}</small>
73            </div>"#,
74                tool.name,
75                if tool.is_query { "query" } else { "update" },
76                tool.description
77            )
78        })
79        .collect::<Vec<_>>()
80        .join("\n");
81
82    let html = format!(
83        r#"<!DOCTYPE html>
84<html>
85<head>
86    <title>Icarus Canister</title>
87    <style>
88        body {{
89            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
90            max-width: 800px;
91            margin: 50px auto;
92            padding: 20px;
93            background: #f5f5f5;
94        }}
95        .container {{
96            background: white;
97            padding: 30px;
98            border-radius: 10px;
99            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
100        }}
101        h1 {{
102            color: #333;
103            margin-bottom: 10px;
104        }}
105        .info {{
106            background: #e7f3ff;
107            padding: 15px;
108            border-radius: 5px;
109            border-left: 4px solid #2196F3;
110            margin: 20px 0;
111        }}
112        code {{
113            background: #f0f0f0;
114            padding: 2px 5px;
115            border-radius: 3px;
116            font-family: "Courier New", monospace;
117        }}
118        .tool {{
119            margin: 10px 0;
120            padding: 10px;
121            background: #f9f9f9;
122            border-radius: 5px;
123        }}
124    </style>
125</head>
126<body>
127    <div class="container">
128        <h1>🚀 Icarus Canister</h1>
129        <p>This is a standard ICP canister built with Icarus SDK.</p>
130        
131        <div class="info">
132            <strong>Canister ID:</strong> <code>{}</code><br>
133            <strong>Version:</strong> <code>{}</code><br>
134            <strong>Status:</strong> <span style="color: green;">✓ Running</span>
135        </div>
136        
137        <h2>Available Tools</h2>
138        {}
139        
140        <h2>Connect with Claude Desktop</h2>
141        <p>To use this canister with Claude Desktop, run:</p>
142        <code>icarus bridge start --canister-id {}</code>
143        
144        <hr style="margin-top: 40px; border: none; border-top: 1px solid #eee;">
145        <p style="text-align: center; color: #666; font-size: 14px;">
146            Powered by <a href="https://icarus.dev" style="color: #2196F3;">Icarus SDK</a>
147        </p>
148    </div>
149</body>
150</html>"#,
151        ic_cdk::api::canister_self().to_text(),
152        metadata.version,
153        tools_html,
154        ic_cdk::api::canister_self().to_text()
155    );
156
157    HttpResponse {
158        status_code: 200,
159        headers: vec![
160            (
161                "Content-Type".to_string(),
162                "text/html; charset=UTF-8".to_string(),
163            ),
164            ("Cache-Control".to_string(), "no-cache".to_string()),
165        ],
166        body: html.into_bytes(),
167    }
168}
169
170/// Get the current owner of the canister
171pub fn get_owner() -> Principal {
172    crate::state::get_owner()
173}