Skip to content

Latest commit

 

History

History
714 lines (602 loc) · 20.9 KB

File metadata and controls

714 lines (602 loc) · 20.9 KB

MCP Server Package Refactoring Plan

Goal: Split mcp-server into two packages:

  • mcp-core: Private workspace package with shared code (tools, API client, server builder)
  • mcp-stdio: Published package (keeping name @sentry/mcp-server) that fully bundles all core code

Status: Planning phase - not yet implemented


Key Decisions

Package Names

  • Core: @sentry/mcp-core (private, not published to npm)
  • Stdio: @sentry/mcp-server (keep existing name for no breaking changes)

Build Strategy

  • Approach: DevDependency + Full Bundling
  • mcp-stdio: Uses @sentry/mcp-core as devDependency, bundles everything at build time
  • mcp-cloudflare: Uses @sentry/mcp-core as workspace dependency (private package, not published)
  • Publishing: mcp-core never published, only mcp-stdio published to npm

Test Strategy

  • Tests move with their code
  • CLI/stdio tests → mcp-stdio package
  • Core tests stay in mcp-core package

Architecture Overview

Current State

packages/
├── mcp-server/           # Contains everything (tools, API, CLI, stdio transport)
├── mcp-cloudflare/       # Imports from @sentry/mcp-server
├── mcp-server-mocks/
├── mcp-server-evals/
└── mcp-test-client/

Target State

packages/
├── mcp-core/             # Shared code (private package)
│   ├── src/
│   │   ├── api-client/   # Sentry API client
│   │   ├── tools/        # 19 MCP tools
│   │   ├── internal/     # Utilities, agents, helpers
│   │   ├── telem/        # Logging, Sentry integration
│   │   ├── utils/        # Shared utilities
│   │   ├── test-utils/   # Test helpers
│   │   ├── server.ts     # buildServer() function
│   │   ├── types.ts      # ServerContext, etc.
│   │   ├── permissions.ts
│   │   ├── skills.ts
│   │   ├── schema.ts
│   │   ├── errors.ts
│   │   ├── constants.ts
│   │   └── version.ts
│   └── scripts/          # Build scripts
│
├── mcp-stdio/            # CLI/stdio package (published)
│   ├── src/
│   │   ├── cli/          # MOVED from mcp-server
│   │   │   ├── parse.ts
│   │   │   ├── resolve.ts
│   │   │   ├── types.ts
│   │   │   └── usage.ts
│   │   ├── transports/   # MOVED from mcp-server
│   │   │   └── stdio.ts
│   │   └── index.ts      # MOVED from mcp-server (CLI entry)
│   └── package.json      # Bundles all of mcp-core
│
├── mcp-cloudflare/       # Uses workspace dep on mcp-core
├── mcp-server-mocks/
├── mcp-server-evals/
└── mcp-test-client/

Detailed File Movement

Files Moving to mcp-stdio

From packages/mcp-server/src/:

src/cli/parse.ts              → mcp-stdio/src/cli/parse.ts
src/cli/parse.test.ts         → mcp-stdio/src/cli/parse.test.ts
src/cli/resolve.ts            → mcp-stdio/src/cli/resolve.ts
src/cli/resolve.test.ts       → mcp-stdio/src/cli/resolve.test.ts
src/cli/types.ts              → mcp-stdio/src/cli/types.ts
src/cli/usage.ts              → mcp-stdio/src/cli/usage.ts
src/transports/stdio.ts       → mcp-stdio/src/transports/stdio.ts
src/index.ts                  → mcp-stdio/src/index.ts

Files Staying in mcp-core

Everything else in packages/mcp-server/ stays, package renamed to mcp-core:

src/api-client/               ✅ STAY - shared API client
src/tools/                    ✅ STAY - all 19 tools
src/internal/                 ✅ STAY - utilities, agents
src/telem/                    ✅ STAY - logging, Sentry
src/utils/                    ✅ STAY - shared utilities
src/test-utils/               ✅ STAY - test helpers
src/server.ts                 ✅ STAY - buildServer()
src/types.ts                  ✅ STAY - ServerContext
src/permissions.ts            ✅ STAY - shared
src/skills.ts                 ✅ STAY - shared
src/schema.ts                 ✅ STAY - shared schemas
src/errors.ts                 ✅ STAY - error classes
src/constants.ts              ✅ STAY - shared constants
src/version.ts                ✅ STAY - version info
scripts/                      ✅ STAY - build scripts
toolDefinitions.json          ✅ STAY - generated file
skillDefinitions.json         ✅ STAY - generated file

Package Configuration

packages/mcp-core/package.json

{
  "name": "@sentry/mcp-core",
  "version": "0.22.0",
  "type": "module",
  "private": true,
  "exports": {
    ".": "./dist/index.js",
    "./api-client": "./dist/api-client/index.js",
    "./api-client/client": "./dist/api-client/client.js",
    "./api-client/errors": "./dist/api-client/errors.js",
    "./api-client/schema": "./dist/api-client/schema.js",
    "./api-client/types": "./dist/api-client/types.js",
    "./server": "./dist/server.js",
    "./types": "./dist/types.js",
    "./tools": "./dist/tools/index.js",
    "./tools/types": "./dist/tools/types.js",
    "./permissions": "./dist/permissions.js",
    "./skills": "./dist/skills.js",
    "./schema": "./dist/schema.js",
    "./errors": "./dist/errors.js",
    "./constants": "./dist/constants.js",
    "./version": "./dist/version.js",
    "./telem/logging": "./dist/telem/logging.js",
    "./telem/sentry": "./dist/telem/sentry.js",
    "./toolDefinitions": "./dist/toolDefinitions.js",
    "./skillDefinitions": "./dist/skillDefinitions.js",
    "./internal/tool-helpers/api-utils": "./dist/internal/tool-helpers/api-utils.js",
    "./internal/error-handling": "./dist/internal/error-handling.js"
  },
  "files": ["./dist/*"],
  "scripts": {
    "build": "pnpm run generate-definitions && tsdown",
    "generate-definitions": "tsx scripts/generate-definitions.ts",
    "generate-otel-namespaces": "tsx scripts/generate-otel-namespaces.ts",
    "measure-tokens": "tsx scripts/measure-token-cost.ts",
    "validate-skills": "tsx scripts/validate-skills-mapping.ts"
  },
  "dependencies": {
    "@ai-sdk/openai": "catalog:",
    "@logtape/logtape": "catalog:",
    "@logtape/sentry": "catalog:",
    "@modelcontextprotocol/sdk": "catalog:",
    "@sentry/core": "catalog:",
    "ai": "catalog:",
    "dotenv": "catalog:",
    "zod": "catalog:",
    "zod-to-json-schema": "catalog:"
  },
  "devDependencies": {
    "@sentry/mcp-server-mocks": "workspace:*",
    "@sentry/mcp-server-tsconfig": "workspace:*",
    "@types/node": "catalog:",
    "tsdown": "catalog:",
    "tsx": "catalog:",
    "typescript": "catalog:",
    "vitest": "catalog:"
  }
}

Key changes from current mcp-server:

  • Added "private": true
  • Removed "bin" field (not an executable)
  • Removed stdio-specific exports
  • Kept all other exports intact

packages/mcp-stdio/package.json

{
  "name": "@sentry/mcp-server",
  "version": "0.22.0",
  "description": "Sentry MCP server for stdio transport",
  "type": "module",
  "bin": {
    "sentry-mcp": "./dist/index.js"
  },
  "files": ["./dist/*"],
  "scripts": {
    "build": "tsdown",
    "dev": "tsdown --watch",
    "start": "tsx src/index.ts"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "catalog:",
    "@sentry/node": "catalog:",
    "@sentry/core": "catalog:",
    "dotenv": "catalog:",
    "zod": "catalog:"
  },
  "devDependencies": {
    "@sentry/mcp-core": "workspace:*",
    "@sentry/mcp-server-mocks": "workspace:*",
    "@sentry/mcp-server-tsconfig": "workspace:*",
    "@types/node": "catalog:",
    "tsdown": "catalog:",
    "tsx": "catalog:",
    "typescript": "catalog:",
    "vitest": "catalog:"
  }
}

Key points:

  • Keeps existing name @sentry/mcp-server (no breaking changes)
  • Has bin field for CLI execution
  • mcp-core is devDependency (build-time only)
  • Dependencies are minimal runtime-only deps
  • tsdown will bundle all mcp-core code

packages/mcp-stdio/tsdown.config.ts

import { defineConfig } from "tsdown";

export default defineConfig({
  entry: ["src/**/*.ts", "!src/**/*.test.ts"],
  format: ["cjs", "esm"],
  dts: true,
  sourcemap: true,
  clean: true,
  external: [
    // Only mark test-only packages as external
    "@sentry/mcp-server-mocks",
    // Everything else (including @sentry/mcp-core) will be bundled
  ],
  env: {
    SENTRY_ENVIRONMENT: "stdio",
    npm_package_version: "{{version}}"
  }
});

Critical configuration:

  • Does NOT list @sentry/mcp-core in external
  • This means tsdown will bundle all mcp-core code into dist/
  • Only mocks are external (for testing purposes)

packages/mcp-cloudflare/package.json

{
  "name": "@sentry/mcp-cloudflare",
  "private": true,
  "dependencies": {
    "@sentry/mcp-core": "workspace:*"
  }
}

Changes:

  • Replace @sentry/mcp-server@sentry/mcp-core
  • Package stays private, so workspace dependency is fine

Import Changes

In mcp-stdio Files

All moved files need import updates:

mcp-stdio/src/index.ts

// BEFORE (relative imports)
import { buildServer } from "./server";
import { startStdio } from "./transports/stdio";
import type { ServerContext } from "./types";

// AFTER (package imports)
import { buildServer } from "@sentry/mcp-core/server";
import { startStdio } from "./transports/stdio";
import type { ServerContext } from "@sentry/mcp-core/types";

mcp-stdio/src/transports/stdio.ts

// BEFORE
import { LIB_VERSION } from "../version";
import type { ServerContext } from "../types";

// AFTER
import { LIB_VERSION } from "@sentry/mcp-core/version";
import type { ServerContext } from "@sentry/mcp-core/types";

mcp-stdio/src/cli/resolve.ts

// BEFORE
import { ALL_SCOPES, parseScopes, expandScopes } from "../permissions";
import { parseSkills, getScopesForSkills } from "../skills";
import { DEFAULT_SCOPES, DEFAULT_SKILLS } from "../constants";
import { UserInputError } from "../errors";

// AFTER
import { ALL_SCOPES, parseScopes, expandScopes } from "@sentry/mcp-core/permissions";
import { parseSkills, getScopesForSkills } from "@sentry/mcp-core/skills";
import { DEFAULT_SCOPES, DEFAULT_SKILLS } from "@sentry/mcp-core/constants";
import { UserInputError } from "@sentry/mcp-core/errors";

In mcp-cloudflare Files

Update all imports from @sentry/mcp-server@sentry/mcp-core:

// BEFORE
import { buildServer } from "@sentry/mcp-server/server";
import type { ServerContext } from "@sentry/mcp-server/types";
import { parseScopes, expandScopes } from "@sentry/mcp-server/permissions";

// AFTER
import { buildServer } from "@sentry/mcp-core/server";
import type { ServerContext } from "@sentry/mcp-core/types";
import { parseScopes, expandScopes } from "@sentry/mcp-core/permissions";

Files to update in mcp-cloudflare:

  • src/server/index.ts
  • src/server/auth/verify.ts
  • src/server/routes/mcp.ts
  • src/server/sentry.config.ts
  • Any other files importing from mcp-server

Implementation Steps

Phase 1: Rename Core Package

  1. Rename directory: packages/mcp-server/packages/mcp-core/
  2. Update package.json:
    • Change name to @sentry/mcp-core
    • Add "private": true
    • Remove bin field
  3. Remove stdio-specific exports from exports field
  4. Test build: cd packages/mcp-core && pnpm build

Phase 2: Create Stdio Package

  1. Create directory: packages/mcp-stdio/
  2. Create initial structure:
    mkdir -p packages/mcp-stdio/src/{cli,transports}
  3. Move files from mcp-core to mcp-stdio:
    • src/cli/*mcp-stdio/src/cli/
    • src/transports/stdio.tsmcp-stdio/src/transports/stdio.ts
    • src/index.tsmcp-stdio/src/index.ts
  4. Create package.json (see config above)
  5. Create tsdown.config.ts (see config above)
  6. Create tsconfig.json:
    {
      "extends": "@sentry/mcp-server-tsconfig/base.json",
      "compilerOptions": {
        "outDir": "./dist",
        "rootDir": "./src"
      },
      "include": ["src/**/*"],
      "exclude": ["node_modules", "dist", "**/*.test.ts"]
    }
  7. Update imports in moved files (see "Import Changes" section)
  8. Test build: cd packages/mcp-stdio && pnpm build
  9. Verify bundling worked:
    # Check that mcp-core code is bundled
    cat dist/index.js | grep "buildServer"
    # Should see the actual function code, not just an import

Phase 3: Update Cloudflare Package

  1. Update packages/mcp-cloudflare/package.json:
    • Change dependency: "@sentry/mcp-server""@sentry/mcp-core": "workspace:*"
  2. Find and replace imports:
    cd packages/mcp-cloudflare
    # Find all imports
    grep -r "@sentry/mcp-server" src/
    # Replace (manually or with sed)
    find src/ -type f -name "*.ts" -exec sed -i '' 's/@sentry\/mcp-server/@sentry\/mcp-core/g' {} +
  3. Test build: cd packages/mcp-cloudflare && pnpm build

Phase 4: Update Other Packages

  1. Update packages/mcp-test-client/package.json:
    • If it imports from mcp-server, update to mcp-core
  2. Update packages/mcp-server-evals/package.json:
    • Same as above
  3. Update packages/mcp-server-mocks/package.json:
    • Check if any changes needed

Phase 5: Update Root Configuration

  1. Update pnpm-workspace.yaml (if needed):
    packages:
      - packages/*
  2. Update turbo.json:
    • Ensure build dependencies are correct
    • mcp-stdio should depend on mcp-core build
  3. Update root package.json scripts if they reference package names

Phase 6: Update Build Pipeline

  1. Verify turbo cache works:
    pnpm -w run build
    # Should build mcp-core first, then mcp-stdio
  2. Test incremental builds:
    # Change a file in mcp-core
    echo "// test" >> packages/mcp-core/src/server.ts
    pnpm -w run build
    # Should rebuild both mcp-core and mcp-stdio
  3. Update GitHub Actions workflows:
    • .github/workflows/test.yml
    • .github/workflows/release.yml
    • Any other workflows that reference package names

Phase 7: Update Documentation

  1. Update CLAUDE.md:
    • Repository map section
    • Package descriptions
  2. Update docs/architecture.mdc:
    • Package structure
    • Dependencies diagram
  3. Update docs/releases/stdio.mdc:
    • New package structure
    • Build process
  4. Update docs/cursor.mdc:
    • Package references
  5. Update AGENTS.md:
    • Package structure
  6. Update packages/mcp-core/README.md:
    • Note it's a private package
    • Explain it's shared code
  7. Update packages/mcp-stdio/README.md:
    • Installation instructions
    • Note it bundles core
  8. Update any other docs mentioning package structure

Phase 8: Testing & Validation

  1. Build tests:

    pnpm -w run build
    # All packages should build successfully
  2. Type check:

    pnpm -w run tsc
    # No type errors
  3. Lint:

    pnpm -w run lint
    # No lint errors
  4. Unit tests:

    pnpm -w run test
    # All tests pass
  5. Test stdio package locally:

    cd packages/mcp-stdio
    node dist/index.js --help
    # Should show help text
    
    node dist/index.js --access-token=test-token --version
    # Should show version
  6. Test with npx simulation:

    cd packages/mcp-stdio
    pnpm pack
    # Creates sentry-mcp-server-*.tgz
    
    npm install -g ./sentry-mcp-server-*.tgz
    sentry-mcp --help
    # Should work globally
    
    npm uninstall -g @sentry/mcp-server
  7. Test cloudflare package:

    cd packages/mcp-cloudflare
    pnpm dev
    # Should start dev server without errors
  8. Test MCP inspector:

    pnpm inspector
    # Connect to stdio server
    # Verify tools load correctly
  9. Verify no mcp-core in published package:

    cd packages/mcp-stdio
    tar -tzf sentry-mcp-server-*.tgz
    # Check package.json
    tar -xzf sentry-mcp-server-*.tgz
    cat package/package.json | grep mcp-core
    # Should NOT appear in dependencies

Verification Checklist

Build Verification

  • pnpm -w run build succeeds
  • mcp-core builds first (turbo dependency)
  • mcp-stdio builds after mcp-core
  • mcp-cloudflare builds successfully
  • All dist/ directories contain expected files

Bundle Verification

  • mcp-stdio/dist/index.js contains bundled mcp-core code
  • mcp-stdio/dist/index.js does NOT import from @sentry/mcp-core
  • File size is reasonable (check ls -lh packages/mcp-stdio/dist/)

Package.json Verification

  • mcp-core has "private": true
  • mcp-core has no bin field
  • mcp-stdio has bin field pointing to dist/index.js
  • mcp-stdio has mcp-core in devDependencies only
  • Packed package.json has no mcp-core reference

Import Verification

  • All mcp-stdio files import from @sentry/mcp-core/*
  • All mcp-cloudflare files import from @sentry/mcp-core/*
  • No relative imports crossing package boundaries

Test Verification

  • All unit tests pass
  • CLI tests work in mcp-stdio package
  • No broken imports in test files
  • Mock imports still work

Runtime Verification

  • node packages/mcp-stdio/dist/index.js --help works
  • node packages/mcp-stdio/dist/index.js --version works
  • Can connect via MCP inspector
  • Tools load and execute correctly
  • Cloudflare dev server starts

Documentation Verification

  • All docs updated with new package names
  • Installation instructions correct
  • Architecture diagrams updated
  • No broken doc links

Rollback Plan

If issues arise during implementation:

  1. Before committing: Simply delete mcp-stdio, rename mcp-core back to mcp-server
  2. After committing: Revert the commit: git revert <commit-hash>
  3. If published: Publish a new patch version with the old structure

Expected Outcomes

For End Users

  • ✅ No breaking changes (package name stays @sentry/mcp-server)
  • ✅ Same installation: npx @sentry/mcp-server
  • ✅ Same CLI flags and behavior
  • ✅ No knowledge of mcp-core needed

For Developers

  • ✅ Clear separation: core vs transport-specific code
  • ✅ Cloudflare can depend on core directly
  • ✅ Better modularity for future transports
  • ✅ Type safety during development
  • ✅ Turbo caching works correctly

For the Codebase

  • ✅ mcp-core is private (never published)
  • ✅ mcp-stdio fully self-contained (no external core dep)
  • ✅ Clean package boundaries
  • ✅ Easier to maintain and extend
  • ✅ Future HTTP/SSE transports can be separate packages

Future Considerations

Adding More Transports

If we want to add other transports in the future:

packages/
├── mcp-core/          # Shared code (private)
├── mcp-stdio/         # Stdio transport (published as @sentry/mcp-server)
├── mcp-http/          # Future: HTTP transport package
├── mcp-sse/           # Future: SSE transport package
└── mcp-cloudflare/    # Web app (uses core via workspace)

Each transport package would:

  • Import from @sentry/mcp-core as devDependency
  • Bundle all core code at build time
  • Be published independently with its own name
  • Have transport-specific configuration and entry points

Notes

Why DevDependency + Bundling?

  • Type safety: Full IDE support during development
  • Clean publishing: No reference to unpublished package
  • Turbo-friendly: Explicit build dependencies
  • User-friendly: Zero knowledge of internal packages needed

Why Keep @sentry/mcp-server Name?

  • No breaking changes for existing users
  • Existing documentation remains valid
  • npx commands don't need updating
  • IDE configs don't need updating

Why Not Publish mcp-core?

  • It's an internal implementation detail
  • No use case for external consumption
  • Reduces maintenance burden (no semver, no changelog)
  • Allows internal refactoring without breaking changes

Questions & Answers

Q: Why not use tsup instead of tsdown? A: tsdown is already used and works well. No need to change.

Q: Should we bundle node_modules too? A: No, only mcp-core. External packages like MCP SDK and Sentry should remain external.

Q: What about source maps? A: Enable in tsdown config for better debugging.

Q: How to handle version bumps? A: Both packages should have same version. Consider using Changesets or manual sync.

Q: What about the README? A: mcp-stdio keeps the user-facing README. mcp-core gets a developer-focused README.


Timeline Estimate

  • Phase 1-2 (Core rename + Stdio creation): 2-3 hours
  • Phase 3-4 (Update other packages): 1 hour
  • Phase 5-6 (Build pipeline): 1 hour
  • Phase 7 (Documentation): 1-2 hours
  • Phase 8 (Testing): 2-3 hours

Total: ~8-12 hours of focused work


Success Criteria

The refactoring is complete when:

  1. pnpm -w run build succeeds for all packages
  2. pnpm -w run test passes all tests
  3. ✅ Can run stdio server: node packages/mcp-stdio/dist/index.js --help
  4. ✅ Can pack and install: npm install -g ./packages/mcp-stdio/*.tgz
  5. ✅ Cloudflare dev server works: cd packages/mcp-cloudflare && pnpm dev
  6. ✅ No @sentry/mcp-core in packed mcp-stdio package.json
  7. ✅ All documentation updated and accurate

End of Plan