> **Building with AI coding agents?** Install the authstack plugin with one command. This equips your agent with accurate Scalekit implementation patterns.
>
> **Recommended** (the `-y` flags skip prompts, so the command runs without an interactive terminal):
> ```bash
> npx -y @scalekit-inc/cli setup -y
> ```
>
> Global:
> ```bash
> npm install -g @scalekit-inc/cli
> scalekit setup -y
> ```
>
> Supports Claude Code, Cursor, GitHub Copilot, Codex + skills for other Agent Skills-compatible agents in `~/.agents/skills`.
> Skills: integrate-agentkit, implement-saaskit, add-mcp-oauth, implement-sso, implement-scim.
> [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# Migrate to the new MCP spec

Migrate your MCP server's auth to the MCP 2026-07-28 spec with zero downtime. Trust both issuers, then enable Resource bound issuer.
Scalekit is changing the issuer of access tokens for MCP servers. The **issuer** is the `iss` claim inside a token, and it tells your MCP server who minted the token. Today the issuer is your environment URL. The new MCP specification (2026-07-28) asks clients to check the issuer on every authorization response and to keep credentials separate for each authorization server. To support this, Scalekit gives each MCP server its own issuer, `<environment URL>/resources/<resource ID>`. The dashboard option that turns this on is called **Resource bound issuer**.

This migration applies only to MCP servers registered in Scalekit in workspaces created before October 21, 2026.

To check, open your MCP server in the Scalekit dashboard. If you see the **Resource bound issuer** option, your MCP server requires the migration, and this guide is relevant for you. If you do not see it, your MCP server already has Resource bound issuer turned on and you do not need to do anything.

If you need any assistance or help, contact [support@scalekit.com](mailto:support@scalekit.com).

This migration guide helps you migrate your MCP server's auth to the new MCP specification with zero downtime.

## Migrate development first, then production

**Resource bound issuer** is a setting on each MCP server. Complete every step for every MCP server in your development environment first. After you validate the result, repeat the same steps for every MCP server in your production environment.

## What changes

| | Before the migration | After the migration |
| --- | --- | --- |
| `iss` of new tokens | `https://example.scalekit.cloud` | `https://example.scalekit.cloud/resources/res_123` |
| Tokens already issued | Keep their original `iss` until they expire | Keep their original `iss` until they expire |

Old and new tokens are both in circulation for a while, so your server must trust both issuers during the migration.

1. ## Trust both issuers in your token validation

   Find the code in your MCP server that validates access tokens. In the quickstart, it is the call that passes `issuer`. Today it passes only your environment URL. Update it to trust two issuers: your environment URL and `<environment URL>/resources/<resource ID>`. In the examples, `ENV_URL` is your environment URL and `RESOURCE_ID` is your resource ID.

   ### If you use a Scalekit SDK

   Update your MCP server to use the latest Scalekit SDK version, which supports trusting multiple issuers. Then add both issuers while calling the validate method.

   **Node.js**

   ```javascript title="Trust both issuers"
   await scalekit.validateToken(token, {
     // Before: issuer: ENV_URL
     issuer: [ENV_URL, `${ENV_URL}/resources/${RESOURCE_ID}`],
     audience: [RESOURCE_ID],
   });
   ```

   **Python**

   ```python title="Trust both issuers"
   options = TokenValidationOptions(
       # Before: issuer=ENV_URL
       issuer=[ENV_URL, f"{ENV_URL}/resources/{RESOURCE_ID}"],
       audience=[RESOURCE_ID],
   )
   scalekit_client.validate_token(token, options=options)
   ```

   **Go**

   ```go title="Trust both issuers"
   ok, err := scalekitClient.ValidateTokenWithOptions(ctx, token, &scalekit.ValidateTokenOptions{
       // Before: Issuer: envURL
       Issuers:  []string{envURL, envURL + "/resources/" + RESOURCE_ID},
       Audience: []string{RESOURCE_ID},
   })
   if err != nil || !ok {
       // Reject the request
   }
   ```

   **Java**

   ```java title="Trust both issuers"
   TokenValidationOptions options = TokenValidationOptions.builder()
       // Before: .issuer(envUrl)
       .issuers(List.of(envUrl, envUrl + "/resources/" + RESOURCE_ID))
       .audience(List.of(RESOURCE_ID))
       .build();
   boolean isValid = scalekitClient.authentication().validateAccessToken(token, options);
   if (!isValid) {
       // Reject the request
   }
   ```

   ### If you use FastMCP

   The Scalekit provider in FastMCP already trusts both your environment URL and `<environment URL>/resources/<resource ID>`, so you do not need to change your validation code. Continue with [Enable Resource bound issuer](#enable-resource-bound-issuer). Make sure you are using the latest version of FastMCP.

   ### If you use your own JWT validator

   Configure your validator to trust exactly two `iss` values, and reject any other issuer. Most JWT libraries accept a list of allowed issuers.

2. ## Release your MCP server

   Release the change. Then check that both kinds of tokens work:

   - **A token issued before the release**: call a tool from a session that was authorized before the release, without authorizing again. The request should still succeed.
   - **A new token**: disconnect your MCP client, for example MCP Inspector, and connect again so that it authorizes again. Call a tool. The request should succeed.

   Nothing changes for your users yet, because tokens still carry your environment URL.

3. ## Enable Resource bound issuer

   Enable the option in the Scalekit dashboard:

   1. Go to **MCP servers** and select your MCP server.
   2. Enable **Resource bound issuer**.
   3. Read the confirmation dialog. It explains that tokens issued from now on carry `<environment URL>/resources/<resource ID>` as the `iss` claim. Confirm after you have read this guide.

   > Image: Resource bound issuer option set to Enabled in the MCP server settings

   Repeat the checks from the previous step. Existing tokens must still work. For a new token, the `iss` claim should be `<environment URL>/resources/<resource ID>`, and your MCP server should accept it.

   > caution: Do not disable Resource bound issuer
   >
   > After you enable it, do not disable it. Switching back changes the issuer your clients see, which can break connected clients.

**Optional: Trust only the server's own issuer**

Wait until every token that was issued before you enabled the option has expired. After that, every token carries the server's own issuer, and you can clean up your code to trust only that issuer. If you are not sure whether older tokens are still in use, skip this section. Trusting both issuers is safe.

**Node.js**

```javascript title="Trust only the server's own issuer"
await scalekit.validateToken(token, {
  issuer: `${ENV_URL}/resources/${RESOURCE_ID}`,
  audience: [RESOURCE_ID],
});
```

**Python**

```python title="Trust only the server's own issuer"
options = TokenValidationOptions(
    issuer=f"{ENV_URL}/resources/{RESOURCE_ID}",
    audience=[RESOURCE_ID],
)
scalekit_client.validate_token(token, options=options)
```

**Go**

```go title="Trust only the server's own issuer"
ok, err := scalekitClient.ValidateTokenWithOptions(ctx, token, &scalekit.ValidateTokenOptions{
    Issuer:   envURL + "/resources/" + RESOURCE_ID,
    Audience: []string{RESOURCE_ID},
})
if err != nil || !ok {
    // Reject the request
}
```

**Java**

```java title="Trust only the server's own issuer"
TokenValidationOptions options = TokenValidationOptions.builder()
    .issuer(envUrl + "/resources/" + RESOURCE_ID)
    .audience(List.of(RESOURCE_ID))
    .build();
boolean isValid = scalekitClient.authentication().validateAccessToken(token, options);
if (!isValid) {
    // Reject the request
}
```

If you use your own JWT validator, remove the environment URL from its list of trusted issuers in the same way.

If you use FastMCP, skip this section. The Scalekit provider keeps trusting both issuers.

## Frequently asked questions

**Do my users need to sign in again?**

No. Tokens issued before you enable the option keep their original `iss` and work until they expire, as long as your MCP server trusts both issuers.

**Do I need to enable the option for every MCP server?**

Yes. The option is set on each MCP server. Enable it for every MCP server in your development environment first, and then for every MCP server in your production environment.

**Can I turn the option off again?**

Leave it enabled on every MCP server. If you need to change it, contact [support@scalekit.com](mailto:support@scalekit.com).

## Why Scalekit is making this change

The Model Context Protocol (MCP) [2026-07-28 specification](https://blog.modelcontextprotocol.io/posts/2026-07-28/) strengthens issuer checks. Authorization servers should return the `iss` parameter per RFC 9207, and clients must validate it before redeeming a code. This closes an authorization-server mix-up hole. The specification also binds client credentials to the issuer that minted them.

To support these checks, each MCP server in Scalekit has its own issuer. That is why your MCP server must trust the server's own issuer.

If a token is rejected unexpectedly, see [Troubleshooting MCP auth](/authenticate/mcp/troubleshooting/).


---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
