Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/mcp-public-introspection-helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@gemstack/mcp": minor
---

Promote MCP-authoring utilities to the public API so inspectors and tooling no longer need internal access.

- `McpServer.introspect()`: a public introspection surface returning the registered tool / resource / prompt classes (constructors, not instances) without starting a session. The supported alternative to the internal `_tools()` / `_resources()` / `_prompts()` accessors, which stay `@internal`.
- `zodToJsonSchema(schema)`: convert a Zod schema to the JSON Schema MCP advertises (exported from the package entry).
- `matchUriTemplate(template, uri)`: match a URI against a `resource://{template}` pattern and extract params.
- New `McpServerIntrospection` and `ZodLikeObject` types exported alongside.

This lets a thin framework binding (e.g. `@rudderjs/mcp`) build a server inspector against the published surface instead of re-declaring internal shapes or carrying local copies of the helpers.
23 changes: 23 additions & 0 deletions packages/mcp/src/McpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ export interface McpServerMetadata {
instructions?: string
}

/**
* The tool / resource / prompt classes a server declares, surfaced for
* inspectors and tooling that enumerate a server without starting a session.
* These are the class constructors (not instances) so a caller can resolve or
* construct them with its own DI. See {@link McpServer.introspect}.
*/
export interface McpServerIntrospection {
tools: (new () => McpTool)[]
resources: (new () => McpResource)[]
prompts: (new () => McpPrompt)[]
}

export interface McpServerOptions {
/**
* DI resolver used to construct tool / resource / prompt classes and to
Expand Down Expand Up @@ -89,6 +101,17 @@ export abstract class McpServer {
return this.prompts
}

/**
* Public introspection surface: the registered tool / resource / prompt
* classes, without starting a session. Returns the class constructors (not
* instances) so inspectors and tooling can resolve or construct them with
* their own DI. This is the supported alternative to the internal
* underscore-prefixed `_tools()` / `_resources()` / `_prompts()` accessors.
*/
introspect(): McpServerIntrospection {
return { tools: this.tools, resources: this.resources, prompts: this.prompts }
}

/** @internal — exposed for tests; counts active notification targets. */
attachedCount(): number {
return this._attached?.size ?? 0
Expand Down
62 changes: 62 additions & 0 deletions packages/mcp/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,68 @@ describe('McpTool', () => {
})
})

// ─── McpServer.introspect ─────────────────────────────────

describe('McpServer.introspect', () => {
class EchoTool extends McpTool {
schema() { return z.object({ name: z.string() }) }
async handle() { return McpResponse.text('hi') }
}
class DocResource extends McpResource {
uri() { return 'doc://readme' }
async handle() { return '# readme' }
}
class GreetPrompt extends McpPrompt {
async handle() { return [{ role: 'user' as const, content: 'hi' }] }
}

it('returns the registered tool / resource / prompt classes', () => {
@Name('demo')
class DemoServer extends McpServer {
protected tools = [EchoTool]
protected resources = [DocResource]
protected prompts = [GreetPrompt]
}

const { tools, resources, prompts } = new DemoServer().introspect()
assert.deepEqual(tools, [EchoTool])
assert.deepEqual(resources, [DocResource])
assert.deepEqual(prompts, [GreetPrompt])
})

it('mirrors the @internal _tools()/_resources()/_prompts() accessors', () => {
@Name('demo')
class DemoServer extends McpServer {
protected tools = [EchoTool]
}

const server = new DemoServer()
const internal = server as unknown as { _tools(): unknown[]; _resources(): unknown[]; _prompts(): unknown[] }
const view = server.introspect()
assert.deepEqual(view.tools, internal._tools())
assert.deepEqual(view.resources, internal._resources())
assert.deepEqual(view.prompts, internal._prompts())
})

it('returns empty arrays for a server that declares nothing', () => {
@Name('empty')
class EmptyServer extends McpServer {}
const view = new EmptyServer().introspect()
assert.deepEqual(view, { tools: [], resources: [], prompts: [] })
})
})

// ─── public MCP-authoring helpers ─────────────────────────

describe('public helper exports', () => {
it('re-exports zodToJsonSchema and matchUriTemplate from the package entry', async () => {
const entry = await import('./index.js')
assert.equal(typeof entry.zodToJsonSchema, 'function')
assert.equal(typeof entry.matchUriTemplate, 'function')
assert.deepEqual(entry.matchUriTemplate('doc://{id}', 'doc://42'), { id: '42' })
})
})

// ─── McpPrompt ────────────────────────────────────────────

describe('McpPrompt', () => {
Expand Down
8 changes: 7 additions & 1 deletion packages/mcp/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { McpServer } from './McpServer.js'
export type { McpServerMetadata, McpServerOptions } from './McpServer.js'
export type { McpServerMetadata, McpServerOptions, McpServerIntrospection } from './McpServer.js'
export { McpTool } from './McpTool.js'
export type { McpToolResult, McpToolProgress, McpToolReturn } from './McpTool.js'
export { McpResource } from './McpResource.js'
Expand Down Expand Up @@ -31,3 +31,9 @@ export { createMcpHttpHandler } from './runtime/node-handler.js'
export { McpTestClient } from './testing.js'
export type { McpTestClientOptions } from './testing.js'
export type { McpObserverEvent, McpObserver, McpObserverRegistry } from './observers.js'
// MCP-authoring utilities, useful for custom inspectors / tooling built on the
// core: convert a Zod schema to the JSON Schema MCP advertises, and match a URI
// against a `resource://{template}` pattern. Both are pure and dependency-light.
export { zodToJsonSchema } from './zod-to-json-schema.js'
export type { ZodLikeObject } from './types.js'
export { matchUriTemplate } from './uri-template.js'
Loading