Content
<div align="center">
# 🔌 Model Context Protocol (MCP)
### A Developer's Guide to Building MCP Servers, Clients, Tools, Resources & Prompts
[](https://modelcontextprotocol.io)
[](https://www.python.org/)
[]()
[]()
[](https://anthropic.com)
**Personal study notes from Anthropic's MCP certification course — distilled into one searchable reference.**
[Overview](#-overview) · [Architecture](#-architecture) · [Tools](#-tools) · [Resources](#-resources) · [Prompts](#-prompts) · [Cheatsheet](#-quick-reference)
</div>
---
## 📑 Table of Contents
| # | Section | What You'll Learn |
|---|---------|-------------------|
| 1 | [Overview](#-overview) | What MCP is, why it exists, the problem it solves |
| 2 | [Architecture](#-architecture) | Client ↔ Server topology, message flow, transport |
| 3 | [Project Setup](#-project-setup) | CLI chatbot project structure & first run |
| 4 | [Defining Tools](#-tools) | `@mcp.tool`, typed parameters, the two server tools |
| 5 | [MCP Inspector](#-server-inspector) | In-browser debugger for testing servers |
| 6 | [Implementing a Client](#-client-implementation) | Session wrapper, `list_tools()`, `call_tool()` |
| 7 | [Defining Resources](#-resources) | Static & templated URIs, MIME types |
| 8 | [Accessing Resources](#-accessing-resources) | `read_resource()` and response parsing |
| 9 | [Defining Prompts](#-prompts-1) | Pre-written, tested instructions as slash commands |
| 10 | [Prompts in the Client](#-prompts-in-the-client) | `list_prompts()` & `get_prompt()` |
| 11 | [MCP Review](#-mcp-review) | Tools vs Resources vs Prompts — when to use what |
| 12 | [Quick Reference](#-quick-reference) | Cheatsheet of decorators & SDK calls |
---
## 🌟 Overview
**Model Context Protocol (MCP)** is a communication layer that gives Claude (and other LLMs) structured access to context and tools — **without forcing every developer to hand-write tool schemas for every service integration.**
### The Problem MCP Solves
In the traditional approach, every developer integrating a service (e.g. GitHub) has to:
- ✍️ Manually author JSON schemas for each tool
- 🔧 Write function implementations for each endpoint
- 🔄 Maintain everything as APIs evolve
- 😩 Repeat the process for every other service (Slack, Drive, Postgres…)
```
❌ Before MCP: You → write tool schemas → write functions → call APIs → maintain forever
✅ With MCP: MCP Server already exposes tools → You just connect your client
```
### What MCP Actually Is
| Concept | Definition |
|---------|-----------|
| **MCP Client** | Communication interface between *your* app and an MCP server |
| **MCP Server** | A process that wraps an outside service (GitHub, Drive, DB…) and exposes its functionality as **tools**, **resources**, and **prompts** |
| **Transport** | Agnostic — stdin/stdout, HTTP, WebSockets — both ends agree |
| **Who builds servers?** | Anyone — but service providers usually ship official ones |
> 💡 **Core insight:** MCP shifts tool definition and execution *away* from your application code and *onto* a dedicated MCP server. The server is the interface to the outside world; your code is just the coordinator.
### MCP & Tool Use — Complementary, Not Identical
- **Tool use** = the *capability* for Claude to call functions
- **MCP** = the *who* and *how* of creating those functions
MCP answers: *"Who authors the tool? Who runs it? How does it reach Claude?"*
---
## 🏗️ Architecture
### High-Level Topology
```mermaid
flowchart LR
User([👤 User]) -->|query| App[🖥️ Your App / Server]
App <-->|list_tools / call_tool| Client[🔌 MCP Client]
Client <-->|JSON-RPC over stdio/HTTP| Server[⚙️ MCP Server]
Server -->|API calls| External[🌐 External Service<br/>GitHub · Drive · DB]
style App fill:#1e293b,stroke:#7C3AED,color:#fff
style Client fill:#7C3AED,stroke:#fff,color:#fff
style Server fill:#EC4899,stroke:#fff,color:#fff
style External fill:#0ea5e9,stroke:#fff,color:#fff
```
### Message Flow — End to End
```mermaid
sequenceDiagram
autonumber
participant U as 👤 User
participant A as 🖥️ Your Server
participant C as 🔌 MCP Client
participant S as ⚙️ MCP Server
participant L as 🤖 Claude
participant X as 🌐 External API
U->>A: "What's in report.pdf?"
A->>C: get available tools
C->>S: list_tools request
S-->>C: tools list
C-->>A: tools
A->>L: query + tools
L-->>A: "call read_doc(doc_id='report.pdf')"
A->>C: call_tool("read_doc", {doc_id})
C->>S: call_tool request
S->>X: read file
X-->>S: contents
S-->>C: tool result
C-->>A: result
A->>L: result
L-->>A: final answer
A-->>U: 💬 formatted reply
```
### Transport Options
MCP is **transport-agnostic**. The client and server just need to agree on a channel:
| Transport | When to Use |
|-----------|-------------|
| **stdin/stdout** | Both processes on the same machine (most common for local dev) |
| **HTTP** | Cross-network / cross-host deployments |
| **WebSockets** | Real-time, bidirectional, long-lived sessions |
The wire format is **always** the JSON-RPC messages defined by the MCP spec.
### Core Message Types
| Message | Direction | Purpose |
|---------|-----------|---------|
| `list_tools` request / result | Client → Server | "What tools do you offer?" |
| `call_tool` request / result | Client → Server | "Run this tool with these args" |
| `list_resources` / `read_resource` | Client → Server | Read app-controlled data |
| `list_prompts` / `get_prompt` | Client → Server | Fetch a pre-written prompt template |
---
## 🧪 Project Setup
The course project is a **CLI-based chatbot** that implements *both* a custom MCP client *and* a custom MCP server — for educational purposes.
> 🔍 **Real-world note:** Production code usually implements **one or the other** (your app is either the consumer or the provider), not both. This project does both so you can see the full handshake.
### Structure
```
mcp-learning/
├── main.py # CLI entrypoint (runs the chatbot)
├── mcp_client.py # Custom MCP client class
├── mcp_server.py # Custom MCP server with tools
├── .env # ANTHROPIC_API_KEY=...
└── pyproject.toml # uv / pip dependencies
```
### Document Store — In-Memory Only
```python
# Fake documents — no disk persistence in this learning project
docs = {
"report.pdf": "Q4 revenue was up 23%...",
"launch.md": "Initial launch target: Sept 15...",
"design.txt": "Color palette: indigo, slate, white...",
}
```
### Setup Commands
```bash
# 1. Get the starter
unzip CLI_project.zip
cd mcp-learning
# 2. Add your API key
echo "ANTHROPIC_API_KEY=sk-ant-..." > .env
# 3. Install dependencies
uv sync # if you use uv
# or
pip install -r requirements.txt
# 4. Run
uv run main.py # with uv
python main.py # without uv
```
### First-Run Verification
When the chat prompt appears, sanity-check with:
```
You: what's one plus one
Bot: 2
```
If you get an answer, your client → server → Claude loop is wired correctly.
---
## 🛠️ Tools
> **Tools** are *model-controlled* primitives. **Claude** decides when to call them.
### The Mental Model
```
Tools = capabilities you GIVE to Claude
(e.g. "run JavaScript", "read a doc", "query a DB")
→ serve the MODEL
```
### Implementation Pattern (Python SDK)
The MCP Python SDK **auto-generates** JSON schemas from decorated functions. No more hand-writing schemas.
```python
from mcp.server import FastMCP
from pydantic import Field
mcp = FastMCP("document-server")
# In-memory document store
docs: dict[str, str] = {
"report.pdf": "Q4 revenue was up 23%...",
"launch.md": "Initial launch target: Sept 15...",
}
```
### Tool #1 — `read_doc_contents`
```python
@mcp.tool(
name="read_doc_contents",
description="Read the full contents of a document by its ID."
)
def read_doc_contents(doc_id: str = Field(description="ID of the document to read")) -> str:
"""Return the document text, or raise if the ID is unknown."""
if doc_id not in docs:
raise ValueError(f"Doc with id {doc_id} not found")
return docs[doc_id]
```
### Tool #2 — `edit_document`
```python
@mcp.tool(
name="edit_document",
description="Find-and-replace a string inside a document."
)
def edit_document(
doc_id: str = Field(description="ID of the document to edit"),
old_string: str = Field(description="Exact text to find"),
new_string: str = Field(description="Replacement text"),
) -> str:
"""Apply a find/replace, validating the doc exists first."""
if doc_id not in docs:
raise ValueError(f"Doc with id {doc_id} not found")
docs[doc_id] = docs[doc_id].replace(old_string, new_string)
return f"Updated {doc_id}"
```
### Anatomy of a Tool Definition
```
@mcp.tool decorator ← marks function as a tool
↓
function definition ← the executable code
↓
typed parameters ← become JSON schema properties
↓
Field(description=...) ← becomes the schema's "description" field
↓
return type ← becomes the tool's output schema
```
### Server Bootstrap — One Line
```python
if __name__ == "__main__":
mcp.run() # stdio transport by default
```
### SDK Wins
| Manual Approach | MCP SDK Approach |
|-----------------|------------------|
| Hand-write JSON schema for each tool | Schema auto-generated from Python signature |
| Wire each tool into the server manually | `@mcp.tool` decorator handles registration |
| Manually format input validation | Type hints + `Field()` do it for you |
| Boilerplate-heavy | One file, a few decorators, done |
---
## 🔍 Server Inspector
The **MCP Inspector** is an in-browser debugger that lets you test an MCP server **without wiring up Claude or your app**.
### Launch
```bash
mcp dev mcp_server.py
```
The terminal prints a localhost URL — open it in your browser.
### Interface Tour
```
┌─────────────────────────────────────────────────────────────┐
│ MCP Inspector │
│ │
│ ┌──────────┐ ┌─────────────────────────────────────┐ │
│ │ Connect │ │ Resources Prompts Tools │ │
│ └──────────┘ └─────────────────────────────────────┘ │
│ │ │ │
│ │ └── Tools panel: │
│ │ • read_doc_contents │
│ │ • edit_document │
│ ▼ │
│ Click a tool → Right panel: │
│ [doc_id: "report.pdf" ] │
│ [ Run Tool ] │
│ Output: "Q4 revenue was up 23%..." │
└─────────────────────────────────────────────────────────────┘
```
### Workflow
1. **Connect** to the server in the sidebar
2. Click the **Tools** tab
3. Pick a tool — parameter fields appear on the right
4. Fill them in → click **Run Tool**
5. Inspect the output or error message
### Why It's Essential
- ✅ Test tools in isolation before integration
- ✅ Iterate on tool design with zero ceremony
- ✅ Debug parameter schemas visually
- ✅ Verify error paths (try a missing `doc_id`!)
> ⚠️ Inspector UI is in active development — expect layout changes, but core functionality stays.
---
## 🔌 Client Implementation
The **client** is a thin wrapper around the SDK's session object. Its job: **make it easy for the rest of your codebase to talk to MCP servers**.
### Why a Wrapper Class?
| Raw `ClientSession` | Wrapped Class |
|---------------------|---------------|
| Manual resource cleanup | `async with` / `async enter` / `async exit` handle it |
| SDK types leak everywhere | Clean method signatures on the wrapper |
| Verbose for every callsite | `client.list_tools()` is enough |
### The Client Class
```python
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
class MCPClient:
def __init__(self, server_params: StdioServerParameters):
self.server_params = server_params
self.session: ClientSession | None = None
self._streams = None
async def connect(self):
"""Open the stdio connection and initialize the session."""
self._streams = await stdio_client(self.server_params).__aenter__()
read, write = self._streams
self.session = await ClientSession(read, write).__aenter__()
await self.session.initialize()
async def cleanup(self):
"""Tear down the session and streams."""
if self.session:
await self.session.__aexit__(None, None, None)
if self._streams:
await self._streams.__aexit__(None, None, None)
# ─── Domain methods ───────────────────────────────────────
async def list_tools(self) -> list:
"""Return the tools the server offers."""
result = await self.session.list_tools()
return result.tools
async def call_tool(self, tool_name: str, tool_input: dict) -> str:
"""Execute a tool on the server and return its result."""
result = await self.session.call_tool(tool_name, tool_input)
return result
```
### Tool-Call Round Trip
```mermaid
flowchart LR
App[Your App] -->|1. list_tools| Client[MCPClient]
Client -->|2. tool list| App
App -->|3. send list to Claude| Claude
Claude -->|4. call read_doc('x')| App
App -->|5. call_tool| Client
Client -->|6. result| App
App -->|7. result| Claude
Claude -->|8. final answer| App
```
### Typical End-to-End Loop (Pseudocode)
```python
async def chat_turn(user_message: str):
# 1. Discover tools
tools = await client.list_tools()
# 2. Ask Claude — it may decide to call a tool
response = await claude.messages.create(
model="claude-sonnet-4-5",
tools=tools,
messages=[{"role": "user", "content": user_message}],
)
# 3. If Claude wants a tool, execute it
if response.stop_reason == "tool_use":
tool_call = response.content[-1]
result = await client.call_tool(
tool_call.name,
tool_call.input,
)
# 4. Send result back to Claude for the final answer
...
```
---
## 📚 Resources
> **Resources** are *app-controlled* primitives. **Your code** decides when to read them.
### The Mental Model
```
Resources = data you FETCH into your app
(e.g. "list of Google Drive files", "user profile")
→ serve the APP
```
### Resource Types
| Type | URI Pattern | Example |
|------|-------------|---------|
| **Static / Direct** | Fixed URI | `docs://documents` |
| **Templated** | URI with `{param}` wildcards | `documents/{doc_id}` |
### Resource Flow
```mermaid
sequenceDiagram
participant App as 🖥️ App Code
participant C as 🔌 MCP Client
participant S as ⚙️ MCP Server
App->>C: read_resource("documents/report.pdf")
C->>S: read_resource request (URI)
S->>S: match URI → call resource function
S-->>C: result.contents
C-->>App: data
```
### Defining Resources
```python
@mcp.resource(
uri="docs://documents", # static
name="All Documents",
description="List every document in the store.",
mime_type="application/json",
)
def list_documents() -> list[str]:
"""Return all document IDs."""
return list(docs.keys())
@mcp.resource(
uri="documents/{doc_id}", # templated
name="Single Document",
description="Fetch a single document by its ID.",
mime_type="text/plain",
)
def read_document(doc_id: str) -> str:
"""URI parameter becomes a function kwarg automatically."""
if doc_id not in docs:
raise ValueError(f"Doc {doc_id} not found")
return docs[doc_id]
```
### Key Implementation Details
- 🎯 URI parameters (`{doc_id}`) become **function keyword arguments** automatically
- 📦 The SDK **auto-serializes** return values (dicts → JSON, str → text)
- 🏷️ `mime_type` is a **hint** to the client for deserialization (`application/json`, `text/plain`, etc.)
- 🧩 **One resource per distinct read operation** — don't cram list + fetch into one URI
### When to Use Resources vs Tools
| Need | Use |
|------|-----|
| App needs to display a file picker | 📚 **Resource** |
| Claude needs to *compute* something | 🛠️ **Tool** |
| Data goes into a UI list, not into a prompt automatically | 📚 **Resource** |
| LLM should decide when/what to fetch | 🛠️ **Tool** |
---
## 📖 Accessing Resources
### Client-Side: `read_resource()`
```python
import json
from pydantic import AnyUrl
async def read_resource(self, uri: str) -> str | dict:
"""Read a resource from the connected MCP server."""
result = await self.session.read_resource(AnyUrl(uri))
resource = result.contents[0] # first item in the contents list
# Parse based on MIME type
if resource.mime_type == "application/json":
return json.loads(resource.text)
return resource.text
```
### Response Parsing Logic
```python
# In the client
async def get_doc_list(self) -> list[str]:
docs = await self.read_resource("docs://documents")
# Returns: ["report.pdf", "launch.md", "design.txt"]
return docs
async def get_doc(self, doc_id: str) -> str:
text = await self.read_resource(f"documents/{doc_id}")
# Returns: "Q4 revenue was up 23%..."
return text
```
### How It Fits in the CLI
```python
# In the chatbot
async def pick_document():
docs = await client.get_doc_list() # read a resource
selected = pick_with_arrows(docs) # UI-level picker
contents = await client.get_doc(selected) # read another resource
return contents # inject into LLM prompt
```
### Why This Matters
- 📚 Document selection happens **before** the LLM call
- ✅ Selected document contents get **automatically included** in the prompt
- 🚫 No need for a `read_doc` *tool* during chat — that fetch is done at the UI layer
- 🔌 `json` and `pydantic.AnyUrl` are the only extra dependencies needed
---
## 💬 Prompts
> **Prompts** are *user-controlled* primitives. **The user** triggers them (slash command, button, etc.).
### The Mental Model
```
Prompts = pre-written, tested instructions the SERVER authors
(e.g. "/format", "/summarize", "/translate")
→ serve the USER
```
### Why Prompts Exist
Without prompts, users have to write their own instructions every time. MCP lets the **server author** encapsulate domain expertise:
> "I know this service inside-out — here's a battle-tested prompt template for the most common workflows. Just expose it."
### Implementation Pattern
```python
from mcp.server.fastmcp.prompts import base
@mcp.prompt(
name="format",
description="Rewrite the given document in clean Markdown."
)
def format_document(doc_id: str) -> list:
"""Return the conversation messages for Claude."""
prompt_text = f"""
Please reformat the contents of document "{doc_id}" as clean Markdown.
Steps:
1. Read the document using the read_doc_contents tool.
2. Restructure headings, lists, and code blocks.
3. Use the edit_document tool to save the result back.
"""
return [base.user_message(prompt_text)]
```
### Workflow
```mermaid
sequenceDiagram
participant U as 👤 User
participant CLI as 💻 Chat CLI
participant C as 🔌 MCP Client
participant S as ⚙️ MCP Server
participant L as 🤖 Claude
U->>CLI: /format
CLI->>U: pick a document (arrow keys)
U->>CLI: selects report.pdf
CLI->>C: get_prompt("format", {doc_id: "report.pdf"})
C->>S: get_prompt request
S-->>C: messages (with doc_id interpolated)
C-->>CLI: messages
CLI->>L: messages
L->>L: calls read_doc_contents, then edit_document
L-->>CLI: reformatted doc
CLI-->>U: ✅ done
```
### Anatomy of a Prompt Definition
```
@mcp.prompt decorator
↓
name + description ← surfaced as a slash command
↓
function with args ← arguments become slash-command parameters
↓
return list[messages] ← direct conversation input for Claude
```
### Real-World Examples
- 💬 Chat starter buttons in Claude's UI → **prompts**
- 📁 "Pick a Google Drive file" autocomplete → **resource**
- ⚙️ "Run this JavaScript snippet" → **tool**
---
## 🎯 Prompts in the Client
### Two Methods to Implement
```python
async def list_prompts(self) -> list:
"""Get every prompt the server exposes."""
result = await self.session.list_prompts()
return result.prompts # or result.props, depending on SDK version
async def get_prompt(self, name: str, arguments: dict) -> list:
"""Fetch a specific prompt with arguments filled in."""
result = await self.session.get_prompt(name, arguments)
return result.messages
```
### How Arguments Flow
```
User types: /format report.pdf
↓
Client receives: name="format", arguments={"doc_id": "report.pdf"}
↓
Prompt function: format_document(doc_id: str) ← kwarg injection
↓
Interpolated: "Please reformat the contents of document "report.pdf"..."
↓
Returned: list of Claude conversation messages
```
### CLI Integration
```python
async def handle_slash_command(cmd: str, arg: str):
if cmd == "format":
prompts = await client.list_prompts()
# prompts[0] is the "format" prompt
messages = await client.get_prompt(
"format",
{"doc_id": arg}
)
# Send messages to Claude
response = await claude.messages.create(
model="claude-sonnet-4-5",
messages=messages,
tools=await client.list_tools(), # so it can read/edit the doc
)
print(response.content)
```
### Core Concept
> **Prompts are server-defined templates.** Clients invoke them by name with parameters; the server interpolates and returns ready-to-send messages.
---
## 🧠 MCP Review
The three MCP primitives serve **three different control loops**. Pick the right one by asking *"who triggers it?"*
### The Decision Framework
```mermaid
flowchart TD
Start([What do I need?]) --> Q1{Who decides<br/>when it runs?}
Q1 -->|🤖 Claude| Tools[🛠️ Build a Tool]
Q1 -->|🖥️ App code| Resources[📚 Expose a Resource]
Q1 -->|👤 User| Prompts[💬 Define a Prompt]
Tools --> T[Add capabilities<br/>to the model<br/>e.g. JS execution, DB queries]
Resources --> R[Get data into the app<br/>for UI or prompt augmentation<br/>e.g. file lists, autocomplete]
Prompts --> P[Predefined workflows<br/>for power users<br/>e.g. /format, /summarize]
style Tools fill:#7C3AED,color:#fff
style Resources fill:#0EA5E9,color:#fff
style Prompts fill:#EC4899,color:#fff
```
### Side-by-Side Comparison
| Aspect | 🛠️ Tools | 📚 Resources | 💬 Prompts |
|--------|----------|--------------|-----------|
| **Controlled by** | Model (Claude) | Application code | User (slash cmd, button) |
| **Primary purpose** | Give Claude *new capabilities* | Get *data* into the app | Trigger *predefined workflows* |
| **Trigger** | Claude's reasoning decides | Your code calls `read_resource` | User types `/command` |
| **Server decorator** | `@mcp.tool()` | `@mcp.resource()` | `@mcp.prompt()` |
| **Client SDK call** | `list_tools()` / `call_tool()` | `list_resources()` / `read_resource()` | `list_prompts()` / `get_prompt()` |
| **Returns** | Computed result | Data (JSON or text) | Conversation messages |
| **Serves** | The **model** | The **app** | The **user** |
| **Real example** | JavaScript execution for math | Google Drive document list | Claude's chat starter buttons |
| **Lifecycle** | Ephemeral — fires on demand | Pulled when app asks for it | Triggered by user gesture |
### Decision Rules of Thumb
```
Need Claude to DO something? → 🛠️ Tool
Need your APP to SHOW data? → 📚 Resource
Need a SHORTCUT for a workflow? → 💬 Prompt
```
### Mnemonic
> **T.A.P.** — **T**ools serve the model, **A**pp pulls resources, **P**rompts serve the user.
---
## ⚡ Quick Reference
### Server-Side Decorators
```python
# ── TOOL ───────────────────────────────────────────
@mcp.tool(name="...", description="...")
def my_tool(arg: str = Field(description="...")) -> str:
return ...
# ── RESOURCE (static) ─────────────────────────────
@mcp.resource(uri="docs://list", mime_type="application/json")
def list_items() -> list[str]:
return [...]
# ── RESOURCE (templated) ──────────────────────────
@mcp.resource(uri="docs://{doc_id}", mime_type="text/plain")
def fetch_item(doc_id: str) -> str:
return ...
# ── PROMPT ────────────────────────────────────────
@mcp.prompt(name="format", description="...")
def my_prompt(doc_id: str) -> list:
return [base.user_message("...")]
```
### Client-Side SDK Calls
```python
# ── Tools ─────────────────────────────────────────
tools = await session.list_tools()
result = await session.call_tool(name, arguments)
# ── Resources ─────────────────────────────────────
all_res = await session.list_resources()
data = await session.read_resource(AnyUrl("docs://x"))
# ── Prompts ───────────────────────────────────────
all_p = await session.list_prompts()
msgs = await session.get_prompt(name, arguments)
```
### Project Cheatsheet
| Task | Command |
|------|---------|
| Install deps | `uv sync` |
| Run chatbot | `uv run main.py` |
| Debug server in browser | `mcp dev mcp_server.py` |
| Quick math sanity check | `"what's one plus one"` |
### Common Pitfalls
- ❌ Forgetting `await session.initialize()` after `connect()`
- ❌ Not cleaning up sessions → leaked file descriptors
- ❌ Treating `read_resource` results as already-parsed when JSON
- ❌ Building a `Tool` when you actually need a `Resource` (or vice versa) — re-check the control loop
---
## 📚 Resources & Further Reading
- 📘 [MCP Official Specification](https://modelcontextprotocol.io)
- 🐍 [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)
- 🎓 [Anthropic Academy](https://anthropic.com) — original certification course
- 🧪 [MCP Inspector](https://github.com/modelcontextprotocol/inspector) — debug UI
---
## 📝 License
These are personal study notes derived from Anthropic's MCP certification course. Shared under the **MIT License** — use freely, attribution appreciated.
---
<div align="center">
**⭐ If this helped you understand MCP, drop a star on the repo! ⭐**
Made with 🧠 + ☕ while learning the Model Context Protocol
</div>
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available