@toolgrain/mcp 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +24 -9
  3. package/dist/auth/audience.d.ts +9 -1
  4. package/dist/auth/audience.d.ts.map +1 -1
  5. package/dist/auth/audience.js +9 -1
  6. package/dist/auth/audience.js.map +1 -1
  7. package/dist/auth/context.d.ts +9 -1
  8. package/dist/auth/context.d.ts.map +1 -1
  9. package/dist/auth/context.js +9 -1
  10. package/dist/auth/context.js.map +1 -1
  11. package/dist/auth/errors.d.ts +19 -1
  12. package/dist/auth/errors.d.ts.map +1 -1
  13. package/dist/auth/errors.js +16 -1
  14. package/dist/auth/errors.js.map +1 -1
  15. package/dist/auth/types.d.ts +21 -0
  16. package/dist/auth/types.d.ts.map +1 -1
  17. package/dist/auth/url.d.ts +9 -1
  18. package/dist/auth/url.d.ts.map +1 -1
  19. package/dist/auth/url.js +9 -1
  20. package/dist/auth/url.js.map +1 -1
  21. package/dist/context/definition.d.ts +28 -1
  22. package/dist/context/definition.d.ts.map +1 -1
  23. package/dist/context/definition.js +10 -1
  24. package/dist/context/definition.js.map +1 -1
  25. package/dist/diagnostics.d.ts +2 -0
  26. package/dist/diagnostics.d.ts.map +1 -1
  27. package/dist/diagnostics.js.map +1 -1
  28. package/dist/server.d.ts +59 -2
  29. package/dist/server.d.ts.map +1 -1
  30. package/dist/server.js +9 -1
  31. package/dist/server.js.map +1 -1
  32. package/dist/tools/context.d.ts +19 -1
  33. package/dist/tools/context.d.ts.map +1 -1
  34. package/dist/tools/context.js +8 -1
  35. package/dist/tools/context.js.map +1 -1
  36. package/dist/tools/definition.d.ts +49 -0
  37. package/dist/tools/definition.d.ts.map +1 -1
  38. package/dist/tools/definition.js +9 -0
  39. package/dist/tools/definition.js.map +1 -1
  40. package/dist/tools/name.d.ts +6 -1
  41. package/dist/tools/name.d.ts.map +1 -1
  42. package/dist/tools/name.js +5 -1
  43. package/dist/tools/name.js.map +1 -1
  44. package/dist/tools/registry.d.ts +4 -0
  45. package/dist/tools/registry.d.ts.map +1 -1
  46. package/dist/tools/registry.js.map +1 -1
  47. package/dist/tools/rich-result.d.ts +56 -2
  48. package/dist/tools/rich-result.d.ts.map +1 -1
  49. package/dist/tools/rich-result.js +7 -1
  50. package/dist/tools/rich-result.js.map +1 -1
  51. package/dist/tools/schema.d.ts +13 -1
  52. package/dist/tools/schema.d.ts.map +1 -1
  53. package/dist/tools/schema.js +11 -1
  54. package/dist/tools/schema.js.map +1 -1
  55. package/dist/transport.d.ts +21 -0
  56. package/dist/transport.d.ts.map +1 -1
  57. package/dist/transport.js.map +1 -1
  58. package/package.json +4 -3
  59. package/src/auth/audience.ts +9 -1
  60. package/src/auth/context.ts +9 -1
  61. package/src/auth/errors.ts +19 -1
  62. package/src/auth/types.ts +21 -0
  63. package/src/auth/url.ts +9 -1
  64. package/src/context/definition.ts +28 -1
  65. package/src/diagnostics.ts +2 -0
  66. package/src/server.ts +63 -3
  67. package/src/tools/context.ts +19 -1
  68. package/src/tools/definition.ts +52 -1
  69. package/src/tools/name.ts +6 -1
  70. package/src/tools/registry.ts +4 -0
  71. package/src/tools/rich-result.ts +68 -6
  72. package/src/tools/schema.ts +8 -0
  73. package/src/transport.ts +21 -0
@@ -1,6 +1,14 @@
1
1
  import { AuthError } from "./errors.js";
2
2
 
3
- /** Validate the audience of an already verified token without disclosing its claims. */
3
+ /**
4
+ * Require an already verified token's audience to contain the exact MCP resource URL.
5
+ * This checks only audience membership, not signatures, issuers, expiry, or revocation.
6
+ *
7
+ * @param audience - Verified `aud` claim: a string or array of strings.
8
+ * @param resource - Expected resource URL to match exactly.
9
+ * @throws `AuthError` with `invalid_token` for missing, malformed, or mismatched audiences.
10
+ * Missing and mismatched claims include public guidance without disclosing received claims.
11
+ */
4
12
  export function validateTokenAudience(
5
13
  audience: unknown,
6
14
  resource: string,
@@ -3,7 +3,15 @@ import type { AuthIdentity, AuthReadonly } from "./types.js";
3
3
 
4
4
  const key = Symbol.for("@toolgrain/mcp/auth-context/v1");
5
5
 
6
- /** Typed integrations bind their accessor to a stable, registered provider symbol. */
6
+ /**
7
+ * Create a synchronous accessor for a provider's already verified identity snapshot.
8
+ * Reading the identity performs no verification, network request, or lazy resolution.
9
+ *
10
+ * @typeParam Identity - Plain identity data returned by the matching provider.
11
+ * @param type - The provider's identity symbol; use `Symbol.for()` across package copies.
12
+ * @returns An accessor accepting a tool context and returning its deeply frozen identity.
13
+ * The accessor throws if that context has no identity for this provider.
14
+ */
7
15
  export function createAuthAccessor<Identity extends object>(type: symbol) {
8
16
  return (ctx: ToolContext): AuthReadonly<Identity> => {
9
17
  const auth = Object.getOwnPropertyDescriptor(ctx, key)?.value as
@@ -1,3 +1,4 @@
1
+ /** Public authorization failure categories mapped by HTTP adapters to 400, 401, 403, or 503. */
1
2
  export type AuthErrorCode =
2
3
  | "invalid_request"
3
4
  | "invalid_token"
@@ -9,11 +10,22 @@ const brand = Symbol.for("@toolgrain/mcp/AuthError/v1");
9
10
  /** Only an explicitly public description is exposed; provider causes stay private. */
10
11
  export class AuthError extends Error {
11
12
  readonly [brand] = true;
13
+ /** Explicitly client-visible guidance; omitted descriptions keep HTTP responses generic. */
12
14
  readonly description?: string | undefined;
13
15
 
16
+ /**
17
+ * Create an authorization failure with optional public guidance and a private cause.
18
+ *
19
+ * @param code - Authorization failure category used by HTTP adapters.
20
+ * @param options - Optional public description and private standard `Error` options.
21
+ */
14
22
  constructor(
23
+ /** Failure category; determines the default message and HTTP status. */
15
24
  readonly code: AuthErrorCode,
16
- options?: ErrorOptions & { description?: string },
25
+ options?: ErrorOptions & {
26
+ /** Public message included in HTTP error bodies and challenges; never include secrets. */
27
+ description?: string;
28
+ },
17
29
  ) {
18
30
  super(
19
31
  options?.description ??
@@ -30,6 +42,12 @@ export class AuthError extends Error {
30
42
  }
31
43
  }
32
44
 
45
+ /**
46
+ * Recognize branded authorization errors, including those from compatible package copies.
47
+ *
48
+ * @param value - Any thrown or returned value to inspect.
49
+ * @returns Whether the value is an `Error` with Toolgrain's authorization-error brand.
50
+ */
33
51
  export function isAuthError(value: unknown): value is AuthError {
34
52
  return value instanceof Error && Reflect.get(value, brand) === true;
35
53
  }
package/src/auth/types.ts CHANGED
@@ -1,27 +1,48 @@
1
1
  /** Implemented by integrations; the HTTP adapter supplies the resolved resource. */
2
2
  export interface AuthProvider<Identity extends object = object> {
3
+ /** Stable provider identity used by `createAuthAccessor()`; use `Symbol.for()` across copies. */
3
4
  readonly type: symbol;
5
+ /** Canonical public MCP resource URL; omission lets the HTTP adapter infer it per request. */
4
6
  readonly resource?: string;
7
+ /** Nonempty list of trusted authorization-server URLs advertised in public metadata. */
5
8
  readonly authorizationServers: readonly string[];
9
+ /**
10
+ * Verify token authenticity, validity, and resource audience before returning plain identity data.
11
+ * The server copies and deeply freezes the identity before exposing it to handlers.
12
+ *
13
+ * @param accessToken - Raw bearer token without its scheme prefix.
14
+ * @param options - Resolved resource and request cancellation signal.
15
+ * @returns Verified plain data; credentials and SDK methods should not be included.
16
+ * @throws `AuthError` for expected rejections; unexpected failures remain private diagnostics.
17
+ */
6
18
  verify(accessToken: string, options: AuthVerifyOptions): Promise<Identity>;
7
19
  }
8
20
 
21
+ /** Resource and cancellation information supplied to an authorization provider. */
9
22
  export interface AuthVerifyOptions {
23
+ /** Shared cancellation for verification, body reading, execution, and server shutdown. */
10
24
  readonly signal: AbortSignal;
25
+ /** Expected token audience, after applying any provider resource override. */
11
26
  readonly resource: string;
12
27
  }
13
28
 
29
+ /** Public authorization configuration copied by `Server.getAuthMetadata()`. */
14
30
  export interface AuthMetadata {
31
+ /** Canonical public resource URL, or absent when inferred from the current HTTP request. */
15
32
  readonly resource?: string;
33
+ /** Trusted authorization-server URLs advertised by protected-resource metadata. */
16
34
  readonly authorizationServers: readonly string[];
17
35
  }
18
36
 
19
37
  /** An opaque, single-use authorization for one server invocation. Always dispose it. */
20
38
  export interface AuthScope {
39
+ /** Cancellation signal spanning verification and the authorized invocation. */
21
40
  readonly signal: AbortSignal;
41
+ /** Release request bookkeeping and prevent future use; safe to call more than once. */
22
42
  dispose(): void;
23
43
  }
24
44
 
45
+ /** Recursively readonly view of the plain identity snapshot exposed by auth accessors. */
25
46
  export type AuthReadonly<T> = T extends readonly (infer Item)[]
26
47
  ? readonly AuthReadonly<Item>[]
27
48
  : T extends object
package/src/auth/url.ts CHANGED
@@ -1,4 +1,12 @@
1
- /** Canonical external URL; HTTP is permitted only for local development. */
1
+ /**
2
+ * Validate an auth configuration URL, returning the original string unchanged.
3
+ * Requires HTTPS, except HTTP on `localhost`, `127.0.0.1`, or `[::1]` for development.
4
+ * Credentials, whitespace, control characters, queries, and fragments are rejected.
5
+ *
6
+ * @param value - Authorization-server, signing-key, or canonical resource URL.
7
+ * @returns The supplied URL after validation; no normalization is applied.
8
+ * @throws `TypeError` for an invalid or disallowed URL.
9
+ */
2
10
  export function authUrl(value: string): string {
3
11
  const url = new URL(value);
4
12
  if (
@@ -12,6 +12,7 @@ export interface ContextBinding {
12
12
 
13
13
  /** Erase values for heterogeneous registrations without exposing unsafe overrides. */
14
14
  export abstract class RegisteredContextProvider {
15
+ /** Nonempty diagnostic label; distinct providers may share a name without sharing identity. */
15
16
  abstract readonly name: string;
16
17
  abstract readonly use: (ctx: ToolContext) => Promise<unknown>;
17
18
 
@@ -26,8 +27,23 @@ Object.defineProperty(RegisteredContextProvider.prototype, brand, {
26
27
 
27
28
  /** An opaque, invariant value contract; use createContext() and provide() for bindings. */
28
29
  export abstract class ContextProvider<Value> extends RegisteredContextProvider {
30
+ /**
31
+ * Read the context's lazy value, sharing one promise across concurrent readers.
32
+ * Always returns a promise, even for synchronous resolvers. Values remain unchanged
33
+ * and application-owned. Missing bindings, cycles, cancellation, and resolver failures reject.
34
+ *
35
+ * @param ctx - Context supplied by Toolgrain to a handler/resolver or made with `toolContext()`.
36
+ * @returns This context's cached value or failure for the provider identity.
37
+ */
29
38
  abstract readonly use: (ctx: ToolContext) => Promise<Value>;
30
39
  // A function property prevents widening Value and then overriding it incompatibly.
40
+ /**
41
+ * Create an override with the same identity and inferred value contract.
42
+ * Register the override instead of the original; existing accessors find it automatically.
43
+ *
44
+ * @param resolve - Replacement factory, invoked only on first access in each context.
45
+ * @returns A frozen provider binding without changing this provider or other registrations.
46
+ */
31
47
  abstract readonly provide: (
32
48
  resolve: (ctx: ToolContext) => Value | PromiseLike<Value>,
33
49
  ) => ContextProvider<Value>;
@@ -60,9 +76,20 @@ class Provider<Value> extends ContextProvider<Value> {
60
76
  };
61
77
  }
62
78
 
63
- /** Infer the resolved value type, including asynchronous factories. */
79
+ /**
80
+ * Define a lazy provider with an inferred value type, including asynchronous factories.
81
+ * Register it in a server or `toolContext()` before calling its `use` accessor.
82
+ * Identity belongs to the definition, not its name. Resources remain application-owned;
83
+ * Toolgrain does not copy, freeze, or automatically dispose the resolved value.
84
+ *
85
+ * @param options - Diagnostic name and factory for the value.
86
+ * @returns A frozen provider with a typed async accessor and override factory.
87
+ * @throws `TypeError` if the name is empty or the resolver is not a function.
88
+ */
64
89
  export function createContext<Value>(options: {
90
+ /** Nonempty diagnostic label, used in missing-provider and dependency-cycle errors. */
65
91
  name: string;
92
+ /** Lazy factory; may await other registered providers and should pass `ctx.signal` to work. */
66
93
  resolve: (ctx: ToolContext) => Value;
67
94
  }): ContextProvider<Awaited<Value>> {
68
95
  if (
@@ -2,7 +2,9 @@ import type { ToolRequest } from "./tools/context.js";
2
2
 
3
3
  /** Private diagnostics for an unexpected tool failure; no arguments or auth data are added. */
4
4
  export interface ToolErrorDetails {
5
+ /** Exposed registration name of the tool whose execution failed. */
5
6
  readonly toolName: string;
7
+ /** Frozen request metadata; arguments and authorization credentials are not added. */
6
8
  readonly request: ToolRequest;
7
9
  }
8
10
 
package/src/server.ts CHANGED
@@ -21,39 +21,99 @@ import {
21
21
  type Transport,
22
22
  } from "./transport.js";
23
23
 
24
+ /** Tool registrations, identity, and shared configuration for an MCP server. */
24
25
  export interface ServerOptions<Tools extends ToolMap = ToolMap> {
26
+ /** Server implementation name advertised to clients. */
25
27
  name: string;
28
+ /** Application version advertised to clients; independent of the protocol revision. */
26
29
  version: string;
30
+ /** Tools made with `tool()`; each registration key becomes its exposed tool name. */
27
31
  tools: Tools;
32
+ /** Optional usage instructions advertised to clients during discovery or initialization. */
28
33
  instructions?: string;
29
- /** Tool failures include frozen tool/request details; other diagnostics omit them. */
34
+ /**
35
+ * Receives original unexpected failures for private diagnostics. Tool failures
36
+ * include frozen tool/request details; auth and transport failures pass only
37
+ * the error. Throws and rejected promises from this callback are ignored.
38
+ */
30
39
  onError?: ErrorHandler;
40
+ /** Eager endpoint authorization; requires a request transport rather than stdio. */
31
41
  auth?: AuthProvider;
42
+ /** Lazy providers or typed overrides available to handlers; registration order is irrelevant. */
32
43
  context?: readonly RegisteredContextProvider[];
33
44
  }
34
45
 
46
+ /** Request-local settings for direct protocol dispatch through `Server.handle()`. */
35
47
  export interface HandleOptions {
48
+ /** Cooperative cancellation signal; handlers must observe it, and late responses are suppressed. */
36
49
  signal?: AbortSignal;
50
+ /**
51
+ * Protocol revision selected by the adapter. If omitted, dispatch is modern except
52
+ * for legacy initialization requests without explicit protocol-version metadata.
53
+ */
37
54
  protocolVersion?: SupportedProtocolVersion;
55
+ /** Single-use scope issued by this server's `authorize()`; dispose it in `finally`. */
38
56
  auth?: AuthScope;
39
57
  }
40
58
 
59
+ /** An MCP dispatcher with one optional transport attachment and terminal shutdown. */
41
60
  export interface Server {
61
+ /**
62
+ * Dispatch a decoded JSON value, including tool validation and MCP result conversion.
63
+ * This independent request path does not retain initialization or session state.
64
+ *
65
+ * @param message - Untrusted, already JSON-decoded protocol message.
66
+ * @param options - Cancellation, protocol selection, and any required authorization scope.
67
+ * @returns A protocol response, or `undefined` for ignored or cancelled messages.
68
+ * @throws If the server is closed or its required authorization scope is invalid.
69
+ */
42
70
  handle(
43
71
  message: unknown,
44
72
  options?: HandleOptions,
45
73
  ): Promise<ProtocolResponse | undefined>;
74
+ /** Return copied wire descriptions using registration keys as tool names. */
46
75
  getToolDefinitions(): ProtocolTool[];
76
+ /** Return copied public auth configuration, or `undefined` for an unprotected server. */
47
77
  getAuthMetadata(): AuthMetadata | undefined;
78
+ /**
79
+ * Verify a bearer token before reading a request body or invoking tools.
80
+ * The identity is copied and deeply frozen before a server-bound scope is issued.
81
+ *
82
+ * @param accessToken - Raw bearer token, without the `Bearer ` prefix.
83
+ * @param options - Resource audience and cancellation settings for this request.
84
+ * @returns A single-use scope; the caller must dispose it in `finally`.
85
+ * @throws For rejected credentials, provider failures, cancellation, or a closed server.
86
+ */
48
87
  authorize(
49
88
  accessToken: string,
50
- options: { resource: string; signal?: AbortSignal },
89
+ options: {
90
+ /** Public MCP resource URL; a configured provider resource takes precedence. */
91
+ resource: string;
92
+ /** Cancels verification and remains active while the returned scope is in use. */
93
+ signal?: AbortSignal;
94
+ },
51
95
  ): Promise<AuthScope>;
96
+ /**
97
+ * Attach and start one single-use transport, resolving when startup completes.
98
+ * Auth-configured servers require a request transport. A closed server cannot restart.
99
+ *
100
+ * @param transport - An unused transport, including one from a compatible package copy.
101
+ * @throws If attachment or startup fails, or the transport is already owned.
102
+ */
52
103
  listen(transport: Transport | RequestTransport): Promise<void>;
104
+ /** Abort active work and close the attached transport; repeated or nested calls are safe. */
53
105
  close(): Promise<void>;
54
106
  }
55
107
 
56
- /** Compose the tool adapter, MCP dispatcher, and transport connection. */
108
+ /**
109
+ * Create a server from reusable tools, optional auth, and lazy context providers.
110
+ * Registration validates tool names, metadata, and exposable schemas immediately.
111
+ * Creating the server does not start a transport or resolve context providers.
112
+ *
113
+ * @param options - Server identity and application-owned tool/provider registrations.
114
+ * @returns An independent server ready for direct dispatch or `listen()`.
115
+ * @throws For invalid registrations, tool schemas, or authorization configuration.
116
+ */
57
117
  export function createServer<const Tools extends ToolMap>(
58
118
  options: ServerOptions<Tools>,
59
119
  ): Server {
@@ -21,14 +21,19 @@ import type { DeepReadonly } from "./json-value.js";
21
21
 
22
22
  /** What the client reported about itself; unverified, so not a basis for security decisions. */
23
23
  export interface ToolClient {
24
+ /** Copied, deeply frozen implementation details supplied by the client, when available. */
24
25
  readonly info?: DeepReadonly<Implementation | LegacyImplementation>;
26
+ /** Copied, deeply frozen capability claims supplied by the client, when available. */
25
27
  readonly capabilities?: DeepReadonly<
26
28
  ClientCapabilities | LegacyClientCapabilities
27
29
  >;
28
30
  }
29
31
 
32
+ /** Copied, immutable protocol metadata for one invocation; client claims remain unverified. */
30
33
  export interface ToolRequest {
34
+ /** JSON-RPC request identifier; identical IDs can occur in independent contexts. */
31
35
  readonly id: RequestId;
36
+ /** Supported protocol revision selected for this invocation. */
32
37
  readonly protocolVersion: SupportedProtocolVersion;
33
38
  /** Absent when the era and transport carry no client information, such as legacy HTTP. */
34
39
  readonly client?: ToolClient;
@@ -36,19 +41,32 @@ export interface ToolRequest {
36
41
  readonly meta: Readonly<Record<string, unknown>>;
37
42
  }
38
43
 
44
+ /** Per-invocation data passed to tool handlers and lazy context resolvers. */
39
45
  export interface ToolContext {
40
46
  /** Aborts when the client cancels or disconnects, or the transport or server closes. */
41
47
  readonly signal: AbortSignal;
48
+ /** Frozen request metadata; contains no verified authorization identity. */
42
49
  readonly request: ToolRequest;
43
50
  }
44
51
 
52
+ /** Optional metadata, cancellation, and provider bindings for a direct-call context. */
45
53
  export interface ToolContextOptions {
54
+ /** Caller-owned cancellation signal; defaults to a fresh, non-aborted signal. */
46
55
  signal?: AbortSignal;
56
+ /** Request fields to override; defaults are ID `0`, the modern revision, and empty metadata. */
47
57
  request?: Partial<ToolRequest>;
58
+ /** Providers or overrides available through their accessors; defaults to no registrations. */
48
59
  context?: readonly RegisteredContextProvider[];
49
60
  }
50
61
 
51
- /** Build a context for calling a definition's `execute` directly, for example in tests. */
62
+ /**
63
+ * Build an unauthenticated context for direct `execute()` calls, for example in tests.
64
+ * Request metadata is copied and deeply frozen; each call creates a fresh provider cache.
65
+ * Direct execution still bypasses server authorization, validation, and MCP conversion.
66
+ *
67
+ * @param options - Optional signal, request metadata, and provider registrations.
68
+ * @returns A context accepted by Toolgrain's provider accessors.
69
+ */
52
70
  export function toolContext(options: ToolContextOptions = {}): ToolContext {
53
71
  // Default each field separately so an explicit undefined cannot replace a default.
54
72
  const {
@@ -20,16 +20,30 @@ export const TOOL_METADATA_KEYS = [
20
20
  "_meta",
21
21
  ] as const;
22
22
 
23
+ /**
24
+ * Optional client-facing tool title, description, behavior hints, icons, and `_meta`.
25
+ * Accepts readonly authoring data; registration checks JSON compatibility and copies
26
+ * metadata for wire descriptions. Hints describe behavior rather than enforce policy.
27
+ */
23
28
  export type ToolMetadata = DeepReadonly<
24
29
  Pick<ProtocolTool, (typeof TOOL_METADATA_KEYS)[number]>
25
30
  >;
26
31
 
32
+ /**
33
+ * Authoring contract for `tool()`; handler input uses the parsed Zod output type.
34
+ *
35
+ * @typeParam Schema - Zod 4 Classic or Mini input schema with an exposable object root.
36
+ * @typeParam Result - Raw handler return value, including a promise-like value.
37
+ * @typeParam Output - Optional Zod output schema; the handler returns its input type.
38
+ */
27
39
  export interface ToolDefinition<
28
40
  Schema extends core.$ZodType,
29
41
  Result,
30
42
  Output extends core.$ZodType = never,
31
43
  > extends ToolMetadata {
44
+ /** Input schema, parsed asynchronously by the server before the handler runs. */
32
45
  readonly input: Schema;
46
+ /** Optional output schema, parsed asynchronously before successful result conversion. */
33
47
  readonly output?: Output;
34
48
  /** Raw handler; server invocation performs validation and result conversion. */
35
49
  readonly execute: (input: core.output<Schema>, ctx: ToolContext) => Result;
@@ -68,21 +82,38 @@ export function isRegisteredTool(value: unknown): value is RegisteredTool {
68
82
  );
69
83
  }
70
84
 
85
+ /**
86
+ * Frozen, reusable tool definition returned by `tool()`. Register the same definition
87
+ * under different keys or create typed variants by passing a spread through `tool()` again.
88
+ * Direct `execute()` calls remain raw application calls.
89
+ *
90
+ * @typeParam Schema - Input schema whose parsed output is passed to the handler.
91
+ * @typeParam Result - Raw handler return type, before server output parsing and conversion.
92
+ * @typeParam Output - Output schema when declared by the definition.
93
+ */
71
94
  export class Tool<
72
95
  Schema extends core.$ZodType,
73
96
  Result,
74
97
  Output extends core.$ZodType = core.$ZodType,
75
98
  > extends RegisteredTool {
99
+ /** Zod input schema used by server validation; defaults and transforms run asynchronously. */
76
100
  declare readonly input: Schema;
101
+ /** Optional Zod output schema used by server result validation. */
77
102
  declare readonly output?: Output;
103
+ /** Raw handler; direct calls bypass authorization, input/output parsing, and MCP conversion. */
78
104
  declare readonly execute: (
79
105
  input: core.output<Schema>,
80
106
  ctx: ToolContext,
81
107
  ) => Result;
108
+ /** Optional human-readable title advertised to clients. */
82
109
  declare readonly title?: string;
110
+ /** Optional description explaining the tool's purpose and behavior to clients. */
83
111
  declare readonly description?: string;
112
+ /** Client-facing behavior hints; Toolgrain does not enforce their assertions. */
84
113
  declare readonly annotations?: NonNullable<ToolMetadata["annotations"]>;
114
+ /** Optional client-facing icon descriptors, with readonly authoring inputs. */
85
115
  declare readonly icons?: NonNullable<ToolMetadata["icons"]>;
116
+ /** Optional JSON-compatible protocol metadata advertised with the tool. */
86
117
  declare readonly _meta?: NonNullable<ToolMetadata["_meta"]>;
87
118
 
88
119
  constructor(definition: ToolDefinition<Schema, Result, core.$ZodType>) {
@@ -116,12 +147,21 @@ type ToolDefinitionWithoutOutput<Schema extends core.$ZodType, Result> = Omit<
116
147
  ToolDefinition<Schema, Result>,
117
148
  "execute"
118
149
  > & {
150
+ /** Raw handler accepting parsed input; server invocation validates its JSON-compatible result. */
119
151
  readonly execute: (
120
152
  input: core.output<Schema>,
121
153
  ctx: ToolContext,
122
154
  ) => Result & JsonSafeOutput<Result>;
123
155
  };
124
156
 
157
+ /**
158
+ * Define a frozen tool with asynchronously validated input and declared output.
159
+ * Successful rich results must include `structuredContent`; explicit text-only errors may omit it.
160
+ *
161
+ * @param definition - Zod schemas, a handler returning the output schema's input type, and metadata.
162
+ * @returns A reusable tool retaining input, output, and handler inference.
163
+ * Its `execute` property stays raw; server invocation performs validation and conversion.
164
+ */
125
165
  export function tool<
126
166
  Schema extends core.$ZodType,
127
167
  Output extends core.$ZodType,
@@ -130,9 +170,20 @@ export function tool<
130
170
  | PromiseLike<OutputResult<NoInfer<Output>>>,
131
171
  >(
132
172
  definition: ToolDefinition<Schema, Result, Output> & {
173
+ /** Required output schema for this overload; parses handler data before MCP conversion. */
133
174
  readonly output: Output;
134
175
  },
135
- ): Tool<Schema, Result, Output> & { readonly output: Output };
176
+ ): Tool<Schema, Result, Output> & {
177
+ /** Declared output schema, retained as required on the inferred tool type. */
178
+ readonly output: Output;
179
+ };
180
+ /**
181
+ * Define a frozen tool without an output schema, preserving the handler's inferred return type.
182
+ * Ordinary strings and JSON values become MCP results; use `mcpResult()` for rich content.
183
+ *
184
+ * @param definition - Zod input schema, a JSON-compatible or explicit-result handler, and metadata.
185
+ * @returns A reusable tool whose direct `execute()` calls bypass server validation and conversion.
186
+ */
136
187
  export function tool<Schema extends core.$ZodType, Result>(
137
188
  definition: ToolDefinitionWithoutOutput<Schema, Result>,
138
189
  ): Tool<Schema, Result, never>;
package/src/tools/name.ts CHANGED
@@ -1,5 +1,10 @@
1
+ /** Registration error for a name outside 1–128 ASCII letters, digits, underscores, hyphens, or dots. */
1
2
  export class ToolNameError extends Error {
2
- constructor(public readonly toolName: string) {
3
+ /** @param toolName - Invalid registration key that could not be exposed as an MCP tool name. */
4
+ constructor(
5
+ /** Invalid exposed registration name. */
6
+ public readonly toolName: string,
7
+ ) {
3
8
  super(
4
9
  `Invalid MCP tool name: ${JSON.stringify(toolName)}. Use 1–128 ASCII letters, digits, underscores, hyphens, or dots.`,
5
10
  );
@@ -7,6 +7,10 @@ import {
7
7
  } from "./definition.js";
8
8
  import { ToolInputError, UnknownToolError } from "./errors.js";
9
9
 
10
+ /**
11
+ * Tools indexed by exposed MCP names. Values must be frozen definitions made with `tool()`;
12
+ * names come from these keys, not from a definition's variable name or title.
13
+ */
10
14
  export type ToolMap = Record<string, RegisteredTool>;
11
15
 
12
16
  export interface ToolEntry extends ToolMetadata {
@@ -1,18 +1,30 @@
1
1
  import type { ContentBlock, MetaObject } from "@toolgrain/protocol";
2
2
  import type { DeepReadonly, JsonCompatible } from "./json-value.js";
3
3
 
4
+ /** Explicit MCP content and optional structured data, wrapped with `mcpResult()`. */
4
5
  export interface McpResultOptions<Structured = unknown> {
6
+ /** Content blocks for the client; readonly authoring arrays are accepted. */
5
7
  content: readonly DeepReadonly<ContentBlock>[];
8
+ /** JSON-compatible data; required for successful rich results with a declared output schema. */
6
9
  structuredContent?: Structured & JsonCompatible<NoInfer<Structured>>;
10
+ /** Marks an expected, client-visible tool failure; omitted or false indicates success. */
7
11
  isError?: boolean;
12
+ /** Optional protocol result metadata, distinct from application structured data. */
8
13
  _meta?: MetaObject;
9
14
  }
10
15
 
11
16
  // Registered symbols let duplicate package copies recognize each other's results.
12
17
  const RESULT_BRAND = Symbol.for("@toolgrain/mcp/result@1");
13
18
 
19
+ /**
20
+ * Branded rich-result wrapper recognized by compatible Toolgrain package copies.
21
+ * Use `mcpResult()` to construct one; a plain object with the same fields remains application data.
22
+ */
14
23
  export class McpResult<Structured> {
15
- constructor(readonly value: McpResultOptions<Structured>) {
24
+ constructor(
25
+ /** Explicit MCP result envelope, before server validation and revision-specific conversion. */
26
+ readonly value: McpResultOptions<Structured>,
27
+ ) {
16
28
  Object.freeze(this);
17
29
  }
18
30
 
@@ -32,34 +44,84 @@ export function isMcpResult(value: unknown): value is McpResult<unknown> {
32
44
  );
33
45
  }
34
46
 
47
+ /** Explicit, client-visible tool error; structured data may be omitted for text-only errors. */
35
48
  export type McpErrorResult<Structured = never> = McpResult<Structured> & {
36
- readonly value: McpResultOptions<Structured> & { isError: true };
49
+ /** Envelope statically marked as an error. */
50
+ readonly value: McpResultOptions<Structured> & {
51
+ /** Explicit expected-failure marker, preserved in the MCP result. */
52
+ isError: true;
53
+ };
37
54
  };
38
55
 
39
56
  /** A result whose structured data is present, as required by an output schema. */
40
57
  export type McpStructuredResult<Structured> = McpResult<Structured> & {
58
+ /** Envelope with required application structured data. */
41
59
  readonly value: McpResultOptions<Structured> & {
60
+ /** Structured value validated against the tool's output schema when one is declared. */
42
61
  structuredContent: Structured;
43
62
  };
44
63
  };
45
64
 
46
- /** Explicitly return MCP content. Ordinary objects always remain application data. */
65
+ /**
66
+ * Explicitly return rich MCP content; ordinary result-shaped objects remain application data.
67
+ * This overload wraps a public tool error that also supplies structured data.
68
+ * @param value - Error content and JSON-compatible data, parsed by any declared output schema.
69
+ * @returns A branded result typed as both an error and a structured result.
70
+ */
47
71
  export function mcpResult<Structured = never>(
48
72
  value: McpResultOptions<Structured> & {
73
+ /** Mark this result as an expected, client-visible tool failure. */
49
74
  isError: true;
75
+ /** Structured error data, subject to output-schema validation when declared. */
50
76
  structuredContent: Structured;
51
77
  },
52
78
  ): McpErrorResult<Structured> & McpStructuredResult<Structured>;
79
+ /**
80
+ * Wrap a text/content-only public tool error, allowed even when the tool declares output.
81
+ * @param value - Explicit error content without structured application data.
82
+ * @returns A branded error result whose `isError` flag is statically true.
83
+ */
53
84
  export function mcpResult(
54
- value: McpResultOptions<never> & { isError: true },
85
+ value: McpResultOptions<never> & {
86
+ /** Mark this result as an expected, client-visible tool failure. */
87
+ isError: true;
88
+ },
55
89
  ): McpErrorResult<never>;
90
+ /**
91
+ * Wrap a public tool error with an optional structured-data contract.
92
+ * @param value - Error content and any JSON-compatible structured data.
93
+ * @returns A branded error result; provided structured data still undergoes output validation.
94
+ */
56
95
  export function mcpResult<Structured>(
57
- value: McpResultOptions<Structured> & { isError: true },
96
+ value: McpResultOptions<Structured> & {
97
+ /** Mark this result as an expected, client-visible tool failure. */
98
+ isError: true;
99
+ },
58
100
  ): McpErrorResult<Structured>;
101
+ /**
102
+ * Wrap rich content with required structured application data.
103
+ * @param value - Client content and JSON-compatible data; a successful output-schema result needs both.
104
+ * @returns A branded structured result; the server validates and converts it for the selected revision.
105
+ */
59
106
  export function mcpResult<Structured>(
60
- value: McpResultOptions<Structured> & { structuredContent: Structured },
107
+ value: McpResultOptions<Structured> & {
108
+ /** Required application data, parsed by the declared output schema when present. */
109
+ structuredContent: Structured;
110
+ },
61
111
  ): McpStructuredResult<Structured>;
112
+ /**
113
+ * Wrap explicit client content without structured application data.
114
+ * @param value - Rich content; successful results of this form require a tool without an output schema.
115
+ * @returns A branded MCP result. Ordinary result-shaped objects are not treated as rich results.
116
+ */
62
117
  export function mcpResult(value: McpResultOptions<never>): McpResult<never>;
118
+ /**
119
+ * Explicitly return MCP content instead of ordinary application JSON.
120
+ * The server validates the envelope, parses supplied output data, and converts it for the protocol revision.
121
+ *
122
+ * @param value - Content blocks, optional structured data, an error marker, and protocol metadata.
123
+ * @returns A frozen result wrapper; content is checked during server invocation.
124
+ */
63
125
  export function mcpResult<Structured = never>(
64
126
  value: McpResultOptions<Structured>,
65
127
  ): McpResult<Structured>;
@@ -24,10 +24,18 @@ const metadata = z.strictObject({
24
24
  _meta: object.optional(),
25
25
  });
26
26
 
27
+ /** Registration error wrapping a schema-conversion or metadata-validation failure. */
27
28
  export class ToolSchemaError extends Error {
29
+ /**
30
+ * @param toolName - Exposed registration name whose description could not be built.
31
+ * @param cause - Original schema or metadata failure, retained as the error's cause.
32
+ * @param schemaKind - Failing part of the description; defaults to `input`.
33
+ */
28
34
  constructor(
35
+ /** Exposed registration name whose schema or metadata failed. */
29
36
  public readonly toolName: string,
30
37
  cause: unknown,
38
+ /** Whether input conversion, output conversion, or metadata validation failed. */
31
39
  public readonly schemaKind: "input" | "output" | "metadata" = "input",
32
40
  ) {
33
41
  super(`Cannot expose ${schemaKind} schema for tool: ${toolName}`, {