├── .env.test.example ├── .gitignore ├── LICENSE ├── README.md ├── biome.json ├── bun.lock ├── meme.png ├── package.json ├── scripts └── setup.ts ├── src ├── index.ts ├── logger.ts ├── specs │ └── index.test.ts └── tools.ts └── tsconfig.json /.env.test.example: -------------------------------------------------------------------------------- 1 | DEEPSEEK_API_KEY= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Caches 14 | 15 | .cache 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | 19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 20 | 21 | # Runtime data 22 | 23 | pids 24 | _.pid 25 | _.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | 47 | bower_components 48 | 49 | # node-waf configuration 50 | 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | 55 | build/Release 56 | 57 | # Dependency directories 58 | 59 | node_modules/ 60 | jspm_packages/ 61 | 62 | # Snowpack dependency directory (https://snowpack.dev/) 63 | 64 | web_modules/ 65 | 66 | # TypeScript cache 67 | 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | 72 | .npm 73 | 74 | # Optional eslint cache 75 | 76 | .eslintcache 77 | 78 | # Optional stylelint cache 79 | 80 | .stylelintcache 81 | 82 | # Microbundle cache 83 | 84 | .rpt2_cache/ 85 | .rts2_cache_cjs/ 86 | .rts2_cache_es/ 87 | .rts2_cache_umd/ 88 | 89 | # Optional REPL history 90 | 91 | .node_repl_history 92 | 93 | # Output of 'npm pack' 94 | 95 | *.tgz 96 | 97 | # Yarn Integrity file 98 | 99 | .yarn-integrity 100 | 101 | # dotenv environment variable files 102 | 103 | .env 104 | .env.development.local 105 | .env.test.local 106 | .env.production.local 107 | .env.local 108 | 109 | # parcel-bundler cache (https://parceljs.org/) 110 | 111 | .parcel-cache 112 | 113 | # Next.js build output 114 | 115 | .next 116 | out 117 | 118 | # Nuxt.js build / generate output 119 | 120 | .nuxt 121 | dist 122 | 123 | # Gatsby files 124 | 125 | # Comment in the public line in if your project uses Gatsby and not Next.js 126 | 127 | # https://nextjs.org/blog/next-9-1#public-directory-support 128 | 129 | # public 130 | 131 | # vuepress build output 132 | 133 | .vuepress/dist 134 | 135 | # vuepress v2.x temp and cache directory 136 | 137 | .temp 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | 163 | # yarn v2 164 | 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.* 170 | 171 | # IntelliJ based IDEs 172 | .idea 173 | 174 | # Finder (MacOS) folder config 175 | .DS_Store 176 | 177 | 178 | server.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Matt Carey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MCP Thinking Server 2 | 3 | > Extract the thinking chain of thought of the Deepseek R1 reasoning model and use it in Claude Desktop or any MCP [client](https://www.youtube.com/watch?v=9mciRwpcLNY). 4 | 5 | ![with a box of scraps](./meme.png) 6 | 7 | ## Setup 8 | 9 | To install dependencies: 10 | 11 | ```bash 12 | bun install 13 | ``` 14 | 15 | Run the setup script to create the MCP config file. You will need to input your [Deepseek API key](https://platform.deepseek.com): 16 | 17 | ```bash 18 | bun setup 19 | ``` 20 | 21 | Open Claude Desktop and enjoy reasoning with Deepseek R1. 22 | 23 | ## Tests 24 | 25 | copy `.env.test.example` to `.env.test`. 26 | 27 | ```bash 28 | cp .env.test.example .env.test 29 | ``` 30 | 31 | Run the tests: 32 | 33 | ```bash 34 | bun test 35 | ``` 36 | 37 | ## Credits 38 | 39 | Thanks to @jacksteamdev's [awesome repo](https://github.com/jacksteamdev/mcp-sqlite-bun-server) for the example of MCP in bun. The setup script and logger are "heavily" inspired by it. 40 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", 3 | "formatter": { 4 | "enabled": true, 5 | "indentStyle": "space", 6 | "indentWidth": 2, 7 | "lineWidth": 90 8 | }, 9 | "linter": { 10 | "enabled": true, 11 | "rules": { 12 | "recommended": true, 13 | "style": { 14 | "useTemplate": "off" 15 | }, 16 | "complexity": { 17 | "noBannedTypes": "info" 18 | } 19 | }, 20 | "ignore": ["**/*.test.ts"] 21 | }, 22 | "javascript": { 23 | "formatter": { 24 | "quoteStyle": "single", 25 | "trailingCommas": "es5" 26 | } 27 | }, 28 | "files": { 29 | "include": ["**/*.ts"] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bun.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "workspaces": { 4 | "": { 5 | "name": "mcp-thinking", 6 | "dependencies": { 7 | "@modelcontextprotocol/sdk": "^1.4.1", 8 | "openai": "^4.80.1", 9 | }, 10 | "devDependencies": { 11 | "@biomejs/biome": "^1.9.4", 12 | "@types/bun": "latest", 13 | "dotenv": "^16.4.7", 14 | }, 15 | "peerDependencies": { 16 | "typescript": "^5.0.0", 17 | }, 18 | }, 19 | }, 20 | "trustedDependencies": [ 21 | "@biomejs/biome", 22 | ], 23 | "packages": { 24 | "@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="], 25 | 26 | "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="], 27 | 28 | "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="], 29 | 30 | "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="], 31 | 32 | "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="], 33 | 34 | "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="], 35 | 36 | "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="], 37 | 38 | "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="], 39 | 40 | "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="], 41 | 42 | "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.4.1", "", { "dependencies": { "content-type": "^1.0.5", "eventsource": "^3.0.2", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-wS6YC4lkUZ9QpP+/7NBTlVNiEvsnyl0xF7rRusLF+RsG0xDPc/zWR7fEEyhKnnNutGsDAZh59l/AeoWGwIb1+g=="], 43 | 44 | "@types/bun": ["@types/bun@1.2.0", "", { "dependencies": { "bun-types": "1.2.0" } }, "sha512-5N1JqdahfpBlAv4wy6svEYcd/YfO2GNrbL95JOmFx8nkE6dbK4R0oSE5SpBA4vBRqgrOUAXF8Dpiz+gi7r80SA=="], 45 | 46 | "@types/node": ["@types/node@18.19.74", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-HMwEkkifei3L605gFdV+/UwtpxP6JSzM+xFk2Ia6DNFSwSVBRh9qp5Tgf4lNFOMfPVuU0WnkcWpXZpgn5ufO4A=="], 47 | 48 | "@types/node-fetch": ["@types/node-fetch@2.6.12", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA=="], 49 | 50 | "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], 51 | 52 | "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], 53 | 54 | "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], 55 | 56 | "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], 57 | 58 | "bun-types": ["bun-types@1.2.0", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-KEaJxyZfbV/c4eyG0vyehDpYmBGreNiQbZIqvVHJwZ4BmeuWlNZ7EAzMN2Zcd7ailmS/tGVW0BgYbGf+lGEpWw=="], 59 | 60 | "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], 61 | 62 | "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], 63 | 64 | "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], 65 | 66 | "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], 67 | 68 | "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], 69 | 70 | "dotenv": ["dotenv@16.4.7", "", {}, "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ=="], 71 | 72 | "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], 73 | 74 | "eventsource": ["eventsource@3.0.2", "", { "dependencies": { "eventsource-parser": "^3.0.0" } }, "sha512-YolzkJNxsTL3tCJMWFxpxtG2sCjbZ4LQUBUrkdaJK0ub0p6lmJt+2+1SwhKjLc652lpH9L/79Ptez972H9tphw=="], 75 | 76 | "eventsource-parser": ["eventsource-parser@3.0.0", "", {}, "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA=="], 77 | 78 | "form-data": ["form-data@4.0.1", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw=="], 79 | 80 | "form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="], 81 | 82 | "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="], 83 | 84 | "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="], 85 | 86 | "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], 87 | 88 | "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], 89 | 90 | "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], 91 | 92 | "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], 93 | 94 | "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], 95 | 96 | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 97 | 98 | "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], 99 | 100 | "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], 101 | 102 | "openai": ["openai@4.80.1", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" }, "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-+6+bbXFwbIE88foZsBEt36bPkgZPdyFN82clAXG61gnHb2gXdZApDyRrcAHqEtpYICywpqaNo57kOm9dtnb7Cw=="], 103 | 104 | "raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="], 105 | 106 | "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], 107 | 108 | "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], 109 | 110 | "statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], 111 | 112 | "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], 113 | 114 | "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], 115 | 116 | "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], 117 | 118 | "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], 119 | 120 | "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], 121 | 122 | "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], 123 | 124 | "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], 125 | 126 | "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], 127 | 128 | "zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="], 129 | 130 | "zod-to-json-schema": ["zod-to-json-schema@3.24.1", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w=="], 131 | 132 | "@types/node-fetch/@types/node": ["@types/node@22.10.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww=="], 133 | 134 | "@types/ws/@types/node": ["@types/node@22.10.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww=="], 135 | 136 | "bun-types/@types/node": ["@types/node@22.10.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww=="], 137 | 138 | "@types/node-fetch/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], 139 | 140 | "@types/ws/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], 141 | 142 | "bun-types/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /meme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzcarey/mcp-thinking/f2d4b3604d8b74d92adb5bac0c3542a95351841c/meme.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mcp-thinking", 3 | "module": "index.ts", 4 | "type": "module", 5 | "scripts": { 6 | "inspect": "bunx @modelcontextprotocol/inspector bun src/index.ts", 7 | "start": "bun src/index.ts", 8 | "setup": "bun scripts/setup.ts", 9 | "check": "biome check .", 10 | "lint": "biome check --write .", 11 | "lint:unsafe": "biome check --write --unsafe .", 12 | "test": "bun test" 13 | }, 14 | "devDependencies": { 15 | "@biomejs/biome": "^1.9.4", 16 | "@types/bun": "latest", 17 | "dotenv": "^16.4.7" 18 | }, 19 | "peerDependencies": { 20 | "typescript": "^5.0.0" 21 | }, 22 | "dependencies": { 23 | "@modelcontextprotocol/sdk": "^1.4.1", 24 | "openai": "^4.80.1" 25 | }, 26 | "trustedDependencies": [ 27 | "@biomejs/biome" 28 | ], 29 | "author": { 30 | "name": "Matt Carey", 31 | "url": "https://github.com/mattcarey" 32 | }, 33 | "keywords": [ 34 | "mcp", 35 | "model context protocol", 36 | "thinking", 37 | "deepseek", 38 | "reasoning", 39 | "claude", 40 | "anthropic claude", 41 | "o1", 42 | "o3" 43 | ], 44 | "license": "MIT" 45 | } 46 | -------------------------------------------------------------------------------- /scripts/setup.ts: -------------------------------------------------------------------------------- 1 | import { homedir } from 'node:os'; 2 | import { join } from 'node:path'; 3 | import { mkdir } from 'node:fs/promises'; 4 | import type { BunFile } from 'bun'; 5 | 6 | /** 7 | * This script is used to setup the config file for the desktop app. 8 | * Modified to support cross-platform paths 9 | */ 10 | 11 | type Platform = 'win32' | 'darwin' | 'linux'; 12 | type ConfigPaths = Record; 13 | interface Config { 14 | mcpServers: { 15 | [key: string]: { 16 | command: string; 17 | args: string[]; 18 | env: { 19 | DEEPSEEK_API_KEY?: string; 20 | }; 21 | }; 22 | }; 23 | } 24 | 25 | const CONFIG_PATHS: ConfigPaths = { 26 | win32: '%APPDATA%/Claude/claude_desktop_config.json', 27 | darwin: '~/Library/Application Support/Claude/claude_desktop_config.json', 28 | linux: '~/.config/Claude/claude_desktop_config.json' 29 | }; 30 | 31 | // Get the default path for current platform, fallback to linux path 32 | const platform = process.platform as Platform; 33 | const defaultPath = CONFIG_PATHS[platform] || CONFIG_PATHS.linux; 34 | 35 | // Replace environment variables and home directory 36 | const CONFIG_PATH = defaultPath 37 | .replace(/%([^%]+)%/g, (_, n) => process.env[n] || '') 38 | .replace(/^~/, homedir()) 39 | .replace(/\//g, platform === 'win32' ? '\\' : '/'); 40 | 41 | let config: Config = { mcpServers: {} }; 42 | 43 | try { 44 | const configFile: BunFile = Bun.file(CONFIG_PATH); 45 | config = await configFile.json() as Config; 46 | } catch (error: unknown) { 47 | // Config doesn't exist yet, use default empty config 48 | console.log(`No existing config found at ${CONFIG_PATH}, will create new one.`); 49 | } 50 | 51 | // Get absolute paths 52 | const bunPath = process.argv[0]; // Current bun executable 53 | const serverPath = join(import.meta.dir, '../src/index.ts'); 54 | 55 | // input your api key 56 | const apiKey = prompt('Enter your Deepseek API key: '); 57 | if (!apiKey) { 58 | console.error('API key is required'); 59 | process.exit(1); 60 | } 61 | 62 | config.mcpServers = { 63 | ...config.mcpServers, 64 | thinking: { 65 | command: bunPath, 66 | args: [serverPath], 67 | env: { 68 | DEEPSEEK_API_KEY: apiKey, 69 | }, 70 | }, 71 | }; 72 | 73 | // Ensure directory exists 74 | const configDir = CONFIG_PATH.substring(0, CONFIG_PATH.lastIndexOf(platform === 'win32' ? '\\' : '/')); 75 | try { 76 | await mkdir(configDir, { recursive: true }); 77 | } catch (error: unknown) { 78 | const err = error as { code?: string; message: string }; 79 | if (err.code !== 'EEXIST') { 80 | console.error(`Failed to create config directory: ${err.message}`); 81 | process.exit(1); 82 | } 83 | } 84 | 85 | // Write config 86 | try { 87 | await Bun.write(CONFIG_PATH, JSON.stringify(config, null, 2)); 88 | console.log( 89 | '\x1b[32m✨ Successfully added thinking server to Claude MCP config! 🎉\x1b[0m' 90 | ); 91 | console.log(CONFIG_PATH.replace(homedir(), '~')); 92 | } catch (error: unknown) { 93 | const err = error as { message: string }; 94 | console.error(`Failed to write config: ${err.message}`); 95 | process.exit(1); 96 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 2 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 3 | import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; 4 | import OpenAI from 'openai'; 5 | import logger from './logger'; 6 | import { THINKING_TOOL } from './tools'; 7 | 8 | export type Message = { 9 | role: 'system' | 'user' | 'assistant'; 10 | content: string; 11 | }; 12 | 13 | const TAG_START = ''; 14 | const TAG_END = ''; 15 | 16 | const getAPIKey = () => { 17 | const apiKey = process.env.DEEPSEEK_API_KEY; 18 | if (!apiKey) { 19 | throw new Error('DEEPSEEK_API_KEY is not set'); 20 | } 21 | return apiKey; 22 | }; 23 | 24 | export const callDeepseek = async (messages: Message[]): Promise => { 25 | const openai = new OpenAI({ 26 | baseURL: 'https://api.deepseek.com', 27 | apiKey: getAPIKey(), 28 | }); 29 | 30 | const stream = await openai.chat.completions.create({ 31 | messages, 32 | model: 'deepseek-reasoner', 33 | stream: true, 34 | }); 35 | 36 | let fullResponse = ''; 37 | for await (const chunk of stream) { 38 | const content = chunk.choices[0]?.delta?.content || ''; 39 | if (!content) continue; 40 | 41 | if (fullResponse.includes(TAG_END)) break; 42 | fullResponse += content; 43 | } 44 | 45 | const start = fullResponse.indexOf(TAG_START) + TAG_START.length; 46 | const end = fullResponse.indexOf(TAG_END); 47 | return fullResponse.slice(start, end).trim(); 48 | }; 49 | 50 | const server = new Server( 51 | { 52 | name: 'thinking', 53 | version: '1.0.0', 54 | }, 55 | { 56 | capabilities: { 57 | tools: {}, 58 | }, 59 | } 60 | ); 61 | 62 | server.setRequestHandler(ListToolsRequestSchema, async () => { 63 | return { 64 | tools: [THINKING_TOOL], 65 | }; 66 | }); 67 | 68 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 69 | const { name, arguments: args } = request.params; 70 | 71 | if (name === 'thinking') { 72 | const { messages } = args as { messages: { role: string; content: string }[] }; 73 | logger.debug('Thinking Tool', { messages }); 74 | 75 | try { 76 | const text = await callDeepseek(messages as Message[]); 77 | logger.info('Thinking Tool Response', { text }); 78 | 79 | return { content: [{ type: 'text', text }] }; 80 | } catch (error) { 81 | logger.error('Thinking Tool Error', { error }); 82 | return { 83 | content: [ 84 | { 85 | type: 'text', 86 | text: 'Failed to invoke thinking tool, please try again later.', 87 | }, 88 | ], 89 | }; 90 | } 91 | } 92 | 93 | return { content: [{ type: 'text', text: 'Tool not found' }] }; 94 | }); 95 | 96 | async function startServer() { 97 | const transport = new StdioServerTransport(); 98 | 99 | try { 100 | await server.connect(transport); 101 | } catch (err) { 102 | process.exit(1); 103 | } 104 | } 105 | 106 | startServer().catch(console.error); 107 | -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | import { appendFileSync } from 'node:fs'; 2 | import { join } from 'node:path/posix'; 3 | 4 | type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL'; 5 | 6 | const formatMessage = ( 7 | level: LogLevel, 8 | message: unknown, 9 | meta?: Record 10 | ) => { 11 | const timestamp = new Date().toISOString(); 12 | const metaStr = meta ? ` ${JSON.stringify(meta)}` : ''; 13 | return `${timestamp} [${level.padEnd(5)}] ${JSON.stringify(message)}${metaStr}\n`; 14 | }; 15 | 16 | const log = (level: LogLevel, message: unknown, meta?: Record) => { 17 | appendFileSync( 18 | join(import.meta.dir, '../server.log'), 19 | formatMessage(level, message, meta) 20 | ); 21 | }; 22 | 23 | export const debug = (message: unknown, meta?: Record) => 24 | log('DEBUG', message, meta); 25 | export const info = (message: unknown, meta?: Record) => 26 | log('INFO', message, meta); 27 | export const warn = (message: unknown, meta?: Record) => 28 | log('WARN', message, meta); 29 | export const error = (message: unknown, meta?: Record) => 30 | log('ERROR', message, meta); 31 | export const fatal = (message: unknown, meta?: Record) => 32 | log('FATAL', message, meta); 33 | 34 | export default { debug, info, warn, error, fatal }; 35 | -------------------------------------------------------------------------------- /src/specs/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'bun:test'; 2 | import { config } from 'dotenv'; 3 | import OpenAI from 'openai'; 4 | import { type Message, callDeepseek } from '../index'; 5 | 6 | config(); 7 | 8 | describe('DeepSeek API Integration', () => { 9 | test('should successfully call DeepSeek API', async () => { 10 | const messages = [ 11 | { role: 'system' as const, content: 'You are a helpful assistant.' }, 12 | { role: 'user' as const, content: 'Say "test" and nothing else.' }, 13 | ]; 14 | 15 | const testDeepseekApi = async (messages: Message[]): Promise => { 16 | const openai = new OpenAI({ 17 | baseURL: 'https://api.deepseek.com', 18 | apiKey: process.env.DEEPSEEK_API_KEY, 19 | }); 20 | 21 | const completion = await openai.chat.completions.create({ 22 | messages, 23 | model: 'deepseek-reasoner', 24 | }); 25 | 26 | if (!completion.choices[0].message.content) { 27 | throw new Error('No text returned from DeepSeek API'); 28 | } 29 | 30 | return completion.choices[0].message.content; 31 | }; 32 | 33 | const response = await testDeepseekApi(messages); 34 | 35 | expect(response).toBeDefined(); 36 | expect(typeof response).toBe('string'); 37 | expect(response.toLowerCase()).toContain('test'); 38 | }); 39 | 40 | test('should handle API errors appropriately', async () => { 41 | const messages = [{ role: 'invalid_role' as any, content: 'This should fail' }]; 42 | 43 | try { 44 | await callDeepseek(messages); 45 | throw new Error('Should not reach here'); 46 | } catch (error) { 47 | expect(error).toBeInstanceOf(Error); 48 | } 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/tools.ts: -------------------------------------------------------------------------------- 1 | const THINKING_TOOL_PROMPT = 2 | 'Use this tool for all thinking and reasoning tasks. ' + 3 | 'The tool accepts a list of user and previous assistant messages relevant to the conversation. ' + 4 | 'Always call this tool before answering the user and include the latest user message in the list. ' + 5 | "The tool will generate a chain of thought reasoning which can be used to answer the user's question."; 6 | 7 | export const ThinkingToolSchema = { 8 | type: 'object', 9 | properties: { 10 | messages: { 11 | type: 'array', 12 | items: { 13 | type: 'object', 14 | properties: { 15 | role: { 16 | type: 'string', 17 | enum: ['user', 'assistant'], 18 | }, 19 | content: { 20 | type: 'string', 21 | }, 22 | }, 23 | required: ['role', 'content'], 24 | }, 25 | }, 26 | }, 27 | required: ['messages'], 28 | }; 29 | 30 | export const THINKING_TOOL = { 31 | name: 'thinking', 32 | description: THINKING_TOOL_PROMPT, 33 | inputSchema: ThinkingToolSchema, 34 | }; 35 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Enable latest features 4 | "lib": ["ESNext", "DOM"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true 24 | }, 25 | "include": ["src/**/*.ts", "scripts/**/*.ts"], 26 | } 27 | --------------------------------------------------------------------------------