1"use strict";(globalThis.webpackChunkvoiden_docs=globalThis.webpackChunkvoiden_docs||[]).push([[5996],{5996:(e,n,s)=>{s.r(n),s.d(n,{assets:()=>l,contentTitle:()=>d,default:()=>a,frontMatter:()=>i,metadata:()=>o,toc:()=>c});const o=JSON.parse('{"id":"mcp/publish","title":"Publishing with @voiden/mcp","description":"This page is for turning your own requests into a server that other AI agents (or teammates) can call over the network \u2014 a step up from testing locally. It involves running a command in a terminal, so it\'s more technical than the rest of the MCP docs; if you just want an assistant to run requests in your own project, see Initialize MCP instead, which needs none of this.","source":"@site/docs/mcp/publish.md","sourceDirName":"mcp","slug":"/mcp/publish","permalink":"/docs/mcp/publish","draft":false,"unlisted":false,"editUrl":"https://github.com/VoidenHQ/docs/tree/main/docs/mcp/publish.md","tags":[],"version":"current","lastUpdatedAt":1790146608000,"frontMatter":{"id":"publish","title":"Publishing with @voiden/mcp","sidebar_label":"Publish (@voiden/mcp)"},"sidebar":"tutorialSidebar","previous":{"title":"Tool Block","permalink":"/docs/mcp/tool-block"},"next":{"title":"Overview","permalink":"/docs/git-integration/overview"}}');var r=s(4848),t=s(8453);const i={id:"publish",title:"Publishing with @voiden/mcp",sidebar_label:"Publish (@voiden/mcp)"},d="Publishing with @voiden/mcp",l={},c=[{value:"Quick start",id:"quick-start",level:2},{value:"How it works",id:"how-it-works",level:2},{value:"How a parameter resolves",id:"how-a-parameter-resolves",level:2},{value:"Hosting flags",id:"hosting-flags",level:2},{value:"Network",id:"network",level:3},{value:"Environment",id:"environment",level:3},{value:"Tool exposure",id:"tool-exposure",level:3},{value:"Inspecting the server",id:"inspecting-the-server",level:3},{value:"Reliability",id:"reliability",level:3},{value:"Auth",id:"auth",level:3},{value:"No auth (default)",id:"no-auth-default",level:4},{value:"<code>--oauth</code>",id:"--oauth",level:4},{value:"<code>--sso-*</code> \u2014 delegate login to your own IdP",id:"--sso---delegate-login-to-your-own-idp",level:4},{value:"<code>--api-key</code> \u2014 a static shared secret",id:"--api-key--a-static-shared-secret",level:4},{value:"Running it from CI/CD",id:"running-it-from-cicd",level:2},{value:"Worked example: deploying to Render",id:"worked-example-deploying-to-render",level:2},{value:"Importing a server config into Voiden",id:"importing-a-server-config-into-voiden",level:2},{value:"FAQ",id:"faq",level:2}];function h(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"publishing-with-voidenmcp",children:"Publishing with @voiden/mcp"})}),"\n",(0,r.jsxs)(n.p,{children:["This page is for turning your own requests into a server that other AI agents (or teammates) can call over the network \u2014 a step up from testing locally. It involves running a command in a terminal, so it's more technical than the rest of the MCP docs; if you just want an assistant to run requests in your own project, see ",(0,r.jsx)(n.a,{href:"/docs/mcp/initialize",children:"Initialize MCP"})," instead, which needs none of this."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"@voiden/mcp"})," is a separate package that turns your ",(0,r.jsx)(n.a,{href:"/docs/mcp/tool-block",children:"Tool Block"}),"-tagged requests into a real MCP server other agents can call. ",(0,r.jsx)(n.code,{children:"voiden-mcp [path]"})," discovers every ",(0,r.jsx)(n.code,{children:"/tool"})," block under ",(0,r.jsx)(n.code,{children:"path"}),", verifies each one, and serves everything that passes \u2014 over stdio by default, or ",(0,r.jsx)(n.code,{children:"--http"})," for a real network endpoint."]}),"\n",(0,r.jsx)(n.admonition,{type:"note",children:(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"@voiden/mcp"})," ",(0,r.jsx)(n.strong,{children:"only"})," publishes ",(0,r.jsx)(n.code,{children:"/tool"})," blocks. It does not include Voiden's 6 built-in project tools (",(0,r.jsx)(n.code,{children:"list_void_files"}),", ",(0,r.jsx)(n.code,{children:"list_requests"}),", ",(0,r.jsx)(n.code,{children:"run_request"}),", ",(0,r.jsx)(n.code,{children:"write_result"}),", ",(0,r.jsx)(n.code,{children:"list_environments"}),", ",(0,r.jsx)(n.code,{children:"select_environment"}),") \u2014 those are local to the Voiden app, via ",(0,r.jsx)(n.a,{href:"/docs/developer-tools/voiden-cli#voiden-agent--register-with-an-agent-editor",children:(0,r.jsx)(n.code,{children:"voiden agent"})}),". Registering ",(0,r.jsx)(n.code,{children:"voiden agent"})," for a project and publishing ",(0,r.jsx)(n.code,{children:"/tool"})," blocks with ",(0,r.jsx)(n.code,{children:"@voiden/mcp"})," are two unrelated things."]})}),"\n",(0,r.jsxs)(n.p,{children:["Running ",(0,r.jsx)(n.code,{children:"@voiden/mcp"})," ",(0,r.jsx)(n.em,{children:"is"}),' the server starting \u2014 it doesn\'t send anything "to" a server elsewhere.']}),"\n",(0,r.jsx)(n.hr,{}
1),"\n",(0,r.jsx)(n.h2,{id:"quick-start",children:"Quick start"}),"\n",(0,r.jsxs)(n.p,{children:["Add at least one ",(0,r.jsx)(n.a,{href:"/docs/mcp/tool-block",children:"Tool block"})," to a request, then from your project folder:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"npx @voiden/mcp .\n"})}),"\n",(0,r.jsxs)(n.p,{children:["That's it \u2014 this discovers every ",(0,r.jsx)(n.code,{children:"/tool"})," block, verifies it, and serves everything that passes over stdio. Nothing to configure first. Add ",(0,r.jsx)(n.code,{children:"--http --port 3000"})," instead of the stdio default if you want a real network endpoint you can hit with ",(0,r.jsx)(n.code,{children:"curl"})," or point another agent at. Everything past this point \u2014 the full flag list, OAuth, CI/CD, deploying somewhere always-on \u2014 is there for when you outgrow this, not required to get going."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"how-it-works",children:"How it works"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Discover"})," \u2014 scan every ",(0,r.jsx)(n.code,{children:".void"})," file for ",(0,r.jsx)(n.code,{children:"/tool"})," blocks."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Validate"})," \u2014 structural checks (unbound placeholders, dangling references, duplicate names, a read-only tool that actually mutates). A tool that fails is excluded."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Verify"})," \u2014 run each tool's verification requests for real."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Serve"})," \u2014 a failing tool is withdrawn (or served flagged ",(0,r.jsx)(n.strong,{children:"degraded"}),", per its own policy); a verified or unverified tool is served."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Run ",(0,r.jsx)(n.code,{children:"voiden-mcp <path> --check"})," any time to see this decision without starting a live server \u2014 it prints served / withdrawn / degraded / excluded, with a reason for each."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"how-a-parameter-resolves",children:"How a parameter resolves"}),"\n",(0,r.jsxs)(n.p,{children:["Every row in a tool's ",(0,r.jsx)(n.strong,{children:"Parameters"})," table is agent-supplied \u2014 the agent provides a fresh value on every call, and ",(0,r.jsx)(n.code,{children:"binds"})," says exactly which ",(0,r.jsx)(n.code,{children:"{{token}}"}),' in the request receives it. There\'s no separate "environment"-sourced kind of param \u2014 every ',(0,r.jsx)(n.code,{children:"{{token}}"})," the request uses currently needs its own Parameters row, even one you only ever want resolved from your environment and never touched by the agent. An undeclared token excludes the tool as ",(0,r.jsx)(n.code,{children:"unresolved-placeholder"}),". See ",(0,r.jsx)(n.a,{href:"/docs/mcp/tool-block",children:"Tool Block"})," for the full field list."]}),"\n",(0,r.jsx)(n.p,{children:"A param can only bind to a token that's already in the request. A hardcoded value has nothing to attach to \u2014 template that spot first."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"hosting-flags",children:"Hosting flags"}),"\n",(0,r.jsxs)(n.p,{children:["Grouped by what they're for \u2014 most projects only ever touch ",(0,r.jsx)(n.strong,{children:"Network"}),"."]}),"\n",(0,r.jsx)(n.admonition,{type:"tip",children:(0,r.jsxs)(n.p,{children:["Most flags also have a matching env var (",(0,r.jsx)(n.code,{children:"--port"})," \u2192 ",(0,r.jsx)(n.code,{children:"VOIDEN_PUBLISH_PORT"}),", and so on) \u2014 CLI flag wins, then env var, then the default below. The Environment flags (",(0,r.jsx)(n.code,{children:"--env"}),", ",(0,r.jsx)(n.code,{children:"--profile"}),", ",(0,r.jsx)(n.code,{children:"--environment"}),") and ",(0,r.jsx)(n.code,{children:"--check"})," are the exceptions \u2014 CLI-only, no env var equivalent."]})}),"\n",(0,r.jsx)(n.h3,{id:"network",children:"Network"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Flag"}),(0,r.jsx)(n.th,{children:"Default"}),(0,r.jsx)(n.th,{children:"What it does"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--port <n>"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"3000"})}),(0,r.jsx)(n.td,{children:"TCP port the server listens on."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--host <addr>"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"127.0.0.1"})}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"127.0.0.1"})," = this machine only. ",(0,r.jsx)(n.code,{children:"0.0.0.0"})," = reachable from the network \u2014 opt-in."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--tunnel"})}),(0,r.jsx)(n.td,{children:"off"}),(0,r.jsxs)(n.td,{children:["Public ",(0,r.jsx)(n.code,{children:"cloudflared"}
1)," quick tunnel \u2014 only needed with no public IP of your own. Requires ",(0,r.jsx)(n.code,{children:"cloudflared"})," on ",(0,r.jsx)(n.code,{children:"PATH"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--public-url <url>"})}),(0,r.jsx)(n.td,{children:"\u2014"}),(0,r.jsxs)(n.td,{children:["The externally-reachable URL clients actually use \u2014 for a manual port-forward or reverse proxy, instead of ",(0,r.jsx)(n.code,{children:"--tunnel"}),"."]})]})]})]}),"\n",(0,r.jsx)(n.h3,{id:"environment",children:"Environment"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Flag"}),(0,r.jsx)(n.th,{children:"What it does"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"-e, --env <path>"})}),(0,r.jsxs)(n.td,{children:["One plain ",(0,r.jsx)(n.code,{children:".env"})," file, merged on top of the process's own env."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--profile [name]"})}),(0,r.jsxs)(n.td,{children:["A project ",(0,r.jsx)(n.a,{href:"/docs/getting-started-section/advanced-environment-config",children:"env profile"})," as the server's initial env \u2014 same system the ",(0,r.jsx)(n.code,{children:"select_environment"})," tool exposes to an agent. Bare flag means ",(0,r.jsx)(n.code,{children:'"default"'}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--environment <name>"})}),(0,r.jsxs)(n.td,{children:["Scope ",(0,r.jsx)(n.code,{children:"--profile"})," to one named environment in it (e.g. ",(0,r.jsx)(n.code,{children:'"dev"'}),", or ",(0,r.jsx)(n.code,{children:'"staging.eu"'}),")."]})]})]})]}),"\n",(0,r.jsx)(n.admonition,{type:"note",children:(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"--env"})," and ",(0,r.jsx)(n.code,{children:"--profile"})," are mutually exclusive \u2014 pick one. ",(0,r.jsx)(n.code,{children:"--environment"})," only does anything alongside ",(0,r.jsx)(n.code,{children:"--profile"}),"."]})}),"\n",(0,r.jsx)(n.h3,{id:"tool-exposure",children:"Tool exposure"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Flag"}),(0,r.jsx)(n.th,{children:"What it does"})]})}),(0,r.jsx)(n.tbody,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--dynamic-tools"})}),(0,r.jsxs)(n.td,{children:["Off by default. Expose just 2 fixed tools (",(0,r.jsx)(n.code,{children:"search_tools"}),"/",(0,r.jsx)(n.code,{children:"call_tool"}),") instead of one per tool \u2014 for a large surface that would otherwise overwhelm an agent's context window."]})]})})]}),"\n",(0,r.jsx)(n.h3,{id:"inspecting-the-server",children:"Inspecting the server"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Flag"}),(0,r.jsx)(n.th,{children:"What it does"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--check"})}),(0,r.jsx)(n.td,{children:"Dry run \u2014 prints served/withdrawn/degraded/excluded and exits, no live server started."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--print-config"})}),(0,r.jsxs)(n.td,{children:["Once the server is up, prints a ready-to-paste ",(0,r.jsx)(n.code,{children:'{"mcpServers": {...}}'})," entry \u2014 also fills a Connection block automatically if pasted into a ",(0,r.jsx)(n.code,{children:".void"})," file."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--verbose"})}),(0,r.jsx)(n.td,{children:"Print plugin-load diagnostics. Without it, a plugin failing to load does so silently \u2014 the server just serves 0 tools with no error."})]})]})]}),"\n",(0,r.jsx)(n.h3,{id:"reliability",children:"Reliability"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Flag"}),(0,r.jsx)(n.th,{children:"Default"}),(0,r.jsx)(n.th,{children:"What it does"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--no-scheduler"})}),(0,r.jsxs)(n.td,{children:["scheduler ",(0,r.jsx)(n.strong,{children:"on"})]}),(0,r.jsxs)(n.td,{children:["Turns off periodic re-verification (each tool re-runs on its own ",(0,r.jsx)(n.a,{href:"/docs/mcp/tool-block#verification",children:"cadence"})," otherwise, over stdio too via a clean restart on change)."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--scheduler-interval-minutes <n>"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"1"})}),(0,r.jsxs)(n.td,{children:["How often the scheduler ",(0,r.jsx)(n.em,{children:"checks"})," what's due \u2014 not how often a tool actually re-verifies, which is still its own cadence."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--no-restart"})}),(0,r.jsxs)(n.td,{children:["restart ",(0,r.jsx)(n.strong,{children:"on"})]}),(0,r.jsxs)(n.td,{children:["Turns off the auto-restart supervisor \u2014 use when something else already supervises the process (systemd, pm2, Docker ",(0,r.jsx)(n.code,{children:"--restart=always"}),")."]})]})]})]}),"\n",(0,r.jsx)(n.admonition,{type:"note",children:(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"@voiden/mcp"})," is still early (",(0,r.jsx)(n.code,{children:"0.0.x"}),") and this flag surface is changing fast \u2014 if something here doesn't match what ",(0,r.jsx)(n.code,{children:"voiden-mcp --help"})," prints for you, trust the CLI's own ",(0,r.jsx)(n.code,{children:"--help"})," output."]})}),"\n",(0,r.jsx)(n.h3,{id:"auth",children:"Auth"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"--http"}),"/",(0,r.jsx)(n.code,{children:"--tunnel"})," serve with ",(0,r.jsx)(n.strong,{children:"no authentication"})," by default \u2014 whoever reaches the URL has full tool access. Tur
1n on auth with ",(0,r.jsx)(n.code,{children:"--oauth"}),", ",(0,r.jsx)(n.code,{children:"--sso-*"}),", ",(0,r.jsx)(n.code,{children:"--api-key"}),", or any combination (whichever credential is presented is checked; any that matches lets the request through)."]}),"\n",(0,r.jsx)(n.h4,{id:"no-auth-default",children:"No auth (default)"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"voiden-mcp . --http --tunnel\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Nothing gates the MCP endpoint. ",(0,r.jsx)(n.code,{children:"/health"})," is always open, with or without auth on."]}),"\n",(0,r.jsx)(n.h4,{id:"--oauth",children:(0,r.jsx)(n.code,{children:"--oauth"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"voiden-mcp . --http --tunnel --oauth\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Adds ",(0,r.jsx)(n.code,{children:".well-known/oauth-protected-resource"}),", ",(0,r.jsx)(n.code,{children:".well-known/oauth-authorization-server"}),", ",(0,r.jsx)(n.code,{children:"/register"}),", ",(0,r.jsx)(n.code,{children:"/authorize"}),", ",(0,r.jsx)(n.code,{children:"/token"}),", ",(0,r.jsx)(n.code,{children:"/revoke"}),", and a bearer-token check in front of the MCP endpoint. Required by MCP clients that mandate a full OAuth 2.1 handshake before connecting at all (e.g. claude.ai's connector UI)."]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"/authorize"})," auto-approves \u2014 no login page. ",(0,r.jsx)(n.code,{children:"voiden-mcp"})," is a single-operator, locally-run tool, so this satisfies clients that require the protocol shape without adding a new identity check."]}),"\n",(0,r.jsxs)(n.li,{children:["Registered clients and issued tokens persist to ",(0,r.jsx)(n.code,{children:"~/.voiden/mcp-oauth.json"})," \u2014 a restart doesn't force reconnected clients to re-authenticate."]}),"\n",(0,r.jsxs)(n.li,{children:["The issuer must be HTTPS or loopback \u2014 combine ",(0,r.jsx)(n.code,{children:"--oauth"})," with ",(0,r.jsx)(n.code,{children:"--tunnel"})," for a public HTTPS URL, or ",(0,r.jsx)(n.code,{children:"--public-url"})," if you're already behind your own port-forward/reverse proxy. Without either, ",(0,r.jsx)(n.code,{children:"--oauth"})," only works on the default ",(0,r.jsx)(n.code,{children:"127.0.0.1"}),"/",(0,r.jsx)(n.code,{children:"localhost"})," bind."]}),"\n"]}),"\n",(0,r.jsxs)(n.h4,{id:"--sso---delegate-login-to-your-own-idp",children:[(0,r.jsx)(n.code,{children:"--sso-*"})," \u2014 delegate login to your own IdP"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"voiden-mcp . --http --tunnel \\\n --sso-authorize-url https://your-idp.example.com/oauth/authorize \\\n --sso-token-url https://your-idp.example.com/oauth/token \\\n --sso-registration-url https://your-idp.example.com/oauth/register\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Replaces ",(0,r.jsx)(n.code,{children:"--oauth"}),"'s auto-approve with a real login at your own IdP. ",(0,r.jsx)(n.code,{children:"--sso-authorize-url"})," + ",(0,r.jsx)(n.code,{children:"--sso-token-url"})," together turn OAuth mode on by themselves \u2014 no need to also pass ",(0,r.jsx)(n.code,{children:"--oauth"}),"."]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"--sso-registration-url"})," is required alongside the two above \u2014 your IdP's Dynamic Client Registration (RFC 7591) endpoint. Many enterprise IdPs (Okta, Auth0, Keycloak) support this when configured for it."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"--sso-revocation-url"})," is optional, for an IdP with a revocation endpoint."]}),"\n",(0,r.jsxs)(n.li,{children:['An IdP with only one fixed, manually-created app and no DCR (how plain "Sign in with Google/GitHub" work) isn\'t supported yet \u2014 ',(0,r.jsx)(n.code,{children:"--sso-client-id"}),"/",(0,r.jsx)(n.code,{children:"--sso-client-secret"})," exist only to fail with an explanatory error if you try."]}),"\n"]}),"\n",(0,r.jsxs)(n.h4,{id:"--api-key--a-static-shared-secret",children:[(0,r.jsx)(n.code,{children:"--api-key"})," \u2014 a static shared secret"]}
1),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'voiden-mcp . --http --tunnel --api-key\n# \u2192 \ud83d\udd11 API key required \u2014 pass "Authorization: Bearer <generated-key>"\n'})}),"\n",(0,r.jsxs)(n.p,{children:["A shared Bearer token \u2014 no browser, no handshake. Independent of ",(0,r.jsx)(n.code,{children:"--oauth"}),"/",(0,r.jsx)(n.code,{children:"--sso-*"}),", and combinable with either. Pass the flag alone to auto-generate a key, persisted under ",(0,r.jsx)(n.code,{children:"~/.voiden/mcp-api-keys.json"})," and printed at startup \u2014 or pass ",(0,r.jsx)(n.code,{children:"--api-key <value>"})," (or set ",(0,r.jsx)(n.code,{children:"VOIDEN_PUBLISH_API_KEY"}),", once ",(0,r.jsx)(n.code,{children:"--api-key"})," is present, to keep the literal value off the command line) to set one explicitly."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"running-it-from-cicd",children:"Running it from CI/CD"}),"\n",(0,r.jsx)(n.p,{children:"CI jobs have no public inbound networking \u2014 a bound port isn't reachable from outside the job. Two options:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Ephemeral / preview"}),": add ",(0,r.jsx)(n.code,{children:"--tunnel"})," for a public URL that lives as long as the job."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Always-on"}),": CI can't host a persistent server itself. Deploy to something that can \u2014 a self-hosted runner, or a platform like Railway/Render/Fly.io."]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"# .github/workflows/publish-mcp.yml (illustrative)\n- name: Run MCP server\n env:\n VOIDEN_PUBLISH_PORT: 3000\n STRIPE_API_KEY: ${{ secrets.STRIPE_API_KEY }}\n run: npx @voiden/mcp ./api --http --tunnel\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"worked-example-deploying-to-render",children:"Worked example: deploying to Render"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://render.com",children:"Render"}),"'s free tier needs no card and gives a stable HTTPS URL. The same shape works on Railway, Fly.io, or any platform that runs a start command and injects ",(0,r.jsx)(n.code,{children:"$PORT"}),"."]}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:["Push a repo with your ",(0,r.jsx)(n.code,{children:".void"})," files."]})," No ",(0,r.jsx)(n.code,{children:"package.json"})," needed \u2014 ",(0,r.jsx)(n.code,{children:"npm install"})," creates one."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"New \u2192 Web Service on Render"}),", pointed at that repo."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Build Command:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"npm install @voiden/mcp@latest\n"})}),"\n","Not ",(0,r.jsx)(n.code,{children:"-g"})," \u2014 Render's build container doesn't run as root."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Start Command:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"npx voiden-mcp . --http --port $PORT --host 0.0.0.0\n"})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Deploy."})," Your MCP endpoint is ",(0,r.jsx)(n.code,{children:"https://your-app.onrender.com/mcp"}),"; ",(0,r.jsx)(n.code,{children:"/health"})," is live too."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["If you see ",(0,r.jsx)(n.code,{children:"0 tool(s) served"})," with no explanation, run ",(0,r.jsx)(n.code,{children:"voiden-mcp . --check"})," locally on the same commit \u2014 it names every excluded tool and why. The most common cause: a cross-file ",(0,r.jsx)(n.code,{children:"requestFilePath"})," still holding an absolute path from wherever it was authored, or one with a leading slash. ",(0,r.jsx)(n.code,{children:"requestFilePath"})," is relative to the project root \u2014 ",(0,r.jsx)(n.code,{children:"/firstrequest.void"})," isn't actually relative (a leading slash means filesystem root), and an absolute path only resolves on the machine that saved it."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"importing-a-server-config-into-voiden",children:"Importing a server config into Voiden"}),"\n",(0,r.jsxs)(n.p,{children:["Paste any MCP server's config JSON \u2014 Claude Desktop, Cursor, Windsurf, ",(0,r.jsx)(n.code,{children:".m
1cp.json"}),", VS Code's ",(0,r.jsx)(n.code,{children:".vscode/mcp.json"}),", or ",(0,r.jsx)(n.code,{children:"--print-config"}),"'s own output \u2014 anywhere in a ",(0,r.jsx)(n.code,{children:".void"})," file, and Voiden fills in a ",(0,r.jsx)(n.a,{href:"/docs/mcp/connection-block",children:"Connection Block"})," automatically. Every server in the config becomes its own block; headers import as an object or an array; ",(0,r.jsx)(n.code,{children:"mcp-remote"}),"-wrapped entries import too. A genuinely local server (spawns its own process) can't become a block and shows a toast explaining why."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"faq",children:"FAQ"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Do I need to buy a domain?"}),"\nNo. ",(0,r.jsx)(n.code,{children:"--tunnel"})," gives a public URL with zero domain and zero signup. If the machine already has a public IP, skip both \u2014 bind the port directly."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsxs)(n.strong,{children:["What happens if the request has a ",(0,r.jsx)(n.code,{children:"{{token}}"})," that isn't declared as any parameter?"]}),"\nThat tool is excluded from what's served as an ",(0,r.jsx)(n.code,{children:"unresolved-placeholder"}),", with the reason printed by ",(0,r.jsx)(n.code,{children:"--check"}),". This applies even to a token you intend to resolve from your environment; every ",(0,r.jsx)(n.code,{children:"{{token}}"})," the request uses currently needs a matching Parameters row."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsxs)(n.strong,{children:["I see ",(0,r.jsx)(n.code,{children:"0 tool(s) served"})," \u2014 why?"]}),"\nRun ",(0,r.jsx)(n.code,{children:"voiden-mcp <path> --check"})," \u2014 it prints exactly which tools were excluded and why. See the Render example above for the two most common causes."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsxs)(n.strong,{children:["Does the scheduler work over stdio, or only ",(0,r.jsx)(n.code,{children:"--http"}),"?"]}),"\nBoth. Over stdio, a state change triggers a clean restart so a reconnecting client picks up the new list \u2014 needs the auto-restart supervisor, on by default."]})]})}function a(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(h,{...e})}):h(e)}},8453:(e,n,s)=>{s.d(n,{R:()=>i,x:()=>d});var o=s(6540);const r={},t=o.createContext(r);function i(e){const n=o.useContext(t);return o.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function d(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:i(e.components),o.createElement(t.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.