chore: import upstream snapshot with attribution
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Docker image release / Build base image (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Has been cancelled
Tests / Check if changed (push) Has been cancelled
Tests / format (push) Has been cancelled
Tests / check-imports (push) Has been cancelled
Tests / Unit / macos-latest (push) Has been cancelled
Tests / Unit / ubuntu-latest (push) Has been cancelled
Tests / Unit / windows-latest (push) Has been cancelled
Tests / mypy (push) Has been cancelled
Tests / Integration / ubuntu-latest (push) Has been cancelled
Tests / Integration / macos-latest (push) Has been cancelled
Tests / Integration / windows-latest (push) Has been cancelled
Tests / notify-slack-on-failure (push) Has been cancelled
Tests / Mark tests as completed (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Docker image release / Build base image (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Has been cancelled
Tests / Check if changed (push) Has been cancelled
Tests / format (push) Has been cancelled
Tests / check-imports (push) Has been cancelled
Tests / Unit / macos-latest (push) Has been cancelled
Tests / Unit / ubuntu-latest (push) Has been cancelled
Tests / Unit / windows-latest (push) Has been cancelled
Tests / mypy (push) Has been cancelled
Tests / Integration / ubuntu-latest (push) Has been cancelled
Tests / Integration / macos-latest (push) Has been cancelled
Tests / Integration / windows-latest (push) Has been cancelled
Tests / notify-slack-on-failure (push) Has been cancelled
Tests / Mark tests as completed (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
// Files under `api/` become HTTP endpoints automatically — this is `/api/mcp`.
|
||||
import { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
|
||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
||||
res.setHeader(
|
||||
"Access-Control-Allow-Headers",
|
||||
"Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version"
|
||||
);
|
||||
res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id");
|
||||
|
||||
if (req.method === "OPTIONS") {
|
||||
return res.status(204).end();
|
||||
}
|
||||
|
||||
if (req.method !== "POST") {
|
||||
res.setHeader("Allow", "POST, OPTIONS");
|
||||
return res.status(405).end("Method Not Allowed");
|
||||
}
|
||||
|
||||
// Get environment variables
|
||||
const { MCP_WORKSPACE_ID, SEARCH_API_TOKEN } = process.env;
|
||||
|
||||
if (!MCP_WORKSPACE_ID || !SEARCH_API_TOKEN) {
|
||||
return res.status(500).json({ error: "MCP service is not configured." });
|
||||
}
|
||||
|
||||
try {
|
||||
// Forward the JSON-RPC body unchanged with the API key injected, so we
|
||||
// don't need to know any MCP methods — new upstream tools just work.
|
||||
const apiResponse = await fetch(
|
||||
`https://api.cloud.deepset.ai/api/v2/workspaces/${MCP_WORKSPACE_ID}/mcp`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept:
|
||||
(req.headers.accept as string) ||
|
||||
"application/json, text/event-stream",
|
||||
"X-Client-Source": "haystack-docs",
|
||||
Authorization: `Bearer ${SEARCH_API_TOKEN}`,
|
||||
// Forward MCP session id so upstream can correlate requests.
|
||||
...(req.headers["mcp-session-id"] && {
|
||||
"Mcp-Session-Id": req.headers["mcp-session-id"] as string,
|
||||
}),
|
||||
// Forward the protocol version the client negotiated.
|
||||
...(req.headers["mcp-protocol-version"] && {
|
||||
"Mcp-Protocol-Version": req.headers[
|
||||
"mcp-protocol-version"
|
||||
] as string,
|
||||
}),
|
||||
},
|
||||
body: JSON.stringify(req.body),
|
||||
}
|
||||
);
|
||||
|
||||
// Pass the response through as-is (status, content-type, raw body).
|
||||
const text = await apiResponse.text();
|
||||
res.status(apiResponse.status);
|
||||
const contentType = apiResponse.headers.get("content-type");
|
||||
if (contentType) res.setHeader("Content-Type", contentType);
|
||||
// Surface the session id back to the client (browsers can read it because
|
||||
// it's in Access-Control-Expose-Headers above).
|
||||
const sessionId = apiResponse.headers.get("mcp-session-id");
|
||||
if (sessionId) res.setHeader("Mcp-Session-Id", sessionId);
|
||||
return res.send(text);
|
||||
} catch (error) {
|
||||
console.error("MCP proxy error:", error);
|
||||
return res.status(502).json({ error: "Failed to reach MCP upstream." });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
|
||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||
if (req.method !== "POST") {
|
||||
res.setHeader("Allow", "POST");
|
||||
return res.status(405).end("Method Not Allowed");
|
||||
}
|
||||
|
||||
const { query, filter } = req.body;
|
||||
|
||||
if (!query) {
|
||||
return res.status(400).json({ error: "Query is required" });
|
||||
}
|
||||
|
||||
const { SEARCH_API_WORKSPACE, SEARCH_API_PIPELINE, SEARCH_API_TOKEN } =
|
||||
process.env;
|
||||
|
||||
if (!SEARCH_API_WORKSPACE || !SEARCH_API_PIPELINE || !SEARCH_API_TOKEN) {
|
||||
console.error(
|
||||
"Search API environment variables are not configured on the server."
|
||||
);
|
||||
return res.status(500).json({ error: "Search service is not configured." });
|
||||
}
|
||||
|
||||
try {
|
||||
// Build the request body with optional filters
|
||||
const requestBody: any = {
|
||||
queries: [query],
|
||||
};
|
||||
|
||||
// Add filters if provided (for future backend filtering support)
|
||||
if (filter && filter !== "all") {
|
||||
requestBody.debug = true;
|
||||
requestBody.filters = {
|
||||
operator: "AND",
|
||||
conditions: [
|
||||
{
|
||||
field: "meta.type",
|
||||
operator: "==",
|
||||
value: filter,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const apiResponse = await fetch(
|
||||
`https://api.cloud.deepset.ai/api/v1/workspaces/${SEARCH_API_WORKSPACE}/pipelines/${SEARCH_API_PIPELINE}/search`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Client-Source": "haystack-docs",
|
||||
Authorization: `Bearer ${SEARCH_API_TOKEN}`,
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
}
|
||||
);
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const errorData = await apiResponse.text();
|
||||
console.error("Haystack API error:", errorData);
|
||||
return res
|
||||
.status(apiResponse.status)
|
||||
.json({ error: `API error: ${apiResponse.statusText}` });
|
||||
}
|
||||
|
||||
const data = await apiResponse.json();
|
||||
return res.status(200).json(data);
|
||||
} catch (error) {
|
||||
console.error("Internal server error:", error);
|
||||
return res.status(500).json({ error: "Failed to fetch search results." });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"lib": ["ES2021", "DOM"],
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["./**/*.ts"]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Returns a JSON 404 for OAuth discovery probes (e.g.
|
||||
// `/.well-known/oauth-protected-resource`, `/.well-known/oauth-authorization-server`).
|
||||
// Per RFC 9728 / the MCP authorization spec a 404 here means "this resource
|
||||
// doesn't require OAuth — connect anonymously." Without this handler the
|
||||
// Docusaurus catch-all serves an HTML 404, which trips MCP clients that try to
|
||||
// JSON-parse the body to extract an OAuth error.
|
||||
import { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
|
||||
export default function handler(_req: VercelRequest, res: VercelResponse) {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.setHeader("Cache-Control", "public, max-age=3600");
|
||||
return res.status(404).end("{}");
|
||||
}
|
||||
Reference in New Issue
Block a user