Skip to content

feat(mcp): add @gemstack/mcp — agent-agnostic MCP server framework (Phase 1)#22

Merged
suleimansh merged 1 commit into
mainfrom
feat/gemstack-mcp-phase1
Jun 26, 2026
Merged

feat(mcp): add @gemstack/mcp — agent-agnostic MCP server framework (Phase 1)#22
suleimansh merged 1 commit into
mainfrom
feat/gemstack-mcp-phase1

Conversation

@suleimansh

Copy link
Copy Markdown
Member

Phase 1 of the @gemstack/mcp graduation. Design + locked decisions: #18. Build issue: #19. Family epic: #1.

Stands up @gemstack/mcp as a standalone, agent-agnostic MCP server framework — the server-authoring axis of the MCP taxonomy, peer of @gemstack/ai-mcp (the agent bridge). It depends on no AI runtime, and ai-sdk must never depend on it.

What landed

Lifted the framework-agnostic core out of @rudderjs/mcp and applied the locked decisions:

  • Strip Rudder wiring (Decision 1): removed provider.ts (McpProvider), the make-* CLI scaffolders, doctor.ts, and the inspector from the core — these stay with the @rudderjs/mcp binding in Phase 2 (Build: @gemstack/mcp Phase 2 — repoint @rudderjs/mcp onto the core (thin binding) #20). Kept Mcp / McpServer / McpTool / McpResource / McpPrompt / McpResponse, the decorators, observers, runtime, testing.
  • DI resolver seam (Decision 2): replaced the globalThis.__rudderjs_* container read with an instance-scoped McpResolver passed at construction — new MyServer({ resolver }). @Handle resolves at call time off the server instance; built-in createResolver().register(token, instance) for the no-container case. A @Handle dependency with no resolver, or a resolver that yields undefined, throws a loud error naming the member + token — never injects undefined. No global setter.
  • Framework-neutral HTTP (Decision 3): createMcpHttpHandler(server) returns a plain node:http (req, res) handler (also fits Express/Connect); createWebRequestHandler(server) returns a Web Standard (request) => Promise<Response> engine for Hono/Vike/edge runtimes.
  • Generic OAuth2 (Decision 4): oauth2McpMiddleware re-typed to a Connect (req, res, next) signature taking a user-supplied verifyToken; dropped the @rudderjs/core MiddlewareHandler import and the passport lazy-load (passport is wired by the binding, not here).
  • Drop @rudderjs/json-schema (Decision 5): inline Zod 4 native z.toJSONSchema with the z.date() -> string/date-time override + open-object fallback. Zero behaviour change.
  • Versioning (Decision 7): fresh 0.0.0 -> 0.1.0 via a minor changeset (matches the family convention).

Runtime deps collapse to @modelcontextprotocol/sdk + zod + reflect-metadata. Zero @rudderjs/*, and nothing imported from @gemstack/ai-*.

Acceptance

  • pnpm build + pnpm typecheck + full suite green standalone (100 tests, zod + node:test).
  • A server authored with only @gemstack/mcp serves over createMcpHttpHandler on raw node:http, no framework present (new integration test using the SDK's real StreamableHTTPClientTransport).
  • @Handle works three ways: via a passed resolver, via createResolver().register, and a no-@Handle tool needs no resolver; a missing resolver / undefined resolution throws a clear named error.
  • One-directional dep check: imports nothing from @gemstack/ai-*; deps = SDK + zod + reflect-metadata.
  • Publish @gemstack/mcp@0.1.0 — on merge, via the release workflow (no pending changeset blocks it).

Notes

Closes #19.

…se 1)

Graduates the framework-agnostic core of @rudderjs/mcp into a standalone,
dependency-light package (runtime deps: @modelcontextprotocol/sdk, zod,
reflect-metadata; zero @rudderjs/*). Implements Phase 1 of gemstack #18.

- Strip Rudder framework wiring from the core: drop provider.ts (McpProvider),
  the make-* CLI scaffolders, doctor.ts, and the inspector (these stay with the
  Rudder binding in Phase 2).
- DI resolver seam (Decision 2): replace the globalThis container read with an
  instance-scoped McpResolver passed at construction. @handle resolves at call
  time off the server; built-in createResolver().register for the no-container
  case; a requested dependency with no resolver, or one that yields undefined,
  fails loudly naming the member and token. No global setter.
- Framework-neutral HTTP (Decision 3): createMcpHttpHandler returns a plain
  node:http (req, res) handler; createWebRequestHandler returns a Web Standard
  (request) => Response engine for Hono/Vike/edge.
- Generic OAuth2 (Decision 4): oauth2McpMiddleware takes a Connect (req,res,next)
  signature + a user-supplied verifyToken; no passport / @rudderjs/core coupling.
- Drop @rudderjs/json-schema (Decision 5): inline Zod 4 native z.toJSONSchema
  with the date-time override + open-object fallback.
- Fresh 0.0.0 -> 0.1.0 via changeset (Decision 7).

Build + typecheck + 100 tests green standalone, including a raw node:http
acceptance test serving a server with no framework present.

Closes #19.
@suleimansh suleimansh added enhancement New feature or request priority: medium Worth doing, not urgent labels Jun 26, 2026
@suleimansh suleimansh self-assigned this Jun 26, 2026
@suleimansh suleimansh merged commit 535ae5c into main Jun 26, 2026
1 check passed
@suleimansh suleimansh deleted the feat/gemstack-mcp-phase1 branch June 26, 2026 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request priority: medium Worth doing, not urgent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build: @gemstack/mcp Phase 1 — scaffold the agnostic core (0.1.0)

1 participant