MCP & agent access control
This guide is for Sellers whose licensed offering is an MCP server, API or agent tool used through AI assistants and agent platforms.
Who does what
Section titled “Who does what”| Responsibility | Your delivery system | LicenChain |
|---|---|---|
| Authenticate the user (OAuth 2.1 / OIDC) | ✅ Your own authorization server or identity provider | — |
| Map a named user to a customer organization, product and seat assignment | ✅ | — |
| Record license terms, Buyer acceptance and issuance | — | ✅ |
| Report whether the corporate license is currently valid | — | ✅ Verify API |
| Allow or deny each session or tool call | ✅ | — |
LicenChain verifies the corporate license held by the Buyer organization. It is not an OAuth identity provider for AI assistants, does not issue user tokens, and is not an MCP host. An NFT or other blockchain record does not by itself enforce access; your delivery system does.
Authorization flow per session or tool call
Section titled “Authorization flow per session or tool call”- Authenticate. The AI client presents an access token issued by your authorization server. Validate it as your MCP server’s OAuth guidance requires (issuer, audience, expiry, scopes).
- Resolve the user. Map the token subject to a customer organization, the licensed product and the user’s seat assignment in your own database.
- Look up the license. Find the
licenceKeyyou stored fromlicence.issuedfor that organization and product. - Verify. Call the verification API from your server — or reuse a short-lived positive result
that has not passed
validUntil. - Decide. Allow only if steps 1–4 all succeed and
valid === true. Otherwise return your normal authorization error to the client.
curl --request POST "$LICENCHAIN_API_BASE_URL/api/integrations/v1/licenses/verify" \ --header "Authorization: Bearer $LICENCHAIN_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "licenceKey": "11155111:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:42"}'// Server-side only. Returns true only when LicenChain reports valid === true.// Every other outcome (non-200, timeout, malformed body, valid !== true) denies new access.const baseUrl = process.env.LICENCHAIN_API_BASE_URL;const apiKey = process.env.LICENCHAIN_API_KEY;
export async function isLicenceValid(licenceKey: string): Promise<boolean> { const payload = { licenceKey }; try { const response = await fetch(`${baseUrl}/api/integrations/v1/licenses/verify`, { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(payload), signal: AbortSignal.timeout(5000), }); if (response.status !== 200) return false; const result: { valid?: unknown } = await response.json(); return result.valid === true; } catch { return false; }}
// Example: the licenceKey you stored from the licence.issued event.const allowed = await isLicenceValid('11155111:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:42');# Server-side only. Returns True only when LicenChain reports valid is True.# Every other outcome (non-200, timeout, malformed body, valid is not True) denies new access.import osimport requests
base_url = os.environ["LICENCHAIN_API_BASE_URL"]api_key = os.environ["LICENCHAIN_API_KEY"]
def is_licence_valid(licence_key: str) -> bool: payload = {"licenceKey": licence_key} try: response = requests.post( f"{base_url}/api/integrations/v1/licenses/verify", headers={"Authorization": f"Bearer {api_key}"}, json=payload, timeout=5, ) if response.status_code != 200: return False return response.json().get("valid") is True except (requests.RequestException, ValueError): return False
# Example: the licenceKey you stored from the licence.issued event.allowed = is_licence_valid("11155111:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:42")Safety rules for agent integrations
Section titled “Safety rules for agent integrations”- Make the verification call in server code that the model cannot influence. Do not let a tool
argument choose the
licenceKey; derive it from the authenticated user’s organization. - Fail closed. If verification is unavailable, deny new sessions and tool calls that need the license.
- Return a generic, non-sensitive error to the agent (for example “License not active for your organization”) rather than raw API responses.
- Re-check the license periodically for long-lived sessions instead of trusting the first result forever.
Example policy
Section titled “Example policy”// Pseudocode for an MCP tool handler in your delivery system.async function authorizeToolCall(accessToken: string): Promise<boolean> { const user = await validateAccessToken(accessToken); // your OAuth / OIDC provider if (!user) return false;
const assignment = await findSeatAssignment(user.id); // your database if (!assignment) return false;
// Server-side LicenChain check on the corporate license (see Verify license). return isLicenceValid(assignment.licenceKey);}validateAccessToken and findSeatAssignment are placeholders for your own identity and data
layer. isLicenceValid is the helper from Verify license.