Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions example/mcp-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# mcp-app

An example MCP (Model Context Protocol) server built with **fastapi-startkit**'s `Application` class and `FastAPIProvider` — no raw FastAPI wiring required.

## What it demonstrates

| Component | Name | Description |
|---|---|---|
| Tool | `echo` | Returns the caller's message unchanged |
| Tool | `word_count` | Counts words, characters, and lines in text |
| Prompt | `code_review` | Generates a structured code-review prompt |
| Resource | `environment` | Exposes selected env vars as a JSON resource |

## Running

```bash
uv run uvicorn app:app --reload
```

The server listens on `http://127.0.0.1:8000`.

## Quick test

```bash
# Initialize
curl -s -X POST http://127.0.0.1:8000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}' | python3 -m json.tool

# List tools
curl -s -X POST http://127.0.0.1:8000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":2}' | python3 -m json.tool

# Call word_count
curl -s -X POST http://127.0.0.1:8000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"word_count","arguments":{"text":"Hello world\nHow are you"}},"id":3}' | python3 -m json.tool
```
23 changes: 23 additions & 0 deletions example/mcp-app/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Dev-tools MCP server example.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude this looks quite a big file, can you split into smaller files,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Split into a mcp/ submodule:

  • mcp/tools.py — EchoTool, WordCountTool
  • mcp/prompts.py — CodeReviewPrompt
  • mcp/resources.py — EnvResource
  • mcp/server.py — DevToolsServer
  • app.py — bootstrap only (~15 lines)


Demonstrates a practical MCP server with tools, a prompt, and a resource,
built with fastapi-startkit's Application class and FastAPIProvider.

Run with:
uv run uvicorn app:app --reload
"""

from fastapi_startkit import Application
from fastapi_startkit.fastapi import FastAPIProvider

from mcp.server import DevToolsServer

mcp_server = DevToolsServer()

app = Application(providers=[FastAPIProvider])
app.include_router(mcp_server.router(prefix="/mcp"))


@app.get("/")
async def index():
return {"message": "Dev-Tools MCP server is running.", "mcp_endpoint": "/mcp"}
Empty file added example/mcp-app/mcp/__init__.py
Empty file.
33 changes: 33 additions & 0 deletions example/mcp-app/mcp/prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import textwrap

from fastapi_startkit.mcp import Argument, Prompt, Response


class CodeReviewPrompt(Prompt):
"""Generate a code-review prompt for a given language."""

name = "code_review"
title = "Code Review"
description = "Generates a structured code-review prompt for the specified programming language."

def arguments(self):
return [
Argument(name="language", description="Programming language to review (e.g. Python, TypeScript)", required=True),
Argument(name="focus", description="Optional focus area (e.g. security, performance, readability)", required=False),
]

async def handle(self, arguments: dict) -> Response:
language = arguments.get("language", "code")
focus = arguments.get("focus", "")
focus_line = f" Focus especially on {focus}." if focus else ""
prompt_text = textwrap.dedent(f"""
You are an expert {language} code reviewer.{focus_line}

Please review the provided code and give structured feedback covering:
1. Correctness — does the code do what it intends?
2. Readability — is the code easy to understand and maintain?
3. Performance — are there obvious inefficiencies?
4. Security — are there any security concerns?
5. Suggested improvements — concrete, actionable next steps.
""").strip()
return Response.text(prompt_text)
20 changes: 20 additions & 0 deletions example/mcp-app/mcp/resources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import json
import os

from fastapi_startkit.mcp import Resource, Response


class EnvResource(Resource):
"""Expose non-sensitive environment variables as a resource."""

uri = "resource:///env"
name = "environment"
description = "A snapshot of selected non-sensitive environment variables."
mime_type = "application/json"

# Variables that are safe to expose
_ALLOWED = {"PATH", "LANG", "TZ", "HOME", "USER", "SHELL", "TERM"}

async def read(self, **kwargs) -> str:
safe = {k: v for k, v in os.environ.items() if k in self._ALLOWED}
return json.dumps(safe, indent=2)
26 changes: 26 additions & 0 deletions example/mcp-app/mcp/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from fastapi_startkit.mcp import Server

from .tools import EchoTool, WordCountTool
from .prompts import CodeReviewPrompt
from .resources import EnvResource


class DevToolsServer(Server):
"""A developer-tools MCP server with echo, word-count, code-review, and env resources."""

name = "dev-tools"
description = "Developer utilities exposed as an MCP server."
instructions = (
"Use `echo` for connectivity checks, `word_count` to analyse text, "
"and `code_review` to get a structured review prompt. "
"Read the `environment` resource for runtime context."
)

def tools(self):
return [EchoTool, WordCountTool]

def prompts(self):
return [CodeReviewPrompt]

def resources(self):
return [EnvResource]
53 changes: 53 additions & 0 deletions example/mcp-app/mcp/tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from pydantic import BaseModel

from fastapi_startkit.mcp import Tool, Response


class EchoInput(BaseModel):
message: str


class EchoTool(Tool):
"""Return the caller's message unchanged — useful for connectivity checks."""

name = "echo"
description = "Echo the provided message back to the caller."

def schema(self):
return EchoInput

async def handle(self, arguments: dict) -> Response:
message = arguments.get("message", "")
return Response.text(message)


class WordCountInput(BaseModel):
text: str


class WordCountOutput(BaseModel):
words: int
characters: int
lines: int


class WordCountTool(Tool):
"""Count words, characters, and lines in a block of text."""

name = "word_count"
description = "Count the number of words, characters, and lines in a piece of text."

def schema(self):
return WordCountInput

def output_schema(self):
return WordCountOutput

async def handle(self, arguments: dict) -> Response:
text: str = arguments.get("text", "")
stats = {
"words": len(text.split()) if text.strip() else 0,
"characters": len(text),
"lines": len(text.splitlines()) if text else 0,
}
return Response.structure(stats)
13 changes: 13 additions & 0 deletions example/mcp-app/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[project]
name = "mcp-app"
version = "0.1.0"
description = "Example MCP server built with fastapi-startkit"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"fastapi-startkit[fastapi]",
"uvicorn[standard]>=0.34.0",
]

[tool.uv.sources]
fastapi-startkit = { path = "../../fastapi_startkit", editable = true }
Loading
Loading