Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
The age of AI is not just about using large language models (LLMs) in chatbots; it’s about integrating AI capabilities deeply into our workflows and applications. Imagine an AI assistant that doesn’t just talk, but acts – managing tasks, retrieving data, and controlling services through natural language. How can we build the bridge between powerful AI models and our custom applications?
Enter a powerful combination: Visual Studio Code (VS Code), GitHub Copilot, and a framework like MCP (ModelContext Protocol). In this post, we’ll explore how this trio allows us to:
We’ll use a simple task management application built with Python and SQLite as our example, exposing its features through an MCP server.
Let’s look at the core components of our example Python application :
import os
import sqlite3
import aiosqlite
import asyncio
import json
from contextlib import asynccontextmanager
from collections.abc import AsyncIterator
from dataclasses import dataclass
from mcp.server.fastmcp import Context, FastMCP
# Global DB connection for resources
_db = None
# Create FastMCP server with dependencies
mcp = FastMCP("My App", dependencies=["pandas", "numpy", "aiosqlite"])
@dataclass
class AppContext:
db: aiosqlite.Connection
async def ensure_db_exists(db_path: str = "tasks.db") -> None:
"""
Ensure the SQLite database file and tasks table exist.
Creates the file and the 'tasks' table if they do not already exist.
Args:
db_path: Path to the SQLite database file.
Returns:
None
"""
if not os.path.exists(db_path):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
description TEXT NOT NULL,
progress INTEGER NOT NULL DEFAULT 0
);
"""
)
conn.commit()
conn.close()
@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
"""
Manage application startup and shutdown.
On startup ensures the database exists and connects asynchronously.
On shutdown closes the database connection.
Args:
server: The FastMCP server instance.
Yields:
AppContext: Holds the async database connection.
"""
await ensure_db_exists()
db = await aiosqlite.connect("tasks.db")
global _db
_db = db
try:
yield AppContext(db=db)
finally:
await db.close()
_db = None
# Pass lifespan to FastMCP server
mcp = FastMCP(
"My App",
dependencies=["pandas", "numpy", "aiosqlite"],
lifespan=app_lifespan
)
# --- Core database functions (can be tested from main) ---
async def add_task_db(db: aiosqlite.Connection, description: str) -> int:
"""
Insert a new task into the database.
Args:
db: Async SQLite connection.
description: The task description text.
Returns:
The ID of the newly created task.
"""
cursor = await db.execute(
"INSERT INTO tasks(description) VALUES (?)",
(description,)
)
await db.commit()
return cursor.lastrowid
async def update_task_db(db: aiosqlite.Connection, task_id: int, progress: int) -> bool:
"""
Update the progress of an existing task.
Args:
db: Async SQLite connection.
task_id: ID of the task to update.
progress: New progress percentage (0-100).
Returns:
True if the update affected a row, False otherwise.
"""
cursor = await db.execute(
"UPDATE tasks SET progress = ? WHERE id = ?",
(progress, task_id)
)
await db.commit()
return cursor.rowcount > 0
async def delete_task_db(db: aiosqlite.Connection, task_id: int) -> bool:
"""
Delete a task by its ID.
Args:
db: Async SQLite connection.
task_id: ID of the task to delete.
Returns:
True if a task was deleted, False otherwise.
"""
cursor = await db.execute(
"DELETE FROM tasks WHERE id = ?",
(task_id,)
)
await db.commit()
return cursor.rowcount > 0
async def list_tasks_db(db: aiosqlite.Connection) -> list[tuple[int, str, int]]:
"""
Retrieve all tasks from the database.
Args:
db: Async SQLite connection.
Returns:
A list of tuples containing (id, description, progress).
"""
cursor = await db.execute("SELECT id, description, progress FROM tasks")
rows = await cursor.fetchall()
return rows
# --- FastMCP tool wrappers ---
@mcp.tool()
async def add_task(ctx: Context, description: str) -> str:
"""
Add a new task.
Args:
ctx: FastMCP request context with DB connection.
description: Text description of the new task.
Returns:
Confirmation message with the new task ID and its description.
"""
db = ctx.request_context.lifespan_context.db
task_id = await add_task_db(db, description)
return f"Added task {task_id}: '{description}'"
@mcp.tool()
async def update_task(ctx: Context, task_id: int, progress: int) -> str:
"""
Update the progress of an existing task.
Args:
ctx: FastMCP request context with DB connection.
task_id: ID of the task to update.
progress: New progress percentage (0-100).
Returns:
Success message if updated, or not-found message.
"""
db = ctx.request_context.lifespan_context.db
success = await update_task_db(db, task_id, progress)
if success:
return f"Updated task {task_id} to progress {progress}%"
return f"Task {task_id} not found."
@mcp.tool()
async def delete_task(ctx: Context, task_id: int) -> str:
"""
Delete a task by ID.
Args:
ctx: FastMCP request context with DB connection.
task_id: ID of the task to delete.
Returns:
Confirmation message if deleted, or not-found message.
"""
db = ctx.request_context.lifespan_context.db
success = await delete_task_db(db, task_id)
if success:
return f"Deleted task {task_id}."
return f"Task {task_id} not found."
@mcp.tool()
async def list_tasks(ctx: Context) -> str:
"""
List all tasks with their progress.
Args:
ctx: FastMCP request context with DB connection.
Returns:
A formatted string of all tasks, "id: description (progress%)" per line.
"""
db = ctx.request_context.lifespan_context.db
tasks = await list_tasks_db(db)
if not tasks:
return "No tasks found."
lines = [f"{tid}: {desc} ({prog}%)" for tid, desc, prog in tasks]
return "\n".join(lines)
# --- FastMCP resource wrappers ---
@mcp.resource("tasks://all")
async def get_all_tasks() -> str:
"""
Return every task as a JSON list of objects.
Returns:
JSON string of a list of {id, description, progress}.
"""
db = _db
rows = await list_tasks_db(db)
payload = [{"id": tid, "description": desc, "progress": prog} for tid, desc, prog in rows]
return json.dumps(payload)
@mcp.resource("tasks://{task_id}")
async def get_task(task_id: int) -> str:
"""
Return one task’s data or an error as JSON.
Args:
task_id: ID of the task to fetch.
Returns:
JSON string of {id, description, progress} or {error, id}.
"""
db = _db
cursor = await db.execute(
"SELECT id, description, progress FROM tasks WHERE id = ?", (task_id,)
)
row = await cursor.fetchone()
if not row:
return json.dumps({"error": "Task not found", "id": task_id})
tid, desc, prog = row
return json.dumps({"id": tid, "description": desc, "progress": prog})
@mcp.resource("config://task-priorities")
def task_priorities() -> str:
"""
Provide static mapping of task priority levels.
Returns:
JSON string of priority mappings {low, medium, high} -> int.
"""
return json.dumps({"low": 0, "medium": 1, "high": 2})
# --- Main for testing ---
if __name__ == "__main__":
mcp.run(transport="stdio")
Our MCP server is now running, exposing tools and resources. How does this demonstrate AI capabilities?
Imagine an LLM-based agent or chatbot connected to this MCP server. Instead of just generating text, it can use the tools we defined:
The MCP server acts as the bridge, allowing the AI’s language understanding capabilities to translate into concrete actions and data retrieval within our custom application’s domain.
Select the copilot chat from the top next to the search
MCP server can be configured from the settings.json of vscode where we use uv as command and below is the screenshot
on running successfully we will be able to see in the list of servers
on running will be able to interact and will be able to call the functions as shown below.
The workflow looks like this:
In essence, combining a modern IDE like VS Code, an AI coding assistant like GitHub Copilot, and an interaction framework like MCP creates a powerful ecosystem for building the next generation of AI-integrated applications. Copilot drastically accelerates the development of the necessary backend services and APIs, while MCP provides the structured interface needed for AI agents to interact meaningfully with those services.
While this simple task management example only scratches the surface, it demonstrates the core potential. Imagine AI agents managing complex workflows, interacting with multiple microservices via MCP, or providing intelligent natural language interfaces to legacy systems. The VS Code + Copilot + MCP combination provides a practical and efficient way to start building that future today.
Ready to try it yourself? Grab the example code from GitHub:
https://github.com/sanjeevrayasam/taskmanager_mcp
Fire up VS Code, enable Copilot, and see how quickly you can build and experiment with your own AI-accessible tools!