https://github.com/MCP-Mirror/geelen_workers-mcp-server
  
  
    Mirror of https://github.com/geelen/workers-mcp-server 
    https://github.com/MCP-Mirror/geelen_workers-mcp-server
  
        Last synced: 6 days ago 
        JSON representation
    
Mirror of https://github.com/geelen/workers-mcp-server
- Host: GitHub
 - URL: https://github.com/MCP-Mirror/geelen_workers-mcp-server
 - Owner: MCP-Mirror
 - Created: 2024-12-25T20:17:27.000Z (10 months ago)
 - Default Branch: main
 - Last Pushed: 2024-12-25T20:17:32.000Z (10 months ago)
 - Last Synced: 2024-12-25T20:27:39.781Z (10 months ago)
 - Language: TypeScript
 - Size: 0 Bytes
 - Stars: 0
 - Watchers: 0
 - Forks: 0
 - Open Issues: 0
 - 
            Metadata Files:
            
- Readme: README.md
 
 
Awesome Lists containing this project
- awesome-mcp-servers - **geelen_workers-mcp-server** - Mirror of https://github.com/geelen/workers-mcp-server `typescript` `mcp` `server` `http` `npm install MCP-Mirror/geelen_workers-mcp-server` (🌐 Web Development)
 
README
          # Workers MCP Server
> **Talk to your Cloudflare Workers from Claude Desktop!**
## NOTE: This has now been superseded by the [Workers MCP](https://github.com/geelen/workers-mcp) package. Go there instead.
This is a proof-of-concept of writing a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) Server in a Cloudflare Worker. This gives you a way to extend Claude Desktop (among other MCP clients) by invoking functions using Cloudflare Worker's [new RPC syntax](https://blog.cloudflare.com/javascript-native-rpc/), which gives you access to any Cloudflare or third-party binding.
You write worker code that looks like this:
```ts
export class ExampleWorkerMCP extends WorkerEntrypoint {
	/**
	 * Generates a random number. This is extra random because it had to travel all the way to
	 * your nearest Cloudflare PoP to be calculated which... something something lava lamps?
	 *
	 * @return {string} A message containing a super duper random number
	 * */
	async getRandomNumber() {
		return `Your random number is ${Math.random()}`
	}
}
```
And, using the provided MCP proxy, your Claude Desktop can see & invoke these messages:

> Yes, I know that `Math.random()` works the same on a Worker as it does on your local machine, but don't tell Claude 🤫
## Neat! How do I play?
1. **Download Claude Desktop** https://claude.ai/download
2. **Clone this repo.**
3. **`pnpm install`**
4. **Check `wrangler.json`**
The current demo uses both the [Email Routing](https://developers.cloudflare.com/email-routing/) API and [Browser Rendering](https://developers.cloudflare.com/browser-rendering/. If you don't have access to these, or they're not enabled, comment out the relevant sections in `wrangler.json` or your deploy will fail.
5. **`pnpm deploy:worker`**
This takes your `src/index.ts` file and generate `dist/docs.json` from it, then deploys it using Wrangler.
6. **`npx workers-mcp secret generate && npx workers-mcp secret upload`**
This generates a secret in `.dev.vars` and uploads it using `wrangler secret put`. You only need to this once.
7. **`npx workers-mcp install  `**
8. **Restart Claude Desktop** You have to do this pretty often, but you _definitely_ have to do it after running the install step above.
To iterate on your server, do the following:
1. Make your change to `src/index.ts`
2. **`pnpm deploy:worker`**
3. (usually) **Restart Claude Desktop**.
You have to do this whenever you add/remove/change your methods or any of the documentation (Claude doesn't detect changes while it's running). But if you're just updating the code within a method then `pnpm deploy:worker` is enough.
---
## NOTE: Specifics below are now outdated
These are replaced with relevant sections in [Workers MCP](https://github.com/geelen/workers-mcp) package.
---
## How it works
Separately to your MCP code inside `src/index.ts`, there are three pieces required to make this work:
### 1. Docs generation: `scripts/generate-docs.ts`
The [MCP Specification](https://spec.modelcontextprotocol.io/specification/server/tools/#listing-tools) separates the `tools/list` and `tools/call` operations into separate steps, and most MCP servers have naturally followed suit and separated their schema definition from the implementation. However, combining them provides a much better DX for the author.
I'm using [ts-blank-space](https://github.com/bloomberg/ts-blank-space) and [jsdoc-api](https://www.npmjs.com/package/jsdoc-api) to parse the TS and emit the schema, slightly tweaked. This gives you LLM-friendly documentation at build time:
```ts
/**
 * Send a text or HTML email to an arbitrary recipient.
 *
 * @param {string} recipient - The email address of the recipient.
 * @param {string} subject - The subject of the email.
 * @param {string} contentType - The content type of the email. Can be text/plain or text/html
 * @param {string} body - The body of the email. Must match the provided contentType parameter
 * @return {Promise} A success message.
 * @throws {Error} If the email fails to send, or if that destination email address hasn't been verified.
 */
async sendEmail(recipient: string, subject: string, contentType: string, body: string) {
  // ...
}
```
```json
{
  "ExampleWorkerMCP": {
    "exported_as": "ExampleWorkerMCP",
    "description": null,
    "methods": [
      {
        "name": "sendEmail",
        "description": "Send a text or HTML email to an arbitrary recipient.",
        "params": [
          {
            "description": "The email address of the recipient.",
            "name": "recipient",
            "type": "string"
          },
          {
            "description": "The subject of the email.",
            "name": "subject",
            "type": "string"
          },
          {
            "description": "The content type of the email. Can be text/plain or text/html",
            "name": "contentType",
            "type": "string"
          },
          {
            "description": "The body of the email. Must match the provided contentType parameter",
            "name": "body",
            "type": "string"
          }
        ],
        "returns": {
          "description": "A success message.",
          "type": "Promise."
        }
      }
    ]
  }
}
```
This list of methods is very similar to the required MCP format for `tools/list`, but also gives us a list of the `WorkerEntrypoint` exports names to look up our service bindings later.
To iterate on your docs, run `pnpm generate:docs:watch` and you'll see the output change as you tweak your JSDoc in your `src/index.ts` (you'll need [watchexec](https://github.com/watchexec/watchexec) installed).
## 2. Public HTTP handler: `lib/WorkerMCP.ts`
Since our `WorkerEntrypoint` is not directly accessible, we need something that defines a default export with a `fetch()` handler. This is what `lib/WorkerMCP.ts` does.
This exposes a single endpoint, `/rpc`, which takes a JSON payload of `{ method: string, args?: any[] }`, then calls that method on your `WorkerEntrypoint`.
### 3. Local MCP proxy: `scripts/local-proxy.ts`
This file uses the `@modelcontextprotocol/sdk` library to build up a normal, local MCP server. This responds to `tools/list` by producing the data from `docs.json` for the specified entrypoint.
On `tools/call`, a `.fetch` call is made to the remote worker on the `/rpc` route, providing a `Bearer` token with the contents of `generated/.shared-secret`. The responses are then piped back to Claude.
Calling `pnpm install:claude  ` adds a sever definition that points to this file in your `claude_desktop_config.json`:
```json
{
  "mcpServers": {
    "": {
      "command": "/node",
      "args": [
        "/node_modules/tsx/dist/cli.mjs",
        "/scripts/local-proxy.ts",
        "",
        "",
        ""
      ]
    }
  }
}
```
In this way you can install as many of these as you like, as long as they each have a distinct ``.
## Limitations
There are lots. This pizza is straight out of the oven. You may well burn your mouth.
1. `docs.json` is only generated from `src/index.ts`. It doesn't currently crawl imports like a bundler, because no bundler I could find preserved comments in-place in order for me to run the docs generator afterwards.
2. Documentation generation only works for class exports. It can, at least, parse `class X {}; export { X as Y }`, but in general most people do `export default class X {}` anyway so this is fine for now.
3. The local proxy <-> remote proxy communication doesn't follow any particular RPC spec, but it probably should.
4. Error handling, non-text return values, streaming responses, etc have not really been thought through but do sorta work.
5. No `wrangler dev` support yet, but `wrangler dev --remote` should be possible so you don't have to deploy so often
6. Following on from the above, the spec includes a [`notifications/tools/list_changed` notification](https://spec.modelcontextprotocol.io/specification/server/tools/#list-changed-notification) that should trigger Claude to refresh its list of the tools available, meaning fewer restarts of Claude Desktop. But I haven't implemented that yet.
7. The docs parsing doesn't yet use TS types to either augment or replace the need for `@param` blocks in the JSDoc
8. The doc generation might be completely superfluous if someone was using a validator like zod or a schema library like typebox. However, I wanted build-time docs generation (i.e. static extraction) and wanted to be as generic as possible, so JSDoc will do for now.
## Future ideas
Obviously, having Claude Desktop talk directly to the Worker would be ideal. Also, `wrangler dev --remote` support would be great: you could iterate on your worker without redeploying, but still access your production bindings.
The docs generator needs to be extracted into a library so we can publish changes, as it needs to grow in scope to be really useful, and likely incorporate other sources of data (`d.ts` files, zod schemas, etc).
## Feedback & Contributions
Give it a try! Then, raise an issue or send a PR . This is all very new, so it could really go in a lot of different directions. We'd love to hear from you!