Skip to content

Instantly share code, notes, and snippets.

@beaucarnes
Created March 31, 2026 20:42
Show Gist options
  • Select an option

  • Save beaucarnes/9ba1644b2d1c996d31bc52cd6f27f207 to your computer and use it in GitHub Desktop.

Select an option

Save beaucarnes/9ba1644b2d1c996d31bc52cd6f27f207 to your computer and use it in GitHub Desktop.
import dotenv from "dotenv";
import express from "express";
import cors from "cors";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { descopeMcpAuthRouter, descopeMcpBearerAuth, DescopeMcpProvider } from "@descope/mcp-express";
import DescopeClient from "@descope/node-sdk";
import { z } from "zod";
dotenv.config();
const descopeClient = DescopeClient({
projectId: process.env.DESCOPE_PROJECT_ID,
managementKey: process.env.DESCOPE_MANAGEMENT_KEY,
});
async function searchWeb(query) {
const response = await fetch(
`https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=5`,
{
headers: {
Accept: "application/json",
"X-Subscription-Token": process.env.BRAVE_API_KEY,
},
}
);
if (!response.ok) {
throw new Error(`Brave Search API error: ${response.status} ${response.statusText}`);
}
const data = await response.json();
if (!data.web || !data.web.results) return [];
return data.web.results.map((result) => ({
title: result.title,
url: result.url,
description: result.description,
}));
}
async function saveToNotion(notionAccessToken, { title, summary, sources }) {
const response = await fetch("https://api.notion.com/v1/pages", {
method: "POST",
headers: {
Authorization: `Bearer ${notionAccessToken}`,
"Content-Type": "application/json",
"Notion-Version": "2022-06-28",
},
body: JSON.stringify({
parent: { database_id: process.env.NOTION_DATABASE_ID },
properties: {
Name: { title: [{ text: { content: title } }] },
Summary: { rich_text: [{ text: { content: summary } }] },
Sources: { url: sources },
Date: { date: { start: new Date().toISOString().split("T")[0] } },
},
}),
});
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`Notion API error: ${response.status} — ${errorBody}`);
}
return await response.json();
}
const server = new McpServer({ name: "Research Assistant", version: "1.0.0" });
server.tool(
"web_search",
"Search the web for information on a topic",
{ query: z.string().describe("The search query to look up") },
async ({ query }) => {
try {
const results = await searchWeb(query);
if (results.length === 0) {
return { content: [{ type: "text", text: `No results found for "${query}"` }] };
}
const formatted = results
.map((r, i) => `${i + 1}. **${r.title}**\n ${r.url}\n ${r.description}`)
.join("\n\n");
return { content: [{ type: "text", text: `Search results for "${query}":\n\n${formatted}` }] };
} catch (error) {
return { content: [{ type: "text", text: `Search failed: ${error.message}` }] };
}
}
);
server.tool(
"save_to_notion",
"Save research findings to the team Notion database",
{
title: z.string().describe("Title of the research finding"),
summary: z.string().describe("Summary of what was found"),
sources: z.string().url().describe("Primary source URL"),
},
async ({ title, summary, sources }, { authInfo }) => {
try {
const userId = authInfo?.sub;
if (!userId) {
return { content: [{ type: "text", text: "Error: No authenticated user found." }] };
}
let notionTokenResponse;
try {
notionTokenResponse = await descopeClient.management.outboundApplication.fetchToken("notion", userId);
} catch {
return { content: [{ type: "text", text: "Error: Could not retrieve Notion credentials. The user may need to authorize Notion access." }] };
}
const notionAccessToken = notionTokenResponse.data.token.accessToken;
const result = await saveToNotion(notionAccessToken, { title, summary, sources });
return { content: [{ type: "text", text: `✅ Saved "${title}" to Notion!\nPage ID: ${result.id}\nURL: ${result.url}` }] };
} catch (error) {
return { content: [{ type: "text", text: `Failed to save to Notion: ${error.message}` }] };
}
}
);
const app = express();
const PORT = process.env.PORT || 3000;
app.use(cors({
origin: true,
methods: "*",
allowedHeaders: "Authorization, Origin, Content-Type, Accept, *",
}));
const mcpProvider = new DescopeMcpProvider({
projectId: process.env.DESCOPE_PROJECT_ID,
managementKey: process.env.DESCOPE_MANAGEMENT_KEY,
serverUrl: process.env.SERVER_URL,
authorizationServerOptions: {
isDisabled: false,
enableAuthorizeEndpoint: true,
enableDynamicClientRegistration: true,
},
dynamicClientRegistrationOptions: {
authPageUrl: `https://api.descope.com/login/${process.env.DESCOPE_PROJECT_ID}?flow=consent`,
nonConfidentialClient: true,
},
});
// Override protected resource metadata so clients discover our auth server
app.get("/.well-known/oauth-protected-resource", (req, res) => {
res.json({
resource: process.env.SERVER_URL,
authorization_servers: [process.env.SERVER_URL],
scopes_supported: ["openid", "profile"],
bearer_methods_supported: ["header"],
});
});
app.use(descopeMcpAuthRouter(undefined, mcpProvider));
app.use(["/mcp"], descopeMcpBearerAuth(mcpProvider));
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
app.post("/mcp", express.json(), async (req, res) => {
res.setHeader("X-Accel-Buffering", "no");
try {
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("Error handling MCP request:", error);
if (!res.headersSent) {
res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "Internal server error" }, id: null });
}
}
});
app.get("/mcp", (req, res) => {
res.status(405).json({ jsonrpc: "2.0", error: { code: -32000, message: "Method not allowed." }, id: null });
});
app.delete("/mcp", (req, res) => {
res.status(405).json({ jsonrpc: "2.0", error: { code: -32000, message: "Method not allowed." }, id: null });
});
const startServer = async () => {
await server.connect(transport);
app.listen(PORT, () => {
console.log(`MCP server running at ${process.env.SERVER_URL}/mcp`);
});
};
startServer().catch((error) => {
console.error("Failed to start server:", error);
process.exit(1);
});
process.on("SIGINT", async () => {
await transport.close();
await server.close();
process.exit(0);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment