Skip to main content
Glama
index.ts5.07 kB
import OAuthProvider from "@cloudflare/workers-oauth-provider"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { McpAgent } from "agents/mcp"; import { z } from "zod"; import { GoogleHandler } from "./google-handler"; // Context from the auth process, encrypted & stored in the auth token // and provided to the MyMCP as this.props type Props = { name: string; email: string; accessToken: string; }; export class MyMCP extends McpAgent<Env, Record<string, never>, Props> { server = new McpServer({ name: "Google OAuth Proxy Demo", version: "0.0.1", }); async init() { // List all tables from D1 EMPLOYEES database this.server.tool("listTables", {}, async () => { const result = await (this.env as any).EMPLOYEES.prepare( "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name" ).all(); const tables = result.results.map((row: any) => row.name).join(", "); return { content: [{ text: `Available tables: ${tables}`, type: "text" }], }; }); // Read schema of a given table this.server.tool( "readSchema", { tableName: z.string().describe("Name of the table to read schema from") }, async ({ tableName }) => { const result = await (this.env as any).EMPLOYEES.prepare( `PRAGMA table_info(${tableName})` ).all(); if (result.results.length === 0) { return { content: [{ text: `Table '${tableName}' not found`, type: "text" }], }; } const schema = result.results.map((col: any) => `${col.name} (${col.type}${col.notnull ? ', NOT NULL' : ''}${col.pk ? ', PRIMARY KEY' : ''}${col.dflt_value ? `, DEFAULT ${col.dflt_value}` : ''})` ).join("\n"); return { content: [{ text: `Schema for table '${tableName}':\n${schema}`, type: "text" }], }; } ); // Execute SELECT queries only this.server.tool( "query", { sql: z.string().describe("SELECT SQL statement to execute") }, async ({ sql }) => { const trimmedSql = sql.trim().toUpperCase(); if (!trimmedSql.startsWith("SELECT")) { return { content: [{ text: "Error: Only SELECT statements are allowed", type: "text" }], }; } try { const result = await (this.env as any).EMPLOYEES.prepare(sql).all(); const resultText = JSON.stringify(result.results, null, 2); return { content: [{ text: resultText, type: "text" }], }; } catch (error: any) { return { content: [{ text: `Query error: ${error.message}`, type: "text" }], }; } } ); // Create or update data this.server.tool( "createOrUpdate", { tableName: z.string().describe("Name of the table"), data: z.record(z.any()).describe("Data to insert or update as key-value pairs"), where: z.string().optional().describe("WHERE clause for UPDATE (if omitted, INSERT is performed)"), }, async ({ tableName, data, where }) => { try { let sql: string; let values: any[]; if (where) { // UPDATE const setClauses = Object.keys(data).map(key => `${key} = ?`).join(", "); sql = `UPDATE ${tableName} SET ${setClauses} WHERE ${where}`; values = Object.values(data); } else { // INSERT const columns = Object.keys(data).join(", "); const placeholders = Object.keys(data).map(() => "?").join(", "); sql = `INSERT INTO ${tableName} (${columns}) VALUES (${placeholders})`; values = Object.values(data); } const result = await (this.env as any).EMPLOYEES.prepare(sql).bind(...values).run(); return { content: [{ text: `Success: ${where ? 'Updated' : 'Inserted'} ${result.meta.changes} row(s)`, type: "text" }], }; } catch (error: any) { return { content: [{ text: `Error: ${error.message}`, type: "text" }], }; } } ); // Delete data this.server.tool( "delete", { tableName: z.string().describe("Name of the table"), where: z.string().describe("WHERE clause to specify which rows to delete"), }, async ({ tableName, where }) => { try { const sql = `DELETE FROM ${tableName} WHERE ${where}`; const result = await (this.env as any).EMPLOYEES.prepare(sql).run(); return { content: [{ text: `Success: Deleted ${result.meta.changes} row(s)`, type: "text" }], }; } catch (error: any) { return { content: [{ text: `Error: ${error.message}`, type: "text" }], }; } } ); } } export default new OAuthProvider({ // NOTE - during the summer 2025, the SSE protocol was deprecated and replaced by the Streamable-HTTP protocol // https://developers.cloudflare.com/agents/model-context-protocol/transport/#mcp-server-with-authentication apiHandlers: { "/sse": MyMCP.serveSSE("/sse"), // deprecated SSE protocol - use /mcp instead "/mcp": MyMCP.serve("/mcp"), // Streamable-HTTP protocol }, authorizeEndpoint: "/authorize", clientRegistrationEndpoint: "/register", defaultHandler: GoogleHandler as any, tokenEndpoint: "/token", });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ross-jill-ws/cloudflare-mcp-lab'

If you have feedback or need assistance with the MCP directory API, please join our Discord server