diff --git a/.changeset/README.md b/.changeset/README.md index 876e491424..1dfdddcea1 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -1,7 +1,7 @@ # Changesets -The four TypeScript servers (`everything`, `filesystem`, `memory`, -`sequentialthinking`) are versioned with +The three TypeScript servers (`everything`, `filesystem`, `memory`) +are versioned with [changesets](https://github.com/changesets/changesets). This directory holds the pending ones: one small Markdown file per change, each naming the packages it bumps, the bump type, and the line that becomes the CHANGELOG entry. The whole diff --git a/.claude/skills/client-smoke/SKILL.md b/.claude/skills/client-smoke/SKILL.md index df76bc1b1d..73a26c94ba 100644 --- a/.claude/skills/client-smoke/SKILL.md +++ b/.claude/skills/client-smoke/SKILL.md @@ -173,7 +173,7 @@ CLI) or **Server Settings → Protocol Era** (the web UI): | `auto` | Probes `server/discover`, and falls back to `initialize` | ⚠️ **Only the legacy era can be exercised against these servers today.** All -seven are on a v1 SDK (`@modelcontextprotocol/sdk` 1.x, `mcp` 1.x), whose +six are on a v1 SDK (`@modelcontextprotocol/sdk` 1.x, `mcp` 1.x), whose newest protocol version is 2025-11-25. None implements `server/discover`. So: - `--protocol-era modern` fails, by design, with `Version negotiation failed: diff --git a/.claude/skills/issue-create/SKILL.md b/.claude/skills/issue-create/SKILL.md index 4712fa66b3..61970f3382 100644 --- a/.claude/skills/issue-create/SKILL.md +++ b/.claude/skills/issue-create/SKILL.md @@ -88,7 +88,6 @@ to "not a bug", at which point filtering by it stops telling you anything. | `src/everything/` | `server-everything` | | `src/filesystem/` | `server-filesystem` | | `src/memory/` | `server-memory` | -| `src/sequentialthinking/` | `server-sequentialthinking` | | `src/fetch/` | `server-fetch` | | `src/git/` | `server-git` | | `src/time/` | `server-time` | diff --git a/.claude/skills/issue-triage/SKILL.md b/.claude/skills/issue-triage/SKILL.md index d0dfbaae7d..f3109eaabc 100644 --- a/.claude/skills/issue-triage/SKILL.md +++ b/.claude/skills/issue-triage/SKILL.md @@ -69,7 +69,7 @@ labels. A security report is never closed by triage. | **SDK or specification** | Reproduces against the SDK directly, or asks for a protocol change | Close, `not planned` | [Elsewhere](#elsewhere) | | **Duplicate** | An open or closed issue already reports it (search all states, as in `issue-create` step 0) | Close as `duplicate` (the `board-ops` API call); delete its card if it has one | [Duplicate](#duplicate) | | **Spam or empty** | No content, unrelated or generated content | Close, `not planned` | None | -| **Security report** | Describes a vulnerability in one of the seven servers | **Stop. A maintainer decides** | [Security reports](#security-reports) | +| **Security report** | Describes a vulnerability in one of the six servers | **Stop. A maintainer decides** | [Security reports](#security-reports) | | **Everything else** | A bug, a usability problem, an enhancement, a question about these servers | [Pass 1](#pass-1--sweep-onto-the-board-no-approval-implied) | None; the score comment | When a class is a judgment call (a feature a maintainer might still take, a @@ -266,7 +266,7 @@ that lists it shows the approval semantics were misapplied. ## Security reports -A vulnerability in one of the seven servers is reported privately, through the +A vulnerability in one of the six servers is reported privately, through the [advisory form](https://github.com/modelcontextprotocol/servers/security/advisories/new), and worked in the `security-advisory` flow (#4870). When one arrives as a public issue or a public PR anyway: @@ -341,12 +341,12 @@ RAW=$(gh api "repos/$R/collaborators?per_page=100" --paginate --slurp) \ # never the manifest: a "server change" can be a no-op, a racing duplicate or a # security fix, and only reading the diff tells which. jq -r --slurpfile m "$D/maintainers.json" ' - ["everything","filesystem","memory","sequentialthinking","fetch","git","time"] as $seven + ["everything","filesystem","memory","fetch","git","time"] as $six | .[] | .author.login as $a | [.files[].path] as $p | (if $a == "app/dependabot" then "dependabot" elif ($m[0] | index($a)) then "maintainer" elif ($p | length) > 0 and ($p | all(. == "README.md" or . == "ADDITIONAL.md")) then "listing?" - elif ($p | any(startswith("src/") and ((split("/")[1]) as $d | $seven | index($d) | not))) then "new-server?" + elif ($p | any(startswith("src/") and ((split("/")[1]) as $d | $six | index($d) | not))) then "new-server?" elif ($p | any(startswith("src/"))) then "server-change?" else "repo-level?" end) as $c | [.number, $c, ([.closingIssuesReferences[].number] | map("#\(.)") | join(" ")), .title[0:70]] @@ -376,7 +376,7 @@ into the issue column. | **No-op or spam** | `no-op` | No effective change (a rename to the same name, whitespace, a README "rename" to itself), or unrelated or generated content | No | [General, PR](#general-pr), without a tracking issue | | **Duplicate** | `duplicate` | Races another open PR, or an existing issue, for the same fix | Once per group, through its best member | [General, PR](#general-pr), naming the group's issue | | **Security fix** | `security` | Fixes a vulnerability: path traversal, symlink or Roots escape, SSRF, injection, a vulnerable dependency | See [Security reports](#security-reports) first | **Not by the loop**: a maintainer's call | -| **Fix or enhancement to keep** | `keep` | A bug fix, or an in-scope enhancement (`CONTRIBUTING.md`, "What we act on"), to one of the seven servers or the repository | Yes, unless an open issue already covers it | [General, PR](#general-pr), naming the issue | +| **Fix or enhancement to keep** | `keep` | A bug fix, or an in-scope enhancement (`CONTRIBUTING.md`, "What we act on"), to one of the six servers or the repository | Yes, unless an open issue already covers it | [General, PR](#general-pr), naming the issue | | **Out of scope** | `out-of-scope` | A feature `CONTRIBUTING.md` is selective about and a maintainer would not take, or a fix for behavior already gone on `v2/main` | No | [General, PR](#general-pr), without a tracking issue | **Finding duplicate groups.** Racing PRs usually share a linked issue or the diff --git a/.claude/skills/local-dev/SKILL.md b/.claude/skills/local-dev/SKILL.md index 45e66435c9..55fa58f3fe 100644 --- a/.claude/skills/local-dev/SKILL.md +++ b/.claude/skills/local-dev/SKILL.md @@ -17,7 +17,7 @@ Driving a running server with a client is `/client-smoke`; tests are Node 22 for the TypeScript servers, and `uv` for the Python ones. ```sh -npm install # at the repo root: all four TypeScript workspaces +npm install # at the repo root: all three TypeScript workspaces npm run build # every workspace; or: npm run build -w src/ ``` @@ -47,7 +47,6 @@ terminal is a running server, not a hung one. | `everything` | `node src/everything/dist/index.js stdio` | First argument picks the transport: `stdio` (the default), `sse`, `streamableHttp` | | `filesystem` | `node src/filesystem/dist/index.js […]` | The allowed directories. A client that supports Roots can supply them instead | | `memory` | `node src/memory/dist/index.js` | `MEMORY_FILE_PATH` sets the graph file | -| `sequentialthinking` | `node src/sequentialthinking/dist/index.js` | None required | | `fetch` | `uv --directory src/fetch run mcp-server-fetch` | `--user-agent`, `--ignore-robots-txt`, `--proxy-url` | | `git` | `uv --directory src/git run mcp-server-git` | `--repository ` | | `time` | `uv --directory src/time run mcp-server-time` | `--local-timezone ` | @@ -84,7 +83,7 @@ the published name, which exercises the package's `bin` or console script rather than a file path: ```sh -npx -y ./src/sequentialthinking # the workspace's bin, from its dist/ +npx -y ./src/memory # the workspace's bin, from its dist/ uvx --from ./src/time mcp-server-time # builds the project, runs its script ``` diff --git a/.claude/skills/project-structure/SKILL.md b/.claude/skills/project-structure/SKILL.md index b8460491f6..a1cd4acf1d 100644 --- a/.claude/skills/project-structure/SKILL.md +++ b/.claude/skills/project-structure/SKILL.md @@ -1,6 +1,6 @@ --- name: project-structure -description: "Find where code lives in this repo's seven servers and decide where a new file goes. Use when choosing the file or directory for a new tool, resource, prompt, transport, helper module, command-line flag or root script; when a server's layout is unclear; or when asking whether code belongs in a server's entry file or a module of its own." +description: "Find where code lives in this repo's six servers and decide where a new file goes. Use when choosing the file or directory for a new tool, resource, prompt, transport, helper module, command-line flag or root script; when a server's layout is unclear; or when asking whether code belongs in a server's entry file or a module of its own." disable-model-invocation: false --- @@ -19,7 +19,7 @@ servers need is written in both (`expandHome` exists in `filesystem` and in ## The TypeScript servers -All four are npm workspaces of the root `package.json` and share one shape: the +All three are npm workspaces of the root `package.json` and share one shape: the entry point is `index.ts`, sources sit at the workspace root (there is no `src/` inside a server), tests are in `__tests__/`, and `tsc` writes `dist/`. Each has the same four config files: `package.json`, `tsconfig.json` (the @@ -31,7 +31,6 @@ includes the tests) and `vitest.config.ts`. | `everything` | `index.ts` picks a transport from `argv`; `server/` holds the factory; `tools/`, `resources/`, `prompts/` hold one file per feature plus an `index.ts`; `transports/` holds one file per transport; `version.ts` reads the package version; `docs/` is shipped | Each feature file exports a `register…` function, wired into its area's `index.ts` (`registerTools`, `registerResources`, `registerPrompts`), which `server/index.ts` calls from `createServer()` | | `filesystem` | `index.ts` is the stdio entry point (argv → allowed directories, `main()`); `server.ts` exports `createServer(allowedDirectories)`; `lib.ts` holds the file operations and path validation, with the allow-list passed in rather than held globally; `path-utils.ts`, `path-validation.ts` and `roots-utils.ts` are focused helpers; `version.ts` reads the package version | Inline in `createServer()` in `server.ts`, with `server.registerTool(...)` | | `memory` | `index.ts` holds the `KnowledgeGraphManager`, `createServer(memoryFilePath)` and the stdio `main()`; `version.ts` reads the package version | Inline in `createServer()`; the resource and its subscription handlers are exported `register…` functions in the same file | -| `sequentialthinking` | `index.ts` holds `createServer()` and the stdio `main()`; `lib.ts` holds `SequentialThinkingServer`, the logic; `version.ts` reads the package version | Inline in `createServer()` | Three things about `everything` that the table cannot hold: @@ -90,9 +89,9 @@ importable. | --- | --- | | A tool, resource or prompt for `everything` | Its own kebab-case file in `tools/`, `resources/` or `prompts/`, exporting a `register…` function, wired into that directory's `index.ts` | | A transport for `everything` | `src/everything/transports/`, plus a `case` in `src/everything/index.ts` and a `start:…` script | -| A tool for `filesystem`, `memory` or `sequentialthinking` | A `server.registerTool(...)` call inside that server's `createServer()` (`server.ts` in `filesystem`, `index.ts` in the other two); the logic it calls goes in `lib.ts` (or the class it extends) | +| A tool for `filesystem` or `memory` | A `server.registerTool(...)` call inside that server's `createServer()` (`server.ts` in `filesystem`, `index.ts` in `memory`); the logic it calls goes in `lib.ts` (or the class it extends) | | A helper module for `everything` | A kebab-case `.ts` file in the feature area it supports, beside its users (`server/logging.ts`, `server/roots.ts`, `resources/session.ts`), imported with the `.js` extension | -| A helper module for `filesystem`, `memory` or `sequentialthinking` | A kebab-case `.ts` file at the workspace root, imported with the `.js` extension | +| A helper module for `filesystem` or `memory` | A kebab-case `.ts` file at the workspace root, imported with the `.js` extension | | A tool for a Python server | `server.py`: the tool-name enum member (`git`, `time`), the model, the `list_tools` entry, the `call_tool` branch | | A command-line flag for a Python server | `__init__.py`, passed into `serve()` | | A test | `/testing` | diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index c567f3429d..01b339a51f 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -77,7 +77,7 @@ Audit first, so the version bumps sit on a tree that was just checked. git fetch origin v2/main git switch -c "v2/chore/$N-release-audit" origin/v2/main -# npm: one lockfile covers the root and all four TypeScript workspaces. +# npm: one lockfile covers the root and all three TypeScript workspaces. npm audit --audit-level=high # Python: each server has its own lockfile. `uv` has no audit of its own in the @@ -157,7 +157,7 @@ git ls-tree -r --name-only origin/v2/main .changeset/ | grep '\.md$' | grep -vx # sort into release order (see 5a). PREV=$(gh release view --repo modelcontextprotocol/servers --json tagName --jq .tagName) git log --oneline "$PREV"..origin/v2/main -- \ - src/everything src/filesystem src/memory src/sequentialthinking + src/everything src/filesystem src/memory ``` If the second command lists changes to what a server publishes and the first @@ -466,7 +466,7 @@ The target is the SHA, so the tag lands on the commit the ledger verified, whatever has reached `main` since. `release.yml` only checks that the tagged commit is on `main`; it cannot know which commit was verified. -- **The tag is a label, not a version.** Seven packages publish at seven +- **The tag is a label, not a version.** Six packages publish at six versions; none is compared with the tag. Naming it for the milestone is what ties the Release to the board. - **Editing a published Release's notes is safe.** `release.yml` runs on @@ -485,7 +485,7 @@ not hold the others back; that package is handled as a failed publish ```sh # Each npm package at the version on main. -for s in everything filesystem memory sequentialthinking; do +for s in everything filesystem memory; do node scripts/npm-publish-guard.mjs "src/$s" # "already on npm" is the pass done # Each Python package: 200 means published. @@ -509,10 +509,10 @@ package went. This is done **once**, after the release that first publishes ⚠️ **Only after `latest` has moved.** The range covers the version users were getting before the release. Deprecating it while it is still `latest` warns every `npx` user toward a version that does not exist yet. Check first, and -go on only when all four lines show a `1.x` `latest` and no deprecation: +go on only when all three lines show a `1.x` `latest` and no deprecation: ```sh -for p in everything filesystem memory sequential-thinking; do +for p in everything filesystem memory; do n="@modelcontextprotocol/server-$p" echo "$n latest=$(npm view "$n" dist-tags.latest) deprecated=$(npm view "$n@2026.8.31" deprecated)" done @@ -526,7 +526,7 @@ do not attempt the login. ```sh MSG="Date-based versions are no longer published. This package has resumed semantic versioning at 1.0.0: install the latest version." -for p in everything filesystem memory sequential-thinking; do +for p in everything filesystem memory; do npm deprecate "@modelcontextprotocol/server-$p@>=2025.0.0 <2027.0.0" "$MSG" done ``` diff --git a/.claude/skills/security-advisory/SKILL.md b/.claude/skills/security-advisory/SKILL.md index e2b85e39c6..1907544872 100644 --- a/.claude/skills/security-advisory/SKILL.md +++ b/.claude/skills/security-advisory/SKILL.md @@ -132,7 +132,7 @@ gh api --paginate repos/modelcontextprotocol/servers/security-advisories \ Before assessing severity, establish that the vulnerable code is **ours**, in this order: -1. **Is it one of the seven servers under `src/`?** Many servers once lived in +1. **Is it one of the six servers under `src/`?** Many servers once lived in this repository and were moved to [`servers-archived`](https://github.com/modelcontextprotocol/servers-archived), which is unmaintained and read-only. A report against one of those is @@ -176,7 +176,7 @@ names the symptom rather than the class: | `git` | Runs git against repository paths it is given | **Path traversal** and **symlink escape** out of the configured repository; **Roots bypass**; **argument injection** (a value that starts with `-` read by git as an option) | | `fetch` | Makes outbound HTTP requests to URLs it is given | **SSRF** (loopback, private and link-local addresses, cloud metadata endpoints, a redirect or a DNS answer that lands on one); **robots bypass** (`robots.txt` not honored, or not re-checked after a redirect, when the server claims to honor it) | -`everything`, `memory`, `sequentialthinking` and `time` reach much less: +`everything`, `memory` and `time` reach much less: `memory` writes one file at a configured path, and `everything`'s HTTP transports are mostly SDK code (see ownership, above). diff --git a/.claude/skills/testing/SKILL.md b/.claude/skills/testing/SKILL.md index 7ba7883001..6d3d578904 100644 --- a/.claude/skills/testing/SKILL.md +++ b/.claude/skills/testing/SKILL.md @@ -16,7 +16,7 @@ clear the per-file coverage gate. Driving a running server by hand, with the Inspector or an LLM client, is `/client-smoke`. It is evidence for a PR, not a substitute for a test. -**Every server is tested in-process, and every file is gated.** The four +**Every server is tested in-process, and every file is gated.** The three TypeScript servers link a real `Client` to the server over an in-memory transport (#4854); the three Python servers link a `ClientSession` to the real `serve()` over in-memory streams (#4855). Both languages hold each file to a @@ -26,7 +26,7 @@ per-file coverage floor, in CI and in `npm run local:gate`. | Server | Directory | File name | Runner | | --- | --- | --- | --- | -| `everything`, `filesystem`, `memory`, `sequentialthinking` | `src//__tests__/` | `.test.ts` | vitest, `globals: true` | +| `everything`, `filesystem`, `memory` | `src//__tests__/` | `.test.ts` | vitest, `globals: true` | | `fetch`, `git`, `time` | `src//tests/` | `test_.py` | pytest, with `pytest-asyncio` (`asyncio_mode = "auto"`) | | Root tooling | `scripts/`, beside the script | `.test.mjs` | `node --test` | @@ -34,13 +34,13 @@ Each vitest config includes only `**/__tests__/**/*.test.ts`, so a test file anywhere else in a workspace is never run and reports nothing. Shared test helpers go in `__tests__/` without the `.test` suffix, as each server's connection harness does (`__tests__/harness.ts` in `everything`, -`__tests__/helpers.ts` in the other three). +`__tests__/helpers.ts` in the other two). ## What each server's tests do | Server | How its tests reach the code | | --- | --- | -| `everything`, `filesystem`, `memory`, `sequentialthinking` | An SDK `Client` connected to the server's `createServer()` over `InMemoryTransport`, in the vitest process, through the server's own harness, plus unit tests of helper modules such as `lib.ts`. `everything` drives its stdio, SSE and Streamable HTTP transports in-process too; the other three have one thin spawn smoke of the built `dist/index.js` | +| `everything`, `filesystem`, `memory` | An SDK `Client` connected to the server's `createServer()` over `InMemoryTransport`, in the vitest process, through the server's own harness, plus unit tests of helper modules such as `lib.ts`. `everything` drives its stdio, SSE and Streamable HTTP transports in-process too; the other two have one thin spawn smoke of the built `dist/index.js` | | `fetch`, `git`, `time` | A `ClientSession` linked in-process to the real `serve()` over in-memory streams (below), plus direct calls on the pure functions in `server.py` | No TypeScript test uses an in-memory transport yet. @@ -75,7 +75,6 @@ per-session setup and teardown the server needs. | `everything` | `createServer()` in `server/index.ts`, returns `{ server, cleanup }` | `__tests__/harness.ts` | `{ capabilities, taskStore, setup, sessionId }`, all optional | | `filesystem` | `createServer(allowedDirectories)` in `server.ts` | `__tests__/helpers.ts` | the allowed directories, then `{ capabilities, listRoots }` | | `memory` | `createServer(memoryFilePath)` in `index.ts` | `__tests__/helpers.ts` | the graph file's path (`makeTempGraph()` makes one) | -| `sequentialthinking` | `createServer()` in `index.ts` | `__tests__/helpers.ts` | `{ disableThoughtLogging }`, defaulting to `"true"` | Each `connect` returns the `client` and a `close()`; call `close()` in `afterEach`. The harnesses also export small result helpers (`call`, @@ -140,18 +139,18 @@ import and type changes alone. ### TypeScript: the spawn smoke of the built binary What only the published entry point can show (that `dist/index.js` starts as a -bin and serves over stdio; in `memory` and `sequentialthinking`, also through +bin and serves over stdio; in `memory`, also through a symlink, as npm's `.bin` entry runs it) is checked by one thin test per server that spawns it with `StdioClientTransport`: `filesystem`'s `bin-smoke.test.ts`, and -`stdio-smoke.test.ts` in `memory` and `sequentialthinking`. `everything` has +`stdio-smoke.test.ts` in `memory`. `everything` has none; its transports are driven in-process. Everything else is tested in-process. Two hazards apply to these tests, and only to them: - ⚠️ **They run the last build, not your edit.** `npm test` does not build. Build first, or run `npm run validate -w src/`, which builds before it tests. -- ⚠️ **A missing `dist/` is handled two ways.** `filesystem` and - `sequentialthinking` guard the smoke with `skipIf(!existsSync(distIndexPath))`, +- ⚠️ **A missing `dist/` is handled two ways.** `filesystem` + guards the smoke with `skipIf(!existsSync(distIndexPath))`, so with no build it is reported as **skipped** and the run stays green having tested nothing over stdio. `memory`'s has no guard and **fails**. A skipped test in this repo usually means "not built". @@ -270,7 +269,7 @@ In Python the same text follows `#`. A bug with no issue yet reads | Root tooling | `npm run test:scripts` | `npm run typecheck -w src/` is the only step that typechecks the test -files in `filesystem`, `memory` and `sequentialthinking`: their build +files in `filesystem` and `memory`: their build `tsconfig.json` excludes tests, and vitest does not typecheck. ## `test` versus `coverage` @@ -283,7 +282,7 @@ line-by-line view). ```sh npm run coverage -w src/ # one server -npm run coverage # all four +npm run coverage # all three ``` **`coverage` is a gate.** Each server's `vitest.config.ts` sets diff --git a/.claude/skills/testing/evals/evals.json b/.claude/skills/testing/evals/evals.json index 12268893c9..6a254edeeb 100644 --- a/.claude/skills/testing/evals/evals.json +++ b/.claude/skills/testing/evals/evals.json @@ -8,7 +8,7 @@ "expect": "testing" }, { - "prompt": "Some sequentialthinking tests show as skipped on my machine. How do I diagnose that?", + "prompt": "Some filesystem tests show as skipped on my machine. How do I diagnose that?", "expect": "testing" }, { diff --git a/.github/ISSUE_TEMPLATE/1-bug_report.yml b/.github/ISSUE_TEMPLATE/1-bug_report.yml index 752ceb698c..5b06b95ca5 100644 --- a/.github/ISSUE_TEMPLATE/1-bug_report.yml +++ b/.github/ISSUE_TEMPLATE/1-bug_report.yml @@ -41,7 +41,6 @@ body: - everything (@modelcontextprotocol/server-everything) - filesystem (@modelcontextprotocol/server-filesystem) - memory (@modelcontextprotocol/server-memory) - - sequentialthinking (@modelcontextprotocol/server-sequential-thinking) - fetch (mcp-server-fetch) - git (mcp-server-git) - time (mcp-server-time) diff --git a/.github/ISSUE_TEMPLATE/2-feature_request.yml b/.github/ISSUE_TEMPLATE/2-feature_request.yml index 32ca5fe830..b171329052 100644 --- a/.github/ISSUE_TEMPLATE/2-feature_request.yml +++ b/.github/ISSUE_TEMPLATE/2-feature_request.yml @@ -40,7 +40,6 @@ body: - everything (@modelcontextprotocol/server-everything) - filesystem (@modelcontextprotocol/server-filesystem) - memory (@modelcontextprotocol/server-memory) - - sequentialthinking (@modelcontextprotocol/server-sequential-thinking) - fetch (mcp-server-fetch) - git (mcp-server-git) - time (mcp-server-time) diff --git a/.github/workflows/sdk-watch.yml b/.github/workflows/sdk-watch.yml index e7813bc161..f8fa624aba 100644 --- a/.github/workflows/sdk-watch.yml +++ b/.github/workflows/sdk-watch.yml @@ -162,8 +162,8 @@ jobs: package line, so read only the entries for this SDK. Cover every version in the range above, not just the newest. If the notes could not be fetched, or the range you need is not in them, say so rather than guessing. - 2. Find how this repo uses the SDK. It holds seven MCP servers under `src/`: the - TypeScript ones (`everything`, `filesystem`, `memory`, `sequentialthinking`) import + 2. Find how this repo uses the SDK. It holds six MCP servers under `src/`: the + TypeScript ones (`everything`, `filesystem`, `memory`) import the `@modelcontextprotocol/*` packages from their `src//` sources, and the Python ones (`fetch`, `git`, `time`) import `mcp` from `src//src/mcp_server_/`. `AGENTS.md` is the map. diff --git a/.github/workflows/typescript.yml b/.github/workflows/typescript.yml index ce55bd4477..71db8ab739 100644 --- a/.github/workflows/typescript.yml +++ b/.github/workflows/typescript.yml @@ -159,7 +159,7 @@ jobs: # the built `dist/index.js` for the TypeScript servers, the console script # through `uv run` for the Python ones — over each transport it implements, # then one tool call. It is one job rather than a step in each matrix leg - # because it is a single client driving all seven servers, and it lives in + # because it is a single client driving all six servers, and it lives in # this workflow because that client is Node. `npm run local:gate` runs the # same `npm run smoke` as its last stage. smoke: @@ -178,7 +178,7 @@ jobs: uses: astral-sh/setup-uv@v7 # A root `npm ci` runs every workspace's `prepare`, which builds it, so - # the four `dist/` trees the smoke launches exist after this step. + # the three `dist/` trees the smoke launches exist after this step. - name: Install dependencies and build run: npm ci diff --git a/AGENTS.md b/AGENTS.md index bba09c93db..54967e939c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ # MCP reference servers This repository holds the official Model Context Protocol reference servers: -seven standalone packages under `src/`, four in TypeScript (published to npm) +six standalone packages under `src/`, three in TypeScript (published to npm) and three in Python (published to PyPI). They are reference implementations, meant to show how each part of the protocol is used, not general-purpose products. @@ -46,7 +46,6 @@ servers/ │ ├── everything/ TS @modelcontextprotocol/server-everything npm Exercises every MCP feature; the protocol showcase │ ├── filesystem/ TS @modelcontextprotocol/server-filesystem npm File operations, access limited to allowed directories / Roots │ ├── memory/ TS @modelcontextprotocol/server-memory npm Knowledge-graph persistence -│ ├── sequentialthinking/ TS @modelcontextprotocol/server-sequential-thinking npm Step-by-step reasoning │ ├── fetch/ Py mcp-server-fetch PyPI Fetches web content and converts it for LLMs │ ├── git/ Py mcp-server-git PyPI Git repository operations │ └── time/ Py mcp-server-time PyPI Time and timezone conversion @@ -69,7 +68,7 @@ servers/ └── CONTRIBUTING.md The contribution policy: issues, not PRs; what is accepted ``` -The four TypeScript servers are **npm workspaces** of the root `package.json`. +The three TypeScript servers are **npm workspaces** of the root `package.json`. The three Python servers are independent `uv` projects, each with its own `pyproject.toml`, `uv.lock` and `.python-version`. Every server versions and publishes independently. @@ -100,7 +99,7 @@ servers, at the version each server's `.python-version` pins. Python dependencies are managed with **`uv`, never `pip`**. ```sh -# TypeScript: one install at the root covers all four workspaces +# TypeScript: one install at the root covers all three workspaces npm install npm run build # every workspace npm test -w src/ # one workspace (vitest) @@ -458,7 +457,7 @@ here. through the issue it closes. - **Label an issue that concerns one server with its scope label** (`server-everything`, `server-filesystem`, `server-memory`, - `server-sequentialthinking`, `server-fetch`, `server-git`, `server-time`). + `server-fetch`, `server-git`, `server-time`). An issue that concerns the repository as a whole carries none. - **Every issue you create gets a milestone.** Milestones are release buckets, so pick by when the work ships. The one exception: an issue that arrives diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 99464a5090..06f168c2d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ who happen to have write access to this repository. The reference servers are developed with an AI-assisted, prompt-driven workflow built around shared conventions and strict gates (see [`AGENTS.md`](./AGENTS.md)): -seven independently published packages in two languages, each with its own +six independently published packages in two languages, each with its own tests, type checks, lint and release steps, all tracked on one project board. A diff written outside that workflow has to be reverse-engineered to fit those @@ -119,8 +119,8 @@ conventions, tests and coverage. A great issue gives us everything we need to act without a round-trip: -- **The server** it concerns (`everything`, `filesystem`, `memory`, - `sequentialthinking`, `fetch`, `git` or `time`), and its version. +- **The server** it concerns (`everything`, `filesystem`, `memory`, `fetch`, + `git` or `time`), and its version. - **A clear reproduction or use case**: exact steps to reproduce a bug, or a concrete description of the problem a feature would solve. - **Expected and actual behavior**: what you expected, and what you saw instead. diff --git a/README.md b/README.md index 9c388c80a4..1db4b6bbe7 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,6 @@ These servers aim to demonstrate MCP features and the official SDKs. - **[Filesystem](src/filesystem)** - Secure file operations with configurable access controls. - **[Git](src/git)** - Tools to read, search, and manipulate Git repositories. - **[Memory](src/memory)** - Knowledge graph-based persistent memory system. -- **[Sequential Thinking](src/sequentialthinking)** - Dynamic and reflective problem-solving through thought sequences. - **[Time](src/time)** - Time and timezone conversion capabilities. ### Archived diff --git a/RELEASING.md b/RELEASING.md index fa33776762..fe5eca9999 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -8,7 +8,7 @@ How the packages in this repository are versioned and published, and what to do ### TypeScript servers: semver, managed by changesets -`everything`, `filesystem`, `memory` and `sequentialthinking` use **semver**, managed by [changesets](https://github.com/changesets/changesets). +`everything`, `filesystem` and `memory` use **semver**, managed by [changesets](https://github.com/changesets/changesets). - A PR that changes what one of them publishes includes a **changeset**: a small file under [`.changeset/`](.changeset/README.md), created with `npm run changeset`, naming the package, the bump type and the line that becomes its CHANGELOG entry. - Merged changesets accumulate on `v2/main`. On every push there, [`version-packages.yml`](.github/workflows/version-packages.yml) creates or updates one rolling **"Version Packages" PR** that consumes them: it bumps each affected `package.json`, writes the package's `CHANGELOG.md`, deletes the consumed changeset files and refreshes `package-lock.json`. Merging that PR is what changes a version. @@ -22,7 +22,7 @@ Semver policy: | **minor** | A new tool, prompt, resource or option | | **major** | A breaking change: a tool removed or renamed, a schema change that breaks clients, a protocol or Node floor bump | -The semver line starts at **`1.0.0`** for all four packages. Their earlier releases were date-stamped (`2025.x`, `2026.x`), which sorts above any semver number, but `npm install` and `npx` resolve the `latest` dist-tag rather than the highest number, so publishing `1.x` is what users get. npm will not move `latest` to a lower version implicitly, so `release.yml` always passes the dist-tag explicitly, and chooses it at publish time: `latest` for the move from the date-stamped line to `1.x` and for any version higher than the registry's current `latest`, and `backfill` otherwise. The second case is an older release's failed leg re-run after a newer version has published: the version becomes installable by number, and `latest` does not move backward. Once `1.0.0` is live, a scope maintainer deprecates the date-stamped range (`>=2025.0.0 <2027.0.0`) by hand, once; the `release` skill has the command and the check to run first. +The semver line starts at **`1.0.0`** for all three packages. Their earlier releases were date-stamped (`2025.x`, `2026.x`), which sorts above any semver number, but `npm install` and `npx` resolve the `latest` dist-tag rather than the highest number, so publishing `1.x` is what users get. npm will not move `latest` to a lower version implicitly, so `release.yml` always passes the dist-tag explicitly, and chooses it at publish time: `latest` for the move from the date-stamped line to `1.x` and for any version higher than the registry's current `latest`, and `backfill` otherwise. The second case is an older release's failed leg re-run after a newer version has published: the version becomes installable by number, and `latest` does not move backward. Once `1.0.0` is live, a scope maintainer deprecates the date-stamped range (`>=2025.0.0 <2027.0.0`) by hand, once; the `release` skill has the command and the check to run first. ### Python servers: CalVer diff --git a/SECURITY.md b/SECURITY.md index 3b0235c2f4..7289ea718b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -27,7 +27,6 @@ discussions, or pull requests. | filesystem | `@modelcontextprotocol/server-filesystem` (npm) | | git | `mcp-server-git` (PyPI) | | memory | `@modelcontextprotocol/server-memory` (npm) | -| sequentialthinking | `@modelcontextprotocol/server-sequential-thinking` (npm) | | time | `mcp-server-time` (PyPI) | **Report these elsewhere:** diff --git a/docs/ai-software-factory.md b/docs/ai-software-factory.md index e0d5356ead..9ff0b41f8e 100644 --- a/docs/ai-software-factory.md +++ b/docs/ai-software-factory.md @@ -40,9 +40,9 @@ As of 2026-10-07, in the nine days since the inception doc merged (#4861), 64 PRs have merged into `v2/main`, every one opened by a maintainer, and 173 of their 181 commits carry an agent co-author trailer. -## Seven servers, two languages, one process +## Six servers, two languages, one process -The repository holds seven reference servers: four in TypeScript, published to +The repository holds six reference servers: three in TypeScript, published to npm and built as npm workspaces of one root, and three in Python, published to PyPI as independent `uv` projects. Every server versions and publishes on its own. That shape is what most of the adaptation from the Inspector was about: diff --git a/package-lock.json b/package-lock.json index 894e5ac485..1dfd1ecf11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,7 @@ "dependencies": { "@modelcontextprotocol/server-everything": "*", "@modelcontextprotocol/server-filesystem": "*", - "@modelcontextprotocol/server-memory": "*", - "@modelcontextprotocol/server-sequential-thinking": "*" + "@modelcontextprotocol/server-memory": "*" }, "devDependencies": { "@changesets/changelog-github": "^1.0.1", @@ -767,10 +766,6 @@ "resolved": "src/memory", "link": true }, - "node_modules/@modelcontextprotocol/server-sequential-thinking": { - "resolved": "src/sequentialthinking", - "link": true - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1280,23 +1275,6 @@ "@types/node": "*" } }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.71.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.0.tgz", @@ -1905,123 +1883,6 @@ "node": ">=18" } }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, "node_modules/content-disposition": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", @@ -2241,15 +2102,6 @@ "node": ">= 0.4" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2894,15 +2746,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -3228,15 +3071,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -4410,15 +4244,6 @@ "node": ">= 0.10" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -5286,15 +5111,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yaml": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", @@ -5311,74 +5127,6 @@ "url": "https://github.com/sponsors/eemeli" } }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -5475,28 +5223,6 @@ "typescript": "^5.8.2", "vitest": "^4.1.11" } - }, - "src/sequentialthinking": { - "name": "@modelcontextprotocol/server-sequential-thinking", - "version": "1.0.0", - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "@modelcontextprotocol/sdk": "^1.31.0", - "chalk": "^5.3.0", - "yargs": "^17.7.2", - "zod": "^4.0.0" - }, - "bin": { - "mcp-server-sequential-thinking": "dist/index.js" - }, - "devDependencies": { - "@types/node": "^22", - "@types/yargs": "^17.0.32", - "@vitest/coverage-v8": "^4.1.11", - "shx": "^0.4.0", - "typescript": "^5.8.2", - "vitest": "^4.1.11" - } } } } diff --git a/package.json b/package.json index 1314868a79..4280237fc1 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,7 @@ "dependencies": { "@modelcontextprotocol/server-everything": "*", "@modelcontextprotocol/server-filesystem": "*", - "@modelcontextprotocol/server-memory": "*", - "@modelcontextprotocol/server-sequential-thinking": "*" + "@modelcontextprotocol/server-memory": "*" }, "devDependencies": { "@changesets/changelog-github": "^1.0.1", diff --git a/scripts/dependabot-alerts.mjs b/scripts/dependabot-alerts.mjs index 967a01c6da..f0620c34f8 100644 --- a/scripts/dependabot-alerts.mjs +++ b/scripts/dependabot-alerts.mjs @@ -16,7 +16,7 @@ // 1. **There is no `dependabot_alert` workflow trigger** (it is a webhook // event only), so this is a daily schedule rather than an event handler. // 2. **Two ecosystems.** npm alerts arrive against the root -// `package-lock.json` (one lockfile for all four TypeScript workspaces); +// `package-lock.json` (one lockfile for all three TypeScript workspaces); // pip alerts arrive against each Python server's `uv.lock`, with PEP 440 // ranges, which `scripts/lib/pep440.mjs` reads. Any other ecosystem is // reported and skipped: filing it properly means knowing how to fix it. diff --git a/scripts/dependency-refresh.test.mjs b/scripts/dependency-refresh.test.mjs index 24721b088e..deddc6f823 100644 --- a/scripts/dependency-refresh.test.mjs +++ b/scripts/dependency-refresh.test.mjs @@ -44,12 +44,11 @@ test("installLabel maps npm's dependent onto a manifest", () => { assert.equal(installLabel(undefined), "root"); // A package-name spelling is mapped too, rather than folded into root. const names = { - "@modelcontextprotocol/server-sequential-thinking": - "src/sequentialthinking", + "@modelcontextprotocol/server-memory": "src/memory", }; assert.equal( - installLabel("@modelcontextprotocol/server-sequential-thinking", names), - "src/sequentialthinking", + installLabel("@modelcontextprotocol/server-memory", names), + "src/memory", ); const byInstall = parseNpmOutdated( JSON.stringify({ @@ -57,12 +56,12 @@ test("installLabel maps npm's dependent onto a manifest", () => { current: "5.6.2", wanted: "5.6.2", latest: "6.0.1", - dependent: "@modelcontextprotocol/server-sequential-thinking", + dependent: "@modelcontextprotocol/server-memory", }, }), names, ); - assert.deepEqual([...byInstall.keys()], ["src/sequentialthinking"]); + assert.deepEqual([...byInstall.keys()], ["src/memory"]); }); test("workspacePackageNames reads each workspace manifest", (t) => { diff --git a/scripts/lib/sweep.mjs b/scripts/lib/sweep.mjs index e69990eac1..01b0f6743f 100644 --- a/scripts/lib/sweep.mjs +++ b/scripts/lib/sweep.mjs @@ -56,7 +56,6 @@ export const SERVERS = [ "everything", "filesystem", "memory", - "sequentialthinking", "fetch", "git", "time", diff --git a/scripts/lib/sweep.test.mjs b/scripts/lib/sweep.test.mjs index 52f5aeff89..634a5af269 100644 --- a/scripts/lib/sweep.test.mjs +++ b/scripts/lib/sweep.test.mjs @@ -145,10 +145,7 @@ test("pickMilestone takes the nearest DATED open milestone", () => { test("scopeLabel names a server only for a path inside one", () => { assert.equal(scopeLabel("src/time/uv.lock"), "server-time"); - assert.equal( - scopeLabel("src/sequentialthinking/package.json"), - "server-sequentialthinking", - ); + assert.equal(scopeLabel("src/memory/package.json"), "server-memory"); assert.equal(scopeLabel("package-lock.json"), null); assert.equal(scopeLabel("src/unknown/uv.lock"), null); }); diff --git a/scripts/release-manifest.test.mjs b/scripts/release-manifest.test.mjs index d855c8181c..749570d975 100644 --- a/scripts/release-manifest.test.mjs +++ b/scripts/release-manifest.test.mjs @@ -92,13 +92,12 @@ test("main writes both step outputs as single-line JSON", (t) => { }); }); -test("this repository's seven packages are all found", () => { +test("this repository's six packages are all found", () => { const { npm, pypi } = expectedPackages(); assert.deepEqual(Object.keys(npm).sort(), [ "everything", "filesystem", "memory", - "sequentialthinking", ]); assert.deepEqual(Object.keys(pypi).sort(), ["fetch", "git", "time"]); }); diff --git a/scripts/release-notes.mjs b/scripts/release-notes.mjs index 913ad6f474..418bb960c0 100644 --- a/scripts/release-notes.mjs +++ b/scripts/release-notes.mjs @@ -4,7 +4,7 @@ // `npm run release:notes -- --milestone vX.Y.Z --merge-sha --merge-branch --ledger-url [...]`. // It replaces the shell recipe the release skill's step 5a carried, and is // modeled on the MCP Inspector's helper (modelcontextprotocol/inspector#2550), -// adapted to a repository that publishes seven packages at seven versions. +// adapted to a repository that publishes six packages at six versions. // // A reader of a release wants to know what changed in the server they use, so // the notes are laid out per PACKAGE rather than as one flat list: diff --git a/scripts/sdk-watch.test.mjs b/scripts/sdk-watch.test.mjs index 8354a7c694..a93470a4e2 100644 --- a/scripts/sdk-watch.test.mjs +++ b/scripts/sdk-watch.test.mjs @@ -221,15 +221,9 @@ test("titles, labels and bodies", () => { const everywhere = { group: group("typescript-sdk-v1"), target: "1", - rows: [ - "everything", - "filesystem", - "memory", - "sequentialthinking", - "fetch", - "git", - "time", - ].map((s) => ({ where: `src/${s}`, behind: true })), + rows: ["everything", "filesystem", "memory", "fetch", "git", "time"].map( + (s) => ({ where: `src/${s}`, behind: true }), + ), }; assert.deepEqual(issueLabels(everywhere), ["v2", "chore", "dependencies"]); // Only servers that are behind are labeled. diff --git a/scripts/smoke-servers.mjs b/scripts/smoke-servers.mjs index ed21b3f0ff..87a770c46d 100755 --- a/scripts/smoke-servers.mjs +++ b/scripts/smoke-servers.mjs @@ -8,7 +8,7 @@ * npm run smoke # the root entry point (all) * * For each server and transport it connects an MCP client, lists the tools, - * and calls one of them: stdio for all seven servers, plus HTTP+SSE and + * and calls one of them: stdio for all six servers, plus HTTP+SSE and * Streamable HTTP for `everything`, the only server that serves them. * * Why it exists when every server has a test suite: the suites import the @@ -132,20 +132,6 @@ export const SERVERS = [ env: (ctx) => ({ MEMORY_FILE_PATH: path.join(ctx.dir, "memory.jsonl") }), call: () => ({ name: "read_graph", arguments: {} }), }, - { - name: "sequentialthinking", - language: "ts", - transports: ["stdio"], - call: () => ({ - name: "sequentialthinking", - arguments: { - thought: "boot smoke", - nextThoughtNeeded: false, - thoughtNumber: 1, - totalThoughts: 1, - }, - }), - }, { name: "fetch", language: "py", diff --git a/scripts/smoke-servers.test.mjs b/scripts/smoke-servers.test.mjs index 22f002f4b5..f2f0697036 100644 --- a/scripts/smoke-servers.test.mjs +++ b/scripts/smoke-servers.test.mjs @@ -45,8 +45,8 @@ test("findUnlistedServers names a server the table lacks, either language", (t) assert.deepEqual(findUnlistedServers(src), ["newpy", "newts"]); }); -test("stdio for all seven; SSE and Streamable HTTP for everything only", () => { - assert.equal(SERVERS.length, 7); +test("stdio for all six; SSE and Streamable HTTP for everything only", () => { + assert.equal(SERVERS.length, 6); for (const server of SERVERS) { assert.ok(server.transports.includes("stdio"), server.name); if (server.name !== "everything") @@ -60,7 +60,7 @@ test("stdio for all seven; SSE and Streamable HTTP for everything only", () => { }); test("selectTargets: no names means every server and transport", () => { - assert.equal(selectTargets([]).length, 9); + assert.equal(selectTargets([]).length, 8); }); test("selectTargets: named servers only, in the table's order", () => { diff --git a/src/everything/version.ts b/src/everything/version.ts index 94358d6a7b..bda77b0c1a 100644 --- a/src/everything/version.ts +++ b/src/everything/version.ts @@ -2,7 +2,7 @@ // it cannot drift from the published version (#4472). The TypeScript servers // are versioned by changesets, whose "Version Packages" PR edits only // package.json and CHANGELOG.md: a version literal in the source would stay -// behind at the first bump. Same module as `memory` and `sequentialthinking`. +// behind at the first bump. Same module as `filesystem` and `memory`. import { createRequire } from "node:module"; import path from "node:path"; diff --git a/src/filesystem/version.ts b/src/filesystem/version.ts index fe1e594525..37e61a17b2 100644 --- a/src/filesystem/version.ts +++ b/src/filesystem/version.ts @@ -2,7 +2,7 @@ // it cannot drift from the published version (#4472). The TypeScript servers // are versioned by changesets, whose "Version Packages" PR edits only // package.json and CHANGELOG.md: a version literal in the source would stay -// behind at the first bump. Same module as `memory` and `sequentialthinking`. +// behind at the first bump. Same module as `everything` and `memory`. import { createRequire } from "node:module"; import path from "node:path"; diff --git a/src/sequentialthinking/CHANGELOG.md b/src/sequentialthinking/CHANGELOG.md deleted file mode 100644 index 09e81ad025..0000000000 --- a/src/sequentialthinking/CHANGELOG.md +++ /dev/null @@ -1,19 +0,0 @@ -# @modelcontextprotocol/server-sequential-thinking - -## 1.0.0 - -The first semantic version, resuming semver after the date-stamped releases (last `2026.8.31`). It describes everything since `2026.8.31`, and is the last release of the legacy-era server, on the 1.x MCP SDK. - -### Patch Changes - -- [#5015](https://github.com/modelcontextprotocol/servers/pull/5015) [`8a4139c`](https://github.com/modelcontextprotocol/servers/commit/8a4139c3d71b66e95d2f7cd2bc485d4781ecf1d7) [@cliffhall](https://github.com/cliffhall) - Advertise the `sequentialthinking` tool as `readOnlyHint: false` and `idempotentHint: false`. Every call appends to the server's thought history, and a call with both `branchFromThought` and `branchId` also appends to that branch, so the tool was never read-only or idempotent. `destructiveHint` and `openWorldHint` stay `false`. ([#4721](https://github.com/modelcontextprotocol/servers/issues/4721)) - -- [#4970](https://github.com/modelcontextprotocol/servers/pull/4970) [`86806cc`](https://github.com/modelcontextprotocol/servers/commit/86806cc3870a112f56870e86cd36bae8239edb02) [@cliffhall](https://github.com/cliffhall) - Build the server with an exported `createServer()` factory and attach stdio only when `index.js` runs as the binary, so the server can be tested in-process. No change to the tool, its schemas or its results. - -- [#5031](https://github.com/modelcontextprotocol/servers/pull/5031) [`bc52d0e`](https://github.com/modelcontextprotocol/servers/commit/bc52d0e07a1d9dca505746165b30f4c99f8cc79b) [@cliffhall](https://github.com/cliffhall) - The thought log on stderr no longer prints "undefined" in its headers: a revision with no `revisesThought` reads "Revision N/M", and a branch with no `branchId` reads "(from thought N)" with no ID. The box border is sized from the header's visible text, so colour escape codes no longer make it wider than its text. - -- [#5053](https://github.com/modelcontextprotocol/servers/pull/5053) [`c63255a`](https://github.com/modelcontextprotocol/servers/commit/c63255ae9616b110d6089fa343fcfdf3f5def534) [@cliffhall](https://github.com/cliffhall) - Stop shipping the compiled test helper `dist/__tests__/helpers.js` in the published package: the build now excludes everything under `__tests__/`, not just `*.test.ts`. - -- [#5036](https://github.com/modelcontextprotocol/servers/pull/5036) [`b5c5ccc`](https://github.com/modelcontextprotocol/servers/commit/b5c5cccb79a5d43b41247065fb9efd174363e799) [@cliffhall](https://github.com/cliffhall) - A `branchId` that names an `Object.prototype` key, such as `"constructor"` or `"__proto__"`, now creates and lists its branch like any other id instead of failing the call. A call that fails no longer adds its thought to the history first, so `thoughtHistoryLength` counts only thoughts that were accepted. - -- [#5087](https://github.com/modelcontextprotocol/servers/pull/5087) [`09edf63`](https://github.com/modelcontextprotocol/servers/commit/09edf633b93cdaf094ba43622294d587afcd1023) [@cliffhall](https://github.com/cliffhall) - Require `@modelcontextprotocol/sdk` 1.31.0 or later (was `^1.30.0`), so an install can no longer resolve an SDK version affected by GHSA-6qxp-vccf-f47h. The advisory concerns the SDK's OAuth client, which this server does not use. diff --git a/src/sequentialthinking/Dockerfile b/src/sequentialthinking/Dockerfile deleted file mode 100644 index f1a88195bc..0000000000 --- a/src/sequentialthinking/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -FROM node:22.12-alpine AS builder - -COPY src/sequentialthinking /app -COPY tsconfig.json /tsconfig.json - -WORKDIR /app - -RUN --mount=type=cache,target=/root/.npm npm install - -RUN --mount=type=cache,target=/root/.npm-production npm ci --ignore-scripts --omit-dev - -FROM node:22-alpine AS release - -COPY --from=builder /app/dist /app/dist -COPY --from=builder /app/package.json /app/package.json -COPY --from=builder /app/package-lock.json /app/package-lock.json - -ENV NODE_ENV=production - -WORKDIR /app - -RUN npm ci --ignore-scripts --omit-dev - -ENTRYPOINT ["node", "dist/index.js"] diff --git a/src/sequentialthinking/README.md b/src/sequentialthinking/README.md deleted file mode 100644 index b6b853aa56..0000000000 --- a/src/sequentialthinking/README.md +++ /dev/null @@ -1,222 +0,0 @@ -# Sequential Thinking MCP Server - -An MCP server implementation that provides a tool for dynamic and reflective problem-solving through a structured thinking process. - -Published on npm as [`@modelcontextprotocol/server-sequential-thinking`](https://www.npmjs.com/package/@modelcontextprotocol/server-sequential-thinking). - -## Features - -- Break down complex problems into manageable steps -- Revise and refine thoughts as understanding deepens -- Branch into alternative paths of reasoning -- Adjust the total number of thoughts dynamically -- Generate and verify solution hypotheses - -## Tool - -### sequential_thinking - -Facilitates a detailed, step-by-step thinking process for problem-solving and analysis. - -**Inputs:** -- `thought` (string): The current thinking step -- `nextThoughtNeeded` (boolean): Whether another thought step is needed -- `thoughtNumber` (integer): Current thought number -- `totalThoughts` (integer): Estimated total thoughts needed -- `isRevision` (boolean, optional): Whether this revises previous thinking -- `revisesThought` (integer, optional): Which thought is being reconsidered -- `branchFromThought` (integer, optional): Branching point thought number -- `branchId` (string, optional): Branch identifier -- `needsMoreThoughts` (boolean, optional): If more thoughts are needed - -## Usage - -The Sequential Thinking tool is designed for: -- Breaking down complex problems into steps -- Planning and design with room for revision -- Analysis that might need course correction -- Problems where the full scope might not be clear initially -- Tasks that need to maintain context over multiple steps -- Situations where irrelevant information needs to be filtered out - -In practice, you do not call `sequential_thinking` directly by hand unless your client exposes raw tool calls. Instead, connect the server to an MCP-aware host and ask the model to think through a problem step by step. The host can then decide to call the tool one or more times while it works. - -### What it looks like in use - -Example prompts that typically benefit from this tool: - -- `Plan a database migration from PostgreSQL 14 to 16, list risks, and revise the plan if downtime exceeds 5 minutes.` -- `Debug why this deployment only fails in production and show your reasoning step by step.` -- `Compare three architecture options for a file sync engine and branch if one assumption turns out to be wrong.` - -### How to tell it is working - -If your host or inspector shows tool activity, you should see repeated calls to `sequential_thinking` with fields such as: - -- `thought` -- `thoughtNumber` -- `totalThoughts` -- `nextThoughtNeeded` - -When the reasoning changes course, you may also see revision or branching fields like `isRevision`, `revisesThought`, `branchFromThought`, or `branchId`. - -### Quick manual verification - -After installing the server in your MCP host: - -1. Restart or reload the host so it reconnects to the server. -2. Confirm the `sequential_thinking` tool appears in the host's MCP tool list or inspector. -3. Ask the host to solve a non-trivial problem in a step-by-step way. -4. Verify that the host invokes the tool multiple times instead of returning a one-shot answer. - -## Configuration - -### Usage with Claude Desktop - -Add this to your `claude_desktop_config.json`: - -#### npx - -```json -{ - "mcpServers": { - "sequential-thinking": { - "command": "npx", - "args": [ - "-y", - "@modelcontextprotocol/server-sequential-thinking" - ] - } - } -} -``` - -On Windows, use `cmd /c` to launch `npx`: - -```json -{ - "mcpServers": { - "sequential-thinking": { - "command": "cmd", - "args": [ - "/c", - "npx", - "-y", - "@modelcontextprotocol/server-sequential-thinking" - ] - } - } -} -``` - -#### docker - -```json -{ - "mcpServers": { - "sequentialthinking": { - "command": "docker", - "args": [ - "run", - "--rm", - "-i", - "mcp/sequentialthinking" - ] - } - } -} -``` - -To disable logging of thought information set env var: `DISABLE_THOUGHT_LOGGING` to `true`. - -### Usage with VS Code - -For quick installation, click one of the installation buttons below... - -[![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=sequentialthinking&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-sequential-thinking%22%5D%7D) [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-NPM-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=sequentialthinking&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-sequential-thinking%22%5D%7D&quality=insiders) - -[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Docker-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=sequentialthinking&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22--rm%22%2C%22-i%22%2C%22mcp%2Fsequentialthinking%22%5D%7D) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Docker-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=sequentialthinking&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22--rm%22%2C%22-i%22%2C%22mcp%2Fsequentialthinking%22%5D%7D&quality=insiders) - -For manual installation, you can configure the MCP server using one of these methods: - -**Method 1: User Configuration (Recommended)** -Add the configuration to your user-level MCP configuration file. Open the Command Palette (`Ctrl + Shift + P`) and run `MCP: Open User Configuration`. This will open your user `mcp.json` file where you can add the server configuration. - -**Method 2: Workspace Configuration** -Alternatively, you can add the configuration to a file called `.vscode/mcp.json` in your workspace. This will allow you to share the configuration with others. - -> For more details about MCP configuration in VS Code, see the [official VS Code MCP documentation](https://code.visualstudio.com/docs/copilot/customization/mcp-servers). - -For NPX installation: - -```json -{ - "servers": { - "sequential-thinking": { - "command": "npx", - "args": [ - "-y", - "@modelcontextprotocol/server-sequential-thinking" - ] - } - } -} -``` - -On Windows, use: - -```json -{ - "servers": { - "sequential-thinking": { - "command": "cmd", - "args": [ - "/c", - "npx", - "-y", - "@modelcontextprotocol/server-sequential-thinking" - ] - } - } -} -``` - -For Docker installation: - -```json -{ - "servers": { - "sequential-thinking": { - "command": "docker", - "args": [ - "run", - "--rm", - "-i", - "mcp/sequentialthinking" - ] - } - } -} -``` - -### Usage with Codex CLI - -Run the following: - -#### npx - -```bash -codex mcp add sequential-thinking npx -y @modelcontextprotocol/server-sequential-thinking -``` - -## Building - -Docker: - -```bash -docker build -t mcp/sequentialthinking -f src/sequentialthinking/Dockerfile . -``` - -## License - -This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/sequentialthinking/__tests__/entry-point.test.ts b/src/sequentialthinking/__tests__/entry-point.test.ts deleted file mode 100644 index 1b763a02d1..0000000000 --- a/src/sequentialthinking/__tests__/entry-point.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -// The in-process half of the entry point: `main()` connects a server to the -// transport it is given, and `isEntryPoint()` decides whether index.ts was run -// as the binary (and so attaches stdio) or merely imported, as these tests do. -// The bootstrap that calls them only runs in a spawned process, which -// stdio-smoke.test.ts covers. - -import { - describe, - it, - expect, - beforeEach, - afterEach, - vi, - type MockInstance, -} from "vitest"; -import { mkdtempSync, rmSync, symlinkSync } from "node:fs"; -import { tmpdir } from "node:os"; -import path from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; -import { isEntryPoint, main } from "../index.js"; - -const indexUrl = new URL("../index.ts", import.meta.url).href; -const indexPath = fileURLToPath(indexUrl); - -describe("main", () => { - let errorSpy: MockInstance; - - beforeEach(() => { - errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - }); - - afterEach(() => { - errorSpy.mockRestore(); - }); - - it("serves the tool on the given transport and announces itself on stderr", async () => { - const [clientTransport, serverTransport] = - InMemoryTransport.createLinkedPair(); - const client = new Client({ name: "main-test", version: "0.0.0" }); - try { - await Promise.all([ - main(serverTransport), - client.connect(clientTransport), - ]); - expect(errorSpy).toHaveBeenCalledWith( - "Sequential Thinking MCP Server running on stdio", - ); - const { tools } = await client.listTools(); - expect(tools.map((t) => t.name)).toEqual(["sequentialthinking"]); - } finally { - await client.close(); - } - }); - - it("rejects when the transport cannot start", async () => { - const [, serverTransport] = InMemoryTransport.createLinkedPair(); - vi.spyOn(serverTransport, "start").mockRejectedValue(new Error("no pipe")); - await expect(main(serverTransport)).rejects.toThrow("no pipe"); - expect(errorSpy).not.toHaveBeenCalled(); - }); -}); - -describe("isEntryPoint", () => { - let dir: string; - - beforeEach(() => { - dir = mkdtempSync(path.join(tmpdir(), "seqthink-entry-")); - }); - - afterEach(() => { - rmSync(dir, { recursive: true, force: true }); - }); - - it("is false when the module is imported (argv[1] is the test runner)", () => { - expect(isEntryPoint(indexUrl, process.argv[1])).toBe(false); - }); - - it("is false with no argv[1]", () => { - expect(isEntryPoint(indexUrl, undefined)).toBe(false); - expect(isEntryPoint(indexUrl, "")).toBe(false); - }); - - it("is true when argv[1] is the module itself", () => { - expect(isEntryPoint(indexUrl, indexPath)).toBe(true); - }); - - it("is true when argv[1] is a symlink to the module, as npx and .bin use", () => { - const link = path.join(dir, "mcp-server-sequential-thinking"); - symlinkSync(indexPath, link); - expect(isEntryPoint(indexUrl, link)).toBe(true); - }); - - it("is false when argv[1] does not exist", () => { - expect(isEntryPoint(indexUrl, path.join(dir, "missing.js"))).toBe(false); - }); - - it("is false when the module URL does not resolve to a file", () => { - expect( - isEntryPoint(pathToFileURL(path.join(dir, "gone.js")).href, indexPath), - ).toBe(false); - }); -}); diff --git a/src/sequentialthinking/__tests__/helpers.ts b/src/sequentialthinking/__tests__/helpers.ts deleted file mode 100644 index 1d0d8940f3..0000000000 --- a/src/sequentialthinking/__tests__/helpers.ts +++ /dev/null @@ -1,116 +0,0 @@ -// Shared harness for the in-process protocol tests (#4854). It links a real -// SDK Client to a server built by `createServer()` over an InMemoryTransport, -// so every test goes through the SDK's input validation, output-schema -// validation and error mapping exactly as a client on the wire would, with no -// process and no build. Tests use only the public Client API and the wire -// shapes it returns, so the Part 3 SDK migration can update them with import -// and type changes alone. - -import { expect } from "vitest"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; -import { createServer } from "../index.js"; - -export const TOOL_NAME = "sequentialthinking"; - -/** The JSON payload of a successful call, as both text and structuredContent. */ -export interface ThoughtResult { - thoughtNumber: number; - totalThoughts: number; - nextThoughtNeeded: boolean; - branches: string[]; - thoughtHistoryLength: number; -} - -/** A tool result as the client sees it on the wire. */ -export interface WireResult { - content: Array<{ type: string; text?: string }>; - structuredContent?: Record; - isError?: boolean; -} - -export interface Connected { - client: Client; - /** Call the tool with raw (unvalidated) arguments, as a client would. */ - think: (args: Record) => Promise; - close: () => Promise; -} - -export interface ConnectOptions { - /** - * The DISABLE_THOUGHT_LOGGING value the server is constructed under - * (`undefined` = unset). It is read once, when the server is built, so it is - * set only around `createServer()` and restored at once. Defaults to "true", - * which keeps the per-thought box off stderr in tests not about logging. - */ - disableThoughtLogging?: string | undefined; -} - -/** Build a fresh server and connect a client to it in-process. */ -export async function connect( - options: ConnectOptions = { disableThoughtLogging: "true" }, -): Promise { - const previous = process.env.DISABLE_THOUGHT_LOGGING; - setEnv(options.disableThoughtLogging); - let server: ReturnType; - try { - server = createServer(); - } finally { - setEnv(previous); - } - const [clientTransport, serverTransport] = - InMemoryTransport.createLinkedPair(); - const client = new Client({ name: "test-client", version: "0.0.0" }); - await Promise.all([ - server.connect(serverTransport), - client.connect(clientTransport), - ]); - return { - client, - think: async (args) => - (await client.callTool({ - name: TOOL_NAME, - arguments: args, - })) as WireResult, - close: async () => { - await client.close(); - await server.close(); - }, - }; -} - -/** The text of a result's single content block. */ -export function textOf(result: WireResult): string { - expect(result.content).toHaveLength(1); - expect(result.content[0].type).toBe("text"); - return result.content[0].text ?? ""; -} - -/** Parse a successful result, checking text and structuredContent agree. */ -export function parseOk(result: WireResult): ThoughtResult { - expect(result.isError).toBeFalsy(); - const parsed = JSON.parse(textOf(result)) as ThoughtResult; - expect(result.structuredContent).toEqual(parsed); - return parsed; -} - -/** Minimal valid arguments, overridable per test. */ -export function thought( - overrides: Record = {}, -): Record { - return { - thought: "a thought", - nextThoughtNeeded: true, - thoughtNumber: 1, - totalThoughts: 3, - ...overrides, - }; -} - -function setEnv(value: string | undefined): void { - if (value === undefined) { - delete process.env.DISABLE_THOUGHT_LOGGING; - } else { - process.env.DISABLE_THOUGHT_LOGGING = value; - } -} diff --git a/src/sequentialthinking/__tests__/input-schema.test.ts b/src/sequentialthinking/__tests__/input-schema.test.ts deleted file mode 100644 index f5ff488b90..0000000000 --- a/src/sequentialthinking/__tests__/input-schema.test.ts +++ /dev/null @@ -1,180 +0,0 @@ -// How the tool's input schema behaves on the wire: which arguments are -// required, how strings are coerced to booleans and numbers, and the exact -// error a client gets back for each kind of bad input. Originally the #4651 -// regression test, which spawned dist/index.js and was skipped when no build -// existed; ported to the in-process client (#4854) so it always runs and -// always tests the source. The error texts are the SDK 1.x mapping of a Zod -// failure and are pinned verbatim: a change to them is a wire change. - -import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { - connect, - parseOk, - textOf, - thought, - type Connected, -} from "./helpers.js"; - -const INVALID = - "MCP error -32602: Input validation error: Invalid arguments for tool sequentialthinking: "; - -describe("sequentialthinking input schema", () => { - let conn: Connected; - - beforeEach(async () => { - conn = await connect(); - }); - - afterEach(async () => { - await conn.close(); - }); - - async function expectInvalid( - args: Record, - detail: string, - ): Promise { - const result = await conn.think(args); - expect(result.isError).toBe(true); - expect(result.structuredContent).toBeUndefined(); - expect(textOf(result)).toBe(INVALID + detail); - } - - describe("required fields (#4651)", () => { - it("rejects a call that omits nextThoughtNeeded", async () => { - await expectInvalid( - { thought: "t", thoughtNumber: 1, totalThoughts: 1 }, - "Invalid input at nextThoughtNeeded", - ); - }); - - it("rejects a call that omits thought", async () => { - const { thought: _omitted, ...args } = thought(); - await expectInvalid( - args, - "Invalid input: expected string, received undefined at thought", - ); - }); - - it("reports every missing numeric field, coerced to NaN", async () => { - await expectInvalid( - { thought: "t" }, - "Invalid input at nextThoughtNeeded\n" + - "Invalid input: expected number, received NaN at thoughtNumber\n" + - "Invalid input: expected number, received NaN at totalThoughts", - ); - }); - - it("accepts a call with only the four required fields", async () => { - expect(parseOk(await conn.think(thought()))).toEqual({ - thoughtNumber: 1, - totalThoughts: 3, - nextThoughtNeeded: true, - branches: [], - thoughtHistoryLength: 1, - }); - }); - }); - - describe("boolean coercion", () => { - it.each([ - ["True", true], - ["FALSE", false], - ["true", true], - ["false", false], - [true, true], - [false, false], - ])("accepts nextThoughtNeeded %j as %j", async (value, expected) => { - const result = parseOk( - await conn.think(thought({ nextThoughtNeeded: value })), - ); - expect(result.nextThoughtNeeded).toBe(expected); - }); - - it.each(["yes", "", "1"])( - "rejects the string %j, naming it in the error", - async (value) => { - await expectInvalid( - thought({ nextThoughtNeeded: value }), - `Expected boolean or "true"/"false" string, received "${value}" at nextThoughtNeeded`, - ); - }, - ); - - it.each([1, 0, null])("rejects the non-string %j", async (value) => { - await expectInvalid( - thought({ nextThoughtNeeded: value }), - "Invalid input at nextThoughtNeeded", - ); - }); - - it("coerces the optional booleans the same way", async () => { - expect( - ( - await conn.think( - thought({ isRevision: "TRUE", needsMoreThoughts: "False" }), - ) - ).isError, - ).toBeFalsy(); - await expectInvalid( - thought({ isRevision: "maybe" }), - 'Expected boolean or "true"/"false" string, received "maybe" at isRevision', - ); - await expectInvalid( - thought({ needsMoreThoughts: "nope" }), - 'Expected boolean or "true"/"false" string, received "nope" at needsMoreThoughts', - ); - }); - }); - - describe("number coercion", () => { - it("coerces numeric strings", async () => { - expect( - parseOk( - await conn.think(thought({ thoughtNumber: "2", totalThoughts: "4" })), - ), - ).toMatchObject({ thoughtNumber: 2, totalThoughts: 4 }); - }); - - // z.coerce.number() is Number(value), so a boolean true becomes 1. - it("coerces true to 1", async () => { - expect( - parseOk(await conn.think(thought({ thoughtNumber: true }))), - ).toMatchObject({ thoughtNumber: 1 }); - }); - - it.each([ - ["thoughtNumber", 0, "Too small: expected number to be >=1"], - ["totalThoughts", -1, "Too small: expected number to be >=1"], - ["thoughtNumber", "", "Too small: expected number to be >=1"], - ["thoughtNumber", null, "Too small: expected number to be >=1"], - ["thoughtNumber", 1.5, "Invalid input: expected int, received number"], - ["thoughtNumber", "abc", "Invalid input: expected number, received NaN"], - ["revisesThought", 0, "Too small: expected number to be >=1"], - [ - "branchFromThought", - "x", - "Invalid input: expected number, received NaN", - ], - ])("rejects %s = %j", async (field, value, detail) => { - await expectInvalid(thought({ [field]: value }), `${detail} at ${field}`); - }); - - it("rejects a non-string branchId", async () => { - await expectInvalid( - thought({ branchFromThought: 1, branchId: 7 }), - "Invalid input: expected string, received number at branchId", - ); - }); - }); - - it("ignores unknown arguments", async () => { - expect( - parseOk(await conn.think(thought({ somethingElse: "x" }))), - ).toMatchObject({ thoughtNumber: 1, thoughtHistoryLength: 1 }); - }); - - it("does not count a rejected call in the history", async () => { - await conn.think(thought({ nextThoughtNeeded: "yes" })); - expect(parseOk(await conn.think(thought())).thoughtHistoryLength).toBe(1); - }); -}); diff --git a/src/sequentialthinking/__tests__/server-version.test.ts b/src/sequentialthinking/__tests__/server-version.test.ts deleted file mode 100644 index 38d832a334..0000000000 --- a/src/sequentialthinking/__tests__/server-version.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -// The server reports its version from package.json, never from a literal -// (AGENTS.md, Versions). `resolvePackageVersion()` tries the source layout and -// then the dist/ layout; these tests cover both, and the failure when neither -// yields a version, by substituting `createRequire` for that one case. The -// wire-level check (serverInfo.version) is in tools-list.test.ts and, for the -// built binary, stdio-smoke.test.ts. - -import { describe, it, expect, afterEach, vi } from "vitest"; -import { createRequire } from "node:module"; -import { existsSync } from "node:fs"; -import path from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; -import { resolvePackageVersion, SERVER_VERSION } from "../version.js"; - -const packageJson = createRequire(import.meta.url)("../package.json") as { - version: string; -}; -const packageRoot = path.join( - path.dirname(fileURLToPath(import.meta.url)), - "..", -); -const distVersionPath = path.join(packageRoot, "dist", "version.js"); - -describe("server version", () => { - afterEach(() => { - vi.doUnmock("node:module"); - vi.resetModules(); - }); - - it("uses package.json version instead of a hardcoded string", () => { - expect(SERVER_VERSION).toBe(packageJson.version); - expect(resolvePackageVersion()).toBe(packageJson.version); - expect(SERVER_VERSION).not.toBe("0.2.0"); - }); - - // CI runs `npm test` before the dedicated build job. `npm ci` usually - // materializes dist/ via prepare, but that is not guaranteed (e.g. local - // `rm -rf dist && npm test`, or install with --ignore-scripts). - it.skipIf(!existsSync(distVersionPath))( - "resolves package.json from the dist layout after build", - async () => { - const distModule = (await import( - pathToFileURL(distVersionPath).href - )) as { - SERVER_VERSION: string; - }; - expect(distModule.SERVER_VERSION).toBe(packageJson.version); - }, - ); - - it("falls through to the parent directory, then throws when no package.json has a version", async () => { - const tried: string[] = []; - vi.doMock("node:module", () => ({ - createRequire: () => (id: string) => { - tried.push(id); - if (tried.length === 1) { - throw new Error("Cannot find module"); - } - return {}; - }, - })); - vi.resetModules(); - await expect(import("../version.js")).rejects.toThrow( - "Could not locate package.json for server version", - ); - expect(tried.map((p) => path.relative(packageRoot, p))).toEqual([ - "package.json", - path.join("..", "package.json"), - ]); - }); -}); diff --git a/src/sequentialthinking/__tests__/stdio-smoke.test.ts b/src/sequentialthinking/__tests__/stdio-smoke.test.ts deleted file mode 100644 index 9c44f30335..0000000000 --- a/src/sequentialthinking/__tests__/stdio-smoke.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -// The one spawn-based test: boots the built binary (dist/index.js) over stdio -// and checks it answers, directly and through a symlink as npx runs it. Every -// behavior is tested in-process elsewhere; this only proves the published -// entry point starts and serves the same server. It needs a build, so it is -// skipped when dist/ is absent (`npm run validate` builds before testing). - -import { describe, it, expect, beforeAll, afterAll } from "vitest"; -import { createRequire } from "node:module"; -import { existsSync, mkdtempSync, rmSync, symlinkSync } from "node:fs"; -import { tmpdir } from "node:os"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; - -const packageJson = createRequire(import.meta.url)("../package.json") as { - version: string; -}; -const packageRoot = path.join( - path.dirname(fileURLToPath(import.meta.url)), - "..", -); -const distIndexPath = path.join(packageRoot, "dist", "index.js"); - -describe.skipIf(!existsSync(distIndexPath))("stdio binary smoke", () => { - let dir: string; - - beforeAll(() => { - dir = mkdtempSync(path.join(tmpdir(), "seqthink-bin-")); - }); - - afterAll(() => { - rmSync(dir, { recursive: true, force: true }); - }); - - async function smoke(script: string): Promise { - const transport = new StdioClientTransport({ - command: process.execPath, - args: [script], - cwd: packageRoot, - env: { DISABLE_THOUGHT_LOGGING: "true" }, - stderr: "pipe", - }); - const client = new Client({ name: "stdio-smoke", version: "0.0.0" }); - try { - await client.connect(transport); - expect(client.getServerVersion()).toEqual({ - name: "sequential-thinking-server", - version: packageJson.version, - }); - const { tools } = await client.listTools(); - expect(tools.map((t) => t.name)).toEqual(["sequentialthinking"]); - const result = await client.callTool({ - name: "sequentialthinking", - arguments: { - thought: "t", - nextThoughtNeeded: false, - thoughtNumber: 1, - totalThoughts: 1, - }, - }); - expect(result.structuredContent).toEqual({ - thoughtNumber: 1, - totalThoughts: 1, - nextThoughtNeeded: false, - branches: [], - thoughtHistoryLength: 1, - }); - } finally { - await client.close(); - } - } - - it("serves the tool when run directly", async () => { - await smoke(distIndexPath); - }); - - it("serves the tool when run through a symlink, as npx does", async () => { - const link = path.join(dir, "mcp-server-sequential-thinking"); - symlinkSync(distIndexPath, link); - await smoke(link); - }); -}); diff --git a/src/sequentialthinking/__tests__/thinking.test.ts b/src/sequentialthinking/__tests__/thinking.test.ts deleted file mode 100644 index e13f08e98b..0000000000 --- a/src/sequentialthinking/__tests__/thinking.test.ts +++ /dev/null @@ -1,395 +0,0 @@ -// The tool's stateful logic as a client observes it: the thought history -// count, the branch list, revisions, totalThoughts auto-adjustment and the -// state kept per server instance. Every case drives the tool through the -// in-process client and reads only the result's text and structuredContent. -// Includes the #4813 regression tests: a branchId that names an -// Object.prototype key is an ordinary branch, and a failed call leaves the -// state unchanged. - -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -import { - connect, - parseOk, - textOf, - thought, - type Connected, -} from "./helpers.js"; - -describe("sequentialthinking thought processing", () => { - let conn: Connected; - - beforeEach(async () => { - conn = await connect(); - }); - - afterEach(async () => { - await conn.close(); - }); - - describe("results", () => { - it("returns the result as pretty-printed JSON text and as structuredContent", async () => { - const result = await conn.think(thought()); - expect(result).toEqual({ - content: [ - { - type: "text", - text: JSON.stringify( - { - thoughtNumber: 1, - totalThoughts: 3, - nextThoughtNeeded: true, - branches: [], - thoughtHistoryLength: 1, - }, - null, - 2, - ), - }, - ], - structuredContent: { - thoughtNumber: 1, - totalThoughts: 3, - nextThoughtNeeded: true, - branches: [], - thoughtHistoryLength: 1, - }, - }); - }); - - it("echoes nextThoughtNeeded false", async () => { - expect( - parseOk( - await conn.think( - thought({ - thoughtNumber: 1, - totalThoughts: 1, - nextThoughtNeeded: false, - }), - ), - ), - ).toEqual({ - thoughtNumber: 1, - totalThoughts: 1, - nextThoughtNeeded: false, - branches: [], - thoughtHistoryLength: 1, - }); - }); - - it("accepts a very long thought", async () => { - expect( - parseOk(await conn.think(thought({ thought: "a".repeat(10000) }))) - .thoughtHistoryLength, - ).toBe(1); - }); - }); - - describe("history", () => { - it("counts every accepted thought", async () => { - for (let n = 1; n <= 3; n++) { - const result = parseOk(await conn.think(thought({ thoughtNumber: n }))); - expect(result.thoughtHistoryLength).toBe(n); - } - }); - - it("does not deduplicate or order thoughts: repeats and gaps all count", async () => { - await conn.think(thought({ thoughtNumber: 2 })); - await conn.think(thought({ thoughtNumber: 2 })); - const result = parseOk(await conn.think(thought({ thoughtNumber: 1 }))); - expect(result).toMatchObject({ - thoughtNumber: 1, - thoughtHistoryLength: 3, - }); - }); - - it("keeps separate history per server instance", async () => { - await conn.think(thought()); - await conn.think(thought({ thoughtNumber: 2 })); - const other = await connect(); - try { - expect(parseOk(await other.think(thought())).thoughtHistoryLength).toBe( - 1, - ); - } finally { - await other.close(); - } - }); - }); - - describe("totalThoughts adjustment", () => { - it("raises totalThoughts to thoughtNumber when it is exceeded", async () => { - expect( - parseOk( - await conn.think(thought({ thoughtNumber: 5, totalThoughts: 3 })), - ), - ).toMatchObject({ thoughtNumber: 5, totalThoughts: 5 }); - }); - - it("leaves totalThoughts alone when it is equal or larger", async () => { - expect( - parseOk( - await conn.think(thought({ thoughtNumber: 3, totalThoughts: 3 })), - ), - ).toMatchObject({ totalThoughts: 3 }); - expect( - parseOk( - await conn.think(thought({ thoughtNumber: 1, totalThoughts: 9 })), - ), - ).toMatchObject({ totalThoughts: 9 }); - }); - - it("lets a later call lower totalThoughts again (no memory of the estimate)", async () => { - await conn.think(thought({ thoughtNumber: 8, totalThoughts: 2 })); - expect( - parseOk( - await conn.think(thought({ thoughtNumber: 2, totalThoughts: 2 })), - ), - ).toMatchObject({ totalThoughts: 2 }); - }); - }); - - describe("revisions", () => { - it("records a revision as an ordinary history entry", async () => { - await conn.think(thought()); - const result = parseOk( - await conn.think( - thought({ thoughtNumber: 2, isRevision: true, revisesThought: 1 }), - ), - ); - expect(result).toEqual({ - thoughtNumber: 2, - totalThoughts: 3, - nextThoughtNeeded: true, - branches: [], - thoughtHistoryLength: 2, - }); - }); - - it("does not check that the revised thought exists", async () => { - expect( - parseOk( - await conn.think(thought({ isRevision: true, revisesThought: 99 })), - ).thoughtHistoryLength, - ).toBe(1); - }); - - it("accepts isRevision without revisesThought, and revisesThought without isRevision", async () => { - expect( - (await conn.think(thought({ isRevision: true }))).isError, - ).toBeFalsy(); - expect( - (await conn.think(thought({ revisesThought: 1 }))).isError, - ).toBeFalsy(); - }); - - it("accepts needsMoreThoughts without changing the result", async () => { - expect( - parseOk(await conn.think(thought({ needsMoreThoughts: true }))), - ).toEqual({ - thoughtNumber: 1, - totalThoughts: 3, - nextThoughtNeeded: true, - branches: [], - thoughtHistoryLength: 1, - }); - }); - }); - - describe("branches", () => { - it("records a branch when both branchFromThought and branchId are given", async () => { - await conn.think(thought()); - expect( - parseOk( - await conn.think( - thought({ - thoughtNumber: 2, - branchFromThought: 1, - branchId: "alt", - }), - ), - ), - ).toMatchObject({ branches: ["alt"], thoughtHistoryLength: 2 }); - }); - - it("lists each branch id once, in first-seen order", async () => { - await conn.think(thought({ branchFromThought: 1, branchId: "b" })); - await conn.think(thought({ branchFromThought: 1, branchId: "a" })); - const result = parseOk( - await conn.think(thought({ branchFromThought: 2, branchId: "b" })), - ); - expect(result).toMatchObject({ - branches: ["b", "a"], - thoughtHistoryLength: 3, - }); - }); - - it("ignores branchId without branchFromThought", async () => { - expect( - parseOk(await conn.think(thought({ branchId: "orphan" }))).branches, - ).toEqual([]); - }); - - it("ignores branchFromThought without branchId", async () => { - expect( - parseOk(await conn.think(thought({ branchFromThought: 1 }))).branches, - ).toEqual([]); - }); - - it("ignores an empty branchId", async () => { - expect( - parseOk( - await conn.think(thought({ branchFromThought: 1, branchId: "" })), - ).branches, - ).toEqual([]); - }); - - it("does not check that the branching point exists", async () => { - expect( - parseOk( - await conn.think(thought({ branchFromThought: 42, branchId: "far" })), - ).branches, - ).toEqual(["far"]); - }); - - it("keeps branches when a thought is both a revision and a branch", async () => { - expect( - parseOk( - await conn.think( - thought({ - isRevision: true, - revisesThought: 1, - branchFromThought: 1, - branchId: "both", - }), - ), - ).branches, - ).toEqual(["both"]); - }); - - it("accepts branch ids with spaces, unicode and punctuation", async () => { - const ids = ["with space", "ünïcödé", "a/b.c-d"]; - let last: string[] = []; - for (const id of ids) { - last = parseOk( - await conn.think(thought({ branchFromThought: 1, branchId: id })), - ).branches; - } - expect(last).toEqual(ids); - }); - }); - - // #4813: a branchId is an opaque string, so one that names an - // Object.prototype member is tracked like any other id, and a call that - // fails leaves thoughtHistory and branches as they were. - describe("#4813: branchId colliding with an Object.prototype key", () => { - it.each(["constructor", "__proto__", "toString", "hasOwnProperty"])( - "creates and lists the branch for branchId %j", - async (branchId) => { - expect( - parseOk( - await conn.think(thought({ branchFromThought: 1, branchId })), - ), - ).toEqual({ - thoughtNumber: 1, - totalThoughts: 3, - nextThoughtNeeded: true, - branches: [branchId], - thoughtHistoryLength: 1, - }); - }, - ); - - it("counts the thought once and lists the branch alongside ordinary ones", async () => { - await conn.think(thought({ branchFromThought: 1, branchId: "alt" })); - parseOk( - await conn.think( - thought({ - thoughtNumber: 2, - branchFromThought: 1, - branchId: "constructor", - }), - ), - ); - expect( - parseOk( - await conn.think( - thought({ - thoughtNumber: 3, - nextThoughtNeeded: false, - branchFromThought: 1, - branchId: "constructor", - }), - ), - ), - ).toEqual({ - thoughtNumber: 3, - totalThoughts: 3, - nextThoughtNeeded: false, - branches: ["alt", "constructor"], - thoughtHistoryLength: 3, - }); - }); - - // Drawing the box and writing it to stderr are the steps that can still - // throw; make each fail once and check nothing was recorded. - it.each([ - [ - "drawing", - () => - vi.spyOn(String.prototype, "padEnd").mockImplementationOnce(() => { - throw new Error("boom"); - }), - ], - [ - "writing", - () => - vi.spyOn(console, "error").mockImplementationOnce(() => { - throw new Error("boom"); - }), - ], - ])( - "leaves the history and branches unchanged when %s the log box fails", - async (_step, fail) => { - const logging = await connect({ disableThoughtLogging: undefined }); - vi.spyOn(console, "error").mockImplementation(() => {}); - try { - await logging.think( - thought({ branchFromThought: 1, branchId: "alt" }), - ); - fail(); - const failed = await logging.think( - thought({ - thoughtNumber: 2, - branchFromThought: 1, - branchId: "new", - }), - ); - expect(failed.isError).toBe(true); - expect(JSON.parse(textOf(failed))).toEqual({ - error: "boom", - status: "failed", - }); - expect( - parseOk(await logging.think(thought({ thoughtNumber: 3 }))), - ).toEqual({ - thoughtNumber: 3, - totalThoughts: 3, - nextThoughtNeeded: true, - branches: ["alt"], - thoughtHistoryLength: 2, - }); - } finally { - vi.restoreAllMocks(); - await logging.close(); - } - }, - ); - - it("is harmless when branchFromThought is absent, since no branch is recorded", async () => { - expect( - parseOk(await conn.think(thought({ branchId: "constructor" }))) - .branches, - ).toEqual([]); - }); - }); -}); diff --git a/src/sequentialthinking/__tests__/thought-logging.test.ts b/src/sequentialthinking/__tests__/thought-logging.test.ts deleted file mode 100644 index 1153d1fadc..0000000000 --- a/src/sequentialthinking/__tests__/thought-logging.test.ts +++ /dev/null @@ -1,180 +0,0 @@ -// The server's other observable output: unless DISABLE_THOUGHT_LOGGING is -// "true" (any case), each accepted thought is drawn as a box on stderr, with -// a header that differs for plain thoughts, revisions and branches. Thoughts -// are driven through the in-process client and stderr is captured by spying -// on console.error, which also keeps the boxes off the test output. chalk's -// colour level depends on the terminal the tests run in, so it is pinned to 0 -// (no colour) around each test; the last test turns colour on to check that -// the border width ignores the header's escape codes. - -import { - describe, - it, - expect, - beforeEach, - afterEach, - vi, - type MockInstance, -} from "vitest"; -import chalk from "chalk"; -import { connect, thought, type Connected } from "./helpers.js"; - -describe("sequentialthinking thought logging", () => { - let errorSpy: MockInstance; - let conn: Connected | undefined; - let chalkLevel: typeof chalk.level; - - beforeEach(() => { - errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - chalkLevel = chalk.level; - chalk.level = 0; - }); - - afterEach(async () => { - await conn?.close(); - conn = undefined; - chalk.level = chalkLevel; - errorSpy.mockRestore(); - }); - - function logged(): string[] { - return errorSpy.mock.calls.map((args) => String(args[0])); - } - - /** The box the server draws, for an uncoloured header. */ - function box(header: string, text: string): string { - const border = "─".repeat(Math.max(header.length, text.length) + 4); - return ` -┌${border}┐ -│ ${header} │ -├${border}┤ -│ ${text.padEnd(border.length - 2)} │ -└${border}┘`; - } - - describe.each([ - ["unset", undefined], - ['"false"', "false"], - ['"1"', "1"], - ['""', ""], - ])("with DISABLE_THOUGHT_LOGGING %s", (_label, value) => { - it("logs each accepted thought", async () => { - conn = await connect({ disableThoughtLogging: value }); - await conn.think(thought()); - expect(logged()).toHaveLength(1); - }); - }); - - it.each(["true", "TRUE", "True"])( - "logs nothing with DISABLE_THOUGHT_LOGGING %j", - async (value) => { - conn = await connect({ disableThoughtLogging: value }); - await conn.think(thought()); - expect(logged()).toEqual([]); - }, - ); - - describe("with logging on", () => { - beforeEach(async () => { - conn = await connect({ disableThoughtLogging: undefined }); - }); - - it("draws a plain thought", async () => { - await conn!.think(thought({ thought: "first", thoughtNumber: 1 })); - expect(logged()).toEqual([box("💭 Thought 1/3", "first")]); - }); - - it("draws a revision, naming the revised thought", async () => { - await conn!.think( - thought({ - thought: "again", - thoughtNumber: 2, - isRevision: true, - revisesThought: 1, - }), - ); - expect(logged()).toEqual([ - box("🔄 Revision 2/3 (revising thought 1)", "again"), - ]); - }); - - // #5000: with no revisesThought, the "revising thought N" suffix is left out. - it("draws a plain revision header when no revisesThought is given", async () => { - await conn!.think(thought({ thought: "x", isRevision: true })); - expect(logged()).toEqual([box("🔄 Revision 1/3", "x")]); - expect(logged()[0]).not.toContain("undefined"); - }); - - it("draws a branch, naming its origin and id", async () => { - await conn!.think( - thought({ thought: "fork", branchFromThought: 1, branchId: "alt" }), - ); - expect(logged()).toEqual([ - box("🌿 Branch 1/3 (from thought 1, ID: alt)", "fork"), - ]); - }); - - // #5000: with no branchId, the Branch header names the origin and omits the ID. - it("omits the ID from a branch header when no branchId is given", async () => { - await conn!.think(thought({ thought: "fork", branchFromThought: 2 })); - expect(logged()).toEqual([box("🌿 Branch 1/3 (from thought 2)", "fork")]); - expect(logged()[0]).not.toContain("undefined"); - }); - - it("prefers the revision header when a thought is both", async () => { - await conn!.think( - thought({ - thought: "both", - isRevision: true, - revisesThought: 1, - branchFromThought: 1, - branchId: "b", - }), - ); - expect(logged()).toEqual([ - box("🔄 Revision 1/3 (revising thought 1)", "both"), - ]); - }); - - it("draws the adjusted totalThoughts", async () => { - await conn!.think( - thought({ thought: "t", thoughtNumber: 4, totalThoughts: 2 }), - ); - expect(logged()).toEqual([box("💭 Thought 4/4", "t")]); - }); - - it("widens the box to fit a thought longer than the header", async () => { - const text = "a much longer thought than the header line is"; - await conn!.think(thought({ thought: text })); - expect(logged()).toEqual([box("💭 Thought 1/3", text)]); - }); - - it("logs nothing for a call rejected by the schema", async () => { - await conn!.think(thought({ nextThoughtNeeded: "yes" })); - expect(logged()).toEqual([]); - }); - - // #4813: a branchId that names an Object.prototype key is an ordinary branch. - it("draws the Branch box for a branchId that names an Object.prototype key", async () => { - await conn!.think( - thought({ branchFromThought: 1, branchId: "constructor" }), - ); - expect(logged()).toEqual([ - box("🌿 Branch 1/3 (from thought 1, ID: constructor)", "a thought"), - ]); - }); - }); - - // #5000: when colour is on, the header carries chalk's escape codes, but the - // border is sized from the visible text, so the box matches the uncoloured one. - it("sizes the border from the visible header, not its escape codes", async () => { - chalk.level = 1; - conn = await connect({ disableThoughtLogging: undefined }); - await conn.think(thought({ thought: "t" })); - const raw = String(errorSpy.mock.calls[0][0]); - const visible = "💭 Thought 1/3"; - const coloured = `${chalk.blue("💭 Thought")} 1/3`; - expect(coloured.length).toBeGreaterThan(visible.length); - expect(raw).toBe(box(visible, "t").replace(visible, coloured)); - }); -}); diff --git a/src/sequentialthinking/__tests__/tools-list.test.ts b/src/sequentialthinking/__tests__/tools-list.test.ts deleted file mode 100644 index d1a9380dba..0000000000 --- a/src/sequentialthinking/__tests__/tools-list.test.ts +++ /dev/null @@ -1,185 +0,0 @@ -// Pins what a client sees when it connects and lists tools: the server's -// identity and capabilities, and the one tool's name, title, description, -// annotations, input schema and output schema, exactly as SDK 1.x emits them -// today. These are characterization tests. The annotations assert the values -// fixed in #4721 (the tool is stateful, so neither read-only nor idempotent). -// The 2781-character description (#799) is pinned as current design: the -// 1024 cap is an OpenAI/Azure client limit, not the MCP spec. - -import { describe, it, expect, beforeAll, afterAll } from "vitest"; -import { createRequire } from "node:module"; -import { connect, TOOL_NAME, type Connected } from "./helpers.js"; - -const packageJson = createRequire(import.meta.url)("../package.json") as { - version: string; -}; - -type ListedTool = Awaited< - ReturnType ->["tools"][number]; - -describe("sequentialthinking: initialize and tools/list", () => { - let conn: Connected; - let tool: ListedTool; - - beforeAll(async () => { - conn = await connect(); - const { tools } = await conn.client.listTools(); - expect(tools).toHaveLength(1); - tool = tools[0]; - }); - - afterAll(async () => { - await conn.close(); - }); - - it("reports its name and the package.json version in serverInfo", () => { - expect(conn.client.getServerVersion()).toEqual({ - name: "sequential-thinking-server", - version: packageJson.version, - }); - }); - - it("advertises only the tools capability, with listChanged", () => { - expect(conn.client.getServerCapabilities()).toEqual({ - tools: { listChanged: true }, - }); - }); - - it("lists exactly one tool, named sequentialthinking, titled Sequential Thinking", () => { - expect(tool.name).toBe(TOOL_NAME); - expect(tool.title).toBe("Sequential Thinking"); - }); - - // #4721: every call appends to the server's thought history, and a call with - // both branchFromThought and branchId also appends to that branch, so the - // tool is neither read-only nor idempotent. It is still not destructive (it - // only appends) and not open-world. - it("advertises the tool as stateful: not read-only, not idempotent, not destructive, closed-world", () => { - expect(tool.annotations).toEqual({ - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: false, - }); - }); - - it("forbids task-augmented execution", () => { - expect(tool.execution).toEqual({ taskSupport: "forbidden" }); - }); - - // #799: some clients cap a tool description at 1024 characters. This one is - // 2781 today; asserting <= 1024 would fail, so the current length is pinned. - it("has a 2781-character description today (#799)", () => { - expect(tool.description).toHaveLength(2781); - expect(tool.description).toMatch( - /^A detailed tool for dynamic and reflective problem-solving through thoughts\.\n/, - ); - expect(tool.description).toMatch( - /11\. Only set nextThoughtNeeded to false when truly done and a satisfactory answer is reached$/, - ); - }); - - it("advertises the input schema as draft-07 JSON Schema", () => { - expect(tool.inputSchema).toEqual({ - $schema: "http://json-schema.org/draft-07/schema#", - type: "object", - properties: { - thought: { type: "string", description: "Your current thinking step" }, - nextThoughtNeeded: { - description: "Whether another thought step is needed", - anyOf: [{ type: "boolean" }, { type: "string" }], - }, - thoughtNumber: { - type: "integer", - minimum: 1, - maximum: Number.MAX_SAFE_INTEGER, - description: "Current thought number (numeric value, e.g., 1, 2, 3)", - }, - totalThoughts: { - type: "integer", - minimum: 1, - maximum: Number.MAX_SAFE_INTEGER, - description: - "Estimated total thoughts needed (numeric value, e.g., 5, 10)", - }, - isRevision: { - description: "Whether this revises previous thinking", - anyOf: [{ type: "boolean" }, { type: "string" }], - }, - revisesThought: { - description: "Which thought is being reconsidered", - type: "integer", - minimum: 1, - maximum: Number.MAX_SAFE_INTEGER, - }, - branchFromThought: { - description: "Branching point thought number", - type: "integer", - minimum: 1, - maximum: Number.MAX_SAFE_INTEGER, - }, - branchId: { description: "Branch identifier", type: "string" }, - needsMoreThoughts: { - description: "If more thoughts are needed", - anyOf: [{ type: "boolean" }, { type: "string" }], - }, - }, - required: [ - "thought", - "nextThoughtNeeded", - "thoughtNumber", - "totalThoughts", - ], - }); - }); - - it("advertises a closed output schema with all five fields required", () => { - expect(tool.outputSchema).toEqual({ - $schema: "http://json-schema.org/draft-07/schema#", - type: "object", - properties: { - thoughtNumber: { type: "number" }, - totalThoughts: { type: "number" }, - nextThoughtNeeded: { type: "boolean" }, - branches: { type: "array", items: { type: "string" } }, - thoughtHistoryLength: { type: "number" }, - }, - required: [ - "thoughtNumber", - "totalThoughts", - "nextThoughtNeeded", - "branches", - "thoughtHistoryLength", - ], - additionalProperties: false, - }); - }); - - it("answers ping", async () => { - await expect(conn.client.ping()).resolves.toEqual({}); - }); - - it("offers no prompts or resources", async () => { - await expect(conn.client.listPrompts()).rejects.toThrow(/Method not found/); - await expect(conn.client.listResources()).rejects.toThrow( - /Method not found/, - ); - }); - - it("reports an unknown tool as a tool error", async () => { - const result = await conn.client.callTool({ - name: "no_such_tool", - arguments: {}, - }); - expect(result).toEqual({ - content: [ - { - type: "text", - text: "MCP error -32602: Tool no_such_tool not found", - }, - ], - isError: true, - }); - }); -}); diff --git a/src/sequentialthinking/index.ts b/src/sequentialthinking/index.ts deleted file mode 100644 index 5bc02e4602..0000000000 --- a/src/sequentialthinking/index.ts +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/env node - -// Entry point of the sequential-thinking server. `createServer()` builds a -// fully registered server without connecting it, so tests can link it to an -// SDK Client over an in-memory transport (#4854); `main()` connects a given -// transport; and stdio is only attached when this file is run as the binary, -// so importing it from a test never touches the runner's stdin/stdout. - -import { realpathSync } from "node:fs"; -import { fileURLToPath } from "node:url"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; -import { z } from "zod"; -import { SequentialThinkingServer } from "./lib.js"; -import { SERVER_VERSION } from "./version.js"; - -/** Safe boolean coercion that correctly handles string "false". A union+transform, - * not z.preprocess (whose input type is `unknown`), so toJSONSchema keeps this required. */ -const coercedBoolean = z - .union([z.boolean(), z.string()]) - .transform((val, ctx) => { - if (typeof val === "boolean") return val; - if (val.toLowerCase() === "true") return true; - if (val.toLowerCase() === "false") return false; - ctx.addIssue({ - code: "custom", - message: `Expected boolean or "true"/"false" string, received "${val}"`, - }); - return z.NEVER; - }); - -/** - * Build the sequential-thinking server with its one tool registered. Each call - * returns an independent server with its own thought history and branches. - */ -export function createServer(): McpServer { - const server = new McpServer({ - name: "sequential-thinking-server", - version: SERVER_VERSION, - }); - - const thinkingServer = new SequentialThinkingServer(); - - server.registerTool( - "sequentialthinking", - { - title: "Sequential Thinking", - description: `A detailed tool for dynamic and reflective problem-solving through thoughts. -This tool helps analyze problems through a flexible thinking process that can adapt and evolve. -Each thought can build on, question, or revise previous insights as understanding deepens. - -When to use this tool: -- Breaking down complex problems into steps -- Planning and design with room for revision -- Analysis that might need course correction -- Problems where the full scope might not be clear initially -- Problems that require a multi-step solution -- Tasks that need to maintain context over multiple steps -- Situations where irrelevant information needs to be filtered out - -Key features: -- You can adjust total_thoughts up or down as you progress -- You can question or revise previous thoughts -- You can add more thoughts even after reaching what seemed like the end -- You can express uncertainty and explore alternative approaches -- Not every thought needs to build linearly - you can branch or backtrack -- Generates a solution hypothesis -- Verifies the hypothesis based on the Chain of Thought steps -- Repeats the process until satisfied -- Provides a correct answer - -Parameters explained: -- thought: Your current thinking step, which can include: - * Regular analytical steps - * Revisions of previous thoughts - * Questions about previous decisions - * Realizations about needing more analysis - * Changes in approach - * Hypothesis generation - * Hypothesis verification -- nextThoughtNeeded: True if you need more thinking, even if at what seemed like the end -- thoughtNumber: Current number in sequence (can go beyond initial total if needed) -- totalThoughts: Current estimate of thoughts needed (can be adjusted up/down) -- isRevision: A boolean indicating if this thought revises previous thinking -- revisesThought: If is_revision is true, which thought number is being reconsidered -- branchFromThought: If branching, which thought number is the branching point -- branchId: Identifier for the current branch (if any) -- needsMoreThoughts: If reaching end but realizing more thoughts needed - -You should: -1. Start with an initial estimate of needed thoughts, but be ready to adjust -2. Feel free to question or revise previous thoughts -3. Don't hesitate to add more thoughts if needed, even at the "end" -4. Express uncertainty when present -5. Mark thoughts that revise previous thinking or branch into new paths -6. Ignore information that is irrelevant to the current step -7. Generate a solution hypothesis when appropriate -8. Verify the hypothesis based on the Chain of Thought steps -9. Repeat the process until satisfied with the solution -10. Provide a single, ideally correct answer as the final output -11. Only set nextThoughtNeeded to false when truly done and a satisfactory answer is reached`, - inputSchema: { - thought: z.string().describe("Your current thinking step"), - nextThoughtNeeded: coercedBoolean.describe( - "Whether another thought step is needed", - ), - thoughtNumber: z.coerce - .number() - .int() - .min(1) - .describe("Current thought number (numeric value, e.g., 1, 2, 3)"), - totalThoughts: z.coerce - .number() - .int() - .min(1) - .describe( - "Estimated total thoughts needed (numeric value, e.g., 5, 10)", - ), - isRevision: coercedBoolean - .optional() - .describe("Whether this revises previous thinking"), - revisesThought: z.coerce - .number() - .int() - .min(1) - .optional() - .describe("Which thought is being reconsidered"), - branchFromThought: z.coerce - .number() - .int() - .min(1) - .optional() - .describe("Branching point thought number"), - branchId: z.string().optional().describe("Branch identifier"), - needsMoreThoughts: coercedBoolean - .optional() - .describe("If more thoughts are needed"), - }, - annotations: { - // Every call appends to the server's in-memory thought history, and a - // call with both branchFromThought and branchId also appends to that - // branch, so the tool is neither read-only nor idempotent (#4721). - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: false, - }, - outputSchema: { - thoughtNumber: z.number(), - totalThoughts: z.number(), - nextThoughtNeeded: z.boolean(), - branches: z.array(z.string()), - thoughtHistoryLength: z.number(), - }, - }, - async (args) => { - const result = thinkingServer.processThought(args); - - if (result.isError) { - return result; - } - - // Parse the JSON response to get structured content - const parsedContent = JSON.parse(result.content[0].text); - - return { - content: result.content, - structuredContent: parsedContent, - }; - }, - ); - - return server; -} - -/** Connect a new server to `transport` and announce it on stderr. */ -export async function main(transport: Transport): Promise { - const server = createServer(); - await server.connect(transport); - console.error("Sequential Thinking MCP Server running on stdio"); -} - -/** - * True when `argv1` (the script node was started with) is this module. Both - * sides go through realpath, so the bin still starts when it is reached - * through a symlink, as `npx` and `node_modules/.bin` do. - */ -export function isEntryPoint( - moduleUrl: string, - argv1: string | undefined, -): boolean { - if (!argv1) { - return false; - } - try { - return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl)); - } catch { - return false; - } -} - -/* v8 ignore start -- runs only when this file is the process entry point (the spawned bin), which stdio-smoke.test.ts exercises; v8 cannot see into that child */ -if (isEntryPoint(import.meta.url, process.argv[1])) { - main(new StdioServerTransport()).catch((error) => { - console.error("Fatal error running server:", error); - process.exit(1); - }); -} -/* v8 ignore stop */ diff --git a/src/sequentialthinking/lib.ts b/src/sequentialthinking/lib.ts deleted file mode 100644 index cffc8210c0..0000000000 --- a/src/sequentialthinking/lib.ts +++ /dev/null @@ -1,144 +0,0 @@ -import chalk from "chalk"; - -export interface ThoughtData { - thought: string; - thoughtNumber: number; - totalThoughts: number; - isRevision?: boolean; - revisesThought?: number; - branchFromThought?: number; - branchId?: string; - needsMoreThoughts?: boolean; - nextThoughtNeeded: boolean; -} - -export class SequentialThinkingServer { - private thoughtHistory: ThoughtData[] = []; - // Null prototype, so any string branchId (even "constructor" or "__proto__") - // is an ordinary own key rather than an inherited Object.prototype member. - private branches: Record = Object.create(null); - private disableThoughtLogging: boolean; - - constructor() { - this.disableThoughtLogging = - (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true"; - } - - private formatThought(thoughtData: ThoughtData): string { - const { - thoughtNumber, - totalThoughts, - thought, - isRevision, - revisesThought, - branchFromThought, - branchId, - } = thoughtData; - - // The label is coloured only after the header is measured, so the border - // is sized from the visible text, not from chalk's escape codes. A missing - // revisesThought or branchId is left out of the header rather than printed - // as "undefined". - let label: string; - let colour: (text: string) => string; - let context = ""; - - if (isRevision) { - label = "🔄 Revision"; - colour = chalk.yellow; - if (revisesThought !== undefined) { - context = ` (revising thought ${revisesThought})`; - } - } else if (branchFromThought) { - label = "🌿 Branch"; - colour = chalk.green; - context = - branchId !== undefined - ? ` (from thought ${branchFromThought}, ID: ${branchId})` - : ` (from thought ${branchFromThought})`; - } else { - label = "💭 Thought"; - colour = chalk.blue; - } - - const counts = ` ${thoughtNumber}/${totalThoughts}${context}`; - const visibleHeader = `${label}${counts}`; - const header = `${colour(label)}${counts}`; - const border = "─".repeat( - Math.max(visibleHeader.length, thought.length) + 4, - ); - - return ` -┌${border}┐ -│ ${header} │ -├${border}┤ -│ ${thought.padEnd(border.length - 2)} │ -└${border}┘`; - } - - public processThought(input: ThoughtData): { - content: Array<{ type: "text"; text: string }>; - isError?: boolean; - } { - try { - // Validation happens at the tool registration layer via Zod - // Adjust totalThoughts if thoughtNumber exceeds it - if (input.thoughtNumber > input.totalThoughts) { - input.totalThoughts = input.thoughtNumber; - } - - // Log before touching any state: drawing and writing the box are the - // steps that can throw, so a failed call leaves thoughtHistory and - // branches as they were. - if (!this.disableThoughtLogging) { - const formattedThought = this.formatThought(input); - console.error(formattedThought); - } - - this.thoughtHistory.push(input); - - if (input.branchFromThought && input.branchId) { - if (!this.branches[input.branchId]) { - this.branches[input.branchId] = []; - } - this.branches[input.branchId].push(input); - } - - return { - content: [ - { - type: "text" as const, - text: JSON.stringify( - { - thoughtNumber: input.thoughtNumber, - totalThoughts: input.totalThoughts, - nextThoughtNeeded: input.nextThoughtNeeded, - branches: Object.keys(this.branches), - thoughtHistoryLength: this.thoughtHistory.length, - }, - null, - 2, - ), - }, - ], - }; - } catch (error) { - return { - content: [ - { - type: "text" as const, - text: JSON.stringify( - { - error: error instanceof Error ? error.message : String(error), - status: "failed", - }, - null, - 2, - ), - }, - ], - isError: true, - }; - } - } -} diff --git a/src/sequentialthinking/package.json b/src/sequentialthinking/package.json deleted file mode 100644 index e3b1b9c790..0000000000 --- a/src/sequentialthinking/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "@modelcontextprotocol/server-sequential-thinking", - "version": "1.0.0", - "description": "MCP server for sequential thinking and problem solving", - "license": "SEE LICENSE IN LICENSE", - "mcpName": "io.github.modelcontextprotocol/server-sequential-thinking", - "author": "Model Context Protocol a Series of LF Projects, LLC.", - "homepage": "https://modelcontextprotocol.io", - "bugs": "https://github.com/modelcontextprotocol/servers/issues", - "repository": { - "type": "git", - "url": "https://github.com/modelcontextprotocol/servers.git" - }, - "type": "module", - "bin": { - "mcp-server-sequential-thinking": "dist/index.js" - }, - "files": [ - "dist" - ], - "scripts": { - "build": "tsc && shx chmod +x dist/*.js", - "prepare": "npm run build", - "watch": "tsc --watch", - "test": "vitest run", - "format": "prettier --write \"**/*.{ts,mts,cts,js,mjs,cjs}\" --ignore-path ../../.prettierignore", - "format:check": "prettier --check \"**/*.{ts,mts,cts,js,mjs,cjs}\" --ignore-path ../../.prettierignore", - "lint": "eslint . --max-warnings 0", - "typecheck": "tsc -p tsconfig.test.json", - "coverage": "vitest run --coverage", - "validate": "npm run format:check && npm run lint && npm run typecheck && npm run build && npm run test" - }, - "dependencies": { - "@modelcontextprotocol/sdk": "^1.31.0", - "chalk": "^5.3.0", - "yargs": "^17.7.2", - "zod": "^4.0.0" - }, - "devDependencies": { - "@types/node": "^22", - "@types/yargs": "^17.0.32", - "@vitest/coverage-v8": "^4.1.11", - "shx": "^0.4.0", - "typescript": "^5.8.2", - "vitest": "^4.1.11" - } -} diff --git a/src/sequentialthinking/tsconfig.json b/src/sequentialthinking/tsconfig.json deleted file mode 100644 index 2e30686734..0000000000 --- a/src/sequentialthinking/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist", - "rootDir": "." - }, - "include": [ - "./**/*.ts" - ], - "exclude": [ - "**/__tests__/**", - "**/*.test.ts", - "vitest.config.ts" - ] -} diff --git a/src/sequentialthinking/tsconfig.test.json b/src/sequentialthinking/tsconfig.test.json deleted file mode 100644 index 1876ccdb6f..0000000000 --- a/src/sequentialthinking/tsconfig.test.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - // The typecheck program for the whole workspace (#4864). `tsconfig.json` is - // the build config and leaves the tests out, so tsc does not emit them into - // `dist/`; this one adds them back without emitting anything. `npm run - // typecheck` runs it, and it is the parser project the root ESLint config - // uses, so what is linted and what is typechecked are the same file set. - "extends": "./tsconfig.json", - "compilerOptions": { - "noEmit": true - }, - "include": ["./**/*.ts"], - "exclude": ["node_modules", "dist", "coverage"] -} diff --git a/src/sequentialthinking/version.ts b/src/sequentialthinking/version.ts deleted file mode 100644 index 4f7a2338eb..0000000000 --- a/src/sequentialthinking/version.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { createRequire } from "node:module"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -/** - * Resolve this package's version from package.json. - * - * Works both from source (`src/sequentialthinking/`) and from the published - * layout (`dist/`), where package.json lives one directory up. - */ -export function resolvePackageVersion(): string { - const require = createRequire(import.meta.url); - const moduleDir = path.dirname(fileURLToPath(import.meta.url)); - const candidates = [ - path.join(moduleDir, "package.json"), - path.join(moduleDir, "..", "package.json"), - ]; - - for (const candidate of candidates) { - try { - const pkg = require(candidate) as { version?: string }; - if (pkg.version) { - return pkg.version; - } - } catch { - // Try the next candidate when running from dist/ or source. - } - } - - throw new Error("Could not locate package.json for server version"); -} - -export const SERVER_VERSION = resolvePackageVersion(); diff --git a/src/sequentialthinking/vitest.config.ts b/src/sequentialthinking/vitest.config.ts deleted file mode 100644 index a0211ed415..0000000000 --- a/src/sequentialthinking/vitest.config.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - globals: true, - environment: "node", - include: ["**/__tests__/**/*.test.ts"], - coverage: { - provider: "v8", - include: ["**/*.ts"], - exclude: ["**/__tests__/**", "**/dist/**"], - // The #4854 gate: every file at >= 90 on all four dimensions. Code that - // genuinely cannot run in-process carries a reasoned `v8 ignore` at the - // source instead of a lower threshold here. - thresholds: { - perFile: true, - lines: 90, - statements: 90, - functions: 90, - branches: 90, - }, - }, - }, -});