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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

> **Published package — `@theorvane/type-mcp@0.3.2`:** provides standard decorators, a separate `@theorvane/type-mcp/legacy` entrypoint for CommonJS legacy decorators, definition validation, explicit instance resolution, MCP SDK compilation, stdio, `@theorvane/type-mcp/http` Streamable HTTP, and the tools-only `@theorvane/type-mcp/langchain` adapter.
>
> **Current `dev` source:** additionally includes SDK v2 protocol negotiation, modern component metadata, tool output schemas, explicit prompt arguments, resource URI templates, completion, invocation context, protocol-backed testing, and image/audio helpers. The examples and capability map below target current source unless they explicitly say “published package.”
> **Current `dev` source:** additionally includes SDK v2 protocol negotiation, modern component metadata, tool output schemas, explicit prompt arguments, resource URI templates, completion, invocation context, protocol-backed testing, image/audio helpers, and component visibility. The examples and capability map below target current source unless they explicitly say “published package.”
>
> **Integration boundary:** LangGraph `ToolNode` composition, graph topology, model choice, authorization, state, persistence, and deployment remain consumer responsibilities.

Expand Down Expand Up @@ -134,6 +134,7 @@ The methods above are ordinary application methods. In current source, use `crea
| `createMcpServer()` | Available | Validates declarations and compiles the decorated server surface with an explicit resolver seam. |
| `McpInvocationContext` | Available | Optional final handler argument exposing request/session identity, cancellation, and progress reporting. |
| `McpImage` / `McpAudio` | Available | Browser-neutral byte helpers normalized to standard MCP media content. |
| `enableMcpComponents()` / `disableMcpComponents()` | Available | SDK-native server visibility filtered by key, name/URI, tag, or component kind. |
| `@theorvane/type-mcp/testing` | Available | Connects the official SDK client and a compiled server through the in-memory protocol transport. |
| `serveStdioServer()` / `startStdioServer()` | Available | SDK v2 factory-based 2025/2026 negotiation plus an instance-based 2025 compatibility helper. |
| `@theorvane/type-mcp/http` / `createMcpHandler()` | Available | Fetch/Streamable HTTP adapter with stateful 2025 sessions and the SDK v2 2026 per-request lifecycle; applications own route hosting, durable session policy, and authorization. |
Expand All @@ -149,6 +150,7 @@ The methods above are ordinary application methods. In current source, use `crea
- [Dynamic prompts and resources](docs/guides/dynamic-declarations.md) — explicit prompt arguments, URI templates, and completion in current source.
- [Invocation context](docs/guides/invocation-context.md) — request identity, cancellation, progress, and streaming constraints.
- [Testing and media helpers](docs/guides/testing-media.md) — in-memory protocol sessions and image/audio byte results.
- [Component visibility](docs/guides/component-visibility.md) — static state, runtime filters, allowlists, and security boundaries.
- [Configuration and compatibility](docs/guides/configuration.md) — Node, ESM/CommonJS, TypeScript decorators, schemas, and release boundaries.
- [Agent integration guide](docs/guides/agent-integration.md) — evidence-first coding-agent workflow and explicit runtime boundaries.
- [HTTP framework integration](docs/guides/http-and-nextjs.md) — published Streamable HTTP example and Fetch/Next.js route shape.
Expand Down
14 changes: 14 additions & 0 deletions docs/api/decorator-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,20 @@ summarizeProduct(input: { readonly sku: string }) {
| Runtime | SDK v2 derives the MCP prompt argument list, validates request strings through the schema, dispatches completion, and passes the parsed object to the handler. Results and handler failures retain TypeMCP normalization and safe errors. |
| Excluded | Automatic argument inference from TypeScript parameter types and prompt template files. Tool icons and prompt icons/custom metadata remain excluded until the compiler exposes them. |

## Component visibility

Tool, resource/template, and prompt options accept `enabled?: boolean` and `tags?: readonly string[]`. Components default to enabled. Tags must be unique non-empty strings and are copied/frozen with definition metadata.

`enableMcpComponents(server, filter)` and `disableMcpComponents(server, filter)` use SDK registered handles. Filters match additively by deterministic key, name/URI, tag, or kind; `matchAll` must be explicit for an all-component operation. Enable accepts `only: true` to establish an exact allowlist.

| Case | Behavior |
| --- | --- |
| Static disabled | Hidden from SDK lists and rejected at dispatch. |
| Runtime transition | SDK list/call behavior and `list_changed` notifications are preserved. The function returns the number of matched components. |
| Excluded | Authentication, authorization, per-session policy, providers, version filtering, and persistence. |

Visibility is surface shaping, not a security boundary. See the [component visibility guide](../guides/component-visibility.md).

## Invocation context

Decorated handlers may declare a final `McpInvocationContext` argument. Tools and URI-template resources receive it after parsed input; static resources and zero-argument prompts receive it as their only argument; prompts with explicit arguments receive it after parsed input.
Expand Down
19 changes: 19 additions & 0 deletions docs/architecture/adr/0006-component-visibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# ADR 0006: Add server-level component visibility

- **Status:** Accepted
- **Date:** 2026-08-27
- **Issue:** [#184](https://github.com/Theorvane/type-mcp/issues/184)

## Context

FastMCP supports static and dynamic visibility by component identity and tags. The official TypeScript SDK v2 registered handles already implement enabled state, list filtering, call blocking, and list_changed notifications. TypeMCP needs a metadata and filtering layer without treating visibility as authorization.

## Decision

Tool, resource/template, and prompt options gain enabled and tags. Tags are validated, copied, and frozen but remain TypeMCP-local metadata. The compiler tracks SDK registered handles in a WeakMap keyed by the compiled server and applies initial disabled state through native handles.

enableMcpComponents and disableMcpComponents match server components by generated key, public name or URI, tag, or kind. Empty filters are rejected unless matchAll is explicit. Enable supports only mode to establish an allowlist. Native SDK handle transitions preserve protocol listing, dispatch, and change notifications.

## Consequences

Visibility changes are process-local and server-wide. They are useful for feature flags and surface shaping, but are not an authentication or authorization boundary. Per-session policy, providers, version constraints, and persistence remain excluded.
50 changes: 50 additions & 0 deletions docs/guides/component-visibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Component visibility

**Availability:** current `dev` source; not included in published `0.3.2`.

Tools, resources/templates, and prompts accept `enabled` and `tags`. Components are enabled by default.

```ts
@McpTool({
input: z.object({}),
enabled: false,
tags: ["admin", "dangerous"],
})
deleteEverything() {
return "deleted";
}
```

A disabled component remains registered, but the official SDK omits it from listings and rejects dispatch as unknown. Tags are TypeMCP-local metadata and must be unique non-empty strings. Definition reads return copied, frozen tag arrays.

## Runtime controls

```ts
import {
disableMcpComponents,
enableMcpComponents,
} from "@theorvane/type-mcp";

const server = await createMcpServer(AdminServer);

enableMcpComponents(server, { tags: ["admin"] });
disableMcpComponents(server, { keys: ["tool:deleteEverything"] });
enableMcpComponents(server, { tags: ["safe"], only: true });
```

Filters combine additively: a component matches when any key, name/URI, tag, or kind matches. Supported kinds are `tool`, `resource`, `template`, and `prompt`. Use `matchAll: true` for an explicit all-components operation; an empty filter is rejected.

Keys are deterministic:

- `tool:{name}`
- `resource:{uri}`
- `template:{uriTemplate}`
- `prompt:{name}`

`only: true` establishes an allowlist: matches are enabled and every other tracked component is disabled. Later calls override current state. Functions return the number of matched components.

TypeMCP calls native SDK registered handles, preserving listing, dispatch blocking, and `list_changed` notifications.

## Security boundary

Visibility is process-local surface shaping for feature flags and maintenance. It is not authentication or authorization. Authorize every operation at the host or handler boundary even when hidden. Per-session visibility and persistent policies are outside this API.
44 changes: 44 additions & 0 deletions docs/planning/2026-08-27_issue-184-component-visibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Task brief — component visibility

**Owner:** Codex

**Date:** 2026-08-27

**Status:** complete

**Related plan:** GitHub issue `#184`

**Stacked base:** PR `#183` / `feat/182-testing-media` until merged to `dev`

## Objective

Decorated components can start disabled and compiled servers can safely reshape their exposed surface through SDK-native visibility handles.

## Scope

**In:** enabled, immutable validated tags, keys/names/tags/kinds filters, allowlist mode, native list/call behavior and notifications, docs.

**Out:** per-session policy, authentication, authorization, providers, versions, persistence, OAuth, and MCP Apps.

## Acceptance criteria

- [x] Static disabled state hides listings and blocks dispatch.
- [x] Runtime enable/disable filters match keys, names/URIs, tags, and kinds.
- [x] Allowlist mode produces the exact selected surface.
- [x] Tags are validated and frozen in definitions.
- [x] Existing untagged enabled components remain compatible.
- [x] Full package verification passes.

## Red → green evidence

| Stage | Command | Result / expected reason |
| --- | --- | --- |
| Red | `npx vitest run test/component-visibility.test.ts` | Failed as expected because decorator tags were not stored. |
| Green | `npx vitest run test/component-visibility.test.ts` | Passed: 2 tests cover static/runtime behavior, every filter family, allowlist mode, empty-filter safety, immutable tags, and invalid tags. |
| Regression | `npm test` | Passed: 34 files and 93 tests; lint, typecheck, build, package/publish packed consumers, production audit, and diff checks passed. |

## Review handoff

- Spec review: pending
- Quality review: pending
- Final checks: all required local checks passed
3 changes: 2 additions & 1 deletion docs/product/mvp-scope.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MVP scope

> **Published package:** `@theorvane/type-mcp@0.3.2` includes the MVP baseline. Current `dev` additionally carries SDK v2 serving, modern server/component metadata, initialization instructions, tool structured output, explicit prompt arguments, resource URI templates, completion, and invocation context. Start with the [README](../../README.md) and [getting-started guide](../guides/getting-started.md) for exact exports and boundaries.
> **Published package:** `@theorvane/type-mcp@0.3.2` includes the MVP baseline. Current `dev` additionally carries SDK v2 serving, modern server/component metadata, initialization instructions, tool structured output, dynamic prompts/resources, invocation context, testing/media helpers, and component visibility. Start with the [README](../../README.md) and [getting-started guide](../guides/getting-started.md) for exact exports and boundaries.

**Status:** The baseline is published in `@theorvane/type-mcp@0.3.2`; rows explicitly marked current `dev` are implemented but unreleased.

Expand All @@ -16,6 +16,7 @@
| Invocation context | Current `dev`: request/session identity, SDK cancellation signal, and progress reporting |
| Testing | Current `dev`: official SDK v2 in-memory client/server session with explicit cleanup |
| Media | Current `dev`: byte-only image/audio helpers with explicit MIME validation |
| Component visibility | Current `dev`: static enabled state and server-level key/name/tag/kind filtering |
| Instance construction | Direct constructor default plus async-capable `InstanceResolver` interface |
| Local transport | stdio helper |
| Web transport | Fetch-standard Streamable HTTP handler |
Expand Down
95 changes: 79 additions & 16 deletions src/compiler/create-mcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
McpServerConstructor,
ZeroArgumentMcpServerConstructor,
} from "../types.js";
import { initializeMcpVisibility, trackMcpComponent } from "../visibility.js";
import {
normalizePromptResult,
normalizeResourceResult,
Expand Down Expand Up @@ -65,9 +66,10 @@ export async function createMcpServer<
? undefined
: { instructions: definition.instructions },
);
initializeMcpVisibility(server);

for (const tool of definition.tools) {
server.registerTool(
const registeredTool = server.registerTool(
tool.name,
{
inputSchema: tool.input,
Expand Down Expand Up @@ -98,6 +100,17 @@ export async function createMcpServer<
}
},
);
trackMcpComponent(
server,
{
key: `tool:${tool.name}`,
name: tool.name,
kind: "tool",
tags: tool.tags,
initiallyEnabled: tool.enabled,
},
registeredTool,
);
}

for (const resource of definition.resources) {
Expand Down Expand Up @@ -131,7 +144,7 @@ export async function createMcpServer<
...(resource._meta === undefined ? {} : { _meta: { ...resource._meta } }),
};
if (!UriTemplate.isTemplate(resource.uri)) {
server.registerResource(
const registeredResource = server.registerResource(
resource.name,
resource.uri,
config,
Expand All @@ -150,14 +163,26 @@ export async function createMcpServer<
}
},
);
trackMcpComponent(
server,
{
key: `resource:${resource.uri}`,
name: resource.name,
identifiers: [resource.uri],
kind: "resource",
tags: resource.tags,
initiallyEnabled: resource.enabled,
},
registeredResource,
);
continue;
}

const input = resource.input;
if (input === undefined) {
throw new TypeError("Validated resource template input is missing");
}
server.registerResource(
const registeredTemplate = server.registerResource(
resource.name,
createResourceTemplate(resource),
config,
Expand All @@ -181,6 +206,18 @@ export async function createMcpServer<
}
},
);
trackMcpComponent(
server,
{
key: `template:${resource.uri}`,
name: resource.name,
identifiers: [resource.uri],
kind: "template",
tags: resource.tags,
initiallyEnabled: resource.enabled,
},
registeredTemplate,
);
}

for (const prompt of definition.prompts) {
Expand All @@ -191,21 +228,36 @@ export async function createMcpServer<
: { description: prompt.description }),
};
if (prompt.args === undefined) {
server.registerPrompt(prompt.name, config, async (context: unknown) => {
try {
if (!isServerContext(context)) {
throw new TypeError("MCP prompt context is unavailable");
const registeredPrompt = server.registerPrompt(
prompt.name,
config,
async (context: unknown) => {
try {
if (!isServerContext(context)) {
throw new TypeError("MCP prompt context is unavailable");
}
const result = await invokeMethod(instance, prompt.methodName, [
createMcpInvocationContext(context),
]);
return normalizePromptResult(result);
} catch {
return normalizePromptResult("Prompt execution failed");
}
const result = await invokeMethod(instance, prompt.methodName, [
createMcpInvocationContext(context),
]);
return normalizePromptResult(result);
} catch {
return normalizePromptResult("Prompt execution failed");
}
});
},
);
trackMcpComponent(
server,
{
key: `prompt:${prompt.name}`,
name: prompt.name,
kind: "prompt",
tags: prompt.tags,
initiallyEnabled: prompt.enabled,
},
registeredPrompt,
);
} else {
server.registerPrompt(
const registeredPrompt = server.registerPrompt(
prompt.name,
{ ...config, argsSchema: prompt.args },
async (input, context) => {
Expand All @@ -220,6 +272,17 @@ export async function createMcpServer<
}
},
);
trackMcpComponent(
server,
{
key: `prompt:${prompt.name}`,
name: prompt.name,
kind: "prompt",
tags: prompt.tags,
initiallyEnabled: prompt.enabled,
},
registeredPrompt,
);
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/decorators/mcp-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export function McpPrompt(
title: options.title,
description: options.description,
args: options.args,
enabled: options.enabled,
tags: options.tags,
});
};
return decorator as
Expand Down
2 changes: 2 additions & 0 deletions src/decorators/mcp-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export function McpResource(options: McpResourceOptions) {
_meta: options._meta,
input: options.input,
complete: options.complete,
enabled: options.enabled,
tags: options.tags,
});
};
}
Expand Down
2 changes: 2 additions & 0 deletions src/decorators/mcp-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export function McpTool(options: McpToolOptions) {
outputSchema: options.outputSchema,
annotations: options.annotations,
_meta: options._meta,
enabled: options.enabled,
tags: options.tags,
});
};
}
Expand Down
10 changes: 10 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type {
} from "./transports/stdio.js";
export { serveStdioServer, startStdioServer } from "./transports/stdio.js";
export type {
McpComponentVisibilityOptions,
McpPromptDefinition,
McpPromptOptions,
McpResourceCompletion,
Expand All @@ -33,3 +34,12 @@ export type {
McpToolDefinition,
McpToolOptions,
} from "./types.js";
export type {
McpComponentKind,
McpComponentVisibilityFilter,
McpEnableComponentsOptions,
} from "./visibility.js";
export {
disableMcpComponents,
enableMcpComponents,
} from "./visibility.js";
Loading
Loading