Calling upstream MCP servers¶
Recipes for connecting upstream MCP servers to mcp-v8 and using them from JavaScript.
Connect a stdio server¶
Use --mcp-server name=stdio:command:arg1:arg2. Arguments after the command are separated by :.
mcp-v8 --http-port 8080 \
--mcp-server github=stdio:npx:-y:@modelcontextprotocol/server-github
The process is spawned as a child of mcp-v8 and kept alive for the lifetime of the server.
Connect an SSE server¶
Use --mcp-server name=sse:URL. mcp-v8 opens an SSE connection to the given endpoint at startup.
mcp-v8 --http-port 8080 \
--mcp-server analytics=sse:http://analytics-mcp.internal:9000/sse
Connect multiple servers at once¶
Repeat --mcp-server for each server. Names must be unique.
mcp-v8 --http-port 8080 \
--mcp-server github=stdio:npx:-y:@modelcontextprotocol/server-github \
--mcp-server db=sse:http://db-mcp.internal:9000/sse
Use --mcp-config for richer configuration¶
--mcp-config takes a path to a JSON file. Use this when you need to pass environment variables to a stdio subprocess or when managing many servers.
Create mcp-servers.json:
[
{
"name": "github",
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "ghp_xxxxxxxxxxxxxxxxxxxx"
}
},
{
"name": "analytics",
"transport": "sse",
"url": "http://analytics-mcp.internal:9000/sse"
}
]
Pass the file to mcp-v8:
mcp-v8 --http-port 8080 --mcp-config mcp-servers.json
--mcp-server entries and --mcp-config entries are merged at startup; all server names must be unique across both sources.
Call an upstream tool from JavaScript¶
Inside a run_js execution the global mcp object is always available when servers are configured.
// List all tools across all servers
const all = mcp.listTools();
// List tools from one server
const tools = mcp.listTools("github");
// Call a tool
const result = await mcp.callTool("github", "list_issues", {
owner: "acme",
repo: "api",
});
console.log(result.content[0].text);
Handle upstream tool errors¶
mcp.callTool throws a McpToolError when the upstream tool returns isError: true.
try {
await mcp.callTool("github", "create_issue", {
owner: "acme",
repo: "api",
title: "New bug",
});
} catch (e) {
if (e instanceof McpToolError) {
console.error("upstream tool failed:", e.result);
} else {
throw e;
}
}
Disable stub tools¶
By default, every upstream tool is also listed in mcp-v8's own list_tools response as a stub. To turn this off:
mcp-v8 --mcp-server myserver=stdio:mcp-my-server --mcp-stubs false
With stubs disabled, upstream tools do not appear in list_tools at all; they remain callable from JavaScript via mcp.callTool.
Change the stub prefix¶
The default stub prefix is runjs__, giving names like runjs__github__create_issue. To use a different prefix:
mcp-v8 \
--mcp-server github=stdio:npx:-y:@modelcontextprotocol/server-github \
--mcp-stub-prefix "up__"
With this setting the stub tool appears as up__github__create_issue. The JavaScript mcp API is unaffected — always use the original server and tool names there.
Gate upstream calls with a policy¶
Add an mcp_tools entry to your --policies-json config. The Rego entrypoint is data.mcp.tools.allow and the policy input is:
{
"operation": "mcp_call_tool",
"server": "<server-name>",
"tool": "<tool-name>",
"arguments": { ... }
}
Example policy — allow only read operations on the github server:
package mcp.tools
default allow := false
allow if {
input.server == "github"
input.tool in {"list_issues", "get_issue", "list_repos", "get_repo"}
}
Pass it via --policies-json:
mcp-v8 \
--mcp-server github=stdio:npx:-y:@modelcontextprotocol/server-github \
--policies-json '{"mcp_tools": {"policies": [{"inline": "package mcp.tools\ndefault allow := false\nallow if { input.tool in {\"list_issues\",\"get_issue\"} }"}]}}'
See the policies concepts page for the full policy configuration format.