Your Astro API Route Can Be an MCP Server
Most MCP tutorials assume a standalone process. Here's how to expose one as just another Astro API route using Streamable HTTP — same auth, same deploy pipeline, no second app to maintain.
Read moreNote
I previously wrote about Proxying Plausible in Remix, and when I moved this blog to Astro last year, I wanted to do the same thing.
It's a similar process but also slightly different and works well, and can also be adapted for other applications outside of plausible pretty easily.
I have been using Plausible since mid-2021 and really enjoy it. Plausible is a more privacy-oriented alternative to Google Analytics and I use it for all my projects, including this site.
Adding Plausible to any site is easy with just one line of code:
<script async defer data-domain="domain.com" src="https://plausible.io/js/script.js" />
Unfortunately, Plausible can be blocked by some blocklist maintainers who favor blocking every tracking code irrespective of its nature, be it good or bad.
You can find more details on Plausible's site. The main takeaway is that Plausible is a more privacy-sensitive alternative to Google Analytics, but it pays the price because of blocklist maintainers who intend to block all tracking scripts.
We could download the file from https://plausible.io/js/script.js, place it in the public folder, reference it in root.tsx, and it would work, but there are two downsides:
https://plausible.io/api/event.Luckily, Astro's API routes can solve this issue, as it allows us to proxy both the script and the requests to /api/event from a loader function within a route.
Start by creating a file at src/pages/js/script.ts.:
import type { APIRoute } from 'astro'
export const GET: APIRoute = async ({ request }) => {
const plausibleScriptData = await fetch('https://plausible.io/js/script.js');
const script = await plausibleScriptData.text()
const { status, headers } = plausibleScriptData
return new Response(script, {
status,
headers,
})
}
This piece of code does the following:
script.js file from Plausible's servers and sends it back to the browser.await response.text();) and passes it as the new body.We still need to proxy calls to https://plausible.io/api/event. For this, create a route at src/pages/event/index.ts. Plausible has this URL coded in their script.js, which is why we use it in our app. Here's what should be in this file:
import type { APIRoute } from 'astro'
export const POST: APIRoute = async ({ request }) => {
const body = await request.text()
const response = await fetch('https://plausible.io/api/event', {
body: body,
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})
const responseBody = await response.text()
const { status, headers } = response
return new Response(responseBody, {
status,
headers,
})
}
This route consists of:
action function called by script.js as a POST request. It takes the method and body from the request and performs a fetch to Plausible's servers, passing along the method, body and a Content-Type: 'application/json' header.reponse body to a string and creates a new response which then passes the proxied response status code, headers, and body back to our app.This direct proxying ensures that whatever Plausible's servers return, Plausible's script will process it.
To finish, update root.tsx by adding the following line inside the <head></head> tags:
<script defer data-domain="yourdomain.com" src="/js/script" />
Here's what will happen:
/api/event, which is also proxied by Remix and is forwarded to Plausible's API.As an added note, if you want the event route in a different location than /api/event, you can add a data-api attribute to the Plausible call. For instance, if we decide to route event at /r2d2/event:
<script defer data-domain="yourdomain.com" data-api="/r2d2/event" src="/js/script" />
You can adopt this process to proxy other requests as well, not just Plausible.
Most MCP tutorials assume a standalone process. Here's how to expose one as just another Astro API route using Streamable HTTP — same auth, same deploy pipeline, no second app to maintain.
Read moreExtending the Datastar chat widget with the browser's native SpeechRecognition API — no new backend, no transcription service, just one more way to fill the same signal.
Read moreTake the Astro + Datastar + Vercel AI SDK stack from this series and run it entirely on your own machine with Ollama — no API key, no per-token cost, no data leaving your laptop. It's a one-import change.
Read more