{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-mcp/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Building Your Own Agent","description":"Checkr developer documentation for background check APIs, embeds, and partner integrations."},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"building-your-own-agent","__idx":0},"children":["Building Your Own Agent"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A minimal Python agent — about 80 lines — that authenticates against the Candidate MCP, runs the OTP flow, and retrieves a candidate's report through an OpenAI tool-calling loop."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"dependencies","__idx":1},"children":["Dependencies"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"shell","header":{"controls":{"copy":{}}},"source":"pip install mcp openai httpx python-dotenv\n","lang":"shell"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["mcp"]}," — official MCP client (handles protocol, sessions, transport)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["openai"]}," — LLM with function calling"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["httpx"]}," — OAuth token exchange"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["python-dotenv"]}," — load credentials from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"configuration","__idx":2},"children":["Configuration"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]}," next to your script. Each value has a different source:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ini","header":{"controls":{"copy":{}}},"source":"# From Checkr Dashboard → Account → Developer Settings → Generate MCP Credentials\nOAUTH_CLIENT_ID=<your_client_id>\nOAUTH_CLIENT_SECRET=<your_client_secret>     # shown once — store in a secrets manager\nOAUTH_TOKEN_ENDPOINT=<your_token_endpoint>\n\n# Production: https://mcp.checkr.com/candidate/\n# Staging:    https://mcp.checkr-staging.com/candidate/\nMCP_URL=https://mcp.checkr-staging.com/candidate/\n\n# Generate at https://platform.openai.com/api-keys — swap for any\n# function-calling-capable model (Claude, Gemini, etc.)\nOPENAI_API_KEY=<your_openai_key>\nOPENAI_BASE_URL=https://api.openai.com/v1\n","lang":"ini"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"imports-and-client-setup","__idx":3},"children":["Imports and client setup"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Load the env vars and instantiate the OpenAI client at module scope."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import asyncio\nimport json\nimport os\n\nimport httpx\nfrom dotenv import load_dotenv\nfrom mcp.client.session import ClientSession\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom openai import OpenAI\n\nload_dotenv()\n\nllm = OpenAI(\n    api_key=os.environ[\"OPENAI_API_KEY\"],\n    base_url=os.environ.get(\"OPENAI_BASE_URL\"),\n)\n\nSYSTEM_PROMPT = (\n    \"You are a background check assistant. \"\n    \"Use the available tools to verify the candidate's identity \"\n    \"and retrieve their report. Present all data clearly. \"\n    \"Never fabricate information.\"\n)\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"get-a-bearer-token","__idx":4},"children":["Get a bearer token"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["get_token()"]}," runs an OAuth Client Credentials grant against the token endpoint."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"def get_token():\n    resp = httpx.post(os.environ[\"OAUTH_TOKEN_ENDPOINT\"], data={\n        \"grant_type\": \"client_credentials\",\n        \"client_id\": os.environ[\"OAUTH_CLIENT_ID\"],\n        \"client_secret\": os.environ[\"OAUTH_CLIENT_SECRET\"],\n        \"scope\": \"candidate:read\",\n    })\n    resp.raise_for_status()\n    return resp.json()[\"access_token\"]\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"adapt-mcp-tools-to-openais-format","__idx":5},"children":["Adapt MCP tools to OpenAI's format"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The MCP server advertises its tools in MCP format; OpenAI's function-calling API expects a slightly different shape. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["mcp_tools_to_openai()"]}," translates between them."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"def mcp_tools_to_openai(tools):\n    return [{\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": tool.name,\n            \"description\": tool.description or \"\",\n            \"parameters\": tool.inputSchema or {\"type\": \"object\", \"properties\": {}},\n        },\n    } for tool in tools]\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"run-the-agent-loop","__idx":6},"children":["Run the agent loop"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["run_agent()"]}," opens an MCP session, discovers the tools, then loops: read user input → ask the LLM → if it asks for a tool, call MCP and feed the result back → otherwise print the response."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"async def run_agent():\n    async with streamablehttp_client(\n        url=os.environ[\"MCP_URL\"],\n        headers={\"Authorization\": f\"Bearer {get_token()}\"},\n    ) as (read, write, _):\n        async with ClientSession(read, write) as session:\n            await session.initialize()\n\n            tools_result = await session.list_tools()\n            openai_tools = mcp_tools_to_openai(tools_result.tools)\n            print(f\"Connected. Tools: {[t.name for t in tools_result.tools]}\")\n\n            messages = [{\"role\": \"system\", \"content\": SYSTEM_PROMPT}]\n\n            while True:\n                user_input = input(\"\\nYou: \").strip()\n                if user_input.lower() in (\"quit\", \"exit\"):\n                    break\n                if not user_input:\n                    continue\n                messages.append({\"role\": \"user\", \"content\": user_input})\n\n                while True:\n                    response = llm.chat.completions.create(\n                        model=\"gpt-4o\",\n                        messages=messages,\n                        tools=openai_tools,\n                    )\n                    choice = response.choices[0]\n                    messages.append(choice.message.model_dump(exclude_none=True))\n\n                    if choice.finish_reason != \"tool_calls\":\n                        print(f\"\\nAgent: {choice.message.content}\")\n                        break\n\n                    for tool_call in choice.message.tool_calls:\n                        tool_name = tool_call.function.name\n                        tool_args = json.loads(tool_call.function.arguments)\n                        print(f\"  [calling {tool_name}({tool_args})]\")\n                        result = await session.call_tool(tool_name, tool_args)\n                        messages.append({\n                            \"role\": \"tool\",\n                            \"tool_call_id\": tool_call.id,\n                            \"content\": json.dumps(\n                                [b.model_dump() for b in result.content],\n                                default=str,\n                            ),\n                        })\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"entry-point","__idx":7},"children":["Entry point"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"if __name__ == \"__main__\":\n    asyncio.run(run_agent())\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"full-file","__idx":8},"children":["Full file"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Save as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["agent.py"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"\"\"\"Minimal Checkr Candidate MCP agent.\"\"\"\n\nimport asyncio\nimport json\nimport os\n\nimport httpx\nfrom dotenv import load_dotenv\nfrom mcp.client.session import ClientSession\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom openai import OpenAI\n\nload_dotenv()\n\nllm = OpenAI(\n    api_key=os.environ[\"OPENAI_API_KEY\"],\n    base_url=os.environ.get(\"OPENAI_BASE_URL\"),\n)\n\nSYSTEM_PROMPT = (\n    \"You are a background check assistant. \"\n    \"Use the available tools to verify the candidate's identity \"\n    \"and retrieve their report. Present all data clearly. \"\n    \"Never fabricate information.\"\n)\n\n\ndef get_token():\n    resp = httpx.post(os.environ[\"OAUTH_TOKEN_ENDPOINT\"], data={\n        \"grant_type\": \"client_credentials\",\n        \"client_id\": os.environ[\"OAUTH_CLIENT_ID\"],\n        \"client_secret\": os.environ[\"OAUTH_CLIENT_SECRET\"],\n        \"scope\": \"candidate:read\",\n    })\n    resp.raise_for_status()\n    return resp.json()[\"access_token\"]\n\n\ndef mcp_tools_to_openai(tools):\n    return [{\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": tool.name,\n            \"description\": tool.description or \"\",\n            \"parameters\": tool.inputSchema or {\"type\": \"object\", \"properties\": {}},\n        },\n    } for tool in tools]\n\n\nasync def run_agent():\n    async with streamablehttp_client(\n        url=os.environ[\"MCP_URL\"],\n        headers={\"Authorization\": f\"Bearer {get_token()}\"},\n    ) as (read, write, _):\n        async with ClientSession(read, write) as session:\n            await session.initialize()\n\n            tools_result = await session.list_tools()\n            openai_tools = mcp_tools_to_openai(tools_result.tools)\n            print(f\"Connected. Tools: {[t.name for t in tools_result.tools]}\")\n\n            messages = [{\"role\": \"system\", \"content\": SYSTEM_PROMPT}]\n\n            while True:\n                user_input = input(\"\\nYou: \").strip()\n                if user_input.lower() in (\"quit\", \"exit\"):\n                    break\n                if not user_input:\n                    continue\n                messages.append({\"role\": \"user\", \"content\": user_input})\n\n                while True:\n                    response = llm.chat.completions.create(\n                        model=\"gpt-4o\",\n                        messages=messages,\n                        tools=openai_tools,\n                    )\n                    choice = response.choices[0]\n                    messages.append(choice.message.model_dump(exclude_none=True))\n\n                    if choice.finish_reason != \"tool_calls\":\n                        print(f\"\\nAgent: {choice.message.content}\")\n                        break\n\n                    for tool_call in choice.message.tool_calls:\n                        tool_name = tool_call.function.name\n                        tool_args = json.loads(tool_call.function.arguments)\n                        print(f\"  [calling {tool_name}({tool_args})]\")\n                        result = await session.call_tool(tool_name, tool_args)\n                        messages.append({\n                            \"role\": \"tool\",\n                            \"tool_call_id\": tool_call.id,\n                            \"content\": json.dumps(\n                                [b.model_dump() for b in result.content],\n                                default=str,\n                            ),\n                        })\n\n\nif __name__ == \"__main__\":\n    asyncio.run(run_agent())\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"run-it","__idx":9},"children":["Run it"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"shell","header":{"controls":{"copy":{}}},"source":"python agent.py\n","lang":"shell"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"Connected. Tools: ['request_candidate_verification', 'verify_candidate_otp', 'get_report']\n\nYou: I need to check my background report\nAgent: I can help with that. What email address is associated with your background check?\n\nYou: alice@example.com\n  [calling request_candidate_verification({\"email\": \"alice@example.com\"})]\nAgent: A verification code has been sent to your email. Please enter the 6-digit code.\n\nYou: 482910\n  [calling verify_candidate_otp({\"email\": \"alice@example.com\", \"code\": \"482910\"})]\n  [calling get_report()]\nAgent: Here are your background check results...\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"adapting-to-your-stack","__idx":10},"children":["Adapting to your stack"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Swap the LLM"]}," — replace ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OpenAI()"]}," with any SDK that supports function calling. The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["mcp_tools_to_openai"]}," converter would change shape; the rest of the loop is the same."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Cache the token"]}," — bearer tokens last 1 hour. In production, cache and refresh shortly before expiry rather than minting a fresh token per request."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Add a web UI"]}," — wrap ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["run_agent()"]}," in a FastAPI/Flask endpoint instead of terminal input."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Node"]}," — use the JS/TypeScript ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://github.com/modelcontextprotocol/typescript-sdk"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@modelcontextprotocol/sdk"]}]}," with the same ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Client"]}," + ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["StreamableHTTPClientTransport"]}," pattern."]}]}]},"headings":[{"value":"Building Your Own Agent","id":"building-your-own-agent","depth":1},{"value":"Dependencies","id":"dependencies","depth":2},{"value":"Configuration","id":"configuration","depth":2},{"value":"Imports and client setup","id":"imports-and-client-setup","depth":2},{"value":"Get a bearer token","id":"get-a-bearer-token","depth":2},{"value":"Adapt MCP tools to OpenAI's format","id":"adapt-mcp-tools-to-openais-format","depth":2},{"value":"Run the agent loop","id":"run-the-agent-loop","depth":2},{"value":"Entry point","id":"entry-point","depth":2},{"value":"Full file","id":"full-file","depth":2},{"value":"Run it","id":"run-it","depth":2},{"value":"Adapting to your stack","id":"adapting-to-your-stack","depth":2}],"frontmatter":{"title":"Building Your Own Agent","sidebarTitle":"Example Agent","description":"A minimal Python agent that connects to the Candidate MCP, verifies a candidate via OTP, and retrieves their report.","seo":{"title":"Building Your Own Agent"}},"lastModified":"2026-10-26T11:44:00.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/mcp/candidate/example-agent","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}