@rudderjs/mcp 0.0.1 → 1.0.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.
Files changed (75) hide show
  1. package/README.md +68 -0
  2. package/dist/Mcp.d.ts +21 -5
  3. package/dist/Mcp.d.ts.map +1 -1
  4. package/dist/Mcp.js +13 -1
  5. package/dist/Mcp.js.map +1 -1
  6. package/dist/McpPrompt.d.ts +8 -5
  7. package/dist/McpPrompt.d.ts.map +1 -1
  8. package/dist/McpPrompt.js.map +1 -1
  9. package/dist/McpResource.d.ts +9 -3
  10. package/dist/McpResource.d.ts.map +1 -1
  11. package/dist/McpResource.js +4 -0
  12. package/dist/McpResource.js.map +1 -1
  13. package/dist/McpTool.d.ts +17 -5
  14. package/dist/McpTool.d.ts.map +1 -1
  15. package/dist/McpTool.js.map +1 -1
  16. package/dist/auth/oauth2.d.ts +25 -0
  17. package/dist/auth/oauth2.d.ts.map +1 -0
  18. package/dist/auth/oauth2.js +128 -0
  19. package/dist/auth/oauth2.js.map +1 -0
  20. package/dist/commands/inspector-ui.d.ts +2 -0
  21. package/dist/commands/inspector-ui.d.ts.map +1 -0
  22. package/dist/commands/inspector-ui.js +300 -0
  23. package/dist/commands/inspector-ui.js.map +1 -0
  24. package/dist/commands/inspector.d.ts +5 -0
  25. package/dist/commands/inspector.d.ts.map +1 -0
  26. package/dist/commands/inspector.js +226 -0
  27. package/dist/commands/inspector.js.map +1 -0
  28. package/dist/commands/make-mcp-prompt.d.ts +3 -0
  29. package/dist/commands/make-mcp-prompt.d.ts.map +1 -0
  30. package/dist/commands/make-mcp-prompt.js +27 -0
  31. package/dist/commands/make-mcp-prompt.js.map +1 -0
  32. package/dist/commands/make-mcp-resource.d.ts +3 -0
  33. package/dist/commands/make-mcp-resource.d.ts.map +1 -0
  34. package/dist/commands/make-mcp-resource.js +26 -0
  35. package/dist/commands/make-mcp-resource.js.map +1 -0
  36. package/dist/commands/make-mcp-server.d.ts +3 -0
  37. package/dist/commands/make-mcp-server.d.ts.map +1 -0
  38. package/dist/commands/make-mcp-server.js +28 -0
  39. package/dist/commands/make-mcp-server.js.map +1 -0
  40. package/dist/commands/make-mcp-tool.d.ts +3 -0
  41. package/dist/commands/make-mcp-tool.d.ts.map +1 -0
  42. package/dist/commands/make-mcp-tool.js +25 -0
  43. package/dist/commands/make-mcp-tool.js.map +1 -0
  44. package/dist/decorators.d.ts +17 -0
  45. package/dist/decorators.d.ts.map +1 -1
  46. package/dist/decorators.js +22 -0
  47. package/dist/decorators.js.map +1 -1
  48. package/dist/index.d.ts +9 -3
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +4 -3
  51. package/dist/index.js.map +1 -1
  52. package/dist/observers.d.ts +32 -0
  53. package/dist/observers.d.ts.map +1 -0
  54. package/dist/observers.js +31 -0
  55. package/dist/observers.js.map +1 -0
  56. package/dist/provider.d.ts +4 -2
  57. package/dist/provider.d.ts.map +1 -1
  58. package/dist/provider.js +78 -40
  59. package/dist/provider.js.map +1 -1
  60. package/dist/runtime.d.ts +30 -0
  61. package/dist/runtime.d.ts.map +1 -1
  62. package/dist/runtime.js +267 -24
  63. package/dist/runtime.js.map +1 -1
  64. package/dist/testing.d.ts.map +1 -1
  65. package/dist/testing.js +3 -1
  66. package/dist/testing.js.map +1 -1
  67. package/dist/types.d.ts +11 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +2 -0
  70. package/dist/types.js.map +1 -0
  71. package/dist/zod-to-json-schema.d.ts +4 -3
  72. package/dist/zod-to-json-schema.d.ts.map +1 -1
  73. package/dist/zod-to-json-schema.js +54 -24
  74. package/dist/zod-to-json-schema.js.map +1 -1
  75. package/package.json +41 -3
package/README.md CHANGED
@@ -88,6 +88,70 @@ export class DescribeWeatherPrompt extends McpPrompt {
88
88
  }
89
89
  ```
90
90
 
91
+ ## Output Schemas
92
+
93
+ Tools can declare an output schema to advertise the structure of their response:
94
+
95
+ ```ts
96
+ @Description('Get current weather data.')
97
+ export class CurrentWeatherTool extends McpTool {
98
+ schema() {
99
+ return z.object({ location: z.string() })
100
+ }
101
+
102
+ outputSchema() {
103
+ return z.object({
104
+ temperature: z.number(),
105
+ conditions: z.string(),
106
+ humidity: z.number(),
107
+ })
108
+ }
109
+
110
+ async handle(input: Record<string, unknown>) {
111
+ return McpResponse.json({ temperature: 22, conditions: 'sunny', humidity: 45 })
112
+ }
113
+ }
114
+ ```
115
+
116
+ ## Resource URI Templates
117
+
118
+ Resources can use `{param}` placeholders for dynamic data:
119
+
120
+ ```ts
121
+ @Description('Weather data for a specific city')
122
+ export class CityWeatherResource extends McpResource {
123
+ uri() { return 'weather://city/{name}' }
124
+
125
+ async handle(params?: Record<string, string>) {
126
+ const city = params?.name ?? 'unknown'
127
+ return `Weather in ${city}: sunny, 22°C`
128
+ }
129
+ }
130
+ ```
131
+
132
+ Template resources are automatically registered via `ListResourceTemplates`. Parameters are extracted from the URI and passed to `handle()`.
133
+
134
+ ## Dependency Injection
135
+
136
+ When running inside a RudderJS app, tool/resource/prompt classes are resolved via the DI container — constructor dependencies are auto-injected:
137
+
138
+ ```ts
139
+ @Injectable()
140
+ @Description('Query the database')
141
+ export class DbQueryTool extends McpTool {
142
+ constructor(private db: DatabaseService) { super() }
143
+
144
+ schema() { return z.object({ query: z.string() }) }
145
+
146
+ async handle(input: Record<string, unknown>) {
147
+ const result = await this.db.query(input.query as string)
148
+ return McpResponse.json(result)
149
+ }
150
+ }
151
+ ```
152
+
153
+ Falls back to plain `new T()` when the DI container is not available.
154
+
91
155
  ## Registration
92
156
 
93
157
  ```ts
@@ -98,6 +162,10 @@ import { WeatherServer } from '../app/Mcp/Servers/WeatherServer.js'
98
162
  // HTTP endpoint (Streamable HTTP transport)
99
163
  Mcp.web('/mcp/weather', WeatherServer)
100
164
 
165
+ // With middleware (auth, rate limiting, etc.)
166
+ Mcp.web('/mcp/weather', WeatherServer)
167
+ .middleware([authMiddleware])
168
+
101
169
  // Local CLI command (stdio transport)
102
170
  Mcp.local('weather', WeatherServer)
103
171
  ```
package/dist/Mcp.d.ts CHANGED
@@ -1,16 +1,32 @@
1
1
  import type { McpServer } from './McpServer.js';
2
+ import type { OAuth2McpOptions } from './auth/oauth2.js';
2
3
  type ServerClass = new () => McpServer;
4
+ export interface McpWebEntry {
5
+ server: ServerClass;
6
+ middleware: unknown[];
7
+ /** Set when `.oauth2()` was chained on the builder. */
8
+ oauth2?: OAuth2McpOptions;
9
+ }
10
+ export interface McpWebBuilder {
11
+ /** Add middleware to this web MCP endpoint. */
12
+ middleware(mw: unknown[]): McpWebBuilder;
13
+ /**
14
+ * Protect this endpoint with OAuth 2.1 bearer tokens. Registers an RFC 9728
15
+ * Protected Resource Metadata endpoint alongside it.
16
+ *
17
+ * Requires `@rudderjs/passport` to be installed (used as the authorization
18
+ * server and token validator).
19
+ */
20
+ oauth2(options?: OAuth2McpOptions): McpWebBuilder;
21
+ }
3
22
  export declare class Mcp {
4
23
  private static webServers;
5
24
  private static localServers;
6
25
  /** Register an MCP server on an HTTP endpoint (Streamable HTTP transport) */
7
- static web(path: string, server: ServerClass, middleware?: unknown[]): void;
26
+ static web(path: string, server: ServerClass, middleware?: unknown[]): McpWebBuilder;
8
27
  /** Register an MCP server as a local CLI command (stdio transport) */
9
28
  static local(name: string, server: ServerClass): void;
10
- static getWebServers(): Map<string, {
11
- server: ServerClass;
12
- middleware: unknown[];
13
- }>;
29
+ static getWebServers(): Map<string, McpWebEntry>;
14
30
  static getLocalServers(): Map<string, ServerClass>;
15
31
  }
16
32
  export {};
package/dist/Mcp.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Mcp.d.ts","sourceRoot":"","sources":["../src/Mcp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,KAAK,WAAW,GAAG,UAAU,SAAS,CAAA;AAEtC,qBAAa,GAAG;IACd,OAAO,CAAC,MAAM,CAAC,UAAU,CAAyE;IAClG,OAAO,CAAC,MAAM,CAAC,YAAY,CAAsC;IAEjE,6EAA6E;IAC7E,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,GAAE,OAAO,EAAO,GAAG,IAAI;IAI/E,sEAAsE;IACtE,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;IAIrD,MAAM,CAAC,aAAa;gBAb6B,WAAW;oBAAc,OAAO,EAAE;;IAcnF,MAAM,CAAC,eAAe;CACvB"}
1
+ {"version":3,"file":"Mcp.d.ts","sourceRoot":"","sources":["../src/Mcp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAExD,KAAK,WAAW,GAAG,UAAU,SAAS,CAAA;AAEtC,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,WAAW,CAAA;IACnB,UAAU,EAAE,OAAO,EAAE,CAAA;IACrB,uDAAuD;IACvD,MAAM,CAAC,EAAE,gBAAgB,CAAA;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,UAAU,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,aAAa,CAAA;IACxC;;;;;;OAMG;IACH,MAAM,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,aAAa,CAAA;CAClD;AAED,qBAAa,GAAG;IACd,OAAO,CAAC,MAAM,CAAC,UAAU,CAAsC;IAC/D,OAAO,CAAC,MAAM,CAAC,YAAY,CAAsC;IAEjE,6EAA6E;IAC7E,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,GAAE,OAAO,EAAO,GAAG,aAAa;IAgBxF,sEAAsE;IACtE,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;IAIrD,MAAM,CAAC,aAAa,IAAI,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC;IAChD,MAAM,CAAC,eAAe,IAAI,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC;CACnD"}
package/dist/Mcp.js CHANGED
@@ -3,7 +3,19 @@ export class Mcp {
3
3
  static localServers = new Map();
4
4
  /** Register an MCP server on an HTTP endpoint (Streamable HTTP transport) */
5
5
  static web(path, server, middleware = []) {
6
- this.webServers.set(path, { server, middleware });
6
+ const entry = { server, middleware };
7
+ this.webServers.set(path, entry);
8
+ const builder = {
9
+ middleware(mw) {
10
+ entry.middleware.push(...mw);
11
+ return builder;
12
+ },
13
+ oauth2(options = {}) {
14
+ entry.oauth2 = options;
15
+ return builder;
16
+ },
17
+ };
18
+ return builder;
7
19
  }
8
20
  /** Register an MCP server as a local CLI command (stdio transport) */
9
21
  static local(name, server) {
package/dist/Mcp.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Mcp.js","sourceRoot":"","sources":["../src/Mcp.ts"],"names":[],"mappings":"AAIA,MAAM,OAAO,GAAG;IACN,MAAM,CAAC,UAAU,GAAgE,IAAI,GAAG,EAAE,CAAA;IAC1F,MAAM,CAAC,YAAY,GAA6B,IAAI,GAAG,EAAE,CAAA;IAEjE,6EAA6E;IAC7E,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,MAAmB,EAAE,aAAwB,EAAE;QACtE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAA;IACnD,CAAC;IAED,sEAAsE;IACtE,MAAM,CAAC,KAAK,CAAC,IAAY,EAAE,MAAmB;QAC5C,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACrC,CAAC;IAED,MAAM,CAAC,aAAa,KAAK,OAAO,IAAI,CAAC,UAAU,CAAA,CAAC,CAAC;IACjD,MAAM,CAAC,eAAe,KAAK,OAAO,IAAI,CAAC,YAAY,CAAA,CAAC,CAAC"}
1
+ {"version":3,"file":"Mcp.js","sourceRoot":"","sources":["../src/Mcp.ts"],"names":[],"mappings":"AAyBA,MAAM,OAAO,GAAG;IACN,MAAM,CAAC,UAAU,GAA6B,IAAI,GAAG,EAAE,CAAA;IACvD,MAAM,CAAC,YAAY,GAA6B,IAAI,GAAG,EAAE,CAAA;IAEjE,6EAA6E;IAC7E,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,MAAmB,EAAE,aAAwB,EAAE;QACtE,MAAM,KAAK,GAAgB,EAAE,MAAM,EAAE,UAAU,EAAE,CAAA;QACjD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QAChC,MAAM,OAAO,GAAkB;YAC7B,UAAU,CAAC,EAAa;gBACtB,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;gBAC5B,OAAO,OAAO,CAAA;YAChB,CAAC;YACD,MAAM,CAAC,UAA4B,EAAE;gBACnC,KAAK,CAAC,MAAM,GAAG,OAAO,CAAA;gBACtB,OAAO,OAAO,CAAA;YAChB,CAAC;SACF,CAAA;QACD,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,sEAAsE;IACtE,MAAM,CAAC,KAAK,CAAC,IAAY,EAAE,MAAmB;QAC5C,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACrC,CAAC;IAED,MAAM,CAAC,aAAa,KAA+B,OAAO,IAAI,CAAC,UAAU,CAAA,CAAC,CAAC;IAC3E,MAAM,CAAC,eAAe,KAA+B,OAAO,IAAI,CAAC,YAAY,CAAA,CAAC,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { z } from 'zod';
1
+ import type { ZodLikeObject } from './types.js';
2
2
  export interface McpPromptMessage {
3
3
  role: 'user' | 'assistant';
4
4
  content: string;
@@ -8,9 +8,12 @@ export declare abstract class McpPrompt {
8
8
  name(): string;
9
9
  /** Description */
10
10
  description(): string;
11
- /** Arguments schema */
12
- arguments?(): z.ZodObject<z.ZodRawShape>;
13
- /** Generate prompt messages */
14
- abstract handle(args: Record<string, unknown>): Promise<McpPromptMessage[]>;
11
+ /** Arguments schema — a Zod object (v3 or v4). */
12
+ arguments?(): ZodLikeObject;
13
+ /**
14
+ * Generate prompt messages. Extra parameters beyond `args` are resolved
15
+ * from the DI container when the method is decorated with `@Handle()`.
16
+ */
17
+ abstract handle(args: Record<string, unknown>, ...deps: unknown[]): Promise<McpPromptMessage[]>;
15
18
  }
16
19
  //# sourceMappingURL=McpPrompt.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"McpPrompt.d.ts","sourceRoot":"","sources":["../src/McpPrompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAI5B,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IAC1B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,8BAAsB,SAAS;IAC7B,kBAAkB;IAClB,IAAI,IAAI,MAAM;IAId,kBAAkB;IAClB,WAAW,IAAI,MAAM;IAIrB,uBAAuB;IACvB,SAAS,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;IAExC,+BAA+B;IAC/B,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAC5E"}
1
+ {"version":3,"file":"McpPrompt.d.ts","sourceRoot":"","sources":["../src/McpPrompt.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IAC1B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,8BAAsB,SAAS;IAC7B,kBAAkB;IAClB,IAAI,IAAI,MAAM;IAId,kBAAkB;IAClB,WAAW,IAAI,MAAM;IAIrB,kDAAkD;IAClD,SAAS,CAAC,IAAI,aAAa;IAE3B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAChG"}
@@ -1 +1 @@
1
- {"version":3,"file":"McpPrompt.js","sourceRoot":"","sources":["../src/McpPrompt.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAOhD,MAAM,OAAgB,SAAS;IAC7B,kBAAkB;IAClB,IAAI;QACF,OAAO,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAA;IAClE,CAAC;IAED,kBAAkB;IAClB,WAAW;QACT,OAAO,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAA;IAC/C,CAAC;CAOF"}
1
+ {"version":3,"file":"McpPrompt.js","sourceRoot":"","sources":["../src/McpPrompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAQhD,MAAM,OAAgB,SAAS;IAC7B,kBAAkB;IAClB,IAAI;QACF,OAAO,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAA;IAClE,CAAC;IAED,kBAAkB;IAClB,WAAW;QACT,OAAO,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAA;IAC/C,CAAC;CAUF"}
@@ -1,11 +1,17 @@
1
1
  export declare abstract class McpResource {
2
- /** Resource URI pattern */
2
+ /** Resource URI pattern — can contain `{param}` placeholders for templates */
3
3
  abstract uri(): string;
4
4
  /** MIME type */
5
5
  mimeType(): string;
6
6
  /** Resource description */
7
7
  description(): string;
8
- /** Handle resource read */
9
- abstract handle(): Promise<string>;
8
+ /** Whether this resource uses URI templates (has `{param}` placeholders) */
9
+ isTemplate(): boolean;
10
+ /**
11
+ * Handle resource read. Receives extracted params if this is a template
12
+ * resource. Extra parameters beyond `params` are resolved from the DI
13
+ * container when the method is decorated with `@Handle()`.
14
+ */
15
+ abstract handle(params?: Record<string, string>, ...deps: unknown[]): Promise<string>;
10
16
  }
11
17
  //# sourceMappingURL=McpResource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"McpResource.d.ts","sourceRoot":"","sources":["../src/McpResource.ts"],"names":[],"mappings":"AAEA,8BAAsB,WAAW;IAC/B,2BAA2B;IAC3B,QAAQ,CAAC,GAAG,IAAI,MAAM;IAEtB,gBAAgB;IAChB,QAAQ,IAAI,MAAM;IAIlB,2BAA2B;IAC3B,WAAW,IAAI,MAAM;IAIrB,2BAA2B;IAC3B,QAAQ,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;CACnC"}
1
+ {"version":3,"file":"McpResource.d.ts","sourceRoot":"","sources":["../src/McpResource.ts"],"names":[],"mappings":"AAEA,8BAAsB,WAAW;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,IAAI,MAAM;IAEtB,gBAAgB;IAChB,QAAQ,IAAI,MAAM;IAIlB,2BAA2B;IAC3B,WAAW,IAAI,MAAM;IAIrB,4EAA4E;IAC5E,UAAU,IAAI,OAAO;IAIrB;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CACtF"}
@@ -8,5 +8,9 @@ export class McpResource {
8
8
  description() {
9
9
  return getDescription(this.constructor) ?? '';
10
10
  }
11
+ /** Whether this resource uses URI templates (has `{param}` placeholders) */
12
+ isTemplate() {
13
+ return this.uri().includes('{');
14
+ }
11
15
  }
12
16
  //# sourceMappingURL=McpResource.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"McpResource.js","sourceRoot":"","sources":["../src/McpResource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,MAAM,OAAgB,WAAW;IAI/B,gBAAgB;IAChB,QAAQ;QACN,OAAO,YAAY,CAAA;IACrB,CAAC;IAED,2BAA2B;IAC3B,WAAW;QACT,OAAO,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAA;IAC/C,CAAC;CAIF"}
1
+ {"version":3,"file":"McpResource.js","sourceRoot":"","sources":["../src/McpResource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,MAAM,OAAgB,WAAW;IAI/B,gBAAgB;IAChB,QAAQ;QACN,OAAO,YAAY,CAAA;IACrB,CAAC;IAED,2BAA2B;IAC3B,WAAW;QACT,OAAO,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAA;IAC/C,CAAC;IAED,4EAA4E;IAC5E,UAAU;QACR,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;IACjC,CAAC;CAQF"}
package/dist/McpTool.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { z } from 'zod';
1
+ import type { ZodLikeObject } from './types.js';
2
2
  export interface McpToolResult {
3
3
  content: Array<{
4
4
  type: 'text';
@@ -15,9 +15,21 @@ export declare abstract class McpTool {
15
15
  name(): string;
16
16
  /** Tool description — override or use @Description decorator */
17
17
  description(): string;
18
- /** Input schema using Zod */
19
- abstract schema(): z.ZodObject<z.ZodRawShape>;
20
- /** Handle the tool call */
21
- abstract handle(input: Record<string, unknown>): Promise<McpToolResult>;
18
+ /** Input schema a Zod object (v3 or v4). */
19
+ abstract schema(): ZodLikeObject;
20
+ /** Optional output schema — advertises the structure of the tool's response. */
21
+ outputSchema?(): ZodLikeObject;
22
+ /**
23
+ * Handle the tool call.
24
+ *
25
+ * Extra parameters beyond `input` are resolved from the DI container when
26
+ * the method is decorated with `@Handle(Token1, …)`. Example:
27
+ *
28
+ * ```ts
29
+ * @Handle(Logger)
30
+ * async handle(input, logger: Logger) { ... }
31
+ * ```
32
+ */
33
+ abstract handle(input: Record<string, unknown>, ...deps: unknown[]): Promise<McpToolResult>;
22
34
  }
23
35
  //# sourceMappingURL=McpTool.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"McpTool.d.ts","sourceRoot":"","sources":["../src/McpTool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAI5B,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAClG,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,8BAAsB,OAAO;IAC3B,yGAAyG;IACzG,IAAI,IAAI,MAAM;IAId,gEAAgE;IAChE,WAAW,IAAI,MAAM;IAIrB,6BAA6B;IAC7B,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;IAE7C,2BAA2B;IAC3B,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;CACxE"}
1
+ {"version":3,"file":"McpTool.d.ts","sourceRoot":"","sources":["../src/McpTool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAClG,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,8BAAsB,OAAO;IAC3B,yGAAyG;IACzG,IAAI,IAAI,MAAM;IAId,gEAAgE;IAChE,WAAW,IAAI,MAAM;IAIrB,8CAA8C;IAC9C,QAAQ,CAAC,MAAM,IAAI,aAAa;IAEhC,gFAAgF;IAChF,YAAY,CAAC,IAAI,aAAa;IAE9B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC;CAC5F"}
@@ -1 +1 @@
1
- {"version":3,"file":"McpTool.js","sourceRoot":"","sources":["../src/McpTool.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAOhD,MAAM,OAAgB,OAAO;IAC3B,yGAAyG;IACzG,IAAI;QACF,OAAO,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;IAChE,CAAC;IAED,gEAAgE;IAChE,WAAW;QACT,OAAO,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAA;IAC/C,CAAC;CAOF"}
1
+ {"version":3,"file":"McpTool.js","sourceRoot":"","sources":["../src/McpTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAQhD,MAAM,OAAgB,OAAO;IAC3B,yGAAyG;IACzG,IAAI;QACF,OAAO,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;IAChE,CAAC;IAED,gEAAgE;IAChE,WAAW;QACT,OAAO,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAA;IAC/C,CAAC;CAoBF"}
@@ -0,0 +1,25 @@
1
+ import type { MiddlewareHandler } from '@rudderjs/core';
2
+ export interface OAuth2McpOptions {
3
+ /** Scopes required on the bearer token. Missing scopes → 403 `insufficient_scope`. */
4
+ scopes?: string[];
5
+ /** Canonical URL of this protected MCP resource. Defaults to the current request URL. */
6
+ resource?: string;
7
+ /**
8
+ * Authorization server URL(s) advertised via RFC 9728. Defaults to the app
9
+ * origin when `@rudderjs/passport` is installed as the in-app AS.
10
+ */
11
+ authorizationServers?: string[];
12
+ /** Scopes advertised in the protected-resource metadata document. */
13
+ scopesSupported?: string[];
14
+ }
15
+ /**
16
+ * Protect an MCP web endpoint with OAuth 2.1 Bearer tokens issued by
17
+ * `@rudderjs/passport`. On failure, adds an RFC 9728 `WWW-Authenticate`
18
+ * header pointing clients at the protected-resource metadata document.
19
+ */
20
+ export declare function oauth2McpMiddleware(mcpPath: string, options?: OAuth2McpOptions): MiddlewareHandler;
21
+ /** Register the RFC 9728 Protected Resource Metadata endpoint for an MCP path. */
22
+ export declare function registerOAuth2Metadata(router: {
23
+ get(path: string, handler: (req: unknown, res: unknown) => unknown, middleware?: unknown[]): unknown;
24
+ }, mcpPath: string, options: OAuth2McpOptions): void;
25
+ //# sourceMappingURL=oauth2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth2.d.ts","sourceRoot":"","sources":["../../src/auth/oauth2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,yFAAyF;IACzF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC/B,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAgCD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,iBAAiB,CA+DtG;AAED,kFAAkF;AAClF,wBAAgB,sBAAsB,CACpC,MAAM,EAAE;IACN,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,EAAE,UAAU,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAA;CACrG,EACD,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,gBAAgB,GACxB,IAAI,CAqBN"}
@@ -0,0 +1,128 @@
1
+ let passportPromise = null;
2
+ function loadPassport() {
3
+ if (!passportPromise) {
4
+ passportPromise = (async () => {
5
+ const { resolveOptionalPeer } = await import('@rudderjs/core');
6
+ return resolveOptionalPeer('@rudderjs/passport');
7
+ })().catch((err) => {
8
+ passportPromise = null;
9
+ throw err;
10
+ });
11
+ }
12
+ return passportPromise;
13
+ }
14
+ /**
15
+ * Protect an MCP web endpoint with OAuth 2.1 Bearer tokens issued by
16
+ * `@rudderjs/passport`. On failure, adds an RFC 9728 `WWW-Authenticate`
17
+ * header pointing clients at the protected-resource metadata document.
18
+ */
19
+ export function oauth2McpMiddleware(mcpPath, options = {}) {
20
+ const metadataPath = `/.well-known/oauth-protected-resource${mcpPath}`;
21
+ const requiredScopes = options.scopes ?? [];
22
+ return async function OAuth2McpMiddleware(req, res, next) {
23
+ const authHeader = req.headers['authorization'];
24
+ const metadataUrl = absoluteUrl(req, metadataPath);
25
+ if (!authHeader?.startsWith('Bearer ')) {
26
+ challenge(res, metadataUrl, 'invalid_token', 'Bearer token required.');
27
+ return;
28
+ }
29
+ let passport;
30
+ try {
31
+ passport = await loadPassport();
32
+ }
33
+ catch {
34
+ challenge(res, metadataUrl, 'invalid_token', 'OAuth provider not configured.');
35
+ return;
36
+ }
37
+ const jwt = authHeader.slice(7).trim();
38
+ let payload;
39
+ try {
40
+ payload = await passport.verifyToken(jwt);
41
+ }
42
+ catch {
43
+ challenge(res, metadataUrl, 'invalid_token', 'Invalid or expired token.');
44
+ return;
45
+ }
46
+ let token;
47
+ try {
48
+ token = await passport.AccessToken.query().where('id', payload.jti).first();
49
+ }
50
+ catch {
51
+ challenge(res, metadataUrl, 'invalid_token', 'Token could not be verified.');
52
+ return;
53
+ }
54
+ if (!token || token.revoked) {
55
+ challenge(res, metadataUrl, 'invalid_token', 'Token has been revoked.');
56
+ return;
57
+ }
58
+ if (requiredScopes.length > 0) {
59
+ const tokenScopes = Array.isArray(payload.scopes) ? payload.scopes : [];
60
+ const granted = tokenScopes.includes('*');
61
+ if (!granted) {
62
+ const missing = requiredScopes.filter((s) => !tokenScopes.includes(s));
63
+ if (missing.length > 0) {
64
+ challenge(res, metadataUrl, 'insufficient_scope', `Missing scope(s): ${missing.join(', ')}`, requiredScopes.join(' '));
65
+ return;
66
+ }
67
+ }
68
+ }
69
+ const raw = req.raw;
70
+ raw['__passport_token'] = token;
71
+ raw['__passport_scopes'] = payload.scopes;
72
+ raw['__passport_user_id'] = payload.sub;
73
+ await next();
74
+ };
75
+ }
76
+ /** Register the RFC 9728 Protected Resource Metadata endpoint for an MCP path. */
77
+ export function registerOAuth2Metadata(router, mcpPath, options) {
78
+ const metadataPath = `/.well-known/oauth-protected-resource${mcpPath}`;
79
+ router.get(metadataPath, (req, res) => {
80
+ const origin = absoluteUrl(req, '');
81
+ const resource = options.resource ?? `${origin}${mcpPath}`;
82
+ const authServers = options.authorizationServers && options.authorizationServers.length > 0
83
+ ? options.authorizationServers
84
+ : [origin];
85
+ const body = {
86
+ resource,
87
+ authorization_servers: authServers,
88
+ bearer_methods_supported: ['header'],
89
+ };
90
+ if (options.scopesSupported && options.scopesSupported.length > 0) {
91
+ body['scopes_supported'] = options.scopesSupported;
92
+ }
93
+ ;
94
+ res.json(body);
95
+ });
96
+ }
97
+ function absoluteUrl(req, path) {
98
+ const host = getHeader(req, 'x-forwarded-host')
99
+ ?? req.host
100
+ ?? getHeader(req, 'host')
101
+ ?? req.hostname
102
+ ?? 'localhost';
103
+ const proto = getHeader(req, 'x-forwarded-proto')
104
+ ?? req.protocol
105
+ ?? 'http';
106
+ return `${proto}://${host}${path}`;
107
+ }
108
+ function getHeader(req, name) {
109
+ const v = req.headers[name];
110
+ if (Array.isArray(v))
111
+ return v[0];
112
+ return v;
113
+ }
114
+ function challenge(res, metadataUrl, error, description, scope) {
115
+ const r = res;
116
+ const parts = [`resource_metadata="${metadataUrl}"`, `error="${error}"`];
117
+ if (description)
118
+ parts.push(`error_description="${description.replace(/"/g, '\\"')}"`);
119
+ if (scope)
120
+ parts.push(`scope="${scope}"`);
121
+ r.header?.('WWW-Authenticate', `Bearer ${parts.join(', ')}`);
122
+ const statusCode = error === 'insufficient_scope' ? 403 : 401;
123
+ const body = { error, error_description: description };
124
+ if (scope)
125
+ body['scope'] = scope;
126
+ r.status(statusCode).json(body);
127
+ }
128
+ //# sourceMappingURL=oauth2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth2.js","sourceRoot":"","sources":["../../src/auth/oauth2.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const INSPECTOR_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<title>MCP Inspector</title>\n<style>\n :root {\n --bg: #0f1115;\n --surface: #1a1d24;\n --border: #2a2f3a;\n --text: #e4e6eb;\n --muted: #8a919e;\n --accent: #5c9cf5;\n --error: #ff7b7b;\n --success: #8be78b;\n }\n * { box-sizing: border-box; }\n body {\n margin: 0; font: 14px/1.5 system-ui, sans-serif;\n background: var(--bg); color: var(--text);\n display: grid; grid-template-columns: 280px 1fr; height: 100vh;\n }\n aside {\n background: var(--surface); border-right: 1px solid var(--border);\n padding: 16px; overflow-y: auto;\n }\n aside h2 { font-size: 11px; text-transform: uppercase; color: var(--muted); margin: 0 0 8px; letter-spacing: .05em; }\n aside ul { list-style: none; padding: 0; margin: 0 0 20px; }\n aside li button {\n display: block; width: 100%; text-align: left;\n padding: 8px 12px; margin-bottom: 2px; border: 0; border-radius: 6px;\n background: transparent; color: var(--text); font: inherit; cursor: pointer;\n }\n aside li button:hover { background: rgba(255,255,255,.04); }\n aside li button.active { background: var(--accent); color: #fff; }\n main { padding: 24px 32px; overflow-y: auto; }\n h1 { margin: 0 0 4px; font-size: 20px; }\n .meta { color: var(--muted); margin-bottom: 24px; }\n section { margin-bottom: 32px; }\n section h3 { font-size: 12px; text-transform: uppercase; color: var(--muted); margin: 0 0 12px; letter-spacing: .05em; }\n .card {\n background: var(--surface); border: 1px solid var(--border); border-radius: 8px;\n padding: 14px 16px; margin-bottom: 8px;\n }\n .card-head { display: flex; justify-content: space-between; align-items: center; cursor: pointer; }\n .card-head strong { color: var(--text); }\n .card-head span { color: var(--muted); font-size: 13px; }\n .card-body { margin-top: 12px; border-top: 1px solid var(--border); padding-top: 12px; }\n .card-body[hidden] { display: none; }\n textarea, input {\n width: 100%; font: 12px ui-monospace, monospace; padding: 8px; border-radius: 6px;\n background: var(--bg); color: var(--text); border: 1px solid var(--border); resize: vertical;\n }\n button.run {\n margin-top: 10px; padding: 6px 14px; background: var(--accent); color: #fff; border: 0;\n border-radius: 6px; cursor: pointer; font: 13px system-ui;\n }\n button.run:hover { filter: brightness(1.1); }\n pre {\n background: var(--bg); padding: 12px; border-radius: 6px; overflow-x: auto;\n font: 12px ui-monospace, monospace; margin: 10px 0 0; max-height: 320px; overflow-y: auto;\n }\n .response.error { color: var(--error); }\n .response.ok { color: var(--success); }\n .empty { color: var(--muted); font-style: italic; }\n .pill {\n display: inline-block; padding: 1px 6px; border-radius: 3px; background: var(--border);\n color: var(--muted); font-size: 11px; margin-left: 6px;\n }\n</style>\n</head>\n<body>\n\n<aside>\n <h1 style=\"font-size:15px;margin:0 0 20px;\">\u26A1 MCP Inspector</h1>\n <h2>Web servers</h2>\n <ul id=\"web-list\"></ul>\n <h2>Local servers</h2>\n <ul id=\"local-list\"></ul>\n</aside>\n\n<main id=\"main\">\n <p class=\"empty\">Select a server from the sidebar.</p>\n</main>\n\n<script>\nasync function api(path, options) {\n const r = await fetch(path, options)\n if (!r.ok) throw new Error((await r.json().catch(() => ({}))).error || r.statusText)\n return r.json()\n}\n\nfunction el(tag, attrs, ...children) {\n const n = document.createElement(tag)\n if (attrs) for (const [k, v] of Object.entries(attrs)) {\n if (k === 'class') n.className = v\n else if (k.startsWith('on')) n[k] = v\n else if (v !== undefined && v !== null) n.setAttribute(k, v)\n }\n for (const c of children) {\n if (c == null) continue\n n.appendChild(typeof c === 'string' ? document.createTextNode(c) : c)\n }\n return n\n}\n\nlet activeKey = null\n\nasync function refreshList() {\n const { web, local } = await api('/api/servers')\n const render = (items, ul) => {\n ul.innerHTML = ''\n if (items.length === 0) {\n ul.appendChild(el('li', { class: 'empty' }, '(none)'))\n return\n }\n for (const s of items) {\n ul.appendChild(el('li', null, el('button', {\n class: s.key === activeKey ? 'active' : '',\n onclick: () => openServer(s.key),\n }, s.label)))\n }\n }\n render(web, document.getElementById('web-list'))\n render(local, document.getElementById('local-list'))\n}\n\nasync function openServer(key) {\n activeKey = key\n await refreshList()\n const main = document.getElementById('main')\n main.innerHTML = 'Loading\u2026'\n try {\n const detail = await api('/api/servers/' + encodeURIComponent(key))\n main.innerHTML = ''\n main.appendChild(el('h1', null, detail.metadata.name || detail.label))\n main.appendChild(el('div', { class: 'meta' },\n 'v' + detail.metadata.version + ' \u00B7 ' + detail.kind + ' \u00B7 ' + detail.key,\n ))\n if (detail.metadata.instructions) main.appendChild(el('p', { class: 'meta' }, detail.metadata.instructions))\n renderTools(main, key, detail.tools)\n renderResources(main, key, detail.resources)\n renderPrompts(main, key, detail.prompts)\n } catch (err) {\n main.innerHTML = ''\n main.appendChild(el('p', { class: 'response error' }, err.message))\n }\n}\n\nfunction renderTools(container, key, tools) {\n container.appendChild(el('section', null,\n el('h3', null, 'Tools (' + tools.length + ')'),\n ...(tools.length === 0 ? [el('p', { class: 'empty' }, 'No tools.')] : tools.map((t) => toolCard(key, t))),\n ))\n}\n\nfunction toolCard(key, t) {\n const inputEl = el('textarea', { rows: 4, 'aria-label': 'input' })\n inputEl.value = JSON.stringify(defaultFromSchema(t.inputSchema), null, 2)\n const outEl = el('pre', { class: 'response' })\n outEl.hidden = true\n const body = el('div', { class: 'card-body', hidden: true },\n el('div', { class: 'meta' }, t.description || '(no description)'),\n el('label', null, 'Input JSON'), inputEl,\n el('button', {\n class: 'run',\n onclick: async () => {\n outEl.hidden = false\n outEl.className = 'response'\n outEl.textContent = 'Running\u2026'\n try {\n const input = JSON.parse(inputEl.value)\n const r = await api('/api/servers/' + encodeURIComponent(key) + '/tools/' + encodeURIComponent(t.name), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(input),\n })\n outEl.className = 'response ' + (r.isError ? 'error' : 'ok')\n outEl.textContent = JSON.stringify(r, null, 2)\n } catch (e) {\n outEl.className = 'response error'\n outEl.textContent = e.message\n }\n },\n }, 'Call tool'),\n outEl,\n )\n const head = el('div', { class: 'card-head', onclick: () => { body.hidden = !body.hidden } },\n el('strong', null, t.name),\n el('span', null, (t.description || '').slice(0, 60)),\n )\n return el('div', { class: 'card' }, head, body)\n}\n\nfunction renderResources(container, key, resources) {\n container.appendChild(el('section', null,\n el('h3', null, 'Resources (' + resources.length + ')'),\n ...(resources.length === 0 ? [el('p', { class: 'empty' }, 'No resources.')] : resources.map((r) => resourceCard(key, r))),\n ))\n}\n\nfunction resourceCard(key, r) {\n const uriEl = el('input', { value: r.uri, 'aria-label': 'uri' })\n const outEl = el('pre', { class: 'response' })\n outEl.hidden = true\n const body = el('div', { class: 'card-body', hidden: true },\n el('label', null, 'URI' + (r.template ? ' (template \u2014 fill in {placeholders})' : '')),\n uriEl,\n el('button', {\n class: 'run',\n onclick: async () => {\n outEl.hidden = false\n outEl.className = 'response'\n outEl.textContent = 'Loading\u2026'\n try {\n const r2 = await api('/api/servers/' + encodeURIComponent(key) + '/resource?uri=' + encodeURIComponent(uriEl.value))\n outEl.className = 'response ok'\n outEl.textContent = JSON.stringify(r2, null, 2)\n } catch (e) {\n outEl.className = 'response error'\n outEl.textContent = e.message\n }\n },\n }, 'Read resource'),\n outEl,\n )\n return el('div', { class: 'card' },\n el('div', { class: 'card-head', onclick: () => { body.hidden = !body.hidden } },\n el('strong', null, r.uri),\n el('span', null, (r.description || '') + (r.template ? ' \u00B7 template' : '')),\n ),\n body,\n )\n}\n\nfunction renderPrompts(container, key, prompts) {\n container.appendChild(el('section', null,\n el('h3', null, 'Prompts (' + prompts.length + ')'),\n ...(prompts.length === 0 ? [el('p', { class: 'empty' }, 'No prompts.')] : prompts.map((p) => promptCard(key, p))),\n ))\n}\n\nfunction promptCard(key, p) {\n const argsEl = el('textarea', { rows: 3, 'aria-label': 'arguments' })\n argsEl.value = JSON.stringify(p.argumentSchema ? defaultFromSchema(p.argumentSchema) : {}, null, 2)\n const outEl = el('pre', { class: 'response' })\n outEl.hidden = true\n const body = el('div', { class: 'card-body', hidden: true },\n el('div', { class: 'meta' }, p.description || '(no description)'),\n el('label', null, 'Arguments JSON'), argsEl,\n el('button', {\n class: 'run',\n onclick: async () => {\n outEl.hidden = false\n outEl.className = 'response'\n outEl.textContent = 'Running\u2026'\n try {\n const args = JSON.parse(argsEl.value)\n const r = await api('/api/servers/' + encodeURIComponent(key) + '/prompts/' + encodeURIComponent(p.name), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(args),\n })\n outEl.className = 'response ok'\n outEl.textContent = JSON.stringify(r, null, 2)\n } catch (e) {\n outEl.className = 'response error'\n outEl.textContent = e.message\n }\n },\n }, 'Get prompt'),\n outEl,\n )\n return el('div', { class: 'card' },\n el('div', { class: 'card-head', onclick: () => { body.hidden = !body.hidden } },\n el('strong', null, p.name),\n el('span', null, (p.description || '').slice(0, 60)),\n ),\n body,\n )\n}\n\nfunction defaultFromSchema(schema) {\n if (!schema || schema.type !== 'object' || !schema.properties) return {}\n const out = {}\n for (const [k, v] of Object.entries(schema.properties)) {\n if (v.type === 'string') out[k] = ''\n else if (v.type === 'number' || v.type === 'integer') out[k] = 0\n else if (v.type === 'boolean') out[k] = false\n else if (v.type === 'array') out[k] = []\n else out[k] = null\n }\n return out\n}\n\nrefreshList()\n</script>\n</body>\n</html>";
2
+ //# sourceMappingURL=inspector-ui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspector-ui.d.ts","sourceRoot":"","sources":["../../src/commands/inspector-ui.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,ouVA0SnB,CAAA"}