1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! Neutral contracts for provider credentials and user connections.
use crate::error::Result;
use crate::typed_id::SessionId;
use async_trait::async_trait;
use uuid::Uuid;
/// Provider credentials resolved for tool-side API clients.
#[derive(Debug, Clone)]
pub struct ProviderCredentials {
pub api_key: String,
pub base_url: Option<String>,
}
#[async_trait]
pub trait ProviderCredentialStore: Send + Sync {
/// Resolve default credentials for a provider type (for example `openai`).
///
/// Implementations may apply environment fallbacks internally, but tools
/// should never read provider env vars directly.
async fn get_default_provider_credentials(
&self,
provider_type: &str,
) -> Result<Option<ProviderCredentials>>;
}
/// Resolves user connection tokens (e.g. GitHub) lazily at tool execution time.
///
/// Instead of eagerly injecting tokens at session creation, tools call this
/// resolver when they need a token. If the user hasn't connected, returns None.
#[async_trait]
pub trait UserConnectionResolver: Send + Sync {
/// Bind credential resolution to a stored input-message invocation. Implementations
/// without this capability remain service-only and fail closed for consumer grants.
fn for_execution(
&self,
_input_message_id: Uuid,
) -> Option<std::sync::Arc<dyn UserConnectionResolver>> {
None
}
/// Bind a configured MCP attachment at a remote execution boundary.
fn for_mcp_operation(
&self,
_server_prefix: &str,
) -> Option<std::sync::Arc<dyn UserConnectionResolver>> {
None
}
/// Get a decrypted connection token for the given provider.
/// Returns None if the user has no connection for this provider.
async fn get_connection_token(
&self,
session_id: SessionId,
provider: &str,
) -> Result<Option<String>>;
/// Resolve a decrypted MCP connection token as a pure function of the
/// attachment's `actsAs`.
///
/// Acting identity is explicit configuration over one credential store:
/// none reads no grant; service reads the responding agent's service virtual
/// user; user reads the current invocation's end user. Neither identity
/// falls back to the other, and session ownership never selects a grant.
///
/// `Ok(None)` means "no credential", which callers surface as
/// `connection_required` rather than an unauthenticated request.
///
/// THREAT[TM-TOOL-041]: the default implementation is fail-closed on
/// purpose. A resolver that has not opted in must never silently fall back
/// to the identity-preferring lookup, because that is the substitution this
/// method exists to remove.
async fn get_mcp_connection_token(
&self,
_session_id: SessionId,
_provider: &str,
_acts_as: crate::mcp_server::McpServerActsAs,
) -> Result<Option<String>> {
Ok(None)
}
/// Invalidate an MCP credential after the remote server rejects it.
///
/// Implementations that own persistent grants can remove the credential
/// selected by `acts_as` and its credential-scoped tool cache. The default
/// is a no-op. Implementations must preserve a replacement grant when its
/// fingerprint differs from the credential the remote server rejected.
async fn invalidate_mcp_connection(
&self,
_session_id: SessionId,
_provider: &str,
_acts_as: crate::mcp_server::McpServerActsAs,
_rejected_credential_fingerprint: &str,
) -> Result<()> {
Ok(())
}
/// Resolve the user ID of the connection used for a session/provider pair.
///
/// This is used by leased resources to bind cleanup to the same provider
/// identity that created the remote resource.
async fn get_connection_user(
&self,
_session_id: SessionId,
_provider: &str,
) -> Result<Option<Uuid>> {
Ok(None)
}
/// Resolve a provider token for a specific user.
///
/// Cleanup workers use this to avoid "first org member wins" behavior when
/// cleaning resources created by a specific provider connection owner.
async fn get_connection_token_for_user(
&self,
_user_id: Uuid,
_provider: &str,
) -> Result<Option<String>> {
Ok(None)
}
/// Get provider-specific metadata stored alongside the connection.
/// Returns None if no metadata is stored or no connection exists.
async fn get_connection_metadata(
&self,
_session_id: SessionId,
_provider: &str,
) -> Result<Option<serde_json::Value>> {
Ok(None)
}
}