Vault dan kredensial adalah primitif autentikasi yang memungkinkan Anda mendaftarkan kredensial untuk layanan pihak ketiga satu kali dan mereferensikannya berdasarkan ID saat pembuatan sesi. Ini berarti Anda tidak perlu menjalankan penyimpanan rahasia (secret store) Anda sendiri, mengirimkan token pada setiap panggilan, atau kehilangan jejak pengguna akhir mana yang diwakili oleh agen saat bertindak.
Referensi vault adalah parameter per sesi, sehingga Anda dapat mengelola produk Anda pada granularitas resource agent dan pengguna Anda pada granularitas resource session.
Membuat vault
Warning: Vault dan kredensial memiliki cakupan workspace, artinya kunci API apa pun dengan akses workspace dapat mereferensikannya saat membuat sesi. Untuk mencabut akses, hapus vault atau kredensial tersebut.
Vault adalah kumpulan credentials yang terkait dengan seorang pengguna akhir. Berikan display_name dan secara opsional tandai dengan metadata agar Anda dapat memetakannya kembali ke catatan pengguna Anda sendiri.
curl --fail-with-body -sS https://haijun.my.id/v1/vaults \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <<'EOF'
{
"display_name": "Alice",
"metadata": {"external_user_id": "usr_abc123"}
}
EOF ant beta:vaults create < alice.vault.yaml display_name: Alice
metadata:
external_user_id: usr_abc123 vault = client.beta.vaults.create(
display_name="Alice",
metadata={"external_user_id": "usr_abc123"},
)
print(vault.id) # "vlt_01ABC..." const vault = await client.beta.vaults.create({
display_name: "Alice",
metadata: { external_user_id: "usr_abc123" },
});
console.log(vault.id); // "vlt_01ABC..." var vault = await client.Beta.Vaults.Create(new()
{
DisplayName = "Alice",
Metadata = new Dictionary<string, string> { ["external_user_id"] = "usr_abc123" },
});
Console.WriteLine(vault.ID); // "vlt_01ABC..." vault, err := client.Beta.Vaults.New(ctx, juglow.BetaVaultNewParams{
DisplayName: "Alice",
Metadata: map[string]string{"external_user_id": "usr_abc123"},
})
if err != nil {
panic(err)
}
fmt.Println(vault.ID) // "vlt_01ABC..." var vault = client.beta().vaults().create(VaultCreateParams.builder()
.displayName("Alice")
.metadata(VaultCreateParams.Metadata.builder()
.putAdditionalProperty("external_user_id", JsonValue.from("usr_abc123"))
.build())
.build());
IO.println(vault.id()); // "vlt_01ABC..." $vault = $client->beta->vaults->create(
displayName: 'Alice',
metadata: ['external_user_id' => 'usr_abc123'],
);
echo $vault->id . "\n"; // "vlt_01ABC..." vault = client.beta.vaults.create(
display_name: "Alice",
metadata: {external_user_id: "usr_abc123"}
)
puts vault.id # "vlt_01ABC..."Responsnya adalah catatan vault lengkap:
{
"type": "vault",
"id": "vlt_01ABC...",
"display_name": "Alice",
"metadata": { "external_user_id": "usr_abc123" },
"created_at": "2026-03-18T10:00:00Z",
"updated_at": "2026-03-18T10:00:00Z",
"archived_at": null
}Menambahkan kredensial
Dua kategori kredensial didukung:
- Kredensial MCP (
mcp_oauth,static_bearer): setiap kredensial dikunci berdasarkanmcp_server_url. Ketika agen terhubung ke server pada URL tersebut saat runtime sesi, token diinjeksikan secara otomatis.
- Variabel lingkungan (
environment_variable): setiap kredensial dikunci berdasarkansecret_name(nama variabel lingkungan) dan disimpan di sandbox sebagai placeholder buram (opaque placeholder). Ketika agen memulai permintaan keluar, placeholder buram tersebut diganti dengan rahasia yang sebenarnya saat egress. Agen tidak pernah melihat nilai rahasia. Gunakan ini untuk layanan apa pun yang melakukan autentikasi melalui variabel lingkungan, seperti CLI, SDK, atau panggilan API langsung.
Nilai kredensial aktual yang Anda berikan (token, access_token, refresh_token, client_secret, secret_value) diperlakukan sebagai field sensitif yang hanya dapat ditulis (write-only) dan tidak pernah dikembalikan dalam respons API.
Note: Kredensial variabel lingkungan (
environment_variable) belum didukung dengan sandbox yang di-hosting sendiri.
MCP OAuth
Gunakan mcp_oauth ketika server MCP menggunakan OAuth 2.0. Jika Anda menyediakan blok refresh, Juglow memperbarui access token atas nama Anda ketika token tersebut kedaluwarsa.
Field refresh.token_endpoint_auth.type menunjukkan cara mengautentikasi panggilan refresh:
none: klien publik
client_secret_basic: autentikasi HTTP Basic dengan client secret
client_secret_post: client secret di dalam body POST
curl --fail-with-body -sS "https://haijun.my.id/v1/vaults/$VAULT_ID/credentials" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <<'EOF'
{
"display_name": "Alice's Slack",
"auth": {
"type": "mcp_oauth",
"mcp_server_url": "https://mcp.slack.com/mcp",
"access_token": "xoxp-...",
"expires_at": "2099-12-31T23:59:59Z",
"refresh": {
"token_endpoint": "https://slack.com/api/oauth.v2.user.access",
"client_id": "1234567890.0987654321",
"scope": "channels:read chat:write",
"refresh_token": "xoxe-1-...",
"token_endpoint_auth": {"type": "client_secret_post", "client_secret": "abc123..."}
}
}
}
EOF ant beta:vaults:credentials create \
--vault-id "$VAULT_ID" \
--display-name "Alice's Slack" <<'YAML'
auth:
type: mcp_oauth
mcp_server_url: https://mcp.slack.com/mcp
access_token: xoxp-...
expires_at: "2099-12-31T23:59:59Z"
refresh:
token_endpoint: https://slack.com/api/oauth.v2.user.access
client_id: "1234567890.0987654321"
scope: channels:read chat:write
refresh_token: xoxe-1-...
token_endpoint_auth:
type: client_secret_post
client_secret: abc123...
YAML credential = client.beta.vaults.credentials.create(
vault_id=vault.id,
display_name="Alice's Slack",
auth={
"type": "mcp_oauth",
"mcp_server_url": "https://mcp.slack.com/mcp",
"access_token": "xoxp-...",
"expires_at": "2099-12-31T23:59:59Z",
"refresh": {
"token_endpoint": "https://slack.com/api/oauth.v2.user.access",
"client_id": "1234567890.0987654321",
"scope": "channels:read chat:write",
"refresh_token": "xoxe-1-...",
"token_endpoint_auth": {"type": "client_secret_post", "client_secret": "abc123..."},
},
},
) const credential = await client.beta.vaults.credentials.create(vault.id, {
display_name: "Alice's Slack",
auth: {
type: "mcp_oauth",
mcp_server_url: "https://mcp.slack.com/mcp",
access_token: "xoxp-...",
expires_at: "2099-12-31T23:59:59Z",
refresh: {
token_endpoint: "https://slack.com/api/oauth.v2.user.access",
client_id: "1234567890.0987654321",
scope: "channels:read chat:write",
refresh_token: "xoxe-1-...",
token_endpoint_auth: {
type: "client_secret_post",
client_secret: "abc123...",
},
},
},
}); var credential = await client.Beta.Vaults.Credentials.Create(vault.ID, new()
{
DisplayName = "Alice's Slack",
Auth = new BetaManagedAgentsMcpOAuthCreateParams
{
Type = BetaManagedAgentsMcpOAuthCreateParamsType.McpOAuth,
McpServerUrl = "https://mcp.slack.com/mcp",
AccessToken = "xoxp-...",
ExpiresAt = DateTimeOffset.Parse("2099-12-31T23:59:59Z"),
Refresh = new()
{
TokenEndpoint = "https://slack.com/api/oauth.v2.user.access",
ClientID = "1234567890.0987654321",
Scope = "channels:read chat:write",
RefreshToken = "xoxe-1-...",
TokenEndpointAuth = new BetaManagedAgentsTokenEndpointAuthPostParam
{
Type = BetaManagedAgentsTokenEndpointAuthPostParamType.ClientSecretPost,
ClientSecret = "abc123...",
},
},
},
}); credential, err := client.Beta.Vaults.Credentials.New(ctx, vault.ID, juglow.BetaVaultCredentialNewParams{
DisplayName: juglow.String("Alice's Slack"),
Auth: juglow.BetaVaultCredentialNewParamsAuthUnion{
OfMCPOAuth: &juglow.BetaManagedAgentsMCPOAuthCreateParams{
Type: juglow.BetaManagedAgentsMCPOAuthCreateParamsTypeMCPOAuth,
MCPServerURL: "https://mcp.slack.com/mcp",
AccessToken: "xoxp-...",
ExpiresAt: juglow.Time(time.Date(2099, time.December, 31, 23, 59, 59, 0, time.UTC)),
Refresh: juglow.BetaManagedAgentsMCPOAuthRefreshParams{
TokenEndpoint: "https://slack.com/api/oauth.v2.user.access",
ClientID: "1234567890.0987654321",
Scope: juglow.String("channels:read chat:write"),
RefreshToken: "xoxe-1-...",
TokenEndpointAuth: juglow.BetaManagedAgentsMCPOAuthRefreshParamsTokenEndpointAuthUnion{
OfClientSecretPost: &juglow.BetaManagedAgentsTokenEndpointAuthPostParam{
Type: juglow.BetaManagedAgentsTokenEndpointAuthPostParamTypeClientSecretPost,
ClientSecret: "abc123...",
},
},
},
},
},
})
if err != nil {
panic(err)
} var credential = client.beta().vaults().credentials().create(vault.id(),
CredentialCreateParams.builder()
.displayName("Alice's Slack")
.auth(BetaManagedAgentsMcpOAuthCreateParams.builder()
.type(BetaManagedAgentsMcpOAuthCreateParams.Type.MCP_OAUTH)
.mcpServerUrl("https://mcp.slack.com/mcp")
.accessToken("xoxp-...")
.expiresAt(OffsetDateTime.parse("2099-12-31T23:59:59Z"))
.refresh(BetaManagedAgentsMcpOAuthRefreshParams.builder()
.tokenEndpoint("https://slack.com/api/oauth.v2.user.access")
.clientId("1234567890.0987654321")
.scope("channels:read chat:write")
.refreshToken("xoxe-1-...")
.clientSecretPostTokenEndpointAuth("abc123...")
.build())
.build())
.build()); $credential = $client->beta->vaults->credentials->create(
vaultID: $vault->id,
displayName: "Alice's Slack",
auth: ManagedAgentsMCPOAuthCreateParams::with(
type: 'mcp_oauth',
mcpServerURL: 'https://mcp.slack.com/mcp',
accessToken: 'xoxp-...',
expiresAt: new DateTimeImmutable('2099-12-31T23:59:59Z'),
refresh: ManagedAgentsMCPOAuthRefreshParams::with(
tokenEndpoint: 'https://slack.com/api/oauth.v2.user.access',
clientID: '1234567890.0987654321',
scope: 'channels:read chat:write',
refreshToken: 'xoxe-1-...',
tokenEndpointAuth: ManagedAgentsTokenEndpointAuthPostParam::with(
type: 'client_secret_post',
clientSecret: 'abc123...',
),
),
),
); credential = client.beta.vaults.credentials.create(
vault.id,
display_name: "Alice's Slack",
auth: {
type: "mcp_oauth",
mcp_server_url: "https://mcp.slack.com/mcp",
access_token: "xoxp-...",
expires_at: "2099-12-31T23:59:59Z",
refresh: {
token_endpoint: "https://slack.com/api/oauth.v2.user.access",
client_id: "1234567890.0987654321",
scope: "channels:read chat:write",
refresh_token: "xoxe-1-...",
token_endpoint_auth: {
type: "client_secret_post",
client_secret: "abc123..."
}
}
}
)Atur refresh.token_endpoint ke endpoint token dari alur OAuth yang menerbitkan refresh token, karena Juglow mengirimkan setiap permintaan refresh ke URL tersebut dan field ini tidak dapat diubah setelah kredensial dibuat.
MCP static bearer
Gunakan static_bearer ketika server MCP menerima bearer token tetap (kunci API, personal access token, atau sejenisnya). Tidak diperlukan alur refresh.
curl --fail-with-body -sS "https://haijun.my.id/v1/vaults/$VAULT_ID/credentials" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <<'EOF'
{
"display_name": "Linear API key",
"auth": {
"type": "static_bearer",
"mcp_server_url": "https://mcp.linear.app/mcp",
"token": "lin_api_your_linear_key"
}
}
EOF ant beta:vaults:credentials create --vault-id "$VAULT_ID" <<'YAML'
display_name: Linear API key
auth:
type: static_bearer
mcp_server_url: https://mcp.linear.app/mcp
token: lin_api_your_linear_key
YAML bearer_credential = client.beta.vaults.credentials.create(
vault_id=vault.id,
display_name="Linear API key",
auth={
"type": "static_bearer",
"mcp_server_url": "https://mcp.linear.app/mcp",
"token": "lin_api_your_linear_key",
},
) const bearerCredential = await client.beta.vaults.credentials.create(vault.id, {
display_name: "Linear API key",
auth: {
type: "static_bearer",
mcp_server_url: "https://mcp.linear.app/mcp",
token: "lin_api_your_linear_key",
},
}); var bearerCredential = await client.Beta.Vaults.Credentials.Create(vault.ID, new()
{
DisplayName = "Linear API key",
Auth = new BetaManagedAgentsStaticBearerCreateParams
{
Type = BetaManagedAgentsStaticBearerCreateParamsType.StaticBearer,
McpServerUrl = "https://mcp.linear.app/mcp",
Token = "lin_api_your_linear_key",
},
}); bearerCredential, err := client.Beta.Vaults.Credentials.New(ctx, vault.ID, juglow.BetaVaultCredentialNewParams{
DisplayName: juglow.String("Linear API key"),
Auth: juglow.BetaVaultCredentialNewParamsAuthUnion{
OfStaticBearer: &juglow.BetaManagedAgentsStaticBearerCreateParams{
Type: juglow.BetaManagedAgentsStaticBearerCreateParamsTypeStaticBearer,
MCPServerURL: "https://mcp.linear.app/mcp",
Token: "lin_api_your_linear_key",
},
},
})
if err != nil {
panic(err)
}
_ = bearerCredential var bearerCredential = client.beta().vaults().credentials().create(vault.id(),
CredentialCreateParams.builder()
.displayName("Linear API key")
.auth(BetaManagedAgentsStaticBearerCreateParams.builder()
.type(BetaManagedAgentsStaticBearerCreateParams.Type.STATIC_BEARER)
.mcpServerUrl("https://mcp.linear.app/mcp")
.token("lin_api_your_linear_key")
.build())
.build()); $bearerCredential = $client->beta->vaults->credentials->create(
vaultID: $vault->id,
displayName: 'Linear API key',
auth: ManagedAgentsStaticBearerCreateParams::with(
type: 'static_bearer',
mcpServerURL: 'https://mcp.linear.app/mcp',
token: 'lin_api_your_linear_key',
),
); bearer_credential = client.beta.vaults.credentials.create(
vault.id,
display_name: "Linear API key",
auth: {
type: "static_bearer",
mcp_server_url: "https://mcp.linear.app/mcp",
token: "lin_api_your_linear_key"
}
)Variabel lingkungan
Gunakan environment_variable untuk melakukan autentikasi ke layanan eksternal melalui variabel lingkungan, seperti CLI, SDK, atau panggilan API langsung. Kredensial variabel lingkungan berfungsi untuk klien yang mengirimkan nilai rahasia secara verbatim dalam permintaan keluar, jadi periksa kriteria kelayakan klien di tab ini sebelum mengonfigurasinya.
Array networking.allowed_hosts mengontrol host keluar mana yang dapat menerima substitusi rahasia. Gunakan "type": "limited" dengan daftar spesifik, atau "type": "unrestricted" jika pemanggil menjangkau domain yang tidak dapat Anda sebutkan sebelumnya.
Membatasi domain sangat disarankan untuk tujuan keamanan, dan mencegah kunci Anda dibagikan ke host yang tidak berwenang.
Note:
networking.allowed_hostspada kredensial vault mengontrol permintaan mana yang menggunakan rahasia, bukan permintaan mana yang diizinkan. Agar agen benar-benar dapat menjangkau suatu domain, domain tersebut juga harus diizinkan pada tingkat environment. Kedua tingkat harus menyertakan domain tersebut (baik melalui networkingunrestrictedatau dengan mencantumkan domain secara eksplisit diallowed_hosts) agar permintaan dengan substitusi rahasia berhasil.
Field opsional injection_location membatasi di mana rahasia disubstitusikan; semantik lengkapnya dijelaskan setelah contoh.
curl --fail-with-body -sS "https://haijun.my.id/v1/vaults/$VAULT_ID/credentials" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <<'EOF'
{
"auth": {
"type": "environment_variable",
"secret_name": "NOTION_API_KEY",
"secret_value": "ntn_your-secret-here",
"networking": {
"type": "limited",
"allowed_hosts": ["api.notion.com"]
},
"injection_location": {"header": true}
},
"display_name": "Notion API key for sandbox"
}
EOF ant beta:vaults:credentials create --vault-id "$VAULT_ID" <<'YAML'
display_name: Notion API key for sandbox
auth:
type: environment_variable
secret_name: NOTION_API_KEY
secret_value: ntn_your-secret-here
injection_location:
header: true
networking:
type: limited
allowed_hosts: [api.notion.com]
YAML env_credential = client.beta.vaults.credentials.create(
vault_id=vault.id,
display_name="Notion API key for sandbox",
auth={
"type": "environment_variable",
"secret_name": "NOTION_API_KEY",
"secret_value": "ntn_your-secret-here",
"networking": {
"type": "limited",
"allowed_hosts": ["api.notion.com"],
},
"injection_location": {"header": True},
},
)
if env_credential.auth.type == "environment_variable":
location = env_credential.auth.injection_location
print(f"header: {location.header}, body: {location.body}") # header: True, body: False const envVarCredential = await client.beta.vaults.credentials.create(vault.id, {
display_name: "Notion API key for sandbox",
auth: {
type: "environment_variable",
secret_name: "NOTION_API_KEY",
secret_value: "ntn_your-secret-here",
networking: {
type: "limited",
allowed_hosts: ["api.notion.com"],
},
injection_location: { header: true },
},
});
if (envVarCredential.auth.type === "environment_variable") {
console.log(envVarCredential.auth.injection_location); // { header: true, body: false }
} var envVarCredential = await client.Beta.Vaults.Credentials.Create(vault.ID, new()
{
DisplayName = "Notion API key for sandbox",
Auth = new BetaManagedAgentsEnvironmentVariableCreateParams
{
Type = BetaManagedAgentsEnvironmentVariableCreateParamsType.EnvironmentVariable,
SecretName = "NOTION_API_KEY",
SecretValue = "ntn_your-secret-here",
Networking = new BetaManagedAgentsLimitedCredentialNetworkingParams
{
Type = BetaManagedAgentsLimitedCredentialNetworkingParamsType.Limited,
AllowedHosts = ["api.notion.com"],
},
InjectionLocation = new() { Header = true },
},
});
if (envVarCredential.Auth.TryPickBetaManagedAgentsEnvironmentVariableAuthResponse(out var envVarAuth))
{
var injectionLocation = envVarAuth.InjectionLocation;
Console.WriteLine($"Header: {injectionLocation.Header}, Body: {injectionLocation.Body}"); // "Header: True, Body: False"
} envVarCredential, err := client.Beta.Vaults.Credentials.New(ctx, vault.ID, juglow.BetaVaultCredentialNewParams{
DisplayName: juglow.String("Notion API key for sandbox"),
Auth: juglow.BetaVaultCredentialNewParamsAuthUnion{
OfEnvironmentVariable: &juglow.BetaManagedAgentsEnvironmentVariableCreateParams{
Type: juglow.BetaManagedAgentsEnvironmentVariableCreateParamsTypeEnvironmentVariable,
SecretName: "NOTION_API_KEY",
SecretValue: "ntn_your-secret-here",
Networking: juglow.BetaManagedAgentsCredentialNetworkingParamsUnion{
OfLimited: &juglow.BetaManagedAgentsLimitedCredentialNetworkingParams{
Type: juglow.BetaManagedAgentsLimitedCredentialNetworkingParamsTypeLimited,
AllowedHosts: []string{"api.notion.com"},
},
},
InjectionLocation: juglow.BetaManagedAgentsInjectionLocationParams{
Header: juglow.Bool(true),
},
},
},
})
if err != nil {
panic(err)
}
if envVarAuth, ok := envVarCredential.Auth.AsAny().(juglow.BetaManagedAgentsEnvironmentVariableAuthResponse); ok {
injectionLocation := envVarAuth.InjectionLocation
fmt.Printf("Header:%t Body:%t\n", injectionLocation.Header, injectionLocation.Body) // "Header:true Body:false"
} var envVarCredential = client.beta().vaults().credentials().create(vault.id(),
CredentialCreateParams.builder()
.displayName("Notion API key for sandbox")
.auth(BetaManagedAgentsEnvironmentVariableCreateParams.builder()
.type(BetaManagedAgentsEnvironmentVariableCreateParams.Type.ENVIRONMENT_VARIABLE)
.secretName("NOTION_API_KEY")
.secretValue("ntn_your-secret-here")
.limitedNetworking(List.of("api.notion.com"))
.injectionLocation(BetaManagedAgentsInjectionLocationParams.builder()
.header(true)
.build())
.build())
.build());
envVarCredential.auth().environmentVariable().ifPresent(envVarAuth -> {
var injectionLocation = envVarAuth.injectionLocation();
IO.println("header=" + injectionLocation.header() + " body=" + injectionLocation.body()); // header=true body=false
}); $envVarCredential = $client->beta->vaults->credentials->create(
vaultID: $vault->id,
displayName: 'Notion API key for sandbox',
auth: ManagedAgentsEnvironmentVariableCreateParams::with(
type: ManagedAgentsEnvironmentVariableCreateParams\Type::ENVIRONMENT_VARIABLE,
secretName: 'NOTION_API_KEY',
secretValue: 'ntn_your-secret-here',
networking: ManagedAgentsLimitedCredentialNetworkingParams::with(
type: ManagedAgentsLimitedCredentialNetworkingParams\Type::LIMITED,
allowedHosts: ['api.notion.com'],
),
injectionLocation: ManagedAgentsInjectionLocationParams::with(header: true),
),
);
if ($envVarCredential->auth instanceof \Juglow\Beta\Vaults\Credentials\ManagedAgentsEnvironmentVariableAuthResponse) {
$injectionLocation = $envVarCredential->auth->injectionLocation;
echo 'header: ' . json_encode($injectionLocation->header) . "\n"; // header: true
echo 'body: ' . json_encode($injectionLocation->body) . "\n"; // body: false
} env_credential = client.beta.vaults.credentials.create(
vault.id,
display_name: "Notion API key for sandbox",
auth: {
type: "environment_variable",
secret_name: "NOTION_API_KEY",
secret_value: "ntn_your-secret-here",
networking: {
type: "limited",
allowed_hosts: ["api.notion.com"]
},
injection_location: {header: true}
}
)
if env_credential.auth.type == :environment_variable
env_credential.auth.injection_location => {header:, body:}
puts "header: #{header}, body: #{body}" # header: true, body: false
endPayload permintaan sering kali disusun dari konten yang sedang dikerjakan agen, sehingga body permintaan merupakan permukaan paparan yang lebih luas. Sebagian besar layanan membaca kunci API dari header permintaan, sehingga mengaktifkan hanya header adalah konfigurasi yang lebih sempit. Ini membatasi substitusi pada nilai header permintaan untuk kredensial tersebut.
injection_location milik kredensial mengontrol bagian mana dari permintaan keluar yang menerima substitusi rahasia. Ini adalah objek opsional, setingkat dengan networking, dengan dua field Boolean: header (header permintaan) dan body (body permintaan). injection_location bersifat independen dari networking.allowed_hosts: allowed_hosts membatasi host mana yang menerima substitusi rahasia, dan injection_location membatasi bagian mana dari permintaan yang menerima substitusi tersebut.
injection_location berperilaku berbeda saat pembuatan dan saat pembaruan:
| Operasi | Perilaku injection_location |
|---|---|
| Membuat kredensial | Jika Anda menyediakan objek tersebut, field apa pun yang Anda hilangkan di dalamnya akan bernilai default false: {"header": true} membuat kredensial khusus header. Hilangkan objek tersebut sepenuhnya dan kedua lokasi akan diaktifkan. |
| Memperbarui kredensial | Field digabungkan secara individual: {"body": false} menonaktifkan substitusi body dan membiarkan header tidak berubah. |
Sebuah kredensial harus memiliki setidaknya satu lokasi yang diaktifkan, sehingga pembuatan atau pembaruan yang akan menonaktifkan kedua lokasi mengembalikan error 400. Memberikan null eksplisit untuk objek injection_location atau untuk salah satu field-nya juga mengembalikan error 400 ("omit the field instead"). Respons selalu mengembalikan kedua field dengan nilai yang telah diselesaikan.
Placeholder di lokasi yang dinonaktifkan tidak disubstitusi maupun dihapus. Permintaan dikirim ke pihak ketiga dengan string placeholder buram literal di lokasi tersebut. Jika sebuah permintaan tiba di pihak ketiga dengan berisi string placeholder literal, berarti lokasi tersebut dinonaktifkan untuk kredensial itu atau host tujuan tidak tercakup oleh networking.allowed_hosts milik kredensial.
Note: Kredensial yang dibuat di Console hanya mengaktifkan injeksi header. Jika klien Anda mengirimkan rahasia di body permintaan, seperti permintaan token berformat form-encoded, placeholder akan diteruskan secara literal dan layanan akan menolaknya dengan error autentikasinya sendiri. Aktifkan injeksi body di formulir Console saat Anda membuat kredensial, atau perbarui kredensial dengan
{"injection_location": {"body": true}}.
Substitusi terjadi saat egress, bukan di dalam sandbox. Apa pun yang memproses kredensial secara lokal akan melihat placeholder buram, bukan nilai sebenarnya: klien yang memvalidasi format kredensial saat startup mungkin menolaknya, dan klien yang menghitung tanda tangan permintaan dari rahasia (misalnya, AWS SigV4) menghasilkan tanda tangan yang tidak valid. Kredensial variabel lingkungan berfungsi untuk klien yang mengirimkan nilai rahasia secara verbatim dalam permintaan keluar, di lokasi yang diaktifkan oleh injection_location milik kredensial.
Substitusi hanya berlaku untuk arah keluar. Jika klien menggunakan rahasia yang tersimpan untuk mengambil token sesi (misalnya, grant client-credentials OAuth), token yang dikembalikan tiba di sandbox tanpa disensor. Untuk alur berbasis pertukaran, lakukan pertukaran tersebut sendiri dan simpan token hasilnya di vault sebagai gantinya.
Tip: Batasi cakupan kunci API hanya pada izin yang dibutuhkan agen. Agen dapat melakukan apa pun yang diizinkan oleh kunci tersebut, sehingga kunci dengan izin yang lebih luas dari yang diperlukan meningkatkan radius dampak jika agen berperilaku tidak terduga.
Kredensial disimpan sebagaimana diberikan dan tidak divalidasi hingga runtime sesi. Kredensial yang tidak valid muncul sebagai error autentikasi atau error hilir selama sesi, yang dipancarkan tetapi tidak menghalangi sesi untuk berlanjut.
Batasan:
- Kunci unik per vault.
mcp_server_url(kredensial MCP) dansecret_name(kredensial variabel lingkungan) harus unik di antara kredensial aktif dalam sebuah vault. Membuat duplikat mengembalikan 409.
- Kunci tidak dapat diubah. Untuk mengubah
mcp_server_urlatausecret_name, arsipkan kredensial dan buat yang baru.
- Maksimum 20 kredensial per vault.
Mereferensikan vault saat pembuatan sesi
Berikan vault_ids saat membuat sesi:
curl --fail-with-body -sS https://haijun.my.id/v1/sessions \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <<EOF
{
"agent": "$AGENT_ID",
"environment_id": "$ENVIRONMENT_ID",
"vault_ids": ["$VAULT_ID"],
"title": "Alice's Slack digest"
}
EOF ant beta:sessions create \
--agent "$AGENT_ID" \
--environment-id "$ENVIRONMENT_ID" \
--vault-id "$VAULT_ID" \
--title "Alice's Slack digest" session = client.beta.sessions.create(
agent=agent.id,
environment_id=environment.id,
vault_ids=[vault.id],
title="Alice's Slack digest",
) const session = await client.beta.sessions.create({
agent: agent.id,
environment_id: environment.id,
vault_ids: [vault.id],
title: "Alice's Slack digest",
}); var session = await client.Beta.Sessions.Create(new()
{
Agent = agent.ID,
EnvironmentID = environment.ID,
VaultIds = [vault.ID],
Title = "Alice's Slack digest",
}); session, err := client.Beta.Sessions.New(ctx, juglow.BetaSessionNewParams{
Agent: juglow.BetaSessionNewParamsAgentUnion{
OfString: juglow.String(agent.ID),
},
EnvironmentID: environment.ID,
VaultIDs: []string{vault.ID},
Title: juglow.String("Alice's Slack digest"),
})
if err != nil {
panic(err)
} var session = client.beta().sessions().create(SessionCreateParams.builder()
.agent(agent.id())
.environmentId(environment.id())
.vaultIds(List.of(vault.id()))
.title("Alice's Slack digest")
.build()); $session = $client->beta->sessions->create(
agent: $agent->id,
environmentID: $environment->id,
vaultIDs: [$vault->id],
title: "Alice's Slack digest",
); session = client.beta.sessions.create(
agent: agent.id,
environment_id: environment.id,
vault_ids: [vault.id],
title: "Alice's Slack digest"
)Perilaku runtime:
- Ketika tidak ada kredensial MCP yang cocok berdasarkan
mcp_server_url, koneksi dicoba tanpa autentikasi dan akan error jika server memerlukan autentikasi.
- Ketika beberapa vault berisi kredensial yang cocok, vault pertama yang memiliki kecocokan yang digunakan.
- Dalam sesi multiagen, kredensial vault berlaku untuk setiap thread. Agen yang definisinya sendiri mendeklarasikan server MCP yang cocok akan melakukan autentikasi dengan kredensial ini. Lihat Menghubungkan agen ke server MCP.
Merotasi kredensial
Nilai rahasia, display_name, dan (pada kredensial variabel lingkungan) injection_location dapat diperbarui. Pembaruan injection_location digabungkan per field, seperti dijelaskan di tab Variabel lingkungan pada Menambahkan kredensial. Untuk sesi yang sedang berjalan, pembaruan injection_location dipropagasikan dengan cara yang sama seperti rotasi rahasia: kredensial sesi diselesaikan ulang tanpa restart, seperti dijelaskan di Siklus hidup kredensial, dan lokasi yang diperbarui berlaku untuk permintaan keluar sesi berikutnya. Field struktural (mcp_server_url, secret_name, token_endpoint, client_id) dikunci setelah pembuatan. Untuk mengubahnya, arsipkan kredensial dan buat yang baru.
curl --fail-with-body -sS \
"https://haijun.my.id/v1/vaults/$VAULT_ID/credentials/$CREDENTIAL_ID" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <<'EOF' > /dev/null
{
"auth": {
"type": "mcp_oauth",
"access_token": "xoxp-new-...",
"expires_at": "2099-12-31T23:59:59Z",
"refresh": {"refresh_token": "xoxe-1-new-..."}
}
}
EOF ant beta:vaults:credentials update \
--vault-id "$VAULT_ID" \
--credential-id "$CREDENTIAL_ID" <<'YAML'
auth:
type: mcp_oauth
access_token: xoxp-new-...
expires_at: "2099-12-31T23:59:59Z"
refresh:
refresh_token: xoxe-1-new-...
YAML client.beta.vaults.credentials.update(
credential.id,
vault_id=vault.id,
auth={
"type": "mcp_oauth",
"access_token": "xoxp-new-...",
"expires_at": "2099-12-31T23:59:59Z",
"refresh": {"refresh_token": "xoxe-1-new-..."},
},
) await client.beta.vaults.credentials.update(credential.id, {
vault_id: vault.id,
auth: {
type: "mcp_oauth",
access_token: "xoxp-new-...",
expires_at: "2099-12-31T23:59:59Z",
refresh: {
refresh_token: "xoxe-1-new-...",
},
},
}); await client.Beta.Vaults.Credentials.Update(credential.ID, new()
{
VaultID = vault.ID,
Auth = new BetaManagedAgentsMcpOAuthUpdateParams
{
Type = BetaManagedAgentsMcpOAuthUpdateParamsType.McpOAuth,
AccessToken = "xoxp-new-...",
ExpiresAt = DateTimeOffset.Parse("2099-12-31T23:59:59Z"),
Refresh = new() { RefreshToken = "xoxe-1-new-..." },
},
}); _, err = client.Beta.Vaults.Credentials.Update(ctx, credential.ID, juglow.BetaVaultCredentialUpdateParams{
VaultID: vault.ID,
Auth: juglow.BetaVaultCredentialUpdateParamsAuthUnion{
OfMCPOAuth: &juglow.BetaManagedAgentsMCPOAuthUpdateParams{
Type: juglow.BetaManagedAgentsMCPOAuthUpdateParamsTypeMCPOAuth,
AccessToken: juglow.String("xoxp-new-..."),
ExpiresAt: juglow.Time(time.Date(2099, time.December, 31, 23, 59, 59, 0, time.UTC)),
Refresh: juglow.BetaManagedAgentsMCPOAuthRefreshUpdateParams{
RefreshToken: juglow.String("xoxe-1-new-..."),
},
},
},
})
if err != nil {
panic(err)
} client.beta().vaults().credentials().update(credential.id(),
CredentialUpdateParams.builder()
.vaultId(vault.id())
.auth(BetaManagedAgentsMcpOAuthUpdateParams.builder()
.type(BetaManagedAgentsMcpOAuthUpdateParams.Type.MCP_OAUTH)
.accessToken("xoxp-new-...")
.expiresAt(OffsetDateTime.parse("2099-12-31T23:59:59Z"))
.refresh(BetaManagedAgentsMcpOAuthRefreshUpdateParams.builder()
.refreshToken("xoxe-1-new-...")
.build())
.build())
.build()); $client->beta->vaults->credentials->update(
$credential->id,
vaultID: $vault->id,
auth: ManagedAgentsMCPOAuthUpdateParams::with(
type: 'mcp_oauth',
accessToken: 'xoxp-new-...',
expiresAt: new DateTimeImmutable('2099-12-31T23:59:59Z'),
refresh: ManagedAgentsMCPOAuthRefreshUpdateParams::with(refreshToken: 'xoxe-1-new-...'),
),
); client.beta.vaults.credentials.update(
credential.id,
vault_id: vault.id,
auth: {
type: "mcp_oauth",
access_token: "xoxp-new-...",
expires_at: "2099-12-31T23:59:59Z",
refresh: {refresh_token: "xoxe-1-new-..."}
}
)Siklus hidup kredensial
Kredensial diselesaikan ulang secara berkala, baik selama sesi maupun selama siklus hidup vault. Ini memastikan bahwa rotasi, pengarsipan, atau penghapusan kredensial dipropagasikan ke sesi yang sedang berjalan tanpa restart.
Untuk mendapatkan notifikasi jika kredensial diarsipkan, dihapus, atau gagal di-refresh, Anda dapat berlangganan webhook vault dan kredensial yang terkait dengan perubahan siklus hidup tersebut.
| Event | Pemicu |
|---|---|
vault.archived | Vault diarsipkan. Event vault_credential.archived juga dipancarkan untuk setiap kredensial di dalamnya. |
vault.deleted | Vault dihapus. Event vault_credential.deleted juga dipancarkan untuk setiap kredensial di dalamnya. |
vault_credential.archived | Kredensial diarsipkan, baik secara langsung maupun sebagai akibat dari pengarsipan vault. |
vault_credential.deleted | Kredensial dihapus, baik secara langsung maupun sebagai akibat dari penghapusan vault. |
vault_credential.refresh_failed | Kredensial mcp_oauth tidak dapat di-refresh (refresh token tidak valid, atau error yang tidak dapat dipulihkan dari server OAuth). |
Note: Ini adalah daftar webhook yang tidak lengkap; lihat Berlangganan webhook untuk daftar lengkapnya.
Untuk kredensial mcp_oauth, penyelesaian ulang juga me-refresh access token jika sudah kedaluwarsa. Jika refresh gagal, event vault_credential.refresh_failed dipancarkan.
Mendiagnosis kegagalan refresh OAuth
Untuk mendiagnosis mengapa refresh gagal, panggil POST /v1/vaults/{vault_id}/credentials/{credential_id}/mcp_oauth_validate (atau client.beta.vaults.credentials.mcp_oauth_validate(...) (typescript: client.beta.vaults.credentials.mcpOAuthValidate(...); csharp: client.Beta.Vaults.Credentials.McpOAuthValidate(...); go: client.Beta.Vaults.Credentials.MCPOAuthValidate(...); java: client.beta().vaults().credentials().mcpOAuthValidate(...); php: $client->beta->vaults->credentials->mcpOAuthValidate(...)) di SDK). Ini memungkinkan Anda memutuskan cara menangani kegagalan tersebut; tindakan yang tepat bergantung pada jenis error.
status tingkat atas memberi tahu Anda apa yang harus dilakukan selanjutnya:
valid: token berfungsi; tidak perlu tindakan.
invalid: grant sudah hilang atau server OAuth menolak refresh dengan 4xx. Minta pengguna akhir untuk melakukan otorisasi ulang.
unknown: error sementara (5xx, 429, atau kegagalan jaringan). Tunggu dan coba lagi.
curl --fail-with-body -sS -X POST \
"https://haijun.my.id/v1/vaults/$VAULT_ID/credentials/$CREDENTIAL_ID/mcp_oauth_validate?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" ant beta:vaults:credentials mcp-oauth-validate \
--vault-id "$VAULT_ID" \
--credential-id "$CREDENTIAL_ID" validation = client.beta.vaults.credentials.mcp_oauth_validate(
credential.id,
vault_id=vault.id,
)
print(validation.status) # "valid", "invalid", or "unknown" const validation = await client.beta.vaults.credentials.mcpOAuthValidate(
credential.id,
{ vault_id: vault.id },
);
console.log(validation.status); // "valid", "invalid", or "unknown" var validation = await client.Beta.Vaults.Credentials.McpOAuthValidate(credential.ID, new()
{
VaultID = vault.ID,
});
Console.WriteLine(validation.Status.Raw()); // "valid", "invalid", or "unknown" validation, err := client.Beta.Vaults.Credentials.MCPOAuthValidate(ctx, credential.ID, juglow.BetaVaultCredentialMCPOAuthValidateParams{
VaultID: vault.ID,
})
if err != nil {
panic(err)
}
fmt.Println(validation.Status) // "valid", "invalid", or "unknown" var validation = client.beta().vaults().credentials().mcpOAuthValidate(credential.id(),
CredentialMcpOAuthValidateParams.builder()
.vaultId(vault.id())
.build());
IO.println(validation.status()); // valid, invalid, or unknown $validation = $client->beta->vaults->credentials->mcpOAuthValidate(
$credential->id,
vaultID: $vault->id,
);
echo $validation->status . "\n"; // "valid", "invalid", or "unknown" validation = client.beta.vaults.credentials.mcp_oauth_validate(
credential.id,
vault_id: vault.id
)
puts validation.status # :valid, :invalid, or :unknownResponsnya adalah objek vault_credential_validation. mcp_probe menyertakan langkah handshake MCP yang gagal; refresh menyertakan hasil dari upaya refresh.
{
"type": "vault_credential_validation",
"credential_id": "vcrd_01ABC...",
"vault_id": "vlt_01XYZ...",
"validated_at": "2026-04-29T17:12:00Z",
"has_refresh_token": false,
"status": "invalid",
"mcp_probe": {
"method": "initialize",
"http_response": {
"status_code": 401,
"content_type": "application/json",
"body": "{\"error\":\"invalid_token\"}",
"body_truncated": false
}
},
"refresh": {
"status": "no_refresh_token",
"http_response": null
}
}Operasi lainnya
- Mendaftar vault atau kredensial: Dipaginasi, terbaru lebih dulu. Catatan yang diarsipkan dikecualikan secara default (berikan
include_archived=trueuntuk menyertakannya).
- Mengarsipkan vault:
POST /v1/vaults/{id}/archive. Berlaku berjenjang ke semua kredensial. Rahasia dibersihkan; catatan dipertahankan untuk audit. Sesi mendatang yang mereferensikan vault ini akan gagal; sesi yang sedang berjalan tetap berlanjut.
- Mengarsipkan kredensial:
POST /v1/vaults/{id}/credentials/{cred_id}/archive. Membersihkan payload rahasia; kunci kredensial (mcp_server_urlatausecret_name) tetap terlihat dan dibebaskan untuk kredensial pengganti.
- Menghapus vault atau kredensial: Penghapusan permanen. Catatan tidak dipertahankan. Gunakan arsip jika Anda memerlukan jejak audit.