Build a Custom MCP Client

This article explains how to build a custom client for the Demandbase Model Context Protocol (MCP) server using OAuth Dynamic Client Registration (DCR), the Authorization Code flow with PKCE, and MCP Streamable HTTP.

The steps are language agnostic. You can implement them with any HTTP client, JSON parser, browser launcher, hosted HTTPS redirect handler, and secure token store.

Prerequisites

  • Model Context Protocol (MCP) must be enabled for your Demandbase account.
  • A valid Demandbase user account.
  • A hosted HTTPS redirect URI for your application.
  • The exact redirect URI must be allowlisted by Demandbase Support for your client_id (application ID).
  • A secure location to store your OAuth client credentials and access tokens.

Connect to the Demandbase MCP Server

Endpoints

Use the following public Demandbase MCP endpoints:

MCP server:
https://gateway.demandbase.com/mcp/servers/db-mcp

Protected resource metadata:
https://gateway.demandbase.com/.well-known/oauth-protected-resource

Important: Do not hardcode the authorization or token endpoints. Discover them from the OAuth metadata.

Step 1: Configure a Redirect URI

Use an HTTPS redirect URI hosted by your application, for example:

https://your-app.example.com/oauth/demandbase/callback

The redirect URI must match exactly. The scheme, host, path, and trailing slash are all significant.

For example, these are different redirect URIs:

https://your-app.example.com/oauth/demandbase/callback
https://your-app.example.com/oauth/demandbase/callback/

Step 2: Discover OAuth Metadata

Fetch the protected resource metadata:

GET https://gateway.demandbase.com/.well-known/oauth-protected-resource
Accept: application/json

Retrieve the following values:

  • resource
  • authorization_servers
  • scopes_supported

Request the authorization server metadata using the advertised authorization server.

Current metadata endpoint:

GET https://gateway.demandbase.com/.well-known/oauth-authorization-server
Accept: application/json

Retrieve:

  • issuer
  • authorization_endpoint
  • token_endpoint
  • registration_endpoint
  • token_endpoint_auth_methods_supported
  • scopes_supported

Store client registrations and tokens using the issuer as the key.

Step 3: Register Your App and Get the Client ID ((application ID)

Register your application using the discovered registration_endpoint.

The response returns a client_id, which serves as your application ID.

Example request:

POST {registration_endpoint}
Accept: application/json
Content-Type: application/json

{
  "client_name": "Your MCP Client",
  "application_type": "web",
  "redirect_uris": ["https://your-app.example.com/oauth/demandbase/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none",
  "scope": "openid profile email offline_access"
}

Example response:

{
  "client_id": "0oaexampleclientid",
  "redirect_uris": ["https://your-app.example.com/oauth/demandbase/callback"],
  "token_endpoint_auth_method": "none",
  "scope": "openid profile email offline_access"
}

Store the registration response securely, including:

  • client_id
  • redirect_uris
  • token_endpoint_auth_method
  • scope
  • client_secret (if issued)

Step 4: Ask Demandbase Support to Allowlist the Redirect URI

Before testing authentication, contact Demandbase Support and provide:

  • Your client_id (application ID)
  • Your exact HTTPS redirect URI

Important: Demandbase Support must allowlist the redirect URI for your application before the OAuth flow can succeed.

Step 5: Authenticate with PKCE

Generate:

code_verifier = high_entropy_random_string
code_challenge = base64url(sha256(code_verifier))
code_challenge_method = S256

Generate and store a random state value with the code_verifier.

Build the authorization URL:

{authorization_endpoint}
  ?response_type=code
  &client_id={client_id}
  &redirect_uri={url_encoded_redirect_uri}
  &scope=openid%20profile%20email%20offline_access
  &state={state}
  &code_challenge={code_challenge}
  &code_challenge_method=S256
  &resource={url_encoded_resource}

Open the authorization URL in the user's browser.

Step 6: Handle the OAuth Callback

After authentication, Demandbase redirects the browser to your hosted redirect URI:

{redirect_uri}?code={authorization_code}&state={state}

Your application should:

  • Validate the state
  • Reject OAuth error responses
  • Extract the authorization code
  • Exchange the code immediately
  • Discard the temporary state and code_verifier

Step 7: Exchange the Code for Tokens

Send a form-encoded request to the discovered token_endpoint:

POST {token_endpoint}
Accept: application/json
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code={authorization_code}&
redirect_uri={exact_redirect_uri}&
client_id={client_id}&
code_verifier={code_verifier}

Store:

  • access_token
  • refresh_token, if returned
  • expires_at
  • scope
  • issuer
  • client_id

When the access token expires, refresh it:

POST {token_endpoint}
Accept: application/json
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&
refresh_token={refresh_token}&
client_id={client_id}

Refresh tokens may rotate. If a new refresh token is returned, replace the previous one.

Step 8: Send MCP Requests to the Server

Send JSON-RPC requests to:

https://gateway.demandbase.com/mcp/servers/db-mcp

Use these headers:

Authorization: Bearer {access_token}
Content-Type: application/json
Accept: application/json, text/event-stream

If the server returns an MCP session ID during initialization, include it in subsequent requests.

Initialize:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {},
    "clientInfo": {
      "name": "your-mcp-client",
      "version": "1.0.0"
    }
  }
}

List Available Tools:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {}
}

Call a tool:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "company_global_directory",
    "arguments": {
      "company_name": "Demandbase"
    }
  }
}

Always use tools/list as the authoritative source for available tools, descriptions, and input schemas.

Step 9: Parse Responses

The MCP server returns either:

  • application/json: Single JSON-RPC response
  • text/event-stream: Server-Sent Events containing JSON-RPC messages

For SSE responses:

  • Collect all data: lines until a blank line.
  • Parse the combined event data as JSON.
  • Continue until the final JSON-RPC response is received.

Always check for a JSON-RPC error object, even when the HTTP status is 200 OK.

After completing these steps, your application can authenticate with the Demandbase MCP server and invoke available MCP tools on behalf of an authorized user.

Troubleshooting

ProblemResolution
Redirect URI errorVerify that Demandbase Support allowlisted the exact redirect URI for your client_id
DCR returns 401 or 403The registration endpoint may require additional authorization. Contact Demandbase Support.
Token exchange returns invalid_grantThe authorization code may be expired, already used, or associated with a different redirect URI or PKCE verifier. Restart the login flow.
MCP request returns 401Refresh the access token or authenticate again.
MCP request returns 403Verify that Demandbase MCP is enabled for the account and user.
Tool validation errorRefresh the tool definition using tools/list and ensure the request matches the tool's inputSchema

Production Checklist

  • Discover OAuth endpoints from metadata.
  • Use the Authorization Code flow with PKCE.
  • Validate the state parameter.
  • Store OAuth tokens securely.
  • Refresh access tokens before they expire.
  • Never log authorization codes or tokens.
  • Validate tool arguments against each tool's inputSchema.
  • Support both JSON and Server-Sent Event responses.
  • Send requests only to the Demandbase MCP gateway endpoint.

References



Did this page help you?