embr-foundry-tool-sample-node MCP Server
embr-foundry-tool-sample-node
A minimal Node.js / Express tool server that exposes the same two tools through two Foundry-consumable surfaces:
OpenAPI (custom skill) at
/api/*+ spec at/openapi.json.MCP (Model Context Protocol) streamable-HTTP at
/mcp.
Same code path, two doors. Lets you compare the two integration paths from a single deployment.
This is the Node sibling of embr-foundry-tool-sample-python
and embr-foundry-tool-sample-dotnet.
What's in the box
ESM Node app on Node 20+ / Express 5.
Two tools:
getWeatherandgetTime— pure functions intools.js.Hand-written OpenAPI 3.1 doc with an
x-api-keysecurity scheme declared (Foundry's custom-OpenAPI tool import rejects anonymous specs).MCP server via the official
@modelcontextprotocol/sdkpackage, mounted at/mcpwith a stateless StreamableHTTP transport (new transport per request, no session management).
Layout
app/
server.js # Express endpoint mapping + auth gate
tools.js # core tool implementations
mcp.js # McpServer + StreamableHTTPServerTransport wiring
openapi.js # hand-written OpenAPI 3.1 document
embr.yaml # Embr deploy config (platform: nodejs)
.env.example # optional TOOL_API_KEY for runtime authSurfaces
Path | Purpose |
| Liveness probe |
| Index — points at the two surfaces |
| OpenAPI tool (returns |
| OpenAPI tool (returns |
| OpenAPI 3.1 spec — feed this to Foundry's "custom OpenAPI tool" |
| MCP streamable-HTTP — register as a remote MCP tool in Foundry |
Auth
The OpenAPI spec declares an x-api-key header security scheme (Foundry
requires at least one auth scheme on imported tools). At runtime:
If
TOOL_API_KEYis unset, requests are accepted without a header. This is the easy mode for the Foundry import flow and local development.If
TOOL_API_KEYis set,/api/*requests must includex-api-key: <value>or get a401.
The MCP surface does not enforce the header — Foundry's MCP connection configuration is the auth boundary there.
Run locally
npm install
npm startThen:
curl http://localhost:5000/health
curl 'http://localhost:5000/api/weather?location=Seattle'
curl 'http://localhost:5000/api/time?timezone=America/New_York'
curl http://localhost:5000/openapi.json | jq .For the MCP surface, post a JSON-RPC initialize:
curl -s -X POST http://localhost:5000/mcp \
-H 'Accept: application/json,text/event-stream' \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'Deploy to Embr
gh repo create embr-foundry-tool-sample-node --source=. --public --push
embr quickstart deploy <your-user>/embr-foundry-tool-sample-node
# optional:
embr variables set TOOL_API_KEY <secret> -p <projectId> -e <envId> -sOnce deployed, the URLs Foundry needs are:
OpenAPI:
https://<your-deploy>/openapi.jsonMCP:
https://<your-deploy>/mcp
Notes
Tool implementations are pure (no external deps), so behavior is fully identical between the OpenAPI route and the MCP tool call.
Stateless MCP: each POST gets a fresh
McpServer+ transport. Keeps the surface trivial to reason about behind Embr's ingress.