Developer reference

Developer docs

Connect an agent, check your account, then discover the tools you can use.

1. What Valet is

Valet connects your agent to your company’s apps through one MCP address. It checks your company’s rules before each app action and records the outcome.

Use Claude’s browser sign-in without a key, or create a personal key on API keys and send it as Authorization: Bearer vk_....

Keys act as you in the selected company and follow the same rules and approvals. Copy a new key when it is shown, keep it private, and revoke it on the API keys page when it is no longer needed.

2. Quick start

  1. Sign up with your email and create a company.
  2. As an administrator, open Apps. Enable catalog apps, add an app from the directory, or add a custom app using its MCP address. Complete its connection steps. New MCP tools need review before use.
  3. Set Rules for who may use each app and which actions need approval. Members can ask their administrator to do this.
  4. Connect your agent below. Call WHOAMI to check your company and role, then SEARCH_TOOLS to find an action. Follow any personal connection link before retrying that action.

3. Connect your client

MCP address: https://valet.getsentiense.com/mcp. Replace vk_... with your own Valet key.

Claude Code

Run this command, then open /mcp in Claude Code and sign in to Valet in your browser.

claude mcp add --transport http valet https://valet.getsentiense.com/mcp

Or connect with an API key:

claude mcp add --transport http valet https://valet.getsentiense.com/mcp --header "Authorization: Bearer vk_..."

Cursor

Add this to .cursor/mcp.json in your project, or ~/.cursor/mcp.json for your user. Keep files containing keys out of source control.

{
  "mcpServers": {
    "valet": {
      "url": "https://valet.getsentiense.com/mcp",
      "headers": {
        "Authorization": "Bearer vk_..."
      }
    }
  }
}

OpenAI Responses API

Install openai. Set OPENAI_API_KEY, OPENAI_MODEL to an MCP-capable model you can use, and VALET_API_KEY in your environment. The Valet address must be reachable from OpenAI.

import os
from openai import OpenAI

response = OpenAI().responses.create(
    model=os.environ["OPENAI_MODEL"],
    input="Call WHOAMI and tell me which company I am connected to.",
    tools=[{
        "type": "mcp",
        "server_label": "valet",
        "server_url": "https://valet.getsentiense.com/mcp",
        "headers": {"Authorization": "Bearer " + os.environ["VALET_API_KEY"]},
        "allowed_tools": ["WHOAMI"],
        "require_approval": "never",
    }],
)
print(response.output_text)

This example exposes only WHOAMI and skips OpenAI’s separate approval step for that account check. Valet’s app rules and approvals still apply when you expose other tools.

OpenAI Agents SDK

Install openai-agents and set the same three environment variables. This example connects from your Python process.

import asyncio
import os
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp

async def main():
    async with MCPServerStreamableHttp(
        name="valet",
        params={
            "url": "https://valet.getsentiense.com/mcp",
            "headers": {"Authorization": "Bearer " + os.environ["VALET_API_KEY"]},
        },
    ) as server:
        agent = Agent(
            name="Account check",
            model=os.environ["OPENAI_MODEL"],
            instructions="Call WHOAMI and report the company and role.",
            mcp_servers=[server],
        )
        result = await Runner.run(agent, "Which company am I connected to?")
        print(result.final_output)

asyncio.run(main())

Python MCP client

Install mcp==2.3.0 (includes httpx2) and set VALET_API_KEY. This calls the tool directly.

import asyncio
import os
import httpx2
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client

async def main():
    headers = {"Authorization": "Bearer " + os.environ["VALET_API_KEY"]}
    async with httpx2.AsyncClient(headers=headers) as http_client:
        async with streamable_http_client("https://valet.getsentiense.com/mcp", http_client=http_client) as (read, write):
            async with ClientSession(read, write) as session:
                await session.initialize()
                result = await session.call_tool("WHOAMI", arguments={})
                print(result)

asyncio.run(main())

Raw JSON-RPC with curl

Save this as a Bash script, replace the key, and run it with bash. It needs curl and awk. Responses may contain SSE data: lines.

The example uses protocol version 2025-11-25 for the initialize flow. Valet is stateless and currently returns no session ID; if a server returns Mcp-Session-Id, the script sends it on both following requests.

#!/usr/bin/env bash
set -euo pipefail
MCP_URL='https://valet.getsentiense.com/mcp'
VALET_API_KEY='vk_...'
DOCS_TMP=$(mktemp -d)
trap 'rm -rf "$DOCS_TMP"' EXIT

# 1. Initialize using a supported Streamable HTTP protocol version.
curl --fail --silent --show-error --no-buffer --max-time 30 "$MCP_URL" \
  -H "Authorization: Bearer $VALET_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -D "$DOCS_TMP/headers" -o "$DOCS_TMP/initialize" \
  --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"valet-docs","version":"1.0.0"}}}'
cat "$DOCS_TMP/initialize"

# Preserve a session ID if returned. Valet currently serves stateless requests.
SESSION_ID=$(awk 'tolower($1) == "mcp-session-id:" {gsub("\r", "", $2); print $2}' "$DOCS_TMP/headers")
SESSION_HEADERS=(-H 'MCP-Protocol-Version: 2025-11-25')
if [ -n "$SESSION_ID" ]; then
  SESSION_HEADERS+=(-H "Mcp-Session-Id: $SESSION_ID")
fi

# 2. Finish initialization. A successful notification has no response body.
curl --fail --silent --show-error --no-buffer --max-time 30 "$MCP_URL" \
  -H "Authorization: Bearer $VALET_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  "${SESSION_HEADERS[@]}" \
  --data '{"jsonrpc":"2.0","method":"notifications/initialized"}'

# 3. Check your account. The result contains person, tenant and role.
curl --fail --silent --show-error --no-buffer --max-time 30 "$MCP_URL" \
  -H "Authorization: Bearer $VALET_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  "${SESSION_HEADERS[@]}" \
  --data '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"WHOAMI","arguments":{}}}'

4. The six tools

WHOAMI
Your person, company, role, discoverable toolkits, and discovery decision counts.
SEARCH_TOOLS
Find available tools for your use case.
GET_TOOL_SCHEMAS
Get input schemas for available tools.
EXECUTE
Execute discovered tools. Each call returns its own status.
MANAGE_CONNECTIONS
Add or remove your personal app connections, or list connection statuses.
WAIT_FOR_CONNECTIONS
After the user confirms connecting, wait for any or all requested toolkits to connect or reach a terminal failure.

5. Rules and approvals

Read actions retrieve data. Write actions create or change data, including sending messages. Destructive actions delete data or make changes that may be hard to undo.

Your administrator can allow, block, or require approval for an action. When approval is needed, EXECUTE returns approval_required, an approval_url and an approval_id. The action has not run.

Show the approval link to the person. After approval, resend the identical tool and arguments with that approval_id. Changed arguments need a new approval. Never retry a denied call.

If a call returns needs_connection, show its connection link or administrator message. After the person confirms they connected, use WAIT_FOR_CONNECTIONS, then retry when connected.

6. Pass-through tools

Some catalog apps provide three tools for their REST API. Find their exact names with SEARCH_TOOLS and fetch their schemas before calling them through EXECUTE.

  • <APP>_API_READ sends GET requests.
  • <APP>_API_WRITE sends POST, PUT or PATCH, selected with method.
  • <APP>_API_DELETE sends DELETE and counts as destructive.

Supply a path starting with /, up to 2048 characters. Put query parameters in query, with string, number or boolean values. Write and delete tools can include a JSON body.

Requests stay on the app’s configured API addresses. If there is more than one, origin selects its zero-based index. You cannot supply a host, custom headers, path traversal, query or fragment delimiters in the path, or method overrides. Valet supplies the connected app’s credentials.

Rules apply to the whole pass-through tool, not individual paths. GET is classified as read, POST/PUT/PATCH as write and DELETE as destructive; check the app’s API reference for what an endpoint does. The request, response and execution limits below also apply.

7. Limits

These are this server’s configured values, using the defaults when no override is set. Browser and MCP request budgets are separate. Rate and concurrency caps apply per web process.

MCP requests per IP per minute
600
MCP requests per person per minute
120
MCP requests per company per minute
1200
Page requests per IP per minute
600
Signed-in page requests per person per minute
120
Signed-in page requests per company per minute
1200
Sign-in requests per IP per minute
20
Concurrent MCP requests per person
8
Concurrent executions per person across batches
8
Calls per EXECUTE batch
10
Concurrent calls within a batch
5
Call timeout (seconds)
45
Batch deadline (seconds)
180
MCP request body (bytes)
1048576
Upstream response (bytes)
5242880
Result text (characters)
100000
Search queries per call
7
Schemas per call
30
Connection wait (seconds)
120
Pending approvals per person
20
Approval lifetime (seconds)
1800
Active API keys per person
10
Active custom apps per company (directory apps excluded)
50

HTTP 429 means a request or concurrency limit was reached. Wait before retrying; use Retry-After when present. A failed or timed-out write may already have reached the app, so check its outcome before retrying.