All Articles
CategoryAI
Reading Time
18 min read
Published
2026-02-13
Word Count
1,976words

Grab a coffee — this one is a deep dive!

Claude Code MCP: Building an AI Plugin Ecosystem with the Model Context Protocol

Summary

MCP architecture, server/client structure, tool use, resource sharing, and transport layer details. A technical deep dive into the Claude Code plugin ecosystem, with examples.

  • MCP architecture consists of three components: Host (Claude Code), Client, and Server.
  • There are three transport types: stdio (local, fastest), SSE (legacy), and Streamable HTTP (modern).
  • In Claude Code, MCP servers are defined in .mcp.json, using the mcp__server__tool call format.
  • For security, Zod-based input validation and rejection of path traversal (../) should be mandatory.
Claude Code MCP: Building an AI Plugin Ecosystem with the Model Context Protocol

# Claude Code MCP: Building an AI Plugin Ecosystem with the Model Context Protocol

AI tools no longer work in isolation. The Model Context Protocol (MCP), announced by Anthropic in late 2025, fundamentally changed how AI agents communicate with the outside world. If you use Claude Code and have ever thought "I wish I could connect this to that tool," MCP is exactly for you. In this post we'll cover everything from MCP's architecture and transport layer details to writing your own server and using it in production.

Note: This guide is based on the Anthropic Docs, the MCP Specification, and real-world project experience. All code examples have been tested.

Table of Contents


1. What Is MCP and Why Does It Matter?

The Model Context Protocol is an open standard that gives AI models access to external tools, databases, and APIs through a standard protocol. Just as USB-C connects different devices through a single port, MCP unifies different AI tools under a single protocol.

The World Before MCP

Before MCP, every AI tool built its own integration method:

Problem
Description
Fragmentation
Writing a separate plugin for every tool
Lack of a standard
Different formats, different protocols
Security risks
Every integration with its own auth mechanism
Hard to maintain
N tools x M models = N*M integrations

The World After MCP

With MCP, all tools can connect to each other through a single standard. You can write one MCP server and use it in Claude Code, in VS Code, and in any other MCP-compatible client.

Pro Tip: You can think of MCP as being like a REST API, but AI-native. REST was designed for humans; MCP is designed for AI agents.

2. Architecture: Server, Client, and Host

MCP consists of the interaction of three main components:

Host

The application the user interacts with — Claude Code, Claude Desktop, or VS Code, for example. The host hosts one or more MCP clients.

Client

The component that runs inside the host and communicates with a specific MCP server. Each client has a 1:1 relationship with a single server.

Server

The component that provides access to external resources. A database, an API, a file system, a browser — anything can be an MCP server.

swift
1Host (Claude Code)
2 ├-- Client A ←→ Server A (Playwright)
3 ├-- Client B ←→ Server B (Firebase)
4 └-- Client C ←→ Server C (GitHub)

Capability Negotiation

When a connection is established, the client and server share their capabilities with each other:

typescript
1// Server capability declaration
2const server = new McpServer({
3 name: "my-server",
4 version: "1.0.0",
5 capabilities: {
6 tools: {}, // Tool-calling support
7 resources: {}, // Resource sharing
8 prompts: {}, // Prompt templates
9 }
10});
Pro Tip: Every server should only declare the capabilities it actually needs. Enabling unnecessary capabilities is a security risk.

3. Transport Layer: stdio vs SSE vs Streamable HTTP

MCP supports three different transport mechanisms:

stdio (Standard I/O)

Ideal for local processes. The server is launched as a child process and communicates over stdin/stdout:

json
1{
2 "mcpServers": {
3 "playwright": {
4 "type": "stdio",
5 "command": "npx",
6 "args": ["@anthropic/mcp-playwright"]
7 }
8 }
9}

SSE (Server-Sent Events)

Establishes a one-way streaming connection to remote servers over HTTP. Available for legacy support.

Streamable HTTP

The newest and recommended transport. Offers two-way communication, session management, and reconnection support.

Transport
Use Case
Performance
Setup
stdio
Local tools
Fastest
Easy
SSE
Remote server (legacy)
Medium
Medium
Streamable HTTP
Remote server (modern)
Fast
Medium

4. Defining and Using Tools

MCP's most powerful feature is tool use. Servers define tools that AI agents can call:

typescript
1import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2import { z } from "zod";
3 
4const server = new McpServer({
5 name: "project-tools",
6 version: "1.0.0"
7});
8 
9// Tool definition
10server.tool(
11 "analyze-code",
12 "Analyzes a code file and produces a quality report",
13 {
14 filePath: z.string().describe("Path of the file to analyze"),
15 language: z.enum(["typescript", "swift", "python"]).describe("Programming language"),
16 includeMetrics: z.boolean().optional().describe("Compute metrics")
17 },
18 async ({ filePath, language, includeMetrics }) => {
19 // Tool logic goes here
20 const analysis = await analyzeFile(filePath, language);
21 
22 return {
23 content: [{
24 type: "text",
25 text: JSON.stringify(analysis, null, 2)
26 }]
27 };
28 }
29);

The Tool-Calling Flow

  1. The client asks the server for the list of available tools (tools/list)
  2. The AI model chooses the appropriate tool based on the user's request
  3. The client calls the chosen tool with parameters (tools/call)
  4. The server runs the tool and returns the result
  5. The AI model interprets the result and presents it to the user

This flow closely resembles the async pattern from our Swift Async/Await post — send the request, wait, process the result.


5. Resource and Prompt Sharing

Resources

Servers can share static or dynamic resources:

typescript
1// Static resource
2server.resource(
3 "project-config",
4 "config://project",
5 async (uri) => ({
6 contents: [{
7 uri: uri.href,
8 mimeType: "application/json",
9 text: JSON.stringify(projectConfig)
10 }]
11 })
12);
13 
14// Dynamic resource template
15server.resource(
16 "blog-post",
17 new ResourceTemplate("blog://{slug}", { list: undefined }),
18 async (uri, { slug }) => ({
19 contents: [{
20 uri: uri.href,
21 mimeType: "text/markdown",
22 text: await getBlogContent(slug)
23 }]
24 })
25);

Prompts

Servers can offer reusable prompt templates:

typescript
1server.prompt(
2 "code-review",
3 { code: z.string(), language: z.string() },
4 ({ code, language }) => ({
5 messages: [{
6 role: "user",
7 content: {
8 type: "text",
9 text: \`Review this \${language} code:\n\n\${code}\`
10 }
11 }]
12 })
13);
Pro Tip: Resources enrich the AI's context, while tools let it take action. Using both together is the most effective approach.

6. Writing Your Own MCP Server

Building an MCP server from scratch is surprisingly easy:

bash
1# Create the project
2mkdir my-mcp-server && cd my-mcp-server
3npm init -y
4npm install @modelcontextprotocol/sdk zod
typescript
1// src/index.ts
2import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4import { z } from "zod";
5 
6const server = new McpServer({
7 name: "ios-helper",
8 version: "1.0.0"
9});
10 
11// Xcode project analysis tool
12server.tool(
13 "analyze-xcode-project",
14 "Analyzes an Xcode project and lists its dependencies and targets",
15 {
16 projectPath: z.string().describe("Path to the .xcodeproj or .xcworkspace")
17 },
18 async ({ projectPath }) => {
19 // pbxproj parse logic
20 const targets = await parseXcodeProject(projectPath);
21 return {
22 content: [{
23 type: "text",
24 text: JSON.stringify(targets, null, 2)
25 }]
26 };
27 }
28);
29 
30// Swift file analysis tool
31server.tool(
32 "swift-complexity",
33 "Calculates the cyclomatic complexity of a Swift file",
34 {
35 filePath: z.string(),
36 threshold: z.number().optional().default(10)
37 },
38 async ({ filePath, threshold }) => {
39 const complexity = await calculateComplexity(filePath);
40 const warnings = complexity.functions
41 .filter(f => f.complexity > threshold)
42 .map(f => \`\${f.name}: \${f.complexity} (limit: \${threshold})\`);
43 
44 return {
45 content: [{
46 type: "text",
47 text: warnings.length > 0
48 ? \`Warning: \${warnings.length} function(s) exceed the threshold:\n\${warnings.join("\n")}\`
49 : "All functions are below the threshold."
50 }]
51 };
52 }
53);
54 
55// Start the server
56const transport = new StdioServerTransport();
57await server.connect(transport);

7. MCP Integration in Claude Code

In Claude Code, you add MCP servers using the .mcp.json file:

json
1{
2 "mcpServers": {
3 "playwright": {
4 "type": "stdio",
5 "command": "npx",
6 "args": ["@anthropic/mcp-playwright"]
7 },
8 "firebase": {
9 "type": "stdio",
10 "command": "npx",
11 "args": ["firebase-mcp-server"],
12 "env": {
13 "FIREBASE_PROJECT_ID": "my-project"
14 }
15 },
16 "context7": {
17 "type": "stdio",
18 "command": "npx",
19 "args": ["-y", "@upstash/context7-mcp"]
20 },
21 "my-custom-server": {
22 "type": "stdio",
23 "command": "node",
24 "args": ["./tools/mcp-server/dist/index.js"]
25 }
26 }
27}

Claude Code calls MCP tools using the following format:

swift
1mcp__<server-name>__<tool-name>

For example: mcp__playwright__browser_navigate, mcp__firebase__get_document

This approach fits well with what we described in our Firebase Advanced Patterns post — you can manage Firestore operations directly from Claude Code via MCP.


8. Real-World Examples

Example 1: UI Testing with Playwright

typescript
1// Browser control over MCP
2// Claude Code calls these tools in order:
3 
4// 1. Navigate to the page
5mcp__playwright__browser_navigate({ url: "https://muhittincamdali.com" })
6 
7// 2. Take a snapshot
8mcp__playwright__browser_snapshot()
9 
10// 3. Click an element
11mcp__playwright__browser_click({ ref: "nav-blog", element: "Blog link" })
12 
13// 4. Take a screenshot
14mcp__playwright__browser_take_screenshot({ type: "png" })

Example 2: Documentation with Context7

typescript
1// Fetch library documentation
2mcp__context7__resolve_library_id({
3 libraryName: "next.js",
4 query: "app router middleware"
5})
6 
7mcp__context7__query_docs({
8 libraryId: "/vercel/next.js",
9 query: "static export configuration"
10})

Example 3: A Database MCP Server

typescript
1server.tool(
2 "query-analytics",
3 "Runs a query against the analytics database",
4 {
5 dateRange: z.object({
6 start: z.string(),
7 end: z.string()
8 }),
9 metric: z.enum(["page_views", "unique_visitors", "bounce_rate"])
10 },
11 async ({ dateRange, metric }) => {
12 const result = await db.query(
13 \`SELECT date, value FROM analytics
14 WHERE metric = ? AND date BETWEEN ? AND ?\`,
15 [metric, dateRange.start, dateRange.end]
16 );
17 return { content: [{ type: "text", text: JSON.stringify(result) }] };
18 }
19);

This pattern resembles the data-layer approach in our GraphQL iOS Integration post — query-based, type-safe access.


9. Security and Best Practices

Input Validation

Runtime type checking with Zod should be mandatory:

typescript
1server.tool(
2 "file-read",
3 "File read (safe)",
4 {
5 path: z.string()
6 .refine(p => !p.includes(".."), "Path traversal is not allowed")
7 .refine(p => p.startsWith("/workspace"), "Only inside the workspace"),
8 maxLines: z.number().min(1).max(1000).default(100)
9 },
10 async ({ path, maxLines }) => {
11 // Safe file read
12 }
13);

Security Checklist

Rule
Description
Input validation
Validate every parameter with Zod
Path traversal
Reject paths containing ../
Rate limiting
Limit tool call frequency
Least privilege
Grant only the permissions that are needed
Audit logging
Log every tool call
Secret management
Keep API keys in environment variables

You should apply most of the principles from our iOS Security Best Practices post to your MCP servers as well.


10. Conclusion and Recommendations

MCP standardizes the way AI tools communicate with each other and with the outside world. By writing your own MCP server, you can connect Claude Code to any tool you want, automate your workflow, and multiply your development speed. You can build even more powerful automation by combining MCP tools with hooks — see our Claude Code Hooks automation guide for details.

Recommendations:

  1. Start simple — begin with a single tool and expand from there
  2. Use stdio — the most performant transport for local tools
  3. Zod is mandatory — runtime type safety, always
  4. Security first — input validation, path checks, rate limiting
  5. Test it — test your tools with the MCP Inspector
Pro Tip: Start by studying the examples in the Claude Code GitHub repository. Community servers are also a great source of inspiration.

ALTIN İPUCU

Bu yazının en değerli bilgisi

Bu ipucu, yazının en önemli çıkarımını içeriyor.

Easter Egg

Gizli bir bilgi buldun!

Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?

Okuyucu Ödülü

🎉 Hero Who Read to the End! Congratulations! Here's a resource just for you: the MCP Awesome List — a community-curated list of 200+ MCP servers. Ready-made servers for every category: databases, cloud, monitoring, testing, and more.

Tags

#Claude Code#MCP#AI#Plugin#TypeScript#Automation
Muhittin Çamdalı

Muhittin Çamdalı

Lead Mobile Engineer

Lead Mobile Engineer with 12+ years of experience. Expert in iOS, Android and cross-platform architectures with Swift, SwiftUI, Kotlin and Flutter. I build performant, user-friendly mobile apps.

iOS Development News

Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.

We respect your privacy. You can unsubscribe at any time.

Share