How AI Connects to the World: A Beginner’s Guide to APIs and MCP

How AI Connects to the World: A Beginner’s Guide to APIs and MCP

Every app you use is powered by APIs. Every AI agent you will build needs MCP. This guide teaches you both from scratch: what they are, how they work, why they exist, real-world use cases, and step-by-step how to use and build them yourself. By the end, you should understand the two protocols that power modern AI development. Two questions started this article: A developer at my cloud computing bootcamp raised her hand during Week 4 and asked two questions that stopped me cold, not because they were basic, but because they exposed a gap that almost every beginner faces: "What exactly IS an API? Like what is actually happening?" And then, a week later: "Everyone keeps saying MCP. What does that actually mean and why should I care?" These are the right questions. They are also questions that most tutorials skip because they assume you already know the answer. This article does not skip them. PART 1: APIs, The Foundation of Everything What is an API, actually? API stands for Application Programming Interface. That definition tells you almost nothing, so let's try a better one. An API is a defined way for two pieces of software to talk to each other. That's it. That's the whole thing. The word "interface" is the key. An interface is a defined boundary between two things. The steering wheel of a car is an interface between you and the car's engine; you do not need to understand combustion to drive. An API is the same thing: a defined boundary between your code and someone else's software, so you do not need to understand their implementation to use their functionality. Here is the simplest possible example. When your weather app shows you tomorrow's forecast, it does not have its own weather satellites. It calls a weather API; it sends a request to a weather company's servers, and the weather company's servers send back forecast data. Your app and the weather company's servers never share code, never share a database, and may be running in completely different programming languages. They communicate only through the API. YOUR APP WEATHER API SERVER │ │ │ "Give me the forecast │ │ for London tomorrow" │ │─────────────────────────────>│ │ │ │ "Temperature: 18°C, │ │ Condition: Cloudy, │ │ Rain probability: 40%" │ │ list: """Search the customer database by name or email""" # AI calls this when it needs customer data return db.search(query, limit=limit) When the AI decides it needs to find a customer, it calls this tool. The tool runs the actual database query and returns results. The AI never touches the database directly, it only communicates through the MCP protocol. Resources: data the AI can read Resources are data sources the AI can access for context: # Example resource: company documentation @mcp.resource("docs://{section}") def get_documentation(section: str) -> str: """Read a section of internal documentation""" return docs_store.get(section) Resources are read-only. The AI can read them to get context for answering questions. A knowledge base, a documentation system, or a file system would all be exposed as resources. Prompts: reusable templates Prompts are pre-built templates that guide how the AI should interact with specific tools: # Example prompt: code review template @mcp.prompt() def code_review_prompt(language: str, code: str) -> str: """A standardised prompt for reviewing code""" return f"""Review this {language} code for: 1. Security vulnerabilities 2. Performance issues 3. Code style and readability Code: {code} Provide specific, actionable feedback.""" Building Your First MCP Server: Complete Step-by-Step We will build an MCP server that connects an AI to your Task API from Part 1. This means any MCP-compatible AI (Claude, GPT-4o, Gemini) can create, read, and manage tasks by talking to your API through the MCP server. Install the MCP SDK: pip install mcp requests Build the complete MCP server:# task_mcp_server.py """ MCP Server for the Task API. This server exposes the Task API to any MCP-compatible AI. Once running, Claude Desktop (or any MCP host) can discover and use these tools automatically. Run with: python task_mcp_server.py """ import json import requests import os from mcp.server import Server from mcp.server.stdio import stdio_server from mcp import types # Task API configuration TASK_API_BASE = os.environ.get("TASK_API_URL", "http://localhost:8000") TASK_API_KEY = os.environ.get("TASK_API_KEY", "sk_dev_abc123") # Create the MCP server server = Server("task-manager") # Shared HTTP session session = requests.Session() session.headers.update({ "Authorization": f"Bearer {TASK_API_KEY}", "Content-Type": "application/json" }) # ── Helper function ─────────────────────────────────────────── def call_api(method: str, path: str, data: dict = None) -> dict: """Call the Task API and handle errors""" url = f"{TASK_API_BASE}{path}" try: if method == "GET": response = session.get(url, params=data) elif method == "POST": response = session.post(url, json=data) elif method == "PATCH": response = session.patch(url, json=data) elif method == "DELETE": response = session.delete(url) if response.status_code == 204: return {"success": True, "message": "Deleted successfully"} return response.json() if response.status_code >= 400: error = response.json() return {"error": True, "message": error.get("detail", "API error")} return response.json() except requests.exceptions.ConnectionError: return { "error": True, "message": "Cannot connect to Task API. Is it running?" } # ── Tool Definitions ────────────────────────────────────────── # This tells the MCP client what tools are available. # The AI reads these descriptions to decide which tool to call. @server.list_tools() async def list_tools() -> list[types.Tool]: """Return all available tools to the MCP client""" return [ types.Tool( name="create_task", description=( "Create a new task in the task management system. " "Use this when the user wants to add a new task, " "to-do item, or action item." ), inputSchema={ "type": "object", "required": ["title"], "properties": { "title": { "type": "string", "description": "The task title (required)" }, "description": { "type": "string", "description": "Detailed task description (optional)" }, "priority": { "type": "string", "enum": ["low", "medium", "high", "critical"], "description": "Task priority level. Default: medium", "default": "medium" }, "due_date": { "type": "string", "description": "Due date in YYYY-MM-DD format (optional)" } } } ), types.Tool( name="list_tasks", description=( "List all tasks. Can filter by status or priority. " "Use this when the user asks to see their tasks, " "what's on their to-do list, or what's due." ), inputSchema={ "type": "object", "properties": { "status": { "type": "string", "enum": ["todo", "in_progress", "done", "cancelled"], "description": "Filter by status (optional)" }, "priority": { "type": "string", "enum": ["low", "medium", "high", "critical"], "description": "Filter by priority (optional)" } } } ), types.Tool( name="get_task", description="Get details of a specific task by its ID.", inputSchema={ "type": "object", "required": ["task_id"], "properties": { "task_id": { "type": "string", "description": "The task ID (starts with task_)" } } } ), types.Tool( name="update_task", description=( "Update a task's details or status. " "Use this to mark tasks as done, change priority, " "or update any task field." ), inputSchema={ "type": "object", "required": ["task_id"], "properties": { "task_id": { "type": "string", "description": "The task ID to update" }, "status": { "type": "string", "enum": ["todo", "in_progress", "done", "cancelled"], "description": "New status" }, "priority": { "type": "string", "enum": ["low", "medium", "high", "critical"], "description": "New priority" }, "title": { "type": "string", "description": "New title" }, "due_date": { "type": "string", "description": "New due date in YYYY-MM-DD format" } } } ), types.Tool( name="delete_task", description=( "Delete a task permanently. " "Use this when the user explicitly asks to remove or delete a task. " "Always confirm with the user before deleting." ), inputSchema={ "type": "object", "required": ["task_id"], "properties": { "task_id": { "type": "string", "description": "The task ID to delete" } } } ) ] # ── Tool Execution ──────────────────────────────────────────── # This runs when the AI decides to call a tool. @server.call_tool() async def call_tool( name: str, arguments: dict ) -> list[types.TextContent]: """Execute a tool call from the AI""" result = None if name == "create_task": result = call_api("POST", "/tasks", { "title": arguments["title"], "description": arguments.get("description"), "priority": arguments.get("priority", "medium"), "due_date": arguments.get("due_date") }) elif name == "list_tasks": params = {} if "status" in arguments: params["status"] = arguments["status"] if "priority" in arguments: params["priority"] = arguments["priority"] result = call_api("GET", "/tasks", params if params else None) elif name == "get_task": result = call_api("GET", f"/tasks/{arguments['task_id']}") elif name == "update_task": task_id = arguments.pop("task_id") result = call_api("PATCH", f"/tasks/{task_id}", arguments) elif name == "delete_task": result = call_api("DELETE", f"/tasks/{arguments['task_id']}") else: result = {"error": True, "message": f"Unknown tool: {name}"} # Return result as formatted text return [types.TextContent( type="text", text=json.dumps(result, indent=2) )] # ── Resources ───────────────────────────────────────────────── # Data the AI can read for context @server.list_resources() async def list_resources() -> list[types.Resource]: """Return available resources""" return [ types.Resource( uri="tasks://summary", name="Task Summary", description="A summary of all current tasks grouped by status", mimeType="application/json" ) ] @server.read_resource() async def read_resource(uri: str) -> str: """Return resource content""" if uri == "tasks://summary": all_tasks = call_api("GET", "/tasks") if "error" in all_tasks: return json.dumps({"error": "Could not load tasks"}) tasks = all_tasks.get("data", []) # Group by status summary = { "todo": [], "in_progress": [], "done": [], "cancelled": [] } for task in tasks: status = task.get("status", "todo") if status in summary: summary[status].append({ "id": task["id"], "title": task["title"], "priority": task["priority"] }) return json.dumps({ "total_tasks": len(tasks), "by_status": { status: len(items) for status, items in summary.items() }, "tasks": summary }, indent=2) raise ValueError(f"Resource not found: {uri}") # ── Prompts ─────────────────────────────────────────────────── @server.list_prompts() async def list_prompts() -> list[types.Prompt]: """Return available prompt templates""" return [ types.Prompt( name="daily_review", description="Review today's tasks and plan priorities", arguments=[ types.PromptArgument( name="focus_area", description="What to focus on today (optional)", required=False ) ] ) ] @server.get_prompt() async def get_prompt( name: str, arguments: dict | None ) -> types.GetPromptResult: """Return a prompt template""" if name == "daily_review": focus = arguments.get("focus_area", "everything") if arguments else "everything" return types.GetPromptResult( description="Daily task review prompt", messages=[ types.PromptMessage( role="user", content=types.TextContent( type="text", text=f"""Please review my current tasks and help me plan my day. Focus area: {focus} Please: 1. Show me all my current tasks using the list_tasks tool 2. Identify the highest priority items 3. Suggest a realistic plan for today 4. Flag any overdue items 5. Ask if I want to create any new tasks Start by fetching my task list.""" ) ) ] ) raise ValueError(f"Prompt not found: {name}") # ── Run the server ───────────────────────────────────────────── async def main(): """Start the MCP server using stdio transport""" async with stdio_server() as (read_stream, write_stream): await server.run( read_stream, write_stream, server.create_initialization_options() ) if __name__ == "__main__": import asyncio asyncio.run(main()) Connect Your MCP Server to Claude Desktop Now let's connect the server to Claude Desktop so you can actually use it: Step 1: Install Claude Desktop. Download from claude.ai/download Step 2: Find the config file # macOS open ~/Library/Application\ Support/Claude/ # Windows # %APPDATA%\Claude\ # The file to edit: # claude_desktop_config.json Step 3: Add your MCP server{ "mcpServers": { "task-manager": { "command": "python", "args": ["/full/path/to/task_mcp_server.py"], "env": { "TASK_API_URL": "http://localhost:8000", "TASK_API_KEY": "sk_dev_abc123" } } } } Step 4: Restart Claude DesktopYou will see a tools icon in the chat interface. Click it; your Task Manager tools should appear. Step 5: Test it by typing:"Show me all my tasks" "Create a high-priority task to review the Q3 report, due 2026-08-01" "Mark task_7Km3pQx9 as done" "What tasks do I have due this week?" Claude will automatically call your MCP tools to answer these questions; no API knowledge required from the user. Testing Your MCP Server Without Claude Desktop You do not need Claude Desktop to test your MCP server. Use the MCP Inspector: pip install mcp[cli] # Test your server interactively mcp dev task_mcp_server.py Or write a test script:# test_mcp_server.py import asyncio import json from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def test_mcp_server(): """Test the MCP server programmatically""" server_params = StdioServerParameters( command="python", args=["task_mcp_server.py"], env={ "TASK_API_URL": "http://localhost:8000", "TASK_API_KEY": "sk_dev_abc123" } ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: # Initialise the connection await session.initialize() # Test 1: List available tools tools = await session.list_tools() print(f"\n Available tools ({len(tools.tools)}):") for tool in tools.tools: print(f" - {tool.name}: {tool.description[:60]}...") # Test 2: Create a task print("\n Creating a task...") result = await session.call_tool( "create_task", arguments={ "title": "Test task from MCP", "priority": "high", "due_date": "2026-08-01" } ) task_data = json.loads(result.content[0].text) print(f" Created: {task_data['id']} - {task_data['title']}") # Test 3: List tasks print("\n Listing tasks...") result = await session.call_tool("list_tasks", arguments={}) tasks = json.loads(result.content[0].text) print(f" Found {tasks['total']} tasks") # Test 4: Mark as done task_id = task_data["id"] print(f"\n Marking {task_id} as done...") result = await session.call_tool( "update_task", arguments={"task_id": task_id, "status": "done"} ) updated = json.loads(result.content[0].text) print(f" Status: {updated['status']}") # Test 5: Read resource print("\n Reading task summary resource...") resource = await session.read_resource("tasks://summary") summary = json.loads(resource.contents[0].text) print(f" Total tasks: {summary['total_tasks']}") print(f" By status: {summary['by_status']}") print("\n All tests passed!") if __name__ == "__main__": asyncio.run(test_mcp_server()) # Run the test python test_mcp_server.py API vs MCP**:** When to Use Which This is the question every developer asks when they first encounter MCP: Situation Use API directly Use MCP Building a traditional web app ✅ ❌ Integrating a service into backend code ✅ ❌ Connecting a tool to an AI agent ❌ ✅ Letting AI discover tools dynamically ❌ ✅ Building one integration that works with multiple AI models ❌ ✅ Giving Claude Desktop access to your systems ❌ ✅ Automated workflows without AI ✅ ❌ Understanding when to use MCP versus a direct API integration is important: AI agent interactions, when an AI model needs to discover and use tools dynamically during a conversation or task. Multi-tool workflows: when an agent needs to chain together multiple tools in a single workflow and the specific tools may vary. Tool discovery: when the AI needs to understand what tools are available and what they can do, without hardcoded knowledge. The simple rule: APIs are for code-to-service communication. MCP is for AI-to-tool communication. Real-World MCP Use Cases By 2026, a majority of enterprise AI tools ship MCP servers. The question "Does it have an MCP server?" is becoming standard before buying enterprise software. Here are the real scenarios where MCP is being deployed: Developer Tools Claude in VS Code can read your codebase, run your tests, check your CI status, and create GitHub issues, all through MCP servers for each tool. You write code. Claude has full context of your environment. Customer Service An AI customer service agent connects to your CRM (Salesforce MCP), your order management system, your knowledge base, and your ticketing system through MCP servers. When a customer asks a question, the AI pulls real-time context from all these systems through a single protocol. Business Automation Scheduled agent: Every morning at 9am: check GitHub issues, create a priority list, and post it to #engineering Slack. The agent calls GitHub MCP to fetch open issues, calls internal priority rules to score each issue, and calls Slack MCP to post the formatted summary. Personal Productivity Connect Claude to your calendar, email, and task manager through MCP. "Schedule a meeting with the engineering team next week and send them the agenda from my draft emails" becomes a single instruction that Claude executes by calling multiple MCP servers. Enterprise Data Access The Model Context Protocol standardises resource shapes, documents, database rows, files, reducing serialisation complexity so AI models receive relevant context optimised for reasoning. Developers can reuse existing MCP server implementations for popular enterprise systems and extend them to domain-specific use cases through the open standard. Security: The Part Nobody Tells You MCP gives AI access to real tools and real data. Security is not optional. Principle of least privilege always # Wrong — broad permissions @server.call_tool() async def call_tool(name: str, arguments: dict): # This can read, write, and delete anything return execute_sql(arguments["query"]) # Correct — specific, limited permissions @server.call_tool() async def call_tool(name: str, arguments: dict): if name == "search_customers": # Only SELECT allowed, only customers table, no sensitive columns query = f""" SELECT id, name, email FROM customers WHERE name ILIKE '%{arguments['query']}%' LIMIT {min(arguments.get('limit', 10), 50)} """ return db.execute_readonly(query) Human confirmation for destructive actions types.Tool( name="delete_customer", description=( "Delete a customer record. " "IMPORTANT: Always ask the user to confirm before calling this tool. " "This action is irreversible." ), # ... schema ) The description is what the AI reads to decide when to call a tool. Including confirmation instructions directly in the description changes AI behaviour. Validate every input @server.call_tool() async def call_tool(name: str, arguments: dict): if name == "execute_query": query = arguments.get("query", "") # Reject anything that modifies data forbidden = ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER", "CREATE"] if any(word in query.upper() for word in forbidden): return [types.TextContent( type="text", text=json.dumps({ "error": "Only SELECT queries are allowed through this tool" }) )] Companies should start with a concrete use case, limit permissions, and measure the value before gradually extending connections. The best approach is to start with a limited use case, connect only a calendar and a CRM to generate sales briefs before extending to email or financial tools. This progression limits risks and makes adoption easier for teams. What to Build Next You now understand APIs and MCP from the ground up. Here is the natural progression: This week: Build the Task API and MCP server from this article. Get it working with Claude Desktop. Next week: Add a second MCP tool, a weather API, a GitHub integration, or a simple database query. Practice the pattern. Next month: Build an MCP server for something you actually use every day. Your own notes system, your company's internal tools, your project management system. Next quarter: Build a multi-tool AI workflow, an agent that chains multiple MCP servers together to complete a complex task automatically. The combination of API knowledge and MCP knowledge is rare right now. Most developers know one or the other. The developers who understand both, who can build the API AND build the MCP server that connects it to AI, are the ones building the tools that the rest of the industry will use. That starts here. References [1] Anthropic. Model Context Protocol Official Documentation. https://modelcontextprotocol.io/introduction [2] SitePoint / Complete Guide. MCP (Model Context Protocol): Complete 2026 Guide for AI Integration. March 2026. https://www.sitepoint.com/model-context-protocol-mcp/ [3] Databricks. What is the Model Context Protocol (MCP)? https://www.databricks.com/blog/what-is-model-context-protocol [4] FastAPI. FastAPI Documentation. https://fastapi.tiangolo.com/ [5] MCP Python SDK. MCP Python SDK Documentation. https://github.com/modelcontextprotocol/python-sdk

Original Source

Read the full article at Hackernoon →

KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.