@outfitter/mcp 0.3.0 → 0.4.1

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.
package/README.md CHANGED
@@ -56,14 +56,82 @@ Creates an MCP server instance.
56
56
  interface McpServerOptions {
57
57
  name: string; // Server name for MCP handshake
58
58
  version: string; // Server version (semver)
59
- logger?: Logger; // Optional structured logger
59
+ logger?: Logger; // Optional structured logger (BYO)
60
60
  defaultLogLevel?: McpLogLevel | null; // Default log forwarding level
61
61
  }
62
62
 
63
63
  const server = createMcpServer({
64
64
  name: "my-server",
65
65
  version: "1.0.0",
66
- logger: createLogger({ name: "mcp" }),
66
+ });
67
+
68
+ // If `logger` is omitted, Outfitter logger factory defaults are used.
69
+ ```
70
+
71
+ ### Bring Your Own Logger (BYO)
72
+
73
+ `createMcpServer` accepts any logger implementing the shared `Logger` contract.
74
+ This lets you use the default Outfitter backend or a custom backend adapter.
75
+
76
+ #### Outfitter factory backend
77
+
78
+ ```typescript
79
+ import { createOutfitterLoggerFactory } from "@outfitter/logging";
80
+
81
+ const loggerFactory = createOutfitterLoggerFactory();
82
+ const server = createMcpServer({
83
+ name: "my-server",
84
+ version: "1.0.0",
85
+ logger: loggerFactory.createLogger({
86
+ name: "mcp",
87
+ context: { surface: "mcp" },
88
+ }),
89
+ });
90
+ ```
91
+
92
+ #### Custom adapter backend
93
+
94
+ ```typescript
95
+ import {
96
+ createLoggerFactory,
97
+ type Logger,
98
+ type LoggerAdapter,
99
+ } from "@outfitter/contracts";
100
+
101
+ type BackendOptions = { write: (line: string) => void };
102
+
103
+ const adapter: LoggerAdapter<BackendOptions> = {
104
+ createLogger(config) {
105
+ const write = config.backend?.write ?? (() => {});
106
+ const createMethod = (level: string): Logger["info"] =>
107
+ ((message: string) => {
108
+ write(`[${level}] ${config.name}: ${message}`);
109
+ }) as Logger["info"];
110
+
111
+ return {
112
+ trace: createMethod("trace"),
113
+ debug: createMethod("debug"),
114
+ info: createMethod("info"),
115
+ warn: createMethod("warn"),
116
+ error: createMethod("error"),
117
+ fatal: createMethod("fatal"),
118
+ child: (childContext) =>
119
+ adapter.createLogger({
120
+ ...config,
121
+ context: { ...(config.context ?? {}), ...childContext },
122
+ }),
123
+ };
124
+ },
125
+ };
126
+
127
+ const loggerFactory = createLoggerFactory(adapter);
128
+ const server = createMcpServer({
129
+ name: "my-server",
130
+ version: "1.0.0",
131
+ logger: loggerFactory.createLogger({
132
+ name: "mcp",
133
+ backend: { write: (line) => console.log(line) },
134
+ }),
67
135
  });
68
136
  ```
69
137
 
@@ -122,7 +190,7 @@ const getUserTool = defineTool({
122
190
  handler: async (input, ctx) => {
123
191
  const user = await db.users.find(input.userId);
124
192
  if (!user) {
125
- return Result.err(new NotFoundError("user", input.userId));
193
+ return Result.err(NotFoundError.create("user", input.userId));
126
194
  }
127
195
  return Result.ok(user);
128
196
  },
@@ -291,8 +359,74 @@ for (const tool of coreTools) {
291
359
  }
292
360
  ```
293
361
 
362
+ ## Tool Annotations
363
+
364
+ Use `TOOL_ANNOTATIONS` presets to declare tool behavior hints without manually specifying all four booleans:
365
+
366
+ ```typescript
367
+ import { defineTool, TOOL_ANNOTATIONS } from "@outfitter/mcp";
368
+
369
+ // Use a preset directly
370
+ const listTool = defineTool({
371
+ name: "list-items",
372
+ description: "List all items",
373
+ inputSchema: z.object({}),
374
+ annotations: TOOL_ANNOTATIONS.readOnly,
375
+ handler: async (input, ctx) => { /* ... */ },
376
+ });
377
+
378
+ // Spread and override for edge cases
379
+ const searchTool = defineTool({
380
+ name: "search",
381
+ description: "Search external APIs",
382
+ inputSchema: z.object({ q: z.string() }),
383
+ annotations: { ...TOOL_ANNOTATIONS.readOnly, openWorldHint: true },
384
+ handler: async (input, ctx) => { /* ... */ },
385
+ });
386
+ ```
387
+
388
+ | Preset | readOnly | destructive | idempotent | openWorld |
389
+ |--------|----------|-------------|------------|-----------|
390
+ | `readOnly` | true | false | true | false |
391
+ | `write` | false | false | false | false |
392
+ | `writeIdempotent` | false | false | true | false |
393
+ | `destructive` | false | true | true | false |
394
+ | `openWorld` | false | false | false | true |
395
+
396
+ For multi-action tools, use the most conservative union of hints. Per-action annotations are an MCP spec limitation.
397
+
398
+ ### adaptHandler
399
+
400
+ When your handler returns domain errors that extend `Error` but not `OutfitterError`, use `adaptHandler` instead of an unsafe cast:
401
+
402
+ ```typescript
403
+ import { adaptHandler, defineTool } from "@outfitter/mcp";
404
+
405
+ const tool = defineTool({
406
+ name: "my-tool",
407
+ inputSchema: z.object({ id: z.string() }),
408
+ handler: adaptHandler(myDomainHandler),
409
+ });
410
+ ```
411
+
294
412
  ## Transport Helpers
295
413
 
414
+ ### wrapToolResult / wrapToolError
415
+
416
+ Format handler output as MCP tool responses. Useful when building custom transport layers or testing:
417
+
418
+ ```typescript
419
+ import { wrapToolResult, wrapToolError } from "@outfitter/mcp";
420
+
421
+ // Wrap a plain value as MCP tool content
422
+ const response = wrapToolResult({ count: 42 });
423
+ // { content: [{ type: "text", text: '{"count":42}' }] }
424
+
425
+ // Wrap an error with isError flag
426
+ const errorResponse = wrapToolError(new Error("not found"));
427
+ // { content: [{ type: "text", text: "not found" }], isError: true }
428
+ ```
429
+
296
430
  ### connectStdio
297
431
 
298
432
  Connect server to stdio transport for Claude Desktop integration.
@@ -393,6 +527,10 @@ Config location:
393
527
  - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
394
528
  - Linux: `~/.config/claude/claude_desktop_config.json`
395
529
 
530
+ ## Upgrading
531
+
532
+ Run `outfitter update --guide` for version-specific migration instructions, or check the [migration docs](https://github.com/outfitter-dev/outfitter/tree/main/plugins/outfitter/shared/migrations) for detailed upgrade steps.
533
+
396
534
  ## Related Packages
397
535
 
398
536
  - [@outfitter/contracts](../contracts/README.md) — Result types and error taxonomy
@@ -0,0 +1,4 @@
1
+ import { BuildMcpToolsOptions, buildMcpTools } from "./shared/@outfitter/mcp-a0cgfsnw";
2
+ import "./shared/@outfitter/mcp-h2twz77x";
3
+ import "./shared/@outfitter/mcp-cqpyer9m";
4
+ export { buildMcpTools, BuildMcpToolsOptions };
@@ -0,0 +1,11 @@
1
+ // @bun
2
+ import {
3
+ buildMcpTools
4
+ } from "./shared/@outfitter/mcp-ktapzh9d.js";
5
+ import"./shared/@outfitter/mcp-nmp5wf0w.js";
6
+ import"./shared/@outfitter/mcp-fjtxsa0x.js";
7
+ import"./shared/@outfitter/mcp-9m5hs2z0.js";
8
+ import"./shared/@outfitter/mcp-zy7b487d.js";
9
+ export {
10
+ buildMcpTools
11
+ };
@@ -0,0 +1,4 @@
1
+ import { ConfigAction, ConfigStore, ConfigToolInput, ConfigToolOptions, ConfigToolResponse, CoreToolDefinition, CoreToolsOptions, DocsSection, DocsToolEntry, DocsToolInput, DocsToolOptions, DocsToolResponse, NormalizedQueryInput, QueryToolInput, QueryToolOptions, QueryToolResponse, createCoreTools, defineConfigTool, defineDocsTool, defineQueryTool } from "./shared/@outfitter/mcp-dwd800vf";
2
+ import "./shared/@outfitter/mcp-h2twz77x";
3
+ import "./shared/@outfitter/mcp-cqpyer9m";
4
+ export { defineQueryTool, defineDocsTool, defineConfigTool, createCoreTools, QueryToolResponse, QueryToolOptions, QueryToolInput, NormalizedQueryInput, DocsToolResponse, DocsToolOptions, DocsToolInput, DocsToolEntry, DocsSection, CoreToolsOptions, CoreToolDefinition, ConfigToolResponse, ConfigToolOptions, ConfigToolInput, ConfigStore, ConfigAction };
@@ -0,0 +1,13 @@
1
+ // @bun
2
+ import {
3
+ createCoreTools,
4
+ defineConfigTool,
5
+ defineDocsTool,
6
+ defineQueryTool
7
+ } from "./shared/@outfitter/mcp-zv3ej45k.js";
8
+ export {
9
+ defineQueryTool,
10
+ defineDocsTool,
11
+ defineConfigTool,
12
+ createCoreTools
13
+ };
package/dist/index.d.ts CHANGED
@@ -54,7 +54,7 @@ interface McpServerOptions {
54
54
  version: string;
55
55
  /**
56
56
  * Optional logger instance for server logging.
57
- * If not provided, a no-op logger is used.
57
+ * If not provided, the server uses the Outfitter logger factory defaults.
58
58
  */
59
59
  logger?: Logger;
60
60
  /**
@@ -90,6 +90,57 @@ interface ToolAnnotations {
90
90
  openWorldHint?: boolean;
91
91
  }
92
92
  /**
93
+ * Common annotation presets for MCP tools.
94
+ *
95
+ * Use these as a starting point and spread-override individual hints:
96
+ *
97
+ * ```typescript
98
+ * annotations: { ...TOOL_ANNOTATIONS.readOnly, openWorldHint: true }
99
+ * ```
100
+ *
101
+ * For multi-action tools (e.g., a single tool with read and write actions),
102
+ * use the most conservative union of hints — if any action is destructive,
103
+ * mark the whole tool as destructive. Per-action annotations are an MCP spec
104
+ * limitation; presets + spread cover most edge cases.
105
+ */
106
+ declare const TOOL_ANNOTATIONS: {
107
+ /** Read-only, safe to call repeatedly. */
108
+ readonly readOnly: {
109
+ readonly readOnlyHint: true;
110
+ readonly destructiveHint: false;
111
+ readonly idempotentHint: true;
112
+ readonly openWorldHint: false;
113
+ };
114
+ /** Creates or updates state, not destructive. */
115
+ readonly write: {
116
+ readonly readOnlyHint: false;
117
+ readonly destructiveHint: false;
118
+ readonly idempotentHint: false;
119
+ readonly openWorldHint: false;
120
+ };
121
+ /** Idempotent write (PUT-like). */
122
+ readonly writeIdempotent: {
123
+ readonly readOnlyHint: false;
124
+ readonly destructiveHint: false;
125
+ readonly idempotentHint: true;
126
+ readonly openWorldHint: false;
127
+ };
128
+ /** Deletes or permanently modifies data. */
129
+ readonly destructive: {
130
+ readonly readOnlyHint: false;
131
+ readonly destructiveHint: true;
132
+ readonly idempotentHint: true;
133
+ readonly openWorldHint: false;
134
+ };
135
+ /** Interacts with external systems (APIs, network). */
136
+ readonly openWorld: {
137
+ readonly readOnlyHint: false;
138
+ readonly destructiveHint: false;
139
+ readonly idempotentHint: false;
140
+ readonly openWorldHint: true;
141
+ };
142
+ };
143
+ /**
93
144
  * Definition of an MCP tool that can be invoked by clients.
94
145
  *
95
146
  * Tools are the primary way clients interact with MCP servers.
@@ -647,6 +698,28 @@ interface McpHandlerContext extends HandlerContext {
647
698
  /** Progress reporter, present when client provides a progressToken */
648
699
  progress?: ProgressReporter;
649
700
  }
701
+ /**
702
+ * Adapt a handler with a domain error type for use with MCP tools.
703
+ *
704
+ * MCP tool definitions constrain `TError extends OutfitterError`. When your
705
+ * handler returns domain-specific errors that extend `Error` but not
706
+ * `OutfitterError`, use this function instead of an unsafe cast:
707
+ *
708
+ * ```typescript
709
+ * import { adaptHandler } from "@outfitter/mcp";
710
+ *
711
+ * const tool = defineTool({
712
+ * name: "my-tool",
713
+ * inputSchema: z.object({ id: z.string() }),
714
+ * handler: adaptHandler(myDomainHandler),
715
+ * });
716
+ * ```
717
+ */
718
+ declare function adaptHandler<
719
+ TInput,
720
+ TOutput,
721
+ TError extends Error
722
+ >(handler: (input: TInput, ctx: HandlerContext) => Promise<Result<TOutput, TError>>): Handler<TInput, TOutput, OutfitterError>;
650
723
  interface BuildMcpToolsOptions {
651
724
  readonly includeSurfaces?: readonly ActionSurface[];
652
725
  }
@@ -746,65 +819,7 @@ interface CoreToolsOptions {
746
819
  type NormalizedQueryInput = Required<Pick<QueryToolInput, "q">> & Omit<QueryToolInput, "q">;
747
820
  type CoreToolDefinition = ToolDefinition<DocsToolInput, DocsToolResponse> | ToolDefinition<ConfigToolInput, ConfigToolResponse> | ToolDefinition<QueryToolInput, QueryToolResponse>;
748
821
  declare function createCoreTools(options?: CoreToolsOptions): CoreToolDefinition[];
749
- import { z as z2 } from "zod";
750
- /**
751
- * JSON Schema representation.
752
- */
753
- interface JsonSchema {
754
- type?: string;
755
- properties?: Record<string, JsonSchema>;
756
- required?: string[];
757
- items?: JsonSchema | JsonSchema[];
758
- description?: string;
759
- default?: unknown;
760
- minimum?: number;
761
- maximum?: number;
762
- exclusiveMinimum?: number;
763
- exclusiveMaximum?: number;
764
- minLength?: number;
765
- maxLength?: number;
766
- pattern?: string;
767
- format?: string;
768
- enum?: unknown[];
769
- const?: unknown;
770
- anyOf?: JsonSchema[];
771
- oneOf?: JsonSchema[];
772
- allOf?: JsonSchema[];
773
- not?: JsonSchema | Record<string, never>;
774
- $ref?: string;
775
- $schema?: string;
776
- $defs?: Record<string, JsonSchema>;
777
- definitions?: Record<string, JsonSchema>;
778
- additionalProperties?: boolean | JsonSchema;
779
- }
780
- /**
781
- * Convert a Zod schema to JSON Schema format.
782
- *
783
- * This is a simplified converter that handles common Zod types.
784
- * For complex schemas, consider using a full zod-to-json-schema library.
785
- *
786
- * @param schema - Zod schema to convert
787
- * @returns JSON Schema representation
788
- *
789
- * @example
790
- * ```typescript
791
- * const zodSchema = z.object({
792
- * name: z.string(),
793
- * age: z.number().optional(),
794
- * });
795
- *
796
- * const jsonSchema = zodToJsonSchema(zodSchema);
797
- * // {
798
- * // type: "object",
799
- * // properties: {
800
- * // name: { type: "string" },
801
- * // age: { type: "number" },
802
- * // },
803
- * // required: ["name"],
804
- * // }
805
- * ```
806
- */
807
- declare function zodToJsonSchema(schema: z2.ZodType<unknown>): JsonSchema;
822
+ import { JsonSchema, zodToJsonSchema } from "@outfitter/contracts/schema";
808
823
  import { OutfitterError as OutfitterError3 } from "@outfitter/contracts";
809
824
  /**
810
825
  * Create an MCP server instance.
@@ -912,6 +927,22 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio";
912
927
  import { CallToolResult } from "@modelcontextprotocol/sdk/types";
913
928
  type McpToolResponse = CallToolResult;
914
929
  /**
930
+ * Wrap a handler success value into an MCP CallToolResult.
931
+ *
932
+ * If the value is already a valid McpToolResponse (has a `content` array),
933
+ * it is returned as-is. Otherwise it is wrapped in a text content block.
934
+ * Plain objects are also attached as `structuredContent` for SDK clients
935
+ * that support structured output.
936
+ */
937
+ declare function wrapToolResult(value: unknown): McpToolResponse;
938
+ /**
939
+ * Wrap an error into an MCP CallToolResult with `isError: true`.
940
+ *
941
+ * Serializes the error (preserving `_tag`, `message`, `code`, `context` if
942
+ * present) and wraps it as a text content block.
943
+ */
944
+ declare function wrapToolError(error: unknown): McpToolResponse;
945
+ /**
915
946
  * Create an MCP SDK server from an Outfitter MCP server.
916
947
  */
917
948
  declare function createSdkServer(server: McpServer): Server;
@@ -919,4 +950,4 @@ declare function createSdkServer(server: McpServer): Server;
919
950
  * Connect an MCP server over stdio transport.
920
951
  */
921
952
  declare function connectStdio(server: McpServer, transport?: StdioServerTransport): Promise<Server>;
922
- export { zodToJsonSchema, shouldEmitLog, mapLogLevelToMcp, defineTool, defineResourceTemplate, defineResource, defineQueryTool, definePrompt, defineDocsTool, defineConfigTool, createSdkServer, createMcpServer, createCoreTools, connectStdio, buildMcpTools, ToolDefinition, ToolAnnotations, TextResourceContent, SerializedTool, ResourceTemplateReadHandler, ResourceTemplateDefinition, ResourceReadHandler, ResourceDefinition, ResourceContent, QueryToolResponse, QueryToolOptions, QueryToolInput, PromptResult, PromptMessageContent, PromptMessage, PromptHandler, PromptDefinition, PromptArgument, ProgressReporter, McpToolResponse, McpServerOptions, McpServer, McpLogLevel, McpHandlerContext, McpError, JsonSchema, InvokeToolOptions, DocsToolResponse, DocsToolOptions, DocsToolInput, DocsToolEntry, DocsSection, CoreToolsOptions, ContentAnnotations, ConfigToolResponse, ConfigToolOptions, ConfigToolInput, ConfigStore, ConfigAction, CompletionResult, CompletionRef, CompletionHandler, BuildMcpToolsOptions, BlobResourceContent };
953
+ export { zodToJsonSchema, wrapToolResult, wrapToolError, shouldEmitLog, mapLogLevelToMcp, defineTool, defineResourceTemplate, defineResource, defineQueryTool, definePrompt, defineDocsTool, defineConfigTool, createSdkServer, createMcpServer, createCoreTools, connectStdio, buildMcpTools, adaptHandler, ToolDefinition, ToolAnnotations, TextResourceContent, TOOL_ANNOTATIONS, SerializedTool, ResourceTemplateReadHandler, ResourceTemplateDefinition, ResourceReadHandler, ResourceDefinition, ResourceContent, QueryToolResponse, QueryToolOptions, QueryToolInput, PromptResult, PromptMessageContent, PromptMessage, PromptHandler, PromptDefinition, PromptArgument, ProgressReporter, McpToolResponse, McpServerOptions, McpServer, McpLogLevel, McpHandlerContext, McpError, JsonSchema, InvokeToolOptions, DocsToolResponse, DocsToolOptions, DocsToolInput, DocsToolEntry, DocsSection, CoreToolsOptions, ContentAnnotations, ConfigToolResponse, ConfigToolOptions, ConfigToolInput, ConfigStore, ConfigAction, CompletionResult, CompletionRef, CompletionHandler, BuildMcpToolsOptions, BlobResourceContent };