Skip to content

Roadmap

Christian Presley edited this page Oct 12, 2026 · 14 revisions

Roadmap

This page is the plan of record of zig-bridge-sdk. It lists the milestones M0 to M6 and their state, the changes that the bridges need in zig-sdk, the open items and the deferred items. The page follows the approved plan of the project. The plan gives no dates. Each milestone has one pull request, and each pull request starts from the pull request of the milestone before it. Milestones M0 to M6 are on main, and the release 0.1.0 has them.

Applies to: zig-bridge-sdk 0.1.2 at milestone M6 (main at 3cbc34b, the commit of the tag v0.1.2). zig-sdk 0.5.1 at commit 52fc1b8, the commit of the tag v0.5.1.

Milestones

The first bridge is the vscode bridge for Visual Studio Code (VS Code). The milestones add its parts in this order. A merge is a signed fast-forward push of the head of a pull request to main. Thus the commit on main after a merge is the last commit of the pull request.

Milestone Deliverables State
M0 Scaffold The package bridge_sdk with the modules bridge and vscode and the executable mcp-bridge-vscode. zig-sdk 0.3.0 at commit c1f55f0 as the only dependency. The conventions of zig-sdk: the documentation linter, the dictionary, the commit policy and the version check. A fixture server, and the schemas of both revisions as test fixtures. CI, with a stub of the consumer job. The wiki pages Home, Roadmap and Threat Model. The trademark notice. Root commits with Conventional Commits messages. Done. Pull request #1, main at bba672c.
M1 Front end and stdio upstream The stdio front end for revision 2025-11-25: the lifecycle, a reader that never blocks, and an error with the recovered id for a bad line. The upstream child process, the detection of its exit, and a debug line for each upstream exchange. initialize through server/discover. The forwarded requests with the result rules and the items fix for array schemas. The error table, the time limits, progress, the exit on upstream loss, a bounded stop at the end of input with a watchdog, and diagnostics. The rules for the Copilot client of VS Code, which can send server/discover before initialize. The command line mcp-bridge-vscode [options] -- <command>. In M1, the bridge removed elicitation, sampling and roots from the capabilities toward the server. It also removed listChanged, subscribe and logging from the capabilities toward VS Code. M2 and M3 give these capabilities again when the other side declares them. Done. Pull request #2, main at 7e72ef4.
M2 Input requests The bridge drives the multi round-trip requests (MRTR) of the server1. It sends each input request to VS Code as a request of revision 2025-11-25: elicitation/create, sampling/createMessage or roots/list. A table of pending requests, the input checks, the round rules and a cap on the input requests of a round. Done. Pull request #3, main at 52a929e.
M3 Notifications and subscriptions The subscriptions/listen stream, with a new stream after a loss2. resources/subscribe and resources/unsubscribe. logging/setLevel and the log messages of the upstream server. The pass-through of the _meta keys traceparent, tracestate and vscode.*. The order of events relative to responses on stdio. The pin of zig-sdk moved to commit cda66dc of the 0.4.0 stack. Done. Pull request #5, main at af8a15d.
M4 HTTP upstream An upstream server at an HTTPS URL over Streamable HTTP3, with OAuth sign-in4. The URL form mcp-bridge-vscode [options] <url>, the headers from the environment, the client registration, the browser opener and the loopback receiver. The token stores and the subcommand logout. Only the data: icons of a remote server reach VS Code. The client ID metadata document site/vscode/client.json on GitHub Pages. See Milestone M4 in detail. Done. Pull request #6, main at f3c174d.
M5 Embedded bridge bridge.embed.serveStdioAuto and vscode.serveStdio. With them, a zig-sdk server serves both revisions on one stdio connection. The first request of the client selects the path. An initialize selects the bridge, and the bridge reaches the server through a ClientLink. A first server/discover selects no path. The option .discover = .refuse is off by default. Embedders take mcp from the bridge package. The full consumer job in CI. The pin of zig-sdk moved to the tag v0.4.0. See Milestone M5 in detail. Done. Pull request #7, main at 64bb1e2.
M6 Start URL of the sign-in The browser opener gets the one-time start URL http://127.0.0.1:<port>/start/<token> of the loopback receiver, and not the authorization URL. Thus the process list of a shared POSIX host does not show the state and the code_challenge of the sign-in. The token has 256 random bits. The first GET gets 303 to the authorization URL, and a second GET stops the sign-in with the cause sign_in_start_reused. The sign-in line on standard error keeps the authorization URL. See Milestone M6 in detail. Done. Pull request #10, main at a8f4791.

Milestone M4 in detail

This table gives the parts of milestone M4 as the code on main has them. The VS Code page gives the options and the configuration. The Threat Model page gives the security requirements and their tests.

Part Content
URL and headers The upstream is an https URL. An http URL is valid only for a loopback host, for example localhost, a name that ends in .localhost, an address in 127.0.0.0/8 or [::1]. The URL has no user information and no fragment. --header-env Name=VAR takes a secret header value from an environment variable, and a variable that is not set is an error. --header K:V is for values that are not secret. The bridge refuses the headers of the protocol and of the connection, each name that starts with mcp-, and Proxy-Authorization. A static Authorization header and the sign-in options exclude each other.
Client registration In this order5: a pre-registered client (--client-id, with the secret from MCP_BRIDGE_CLIENT_SECRET), a client ID metadata document, then dynamic client registration. A client with a secret needs --client-issuer. The default document is https://christianpresley.github.io/zig-bridge-sdk/vscode/client.json on GitHub Pages. It lists only the redirect port 41894. Thus with a different --redirect-port and no registration option, the bridge uses dynamic client registration. The provider of the bridge records the cause of each challenge that it did not answer, for a clear error message.
Sign-in The bridge validates the authorization URL. Then it writes the URL once to standard error. It opens the URL without a shell: ShellExecuteW on Windows, and $BROWSER, open or xdg-open on other systems. A loopback receiver on 127.0.0.1 at a fixed port (41894, or --redirect-port) takes one redirect. It checks state, and it has head limits. A sign-in has a time limit of 300 s (--sign-in-timeout).
Sign-in after the start Before notifications/initialized, the bridge opens the browser directly. A later challenge goes to VS Code as a URL elicitation, one sign-in at a time. A declined sign-in starts a cooldown of 60 s for all scopes. Each request gets one step-up attempt.
OAuth identity The fixed client_name mcp-bridge-vscode (zig-bridge-sdk), the keychain service zig-bridge-sdk/mcp-bridge-vscode, and the storage identity `mcp-bridge-vscode
Token storage --token-store auto tries the keychain of the host first. On a Linux host without a display, the keychain shows no unlock prompt. Then it tries encrypted files with a key from MCP_BRIDGE_TOKEN_KEY or --token-key-file, and then memory. A keychain that stops to answer later switches to the next store for the rest of the process.
Accounts --account <label> keeps the sign-ins of two accounts apart. logout [--account <label>] <url> and logout --all delete stored sign-ins. An index in the same store gives the keys. Without an index entry, logout uses the protected resource metadata of the server, but only when its resource covers the URL. A sign-in that ends at its time limit tells the user to run logout, because the authorization server can forget a stored client.
Test and environment options --no-browser, --ca-file (zig-sdk change J) and the proxy of HTTPS_PROXY, ALL_PROXY and NO_PROXY (zig-sdk change K). With SANDBOX_RUNTIME=1 and no usable stored token, initialize fails at once with a message that names the sandbox. The executable has no option that permits http to other hosts or disables a check of the sign-in.
Icons For a remote upstream server, VS Code gets only the icons with a data: URI. The rule applies to the server, to the items of the list results and to the resource links in results.
Requests over HTTP A failed request over HTTP fails alone, and the bridge continues. --max-response-bytes (64 MiB) limits one JSON response and one event of a stream. The error of a failed request names the HTTP status. A listen stream opens again after the status 408, 429 or 5xx except 501. After 401 or 403, it opens again at the next sign-in.
Client ID metadata document site/vscode/client.json. The workflow .github/workflows/pages.yml publishes it on GitHub Pages, and test/site_test.zig checks it with the checks of zig-sdk.
Tests The unit tests of bridge.oauth, the transcript tests in bridges/vscode/test/https_test.zig and bridges/vscode/test/oauth_test.zig, and the process tests of the URL form. The CI job interop also runs .github/interop/oauth_stdio_client.mjs.

Milestone M5 in detail

This table gives the parts of milestone M5 as the code on main has them. The VS Code page gives the steps for the author of a zig-sdk server.

Part Content
API bridge.embed.serveStdioAuto(io, gpa, server, in, out, options) in src/bridge/embed.zig. vscode.serveStdio(io, gpa, server, options) calls it with vscode.profile. A zig-sdk server calls vscode.serveStdio in place of mcp.transport.stdio.serve.
The first line The function reads the first lines with the line framer and the line limit of the server. It parses them with the depth limit of the server. A blank line selects nothing, and a line that is too long gets no response.
The paths Only a request with the method initialize selects the legacy path. Each other message selects the modern path, also a line that is not valid JSON-RPC.
server/discover A server/discover request before the first other request selects no path. With .discover = .answer, the default, the server answers it. With .discover = .refuse, the bridge answers -32601.
Legacy path A Frontend with the profile of the product. Its upstream server is the same server through mcp.transport.memory.ClientLink.
Modern path The stdio transport of zig-sdk. A request above the limit of requests in flight gets -32603 at once.
Limits The two paths take their limits from server.options.limits: the line length, the depth, the requests in flight and shutdown_grace.
End of the input The function stops in a bounded time and returns. It arms no watchdog.
Executables The server of the tests bridge-embedded-server (zig build embedded-server) calls vscode.serveStdio. The example in examples/consumer/ is a zig-sdk server that calls it.
Tests The unit tests of bridge.embed, the transcript tests in bridges/vscode/test/embed_test.zig, five process tests and a fuzz target of the first line. The CI jobs interop and consumer also start a server with the bridge in its process.

Milestone M6 in detail

This table gives the parts of milestone M6 as the code on main has them. The Threat Model page gives the security requirements and their tests. The VS Code page gives the steps of the sign-in.

Part Content
Start token When SignIn opens a browser, newStartToken makes a token from 32 bytes (256 bits) of the secure random source of Io. The token is base64url without padding, with 43 characters. writeStartUrl makes the start URL http://127.0.0.1:<port>/start/<token>. Each sign-in has a new token.
Destination of the URL Opener.delivery gives the destination (Delivery). Before notifications/initialized, the browser gets only the start URL (Delivery.browser), also through ShellExecuteW on Windows. After it, VS Code gets the authorization URL in a URL elicitation (Delivery.user), because the user must see the full URL before the consent. With --no-browser, the bridge opens nothing before notifications/initialized (Delivery.none). For Delivery.user and Delivery.none, the receiver serves no start path.
Sign-in line The line mcp-bridge-vscode: sign in at <url> on standard error keeps the authorization URL. Thus the user can open it when the browser does not open.
Start path Receiver serves /start/<token> on the redirect port. The first GET gets 303 See Other with the checked authorization URL as Location. The response also has Cache-Control: no-store, Referrer-Policy: no-referrer, no body and Connection: close. Another method gets 400.
Second request A second GET before the redirect gets 410 and a page that tells the user to start the sign-in again. It stops the sign-in with Reason.start_reused and the cause sign_in_start_reused, because another program possibly sent one of the two requests. The receiver sends the whole page before the stop. A redirect after the stop gets 400, thus the bridge gets no code.
Late request After the bridge took a redirect with a code, a second GET cannot stop the sign-in. It gets 410, and the bridge writes a warning. The page tells the user that another program possibly signed in with a different account.
Wrong token A start path with a different token gets 404, and the wait continues. Thus a guess cannot stop the sign-in. The compare of the token (std.crypto.timing_safe.eql) takes the same time for each token of the correct length.
Limits The start path does not work after the time limit of the sign-in. After a redirect that came without the start URL, the start path gets 404, because the sign-in needs no second authorization request. Without random data for the token, the bridge does not open the browser, and the user opens the URL of the sign-in line.
Residual risk Another local user can read the start URL and send the first GET. That user can then complete a sign-in with a different account before the browser of the user sends its GET. The bridge then gets the code of that account and shows no error. On a host that other users share, use --no-browser.
Documents SECURITY.md and bridges/vscode/README.md give the start URL, its residual risk and the limits of --no-browser. The README also has the new lines of the Output channel and the new error message.
Tests The unit tests of bridge.oauth for the token, the start path, a guess and the time limit. Other unit tests send a second GET during the wait and after the redirect. The transcript tests in bridges/vscode/test/https_test.zig and bridges/vscode/test/oauth_test.zig. A POSIX process test in test/process_test.zig, in which a BROWSER script starts bridge-fixture-server --browse.

Changes in zig-sdk 0.4.0

Milestones M3, M4 and M5 need zig-sdk 0.4.0 with these changes. The changes came in three stacked pull requests on zig-sdk. Pull request #23 has the changes A, B, E and I, #24 has C, G and H, and #25 has J and K. The plan has no change D. Pull request #26 has the first release commit of zig-sdk 0.4.0. Pull requests #27 to #30 added fixes of gRPC and HTTP/2, and mcp.util.loopback.

Pull request #31 put these fixes into the release 0.4.0, and the tag v0.4.0 is on its commit 8334b15. Milestones M3 and M4 pinned commit cda66dc of the 0.4.0 stack. Milestone M5 moved the pin to the commit 8334b15. The bridges do not use HTTP/2 or gRPC, thus that move changed no code of the bridges. Milestones M1 and M2 used zig-sdk 0.3.0.

Pull request #23 also moved the notification callback of the stdio client into its spawn options. The bridge uses that callback for the log messages of a stdio upstream.

Change Content Milestone State
A RequestOptions.log_level: a log level for one request, which buildMeta uses instead of the level of the client. M3 Merged, zig-sdk #23
B RequestOptions.meta: more _meta entries for one request. A key that fails validateKey or that the SDK owns gives error.InvalidMeta. M3 Merged, zig-sdk #23
C The server sends the acknowledgment of a listen stream before publish can see the subscription, on all transports. ClientLink writes the frames of one request one at a time, and its doc header tells its limits. M3, M5 Merged, zig-sdk #24
E The collector parses notifications into a scratch arena for each frame, and params are valid only during the callback. The changelog of zig-sdk lists this change of behavior under "Changed". M3, M4 Merged, zig-sdk #23
G A size limit for each event in sse.Parser, on every SSE response, and no limit for the full stream. It comes only together with E. M4 Merged, zig-sdk #24
H The client reports failures. The router fails the request of a frame that it cannot read, and it logs each frame that it drops. An HTTP body over the limit is not retryable. The diagnostics have the HTTP status and the exit status of the stdio child. M3 to M5 Merged, zig-sdk #24
I RequestOptions.inline_notifications, off by default: the router calls on_notification on the reader task, in the order of the server. M3 Merged, zig-sdk #23
J A trust option (ca_bundle) for OAuthClient and its fetcher. The client reads the stored token record again before a refresh and after grant_refused, and never writes cleared tokens over a newer record. M4 Merged, zig-sdk #25
K Proxies: CONNECT through HTTPS_PROXY or ALL_PROXY, with NO_PROXY, in the HTTP client and in the OAuth fetcher. M4 Merged, zig-sdk #25

Open items

Item State
An optional VS Code extension that bundles mcp-bridge-vscode, with lm.registerMcpServerDefinitionProvider6 and a .vsix package for each platform Decision needed by the user, not planned. The extension cannot wrap the servers that the user configures. It adds TypeScript tooling to a project that uses only Zig. Its delivery to the agent host of VS Code has no test.
A one-time start URL for the browser opener on a shared host Done in M6, pull request #10. The browser opener gets only the one-time start URL of the loopback receiver. Thus the process list does not show the state of the sign-in. A risk stays: another local user can read the start URL and send the first GET. On a shared host, --no-browser stays the recommendation. See Milestone M6 in detail.
The default of .discover in vscode.serveStdio Issue 48347 closed on 2026-10-12. The manual check of that day observed the MRTR rounds of Copilot runtime 1.0.95 on the modern path. With the default .answer, a tool that asks for input now completes there, thus the default stays. The option .refuse serves an older Copilot runtime. Examine the option again when issue 48888 closes, or when VS Code speaks revision 2026-07-289.
The Dependabot pull requests for actions/setup-node Done. Pull request #8 has the bump from 6 to 7 as a commit of the maintainer, and the Dependabot pull request #4 is closed. zig-sdk has the same bump in its pull request #32, and its Dependabot pull request #22 is closed.
GitHub Pages for the client ID metadata document site/vscode/client.json Done. GitHub Pages publishes site/ with the workflow pages.yml, and the document is at https://christianpresley.github.io/zig-bridge-sdk/vscode/client.json. CODEOWNERS names the owner of each file of the repository, also of the files in site/.
Rulesets for main and for the tags v* Done. The ruleset main-protection requires signed commits, a linear history and the checks fmt-lint, commit-policy, consumer and test in Debug on the three systems. It also blocks the deletion of main. The ruleset release-tags requires signatures for the tags v*, and it blocks their deletion and their update.
Release 0.1.0, source only Released: v0.1.0. The signed tag v0.1.0 is on the commit a8f4791 of main, and the release has the milestones M0 to M6. The pin of zig-sdk is the commit 8334b15 of the tag v0.4.0. The release has no executables.
Two failures of the Debug tests at the stop of the loopback receiver Done on main after 0.1.0. On Windows, a cancel during the accept of the wake connection wrote a stack trace of std to stderr. Pull request #11 makes the accept ignore a cancel. On macOS, a late cancel signal of std interrupted the wake connect, and the second connect stopped the bridge with a panic. zig-sdk pull request #36 connects with a socket that does not block. Pull request #12 pins the commit 7a937c4 of zig-sdk, after the tag v0.5.0. No tag has that commit yet. Release 0.1.1 moved the pin to the commit 52fc1b8 of the tag v0.5.1.
Release 0.1.1 Released: v0.1.1. The signed tag v0.1.1 is on the commit 939be9c of main. The pin of zig-sdk is the commit 52fc1b8 of the tag v0.5.1.
Release 0.1.2 Released: v0.1.2. The signed tag v0.1.2 is on the commit 3cbc34b of main. It has the listener fix of pull request 14. The pin of zig-sdk is the commit 52fc1b8 of the tag v0.5.1.
A test of the embedded server that failed on macOS in ReleaseSafe Done on main in pull request 17. The test two messages in one chunk failed on the macOS runner with five frames instead of two. The frames were the result of initialize, the three list changes of the listen stream, and the ping result. The reader handled the notifications/initialized after the result, when it lagged the upstream server under load, so the listen stream started. That is consistent with the protocol. The test now accepts the list changes of the listen stream and nothing else. Pull request 14 in release 0.1.2 did not prevent this, and the correction is under [Unreleased] in the changelog.
A manual check in VS Code Done for the Local harness on 2026-10-12, with VS Code Insiders 1.121.0-insider and the bridge 0.1.2. The three stdio servers start, list their tools and prompts, answer the tool calls and a URL elicitation, and stop without a process that stays. Also done for the Copilot harness on 2026-10-12, with VS Code Insiders 1.142.0-insider and Copilot runtime 1.0.95. The check covered the tool calls, the sampling, the URL and form elicitations, and the list changes. It also covered the MRTR rounds on the modern path of the consumer example. The section Manual check in VS Code of the VS Code page gives the results. Open: the accept of a form with typed text, and the Local harness with the form elicitation, the sampling and the roots.
The CI jobs interop and consumer on Windows Done. Pull request 19 runs the two jobs on ubuntu-24.04 and windows-2025. The required check consumer keeps its name and covers both systems.
A stack trace of Zig std in the Debug tests on Windows, when a client resets the TLS connection Done on main after 0.1.2. A client that does not trust the CA of the HTTPS fixture resets the connection during the handshake. Zig std 0.16.0 maps the status CONNECTION_RESET of a read to error.Unexpected, and it writes a stack trace in a Debug build. The fixture handles the error. The build now gives the tests a copy of the test runner of the toolchain with unexpected_error_tracing off on Windows. Zig issue 3195614 reports the gap, and Zig pull request 3202715 maps the status to error.ConnectionResetByPeer on master since 2026-04-25. Zig 0.17.0 has that change. Remove the test runner change when the project moves to Zig 0.17.

Deferred

The project records these items. The milestones M0 to M6 do not contain them.

Item Use cases Constraints
An HTTP front end: a legacy Streamable HTTP endpoint that the bridge serves10 VS Code in a web browser only, where the extension host cannot start a stdio server11. Servers on a central host that users add by URL, for example an HTTP form of serveStdioAuto. After the first POST, no HTTP status of 300 or more, except the 400 or 404 retry for Mcp-Session-Id. No 4xx status on the first POST12. A real Mcp-Session-Id for each connection, with its own state. SSE for the messages from the server to the client. Answers to cross-origin preflight requests, and Mcp-Session-Id in the exposed headers. Authorization toward VS Code.
Enterprise-managed authorization (ID-JAG) in the bridge13 Organizations that control the access to their servers through their identity provider. The settings oauth.clientId and oauth.enterpriseManaged of mcp.json do not apply to the stdio entry of the bridge. For a server that accepts a static token, --header-env with Authorization is the alternative.

The bridge does not support VS Code in a web browser only. That VS Code cannot start a stdio server, and the bridge is a stdio server.

The front end uses stdio, so the plan leaves out the HTTP parts of revision 2025-11-25: the Mcp-* headers, Last-Event-ID, the HTTP+SSE transport and DELETE. The bridge also does not translate the core tasks of revision 2025-11-25.

References

  1. MCP-MRTR-2026-07-28: Multi round-trip requests 2026-07-28.
  2. MCP-SUBSCRIPTIONS-2026-07-28: Subscriptions 2026-07-28.
  3. MCP-HTTP-2026-07-28: Streamable HTTP transport 2026-07-28.
  4. MCP-AUTH-2026-07-28: Authorization overview 2026-07-28.
  5. MCP-AUTH-CLIENTREG-2026-07-28: Client registration 2026-07-28.
  6. vscode.d.ts: The extension API of VS Code.
  7. GitHub copilot-cli issue 4834: Multi round-trip requests in the Copilot runtime. The issue closed on 2026-10-12.
  8. GitHub copilot-cli issue 4888: initialize after a time limit or after a discover result that the runtime cannot use. The issue is open.
  9. VS Code issue 329848: Revision 2026-07-28 in VS Code.
  10. MCP-TRANSPORTS-2025-11-25: Transports 2025-11-25.
  11. mainThreadMcp.ts: The MCP part of the main thread of VS Code.
  12. extHostMcp.ts: The HTTP client of VS Code for MCP servers.
  13. MCP-EXT-AUTH-ENTERPRISE: Enterprise-managed authorization extension.
  14. Zig issue 31956: std.Io.Threaded on Windows leaves several common status values of the kernel unmapped in socket paths. The issue is open.
  15. Zig pull request 32027: The missing CONNECTION_REFUSED and CONNECTION_RESET errors for Windows. Merged on 2026-04-25.

Clone this wiki locally