zuplo 6.70.56 → 6.70.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,291 @@
1
+ ---
2
+ title: "MCP Gateway quickstart (Local Dev)"
3
+ sidebar_label: "Quickstart (Local Dev)"
4
+ description:
5
+ Build an MCP Gateway locally with the Zuplo CLI. Register the plugin, add one
6
+ upstream MCP server, run it with the loopback dev-login shortcut, and connect
7
+ Claude Desktop. No identity provider setup required to start.
8
+ ---
9
+
10
+ <QuickstartPicker mode="local" alternateLink="/mcp-gateway/quickstart" />
11
+
12
+ Build a Zuplo MCP Gateway fronting Linear, running locally at
13
+ `http://127.0.0.1:9000/mcp/linear-v1`. By the end, Claude Desktop connects over
14
+ the gateway's per-user OAuth flow and answers "list my open Linear issues" with
15
+ real results.
16
+
17
+ Any Zuplo project becomes a gateway by adding a plugin, a couple of policies,
18
+ and a route. This guide uses Linear as the upstream and the built-in
19
+ **dev-login** shortcut for sign-in, so you skip identity-provider setup to try
20
+ it out. For production, swap in your provider: the gateway wraps Auth0, Okta,
21
+ Microsoft Entra, Google, Clerk, Cognito, Keycloak, Logto, OneLogin, PingOne, and
22
+ WorkOS, plus a generic OIDC fallback. See the
23
+ [provider catalog](./auth/overview.mdx#identity-providers).
24
+
25
+ Prefer the browser with no local setup? The
26
+ [Portal quickstart](./quickstart.mdx) reaches the same result through the Zuplo
27
+ Portal UI.
28
+
29
+ ## Prerequisites
30
+
31
+ - [Node.js](https://nodejs.org/en/download) 20 or higher.
32
+ - A local Zuplo project. Create an empty one with:
33
+
34
+ ```bash
35
+ npx create-zuplo-api@latest --empty
36
+ ```
37
+
38
+ Then `cd` into the new directory. See
39
+ [`create-zuplo-api`](../cli/create-zuplo-api.mdx) for other options, or
40
+ [import an existing portal project](../articles/local-development.mdx#import-your-existing-project)
41
+ by connecting it to Git and cloning it.
42
+
43
+ :::note
44
+
45
+ New projects created with `create-zuplo-api` ship a recent `compatibilityDate`,
46
+ so MCP Gateway features work out of the box. If you're adding the gateway to an
47
+ older project and the build complains about the compatibility date, see
48
+ [Compatibility dates](./code-config/compatibility-dates.mdx).
49
+
50
+ :::
51
+
52
+ <Stepper>
53
+
54
+ 1. **Register the MCP Gateway plugin**
55
+
56
+ Open `modules/zuplo.runtime.ts` (create it if it doesn't exist) and register
57
+ `McpGatewayPlugin`:
58
+
59
+ ```ts title="modules/zuplo.runtime.ts"
60
+ import { RuntimeExtensions } from "@zuplo/runtime";
61
+ import { McpGatewayPlugin } from "@zuplo/runtime/mcp-gateway";
62
+
63
+ export function runtimeInit(runtime: RuntimeExtensions) {
64
+ runtime.addPlugin(new McpGatewayPlugin());
65
+ }
66
+ ```
67
+
68
+ The plugin registers the OAuth metadata, authorization endpoints, consent
69
+ page, and upstream connect callbacks the gateway needs.
70
+
71
+ 2. **Add an OAuth policy with the dev-login shortcut**
72
+
73
+ Setting up a real identity provider for local development is friction. You'd
74
+ register a loopback callback, manage test users, and so on. The gateway
75
+ exposes a loopback-only shortcut that skips the IdP round-trip entirely and
76
+ signs you in as a fixed `dev-browser-user`.
77
+
78
+ Open `config/policies.json` and add the generic OAuth policy pointed at the
79
+ dev-login URL:
80
+
81
+ ```json title="config/policies.json"
82
+ {
83
+ "name": "dev-oauth",
84
+ "policyType": "mcp-oauth-inbound",
85
+ "handler": {
86
+ "module": "$import(@zuplo/runtime/mcp-gateway)",
87
+ "export": "McpOAuthInboundPolicy",
88
+ "options": {
89
+ "oidc": {
90
+ "issuer": "http://127.0.0.1:9000",
91
+ "jwksUrl": "http://127.0.0.1:9000/.well-known/jwks.json"
92
+ },
93
+ "browserLogin": {
94
+ "url": "http://127.0.0.1:9000/oauth/dev-login"
95
+ }
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ :::caution
102
+
103
+ `/oauth/dev-login` returns `403 Forbidden` for any request that doesn't
104
+ arrive over loopback, so it's safe to leave configured, but only useful in
105
+ local dev. Production deployments should use a real OIDC provider through one
106
+ of the [IdP wrappers](./auth/overview.mdx#identity-providers). A common
107
+ pattern is keeping two OAuth policies (one for production, one for dev) and
108
+ selecting between them in `routes.oas.json` by environment.
109
+
110
+ :::
111
+
112
+ When you do switch to a real provider, its policy reads credentials from
113
+ `$env(...)` references. Define those values in a `.env` file at the project
114
+ root:
115
+
116
+ ```bash title=".env"
117
+ MCP_AUTH0_DOMAIN=your-tenant.us.auth0.com
118
+ MCP_AUTH0_CLIENT_ID=your-auth0-web-app-client-id
119
+ MCP_AUTH0_CLIENT_SECRET=your-auth0-web-app-client-secret
120
+ ```
121
+
122
+ `.env` is read when `npm run dev` starts, so restart the dev server after
123
+ adding or changing a variable. Never commit `.env`. Check in a `.env.example`
124
+ with placeholder values instead. The dev-login shortcut above needs no
125
+ environment variables, so you can skip this until you wire up a provider.
126
+
127
+ 3. **Add a token-exchange policy for the upstream**
128
+
129
+ Each OAuth-protected upstream gets its own `mcp-token-exchange-inbound`
130
+ policy. It looks up the user's upstream credential and attaches it as the
131
+ upstream `Authorization` header. Add this entry to `config/policies.json`:
132
+
133
+ ```json title="config/policies.json"
134
+ {
135
+ "name": "mcp-token-exchange-linear",
136
+ "policyType": "mcp-token-exchange-inbound",
137
+ "handler": {
138
+ "module": "$import(@zuplo/runtime/mcp-gateway)",
139
+ "export": "McpTokenExchangeInboundPolicy",
140
+ "options": {
141
+ "displayName": "Linear",
142
+ "protectedResourceMetadataUrl": "https://mcp.linear.app/.well-known/oauth-protected-resource",
143
+ "authMode": "user-oauth",
144
+ "scopes": [],
145
+ "clientRegistration": { "mode": "auto" }
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
151
+ `authMode: "user-oauth"` means each user connects their own Linear account
152
+ the first time they call the route. `clientRegistration: { "mode": "auto" }`
153
+ lets the gateway register itself with Linear's OAuth server on demand, so no
154
+ upstream client credentials in source control.
155
+
156
+ 4. **Add the route**
157
+
158
+ Open `config/routes.oas.json` and add an MCP route. The handler points at
159
+ Linear's MCP server URL; the inbound policy chain runs the OAuth policy
160
+ followed by the token-exchange policy:
161
+
162
+ ```json title="config/routes.oas.json"
163
+ {
164
+ "openapi": "3.1.0",
165
+ "info": { "title": "MCP Gateway", "version": "0.1.0" },
166
+ "paths": {
167
+ "/mcp/linear-v1": {
168
+ "get,post": {
169
+ "operationId": "linear-mcp-server",
170
+ "summary": "Linear MCP Proxy",
171
+ "x-zuplo-route": {
172
+ "corsPolicy": "none",
173
+ "handler": {
174
+ "module": "$import(@zuplo/runtime/mcp-gateway)",
175
+ "export": "McpProxyHandler",
176
+ "options": {
177
+ "rewritePattern": "https://mcp.linear.app/mcp"
178
+ }
179
+ },
180
+ "policies": {
181
+ "inbound": ["dev-oauth", "mcp-token-exchange-linear"]
182
+ }
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+ ```
189
+
190
+ `operationId` is the stable identifier for the route. It appears in analytics
191
+ and is part of the per-user upstream connection key, so pick it once and
192
+ don't change it. The path is whatever you set; `/mcp/<provider>-v<n>` is the
193
+ convention.
194
+
195
+ 5. **Run the gateway**
196
+
197
+ From the project root:
198
+
199
+ ```bash
200
+ npm run dev
201
+ ```
202
+
203
+ The route is now reachable at `http://127.0.0.1:9000/mcp/linear-v1`.
204
+
205
+ :::tip{title="Checkpoint: confirm the OAuth policy is wired up"}
206
+
207
+ Send an unauthenticated POST and expect a `401`:
208
+
209
+ ```bash
210
+ curl -i -X POST http://127.0.0.1:9000/mcp/linear-v1 \
211
+ -H "Content-Type: application/json" \
212
+ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
213
+ ```
214
+
215
+ The response should be `401 Unauthorized` with a `WWW-Authenticate: Bearer`
216
+ header pointing at `/.well-known/oauth-protected-resource/mcp/linear-v1`.
217
+ That 401 confirms the OAuth policy is loaded. If you see a 200, 404, or 500
218
+ instead, the OAuth policy isn't attached to the route.
219
+
220
+ :::
221
+
222
+ :::caution{title="Use 127.0.0.1, not localhost"}
223
+
224
+ OAuth metadata and callback URLs key off the request origin. Other loopback
225
+ aliases (`localhost`, `::1`) can break OAuth subtly in local dev. See
226
+ [Local development](./code-config/local-development.mdx) for the full set of
227
+ local-only details, including the known `workerd` restart quirk.
228
+
229
+ :::
230
+
231
+ 6. **Connect Claude Desktop**
232
+
233
+ Open Claude Desktop, go to **Settings → Connectors**, scroll to the bottom,
234
+ and click **Add custom connector**. Paste
235
+ `http://127.0.0.1:9000/mcp/linear-v1` and click **Add**.
236
+
237
+ Claude Desktop opens the gateway's OAuth flow in a browser:
238
+ 1. The dev-login shortcut signs you in without any IdP prompt.
239
+ 2. The gateway's consent page lists Linear with a **Connect** button.
240
+ 3. Click **Connect**, complete Linear's OAuth flow, then click **Authorize**
241
+ to finish.
242
+
243
+ :::tip{title="Checkpoint: Claude is connected"}
244
+
245
+ Back in Claude Desktop, the new connector appears in **Settings →
246
+ Connectors** marked as connected. Subsequent requests reuse the tokens the
247
+ gateway just issued.
248
+
249
+ :::
250
+
251
+ For per-client setup details, see
252
+ [Connect MCP clients](./connect-clients/overview.mdx).
253
+
254
+ 7. **Test it**
255
+
256
+ In Claude Desktop, prompt the model with something that requires Linear.
257
+ "list my open issues" works well. Claude asks for permission to call the
258
+ tool, then returns results proxied through the gateway.
259
+
260
+ </Stepper>
261
+
262
+ You now have a working MCP Gateway in front of Linear, running locally: Claude
263
+ Desktop signs in through the dev-login shortcut, the gateway exchanges that for
264
+ a per-user Linear token, and every call is proxied through. The same shape (one
265
+ OAuth policy, one token-exchange policy per upstream, one route per upstream)
266
+ scales out to as many upstream MCP servers as you want to front.
267
+
268
+ :::caution{title="Deploy to production before sharing"}
269
+
270
+ The local gateway on `127.0.0.1` is for development only, and the dev-login
271
+ shortcut works over loopback alone. Before giving others access, swap in a real
272
+ identity provider and ship the gateway through the Zuplo Portal. See
273
+ [environments](../articles/environments.mdx) for setting up a production
274
+ deployment.
275
+
276
+ :::
277
+
278
+ ## Next steps
279
+
280
+ - [Deploy from the Portal](./quickstart.mdx): swap the dev-login shortcut for a
281
+ real identity provider and ship the gateway through the Zuplo Portal.
282
+ - [Local development](./code-config/local-development.mdx): the dev-login
283
+ shortcut in depth, environment variables, and local-only quirks.
284
+ - [Connect more clients](./connect-clients/overview.mdx): Claude Code, Cursor,
285
+ VS Code, ChatGPT, and any other MCP client.
286
+ - [How it works](./how-it-works.mdx): the request lifecycle and the two OAuth
287
+ surfaces.
288
+ - [Add more upstreams](./code-config/multi-upstream.mdx): front several upstream
289
+ MCP servers from one Zuplo project.
290
+ - [Capability filtering](./capability-filtering.mdx): curate the tools, prompts,
291
+ and resources each route exposes.