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",
});