@outfitter/mcp 0.2.0 → 0.3.0

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
@@ -54,9 +54,10 @@ Creates an MCP server instance.
54
54
 
55
55
  ```typescript
56
56
  interface McpServerOptions {
57
- name: string; // Server name for MCP handshake
58
- version: string; // Server version (semver)
59
- logger?: Logger; // Optional structured logger
57
+ name: string; // Server name for MCP handshake
58
+ version: string; // Server version (semver)
59
+ logger?: Logger; // Optional structured logger
60
+ defaultLogLevel?: McpLogLevel | null; // Default log forwarding level
60
61
  }
61
62
 
62
63
  const server = createMcpServer({
@@ -66,6 +67,41 @@ const server = createMcpServer({
66
67
  });
67
68
  ```
68
69
 
70
+ ### Log Forwarding
71
+
72
+ MCP servers can forward log messages to the connected client. The default log level is resolved from environment configuration:
73
+
74
+ **Precedence** (highest wins):
75
+ 1. `OUTFITTER_LOG_LEVEL` environment variable
76
+ 2. `options.defaultLogLevel`
77
+ 3. `OUTFITTER_ENV` profile defaults (`"debug"` in development, `null` otherwise)
78
+ 4. `null` (no forwarding)
79
+
80
+ ```typescript
81
+ const server = createMcpServer({
82
+ name: "my-server",
83
+ version: "1.0.0",
84
+ // Forwarding level auto-resolved from OUTFITTER_ENV
85
+ });
86
+
87
+ // With OUTFITTER_ENV=development → forwards at "debug"
88
+ // With OUTFITTER_ENV=production → no forwarding (null)
89
+ // With OUTFITTER_LOG_LEVEL=error → forwards at "error"
90
+ ```
91
+
92
+ Set `defaultLogLevel: null` to explicitly disable forwarding regardless of environment. The MCP client can always override via `logging/setLevel`.
93
+
94
+ #### `sendLogMessage(level, data, loggerName?)`
95
+
96
+ Send a log message to the connected MCP client.
97
+
98
+ ```typescript
99
+ server.sendLogMessage("info", "Indexing complete", "my-server");
100
+ server.sendLogMessage("warning", { message: "Rate limited", retryAfter: 30 });
101
+ ```
102
+
103
+ Only sends if the message level meets or exceeds the current client log level threshold.
104
+
69
105
  ### defineTool(definition)
70
106
 
71
107
  Helper for defining typed tools with better type inference.
package/dist/index.d.ts CHANGED
@@ -2,6 +2,32 @@ import { ActionRegistry, ActionSurface, AnyActionSpec } from "@outfitter/contrac
2
2
  import { Handler, HandlerContext, Logger, OutfitterError, Result, TaggedErrorClass } from "@outfitter/contracts";
3
3
  import { z } from "zod";
4
4
  /**
5
+ * @outfitter/mcp - Logging Bridge
6
+ *
7
+ * Maps Outfitter log levels to MCP log levels for
8
+ * server-to-client log message notifications.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ /**
13
+ * MCP log levels as defined in the MCP specification.
14
+ * Ordered from least to most severe.
15
+ */
16
+ type McpLogLevel = "debug" | "info" | "notice" | "warning" | "error" | "critical" | "alert" | "emergency";
17
+ /**
18
+ * Outfitter log levels.
19
+ */
20
+ type OutfitterLogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
21
+ /**
22
+ * Map an Outfitter log level to the corresponding MCP log level.
23
+ */
24
+ declare function mapLogLevelToMcp(level: OutfitterLogLevel): McpLogLevel;
25
+ /**
26
+ * Check whether a message at the given level should be emitted
27
+ * based on the client-requested threshold.
28
+ */
29
+ declare function shouldEmitLog(messageLevel: McpLogLevel, threshold: McpLogLevel): boolean;
30
+ /**
5
31
  * Configuration options for creating an MCP server.
6
32
  *
7
33
  * @example
@@ -31,6 +57,19 @@ interface McpServerOptions {
31
57
  * If not provided, a no-op logger is used.
32
58
  */
33
59
  logger?: Logger;
60
+ /**
61
+ * Default MCP log level for client-facing log forwarding.
62
+ *
63
+ * Precedence (highest wins):
64
+ * 1. `OUTFITTER_LOG_LEVEL` environment variable
65
+ * 2. This option
66
+ * 3. Environment profile (`OUTFITTER_ENV`)
67
+ * 4. `null` (no forwarding until client opts in)
68
+ *
69
+ * Set to `null` to explicitly disable forwarding regardless of environment.
70
+ * The MCP client can always override via `logging/setLevel`.
71
+ */
72
+ defaultLogLevel?: McpLogLevel | null;
34
73
  }
35
74
  /**
36
75
  * Behavioral hints for MCP tools.
@@ -560,6 +599,16 @@ interface McpServer {
560
599
  */
561
600
  setLogLevel?(level: string): void;
562
601
  /**
602
+ * Send a log message to connected clients.
603
+ * Filters by the client-requested log level threshold.
604
+ * No-op if no SDK server is bound or if the message is below the threshold.
605
+ *
606
+ * @param level - MCP log level for the message
607
+ * @param data - Log data (string, object, or any serializable value)
608
+ * @param loggerName - Optional logger name for client-side filtering
609
+ */
610
+ sendLogMessage(level: McpLogLevel, data: unknown, loggerName?: string): void;
611
+ /**
563
612
  * Bind the SDK server instance for notifications.
564
613
  * Called internally by the transport layer.
565
614
  * @param sdkServer - The MCP SDK Server instance
@@ -697,32 +746,6 @@ interface CoreToolsOptions {
697
746
  type NormalizedQueryInput = Required<Pick<QueryToolInput, "q">> & Omit<QueryToolInput, "q">;
698
747
  type CoreToolDefinition = ToolDefinition<DocsToolInput, DocsToolResponse> | ToolDefinition<ConfigToolInput, ConfigToolResponse> | ToolDefinition<QueryToolInput, QueryToolResponse>;
699
748
  declare function createCoreTools(options?: CoreToolsOptions): CoreToolDefinition[];
700
- /**
701
- * @outfitter/mcp - Logging Bridge
702
- *
703
- * Maps Outfitter log levels to MCP log levels for
704
- * server-to-client log message notifications.
705
- *
706
- * @packageDocumentation
707
- */
708
- /**
709
- * MCP log levels as defined in the MCP specification.
710
- * Ordered from least to most severe.
711
- */
712
- type McpLogLevel = "debug" | "info" | "notice" | "warning" | "error" | "critical" | "alert" | "emergency";
713
- /**
714
- * Outfitter log levels.
715
- */
716
- type OutfitterLogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
717
- /**
718
- * Map an Outfitter log level to the corresponding MCP log level.
719
- */
720
- declare function mapLogLevelToMcp(level: OutfitterLogLevel): McpLogLevel;
721
- /**
722
- * Check whether a message at the given level should be emitted
723
- * based on the client-requested threshold.
724
- */
725
- declare function shouldEmitLog(messageLevel: McpLogLevel, threshold: McpLogLevel): boolean;
726
749
  import { z as z2 } from "zod";
727
750
  /**
728
751
  * JSON Schema representation.
package/dist/index.js CHANGED
@@ -2,8 +2,43 @@
2
2
  import { DEFAULT_REGISTRY_SURFACES } from "@outfitter/contracts";
3
3
 
4
4
  // src/server.ts
5
+ import { getEnvironment, getEnvironmentDefaults } from "@outfitter/config";
5
6
  import { generateRequestId, Result } from "@outfitter/contracts";
6
7
 
8
+ // src/logging.ts
9
+ var MCP_LEVEL_ORDER = [
10
+ "debug",
11
+ "info",
12
+ "notice",
13
+ "warning",
14
+ "error",
15
+ "critical",
16
+ "alert",
17
+ "emergency"
18
+ ];
19
+ function mapLogLevelToMcp(level) {
20
+ switch (level) {
21
+ case "trace":
22
+ case "debug":
23
+ return "debug";
24
+ case "info":
25
+ return "info";
26
+ case "warn":
27
+ return "warning";
28
+ case "error":
29
+ return "error";
30
+ case "fatal":
31
+ return "emergency";
32
+ default: {
33
+ const _exhaustiveCheck = level;
34
+ return _exhaustiveCheck;
35
+ }
36
+ }
37
+ }
38
+ function shouldEmitLog(messageLevel, threshold) {
39
+ return MCP_LEVEL_ORDER.indexOf(messageLevel) >= MCP_LEVEL_ORDER.indexOf(threshold);
40
+ }
41
+
7
42
  // src/schema.ts
8
43
  function zodToJsonSchema(schema) {
9
44
  return convertZodType(schema);
@@ -367,6 +402,40 @@ function createNoOpLogger() {
367
402
  child: () => createNoOpLogger()
368
403
  };
369
404
  }
405
+ var VALID_MCP_LOG_LEVELS = new Set([
406
+ "debug",
407
+ "info",
408
+ "notice",
409
+ "warning",
410
+ "error",
411
+ "critical",
412
+ "alert",
413
+ "emergency"
414
+ ]);
415
+ var DEFAULTS_TO_MCP = {
416
+ debug: "debug",
417
+ info: "info",
418
+ warn: "warning",
419
+ error: "error"
420
+ };
421
+ function resolveDefaultLogLevel(options) {
422
+ const envLogLevel = process.env["OUTFITTER_LOG_LEVEL"];
423
+ if (envLogLevel !== undefined && VALID_MCP_LOG_LEVELS.has(envLogLevel)) {
424
+ return envLogLevel;
425
+ }
426
+ if (options.defaultLogLevel !== undefined && (options.defaultLogLevel === null || VALID_MCP_LOG_LEVELS.has(options.defaultLogLevel))) {
427
+ return options.defaultLogLevel;
428
+ }
429
+ const env = getEnvironment();
430
+ const defaults = getEnvironmentDefaults(env);
431
+ if (defaults.logLevel !== null) {
432
+ const mapped = DEFAULTS_TO_MCP[defaults.logLevel];
433
+ if (mapped !== undefined) {
434
+ return mapped;
435
+ }
436
+ }
437
+ return null;
438
+ }
370
439
  function createMcpServer(options) {
371
440
  const { name, version, logger: providedLogger } = options;
372
441
  const logger = providedLogger ?? createNoOpLogger();
@@ -376,6 +445,7 @@ function createMcpServer(options) {
376
445
  const prompts = new Map;
377
446
  let sdkServer = null;
378
447
  const subscriptions = new Set;
448
+ let clientLogLevel = resolveDefaultLogLevel(options);
379
449
  function createHandlerContext(toolName, requestId, signal, progressToken) {
380
450
  const ctx = {
381
451
  requestId,
@@ -752,10 +822,25 @@ function createMcpServer(options) {
752
822
  sdkServer?.sendPromptListChanged?.();
753
823
  },
754
824
  setLogLevel(level) {
825
+ clientLogLevel = level;
755
826
  logger.debug("Client log level set", { level });
756
827
  },
828
+ sendLogMessage(level, data, loggerName) {
829
+ if (!sdkServer || clientLogLevel === null || !shouldEmitLog(level, clientLogLevel)) {
830
+ return;
831
+ }
832
+ const params = {
833
+ level,
834
+ data
835
+ };
836
+ if (loggerName !== undefined) {
837
+ params.logger = loggerName;
838
+ }
839
+ sdkServer.sendLoggingMessage?.(params);
840
+ },
757
841
  bindSdkServer(server2) {
758
842
  sdkServer = server2;
843
+ clientLogLevel = resolveDefaultLogLevel(options);
759
844
  logger.debug("SDK server bound for notifications");
760
845
  },
761
846
  async start() {
@@ -969,39 +1054,6 @@ function createCoreTools(options = {}) {
969
1054
  defineQueryTool(options.query)
970
1055
  ];
971
1056
  }
972
- // src/logging.ts
973
- var MCP_LEVEL_ORDER = [
974
- "debug",
975
- "info",
976
- "notice",
977
- "warning",
978
- "error",
979
- "critical",
980
- "alert",
981
- "emergency"
982
- ];
983
- function mapLogLevelToMcp(level) {
984
- switch (level) {
985
- case "trace":
986
- case "debug":
987
- return "debug";
988
- case "info":
989
- return "info";
990
- case "warn":
991
- return "warning";
992
- case "error":
993
- return "error";
994
- case "fatal":
995
- return "emergency";
996
- default: {
997
- const _exhaustiveCheck = level;
998
- return _exhaustiveCheck;
999
- }
1000
- }
1001
- }
1002
- function shouldEmitLog(messageLevel, threshold) {
1003
- return MCP_LEVEL_ORDER.indexOf(messageLevel) >= MCP_LEVEL_ORDER.indexOf(threshold);
1004
- }
1005
1057
  // src/transport.ts
1006
1058
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1007
1059
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@outfitter/mcp",
3
3
  "description": "MCP server framework with typed tools for Outfitter",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist"
@@ -40,8 +40,9 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@modelcontextprotocol/sdk": "^1.12.1",
43
+ "@outfitter/config": "0.3.0",
43
44
  "@outfitter/contracts": "0.2.0",
44
- "@outfitter/logging": "0.2.0",
45
+ "@outfitter/logging": "0.3.0",
45
46
  "zod": "^4.3.5"
46
47
  },
47
48
  "devDependencies": {