DEV Community

Cover image for Genkit AI & MCP: 🎧 Building Your 2010s British Song Selector!
Giorgio Boa
Giorgio Boa

Posted on

Genkit AI & MCP: 🎧 Building Your 2010s British Song Selector!

In the age of AI, the ability to build robust and scalable backends is paramount. Genkit, an open-source framework from Google, significantly simplifies this process, offering an intuitive way to define AI workflows, tools, and integrations.

One of its most powerful features is the ability to expose these functionalities via the Model Context Protocol (MCP), enabling standardised interaction with other AI systems.

Model Context Protocol (MCP)

It's crucial to understand what the MCP is and why it's so valuable. Imagine a scenario where you have multiple AI models or services, each specialising in a different task – one for natural language understanding, another for image generation, and perhaps a third for data retrieval. Traditionally, integrating these diverse components could be a complex, ad-hoc process involving custom APIs and data formats.

The MCP addresses this challenge by providing a standardised communication layer for AI models and their clients. It defines a common language and structure for exchanging information, allowing different AI systems to "talk" to each other seamlessly.

This protocol enables several key benefits.

  • Interoperability: Any system that speaks MCP can interact with another MCP-compliant system, regardless of its underlying technology or programming language.
  • Tool Calling: A core feature of MCP is its support for "tool calling." This allows AI models to dynamically discover and invoke external functions or services (like our song selector) to perform specific actions or retrieve information. It essentially gives AI models "eyes and hands" to interact with the real world or other digital services.
  • Developer Experience: By standardising the interface, MCP simplifies the development and integration of AI applications, reducing boilerplate code and accelerating iteration cycles.

In essence, MCP acts as a universal adapter for AI components, making them more modular, reusable, and easier to integrate into larger, more sophisticated AI architectures.

Genkit & MCPs

The first step in any well-structured application is defining its data model. For our song selector, we'll use zod to describe a "Song" with its key attributes.

const SongSchema = z.object({
  id: z.string().describe("Unique song ID (e.g., SONG-1234)"),
  title: z.string().describe("Song title"),
  artist: z.string().describe("Artist or band name"),
  year: z.number().int().min(2010).max(2019).describe("Year of publication"),
  genre: z.string().describe("Musical genre (e.g., Pop, Indie, Rock)"),
  mood: z
    .string()
    .optional()
    .describe("Associated mood (e.g., cheerful, melancholic)"),
});
Enter fullscreen mode Exit fullscreen mode

This schema captures all essential information to identify and categorise a song, including optional fields like mood.

Genkit defineFlow and defineTool

  • defineFlow: is a core Genkit function used to orchestrate complex AI tasks and multi-step processes. It allows you to chain together various operations, including calling other AI models, invoking custom tools, performing data transformations, and interacting with external services. Think of a flow as a recipe for an AI-powered process, defining the sequence of actions and how data moves between them. Flows are central to building sophisticated AI applications in Genkit, providing structure, observability, and reusability for your intelligent workflows.

  • defineTool: is a function for encapsulating specific, actionable capabilities that AI models or external clients can invoke. It allows you to expose discrete functions (like searching a database, sending an email, or, in our example, finding a song) as a clearly defined API. Each tool comes with a name, a description, and defined input/output schemas using Zod, making its purpose and usage explicit. Tools are crucial for giving AI models the "ability to act" in the real world or interact with your application's business logic in a structured and controlled manner.

Populate and Read Songs

To test our application, we need a dataset. Genkit, in conjunction with libraries like genthetic, can generate realistic sample data.

Let's create a flow to populate a database (e.g., Firestore) with simulated songs and another to list them.

ai.defineFlow("populateSongs", async () => {
  const SongSynth = g
    .defineType({
      name: "Song",
      schema: SongSchema,
    })
    .generate({
      unique: true,
      instructions:
        "Generate popular British songs from the 2010s. Ensure years are between 2010 and 2019. Include diverse genres and moods.",
    });
  const { complete } = SongSynth.synthesize({
    batchSize: 5,
    count: 20, // Generate 20 sample songs
    logging: "debug",
  });
  const songs = (await complete()) as z.infer<typeof SongSchema>[];
  // Insert songs into your database (e.g., Firestore)
  for (const s of songs) {
    await firestore.collection("songs").doc(s.id).set(s);
  }
  return songs;
});

ai.defineFlow("listSongs", async (_, ctx) => {
  // Retrieve and stream all songs from the database
  for (const ref of await firestore.collection("songs").listDocuments()) {
    ctx.sendChunk((await ref.get()).data());
  }
});
Enter fullscreen mode Exit fullscreen mode

These flows allow us to easily manage sample data during development.

Music Search Engine

ai.defineTool(
  {
    name: "findSongByCriteria",
    description: "Searches for British songs from the 2010s based on title, artist, genre, or mood.",
    inputSchema: z.object({
      query: z.string().optional().describe("Generic search term"),
      artist: z.string().optional().describe("Filter by artist"),
      genre: z.string().optional().describe("Filter by musical genre"),
      mood: z.string().optional().describe("Filter by associated mood"),
    }),
    outputSchema: z.array(SongSchema).optional(),
  },
  async (input) => {
    let collectionRef = firestore.collection("songs");
    // Implement your database query logic here
    // Simplified example:
    if (input.artist) {
      collectionRef = collectionRef.where("artist", "==", input.artist);
    }
    if (input.genre) {
      collectionRef = collectionRef.where("genre", "==", input.genre);
    }
    if (input.mood) {
      collectionRef = collectionRef.where("mood", "==", input.mood);
    }
    // Add logic for generic 'query' (e.g., full-text search)

    const snapshot = await collectionRef.get();
    const results = snapshot.docs.map((doc) => doc.data() as z.infer<typeof SongSchema>);
    return results;
  }
);
Enter fullscreen mode Exit fullscreen mode

This findSongByCriteria tool will be the heart of our song selector, enabling flexible searches.

Setting Up the MCP Server

Finally, we'll configure the MCP server to expose our tools and flows. We'll use genkitx-mcp and express to create a Server-Sent Events (SSE) endpoint.

import { mcpServer } from "genkitx-mcp";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";

const mcp = mcpServer(ai, { name: "song-selector-2010s", version: "0.0.1" });
const app = express();
let transport: SSEServerTransport | null = null;

app.get("/sse", (req, res) => {
  transport = new SSEServerTransport("/messages", res);
  mcp.server!.connect(transport);
});

app.post("/messages", (req, res) => {
  if (transport) {
    transport.handlePostMessage(req, res);
  }
});

const port = 3000;
app.listen(port, () => {
  console.log(`MCP server listening on http://localhost:${port}`);
  console.log(
    `Connect MCP clients (e.g., Cline) to the http://localhost:${port}/sse endpoint.`
  );
});
Enter fullscreen mode Exit fullscreen mode

By running this server, we'll have an MCP endpoint ready to be queried. An MCP client (like Cline) can connect and invoke our tools with requests such as: "Find a cheerful song by Ed Sheeran from 2015" or "Give me a list of Indie songs from the 2010s."


Genkit drastically simplifies the creation of complex AI applications, from data definition to flow orchestration and feature exposure via tools. The integration with the Model Context Protocol transforms our song selector into an interoperable service, ready to be used by other intelligent systems or user interfaces. This flexibility makes Genkit a valuable tool for any developer venturing into the world of AI and intelligent backends.

Inspired by this article.


You can follow me on GitHub, where I'm creating cool projects.

I hope you enjoyed this article, don't forget to give ❤️.
Until next time 👋

Top comments (0)