@loopstack/mcp-module 0.2.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 (66) hide show
  1. package/README.md +213 -0
  2. package/dist/config/mcp-tool-config.schema.d.ts +11 -0
  3. package/dist/config/mcp-tool-config.schema.d.ts.map +1 -0
  4. package/dist/config/mcp-tool-config.schema.js +38 -0
  5. package/dist/config/mcp-tool-config.schema.js.map +1 -0
  6. package/dist/errors.d.ts +14 -0
  7. package/dist/errors.d.ts.map +1 -0
  8. package/dist/errors.js +17 -0
  9. package/dist/errors.js.map +1 -0
  10. package/dist/index.d.ts +8 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +8 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/mcp.module.d.ts +3 -0
  15. package/dist/mcp.module.d.ts.map +1 -0
  16. package/dist/mcp.module.js +31 -0
  17. package/dist/mcp.module.js.map +1 -0
  18. package/dist/services/env-reader.d.ts +8 -0
  19. package/dist/services/env-reader.d.ts.map +1 -0
  20. package/dist/services/env-reader.js +10 -0
  21. package/dist/services/env-reader.js.map +1 -0
  22. package/dist/services/index.d.ts +5 -0
  23. package/dist/services/index.d.ts.map +1 -0
  24. package/dist/services/index.js +5 -0
  25. package/dist/services/index.js.map +1 -0
  26. package/dist/services/mcp-client.service.d.ts +39 -0
  27. package/dist/services/mcp-client.service.d.ts.map +1 -0
  28. package/dist/services/mcp-client.service.js +309 -0
  29. package/dist/services/mcp-client.service.js.map +1 -0
  30. package/dist/services/mcp-sdk.types.d.ts +14 -0
  31. package/dist/services/mcp-sdk.types.d.ts.map +1 -0
  32. package/dist/services/mcp-sdk.types.js +2 -0
  33. package/dist/services/mcp-sdk.types.js.map +1 -0
  34. package/dist/services/metrics-port.d.ts +26 -0
  35. package/dist/services/metrics-port.d.ts.map +1 -0
  36. package/dist/services/metrics-port.js +6 -0
  37. package/dist/services/metrics-port.js.map +1 -0
  38. package/dist/tools/index.d.ts +5 -0
  39. package/dist/tools/index.d.ts.map +1 -0
  40. package/dist/tools/index.js +5 -0
  41. package/dist/tools/index.js.map +1 -0
  42. package/dist/tools/mcp-call.tool.d.ts +19 -0
  43. package/dist/tools/mcp-call.tool.d.ts.map +1 -0
  44. package/dist/tools/mcp-call.tool.js +36 -0
  45. package/dist/tools/mcp-call.tool.js.map +1 -0
  46. package/dist/tools/mcp-connection-args.schema.d.ts +11 -0
  47. package/dist/tools/mcp-connection-args.schema.d.ts.map +1 -0
  48. package/dist/tools/mcp-connection-args.schema.js +13 -0
  49. package/dist/tools/mcp-connection-args.schema.js.map +1 -0
  50. package/dist/tools/mcp-list-tools.tool.d.ts +17 -0
  51. package/dist/tools/mcp-list-tools.tool.d.ts.map +1 -0
  52. package/dist/tools/mcp-list-tools.tool.js +32 -0
  53. package/dist/tools/mcp-list-tools.tool.js.map +1 -0
  54. package/dist/tools/mcp-tool-base.d.ts +8 -0
  55. package/dist/tools/mcp-tool-base.d.ts.map +1 -0
  56. package/dist/tools/mcp-tool-base.js +26 -0
  57. package/dist/tools/mcp-tool-base.js.map +1 -0
  58. package/dist/types.d.ts +2 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +2 -0
  61. package/dist/types.js.map +1 -0
  62. package/dist/utils/url-security.d.ts +8 -0
  63. package/dist/utils/url-security.d.ts.map +1 -0
  64. package/dist/utils/url-security.js +192 -0
  65. package/dist/utils/url-security.js.map +1 -0
  66. package/package.json +50 -0
package/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # @loopstack/mcp-module
2
+
3
+ > Remote-MCP client tools for the [Loopstack AI](https://loopstack.ai) automation framework.
4
+
5
+ Lets a Loopstack agent list and call tools on remote Model Context Protocol (MCP)
6
+ servers over HTTPS — Streamable HTTP or legacy SSE — with a strict SSRF allowlist
7
+ and zero-trust handling of authentication secrets.
8
+
9
+ ## What this is (and isn't)
10
+
11
+ - **Is:** a _client_ module — your Loopstack app reaches _out_ to a remote MCP
12
+ server (Linear, GitHub, internal tools, etc.).
13
+ - **Isn't:** an MCP _server_ — it does not expose your workflows over MCP.
14
+
15
+ ## Tools
16
+
17
+ | Tool | Purpose |
18
+ | ------------------ | ----------------------------------------------- |
19
+ | `McpListToolsTool` | Discover the tools a remote MCP server exposes. |
20
+ | `McpCallTool` | Invoke a tool on a remote MCP server. |
21
+
22
+ Both take `serverUrl`, `transport` (`streamableHttp` or `sse`), and `timeoutMs`
23
+ at call time. `McpCallTool` additionally takes `toolName` and `arguments`.
24
+
25
+ ## Security model
26
+
27
+ Every connection passes three checks before any bytes go out:
28
+
29
+ 1. **Allowlist** — `serverUrl`'s hostname must match `allowedHosts`. Exact match
30
+ or `*.example.com` (which also matches `example.com`).
31
+ 2. **Scheme** — `https://` by default; `http://` only if `allowInsecureHttp: true`.
32
+ 3. **Public-IP resolution** — DNS (or a literal IP) must resolve to a routable
33
+ public address. Loopback, RFC1918, link-local, ULA, and IPv4-mapped
34
+ equivalents are rejected. Override with `allowPrivateHosts: true` for trusted
35
+ local MCP proxies.
36
+
37
+ Userinfo in the URL (`https://user:pw@host/...`) is rejected — credentials must
38
+ flow through headers.
39
+
40
+ ## Authentication
41
+
42
+ Configure auth headers via `@InjectTool(...)`:
43
+
44
+ ```ts
45
+ @InjectTool({
46
+ allowedHosts: ['mcp.linear.app'],
47
+ hostHeaderEnv: {
48
+ 'mcp.linear.app': { Authorization: 'LINEAR_MCP_TOKEN' },
49
+ },
50
+ })
51
+ private readonly mcpCallTool: McpCallTool;
52
+ ```
53
+
54
+ Three knobs, in increasing specificity:
55
+
56
+ | Knob | Use for |
57
+ | ---------------- | ----------------------------------------------------------------------------------------------------------------------- |
58
+ | `defaultHeaders` | Static, **non-secret** values (e.g. `X-Trace: on`). Sensitive header names like `Authorization` are rejected here. |
59
+ | `headerEnv` | `header → env-var` mapping applied to _every_ host. Value is read from `process.env` at call time. |
60
+ | `hostHeaderEnv` | `host → { header → env-var }`. Use the hostname or `'*'` (applied to all). Host-specific entries override the wildcard. |
61
+
62
+ Precedence (later wins): `defaultHeaders` → `headerEnv` → `hostHeaderEnv['*']` → `hostHeaderEnv[hostname]`. `hostHeaderEnv['*']` outranks `headerEnv` because it lives in the same map as the host-specific entries — keeping all host-scoped knobs together in `hostHeaderEnv` is the intended override layer. Keys are matched case-insensitively (HTTP semantics).
63
+
64
+ Header _names_ are logged on connect (e.g. `headers=[Authorization]`); values
65
+ never are. If a referenced env var is unset or empty, the header is silently
66
+ omitted — so missing `LINEAR_MCP_TOKEN` means no `Authorization` header, not a
67
+ crash.
68
+
69
+ The header value is sent **raw**. If the remote server expects `Bearer <token>`,
70
+ your env var must contain the `Bearer ` prefix:
71
+
72
+ ```env
73
+ LINEAR_MCP_TOKEN="Bearer lin_oauth_..."
74
+ ```
75
+
76
+ ## Registering the tools (workspace vs workflow)
77
+
78
+ Import `McpModule` in your Nest module so the tool classes are available. Then
79
+ register **instances** with `@InjectTool()` — where you put that decorator
80
+ depends on how you run the LLM loop.
81
+
82
+ | How you run the agent | Where to `@InjectTool` MCP tools |
83
+ | -------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
84
+ | **`ChatAgentWorkflow` / `AgentWorkflow` as a sub-workflow** (`this.agent.run({ tools: [...] })`) | **Workspace** — the child agent resolves tools from the executing workflow first, then the workspace. Tools on the parent workflow are not visible while the sub-agent runs. |
85
+ | **Inline agent loop in one workflow** (your transitions call `this.llmGenerateText.call()` / `this.llmDelegateToolCalls.call()` on the same class) | **That workflow** — same pattern as other registry agents (e.g. Google Workspace). |
86
+
87
+ See [@loopstack/agent — Tool Resolution]() for the full resolution order.
88
+
89
+ ### With `ChatAgentWorkflow` (register on the workspace)
90
+
91
+ This is the pattern used by `@loopstack/mcp-linear-example-workflow` and the app
92
+ template: declare MCP tools once on the workspace, then pass their property names
93
+ to `agent.run()`.
94
+
95
+ ```ts
96
+ import { Injectable } from '@nestjs/common';
97
+ import { ChatAgentWorkflow } from '@loopstack/agent';
98
+ import { InjectTool, InjectWorkflow, Workspace } from '@loopstack/common';
99
+ import { McpCallTool, McpListToolsTool } from '@loopstack/mcp-module';
100
+ import { MyMcpWorkflow } from './my-mcp.workflow';
101
+
102
+ const mcpToolConfig = {
103
+ allowedHosts: ['mcp.linear.app', 'mcp.github.com'],
104
+ hostHeaderEnv: {
105
+ 'mcp.linear.app': { Authorization: 'LINEAR_MCP_TOKEN' },
106
+ 'mcp.github.com': { Authorization: 'GITHUB_MCP_TOKEN' },
107
+ },
108
+ } as const;
109
+
110
+ @Injectable()
111
+ @Workspace({ uiConfig: { title: 'My Workspace' } })
112
+ export class MyWorkspace {
113
+ @InjectWorkflow() mcpAgent: MyMcpWorkflow;
114
+
115
+ @InjectTool(mcpToolConfig)
116
+ mcpListTools: McpListToolsTool;
117
+
118
+ @InjectTool(mcpToolConfig)
119
+ mcpCallTool: McpCallTool;
120
+ }
121
+ ```
122
+
123
+ ```ts
124
+ // my-mcp.workflow.ts — parent only starts the sub-agent
125
+ @InjectWorkflow({ model: 'claude-sonnet-4-6' }) private agent: ChatAgentWorkflow;
126
+
127
+ await this.agent.run({
128
+ system: '...',
129
+ tools: ['mcpListTools', 'mcpCallTool'],
130
+ userMessage: '...',
131
+ });
132
+ ```
133
+
134
+ ### Inline agent loop (register on the workflow)
135
+
136
+ If your workflow owns the LLM turns and tool delegation (no `ChatAgentWorkflow`
137
+ sub-workflow), inject MCP tools on that same workflow class alongside
138
+ `LlmGenerateTextTool` and `LlmDelegateToolCallsTool`.
139
+
140
+ The agent picks `serverUrl` per call, so it can hop between any of the
141
+ allowlisted hosts within the same chat.
142
+
143
+ ## Multiple MCP servers
144
+
145
+ `serverUrl` is a per-call argument. **An agent can reach any host listed in
146
+ `allowedHosts`** — there is no "primary" server. To add a new server:
147
+
148
+ 1. Add its hostname to `allowedHosts` on both `@InjectTool` configs.
149
+ 2. Add its auth mapping to `hostHeaderEnv`.
150
+ 3. Set the corresponding env var.
151
+
152
+ Dynamic, end-user-driven server registration (paste any URL mid-chat) is
153
+ deliberately _not_ supported — the allowlist exists to prevent agents from
154
+ being tricked into hitting internal hosts. Adding that flow safely requires
155
+ a per-user registry plus an auth onboarding step (out of scope here).
156
+
157
+ ## Transport guidance
158
+
159
+ | Server | Transport |
160
+ | ------------------------- | -------------------------- |
161
+ | Linear (`mcp.linear.app`) | `sse` |
162
+ | Most modern hosted MCP | `streamableHttp` (default) |
163
+
164
+ Pass `transport: 'sse'` per call when needed.
165
+
166
+ ## Errors
167
+
168
+ All failures throw subclasses of `McpError`:
169
+
170
+ - `McpUrlSecurityError` — SSRF / allowlist / scheme / userinfo violations.
171
+ - `McpAuthError` — 401 / 403 from the remote (or transport-equivalent).
172
+ - `McpTimeoutError` — call exceeded `timeoutMs`.
173
+ - `McpProtocolError` — malformed MCP response.
174
+ - `McpTransportError` — DNS, TCP, TLS, abort, fallback.
175
+
176
+ Catch `McpError` for any failure, or a specific subclass to react to a category.
177
+
178
+ ## Observability
179
+
180
+ The service logs structured events with header _names_ only, never values:
181
+
182
+ ```
183
+ mcp.connect host=mcp.linear.app transport=sse headers=[Authorization]
184
+ mcp.connect.done host=mcp.linear.app transport=sse outcome=success latencyMs=412
185
+ mcp.callTool host=mcp.linear.app transport=sse toolName=createIssue outcome=success latencyMs=623
186
+ ```
187
+
188
+ For metrics, implement `McpMetricsPort` and bind it via the `MCP_METRICS`
189
+ provider token — the default is a no-op:
190
+
191
+ ```ts
192
+ @Module({
193
+ providers: [{ provide: MCP_METRICS, useClass: MyOtelMetrics }],
194
+ })
195
+ ```
196
+
197
+ Same pattern for `MCP_ENV_READER` if you need to source secrets from somewhere
198
+ other than `process.env` (e.g. a secrets manager).
199
+
200
+ ## Testing
201
+
202
+ The module ships unit tests for:
203
+
204
+ - `hostMatchesAllowlist`, `assertIpIsPublic`, `assertMcpUrlSafe` (with mocked DNS)
205
+ - `McpClientService.mergeHeaders` (precedence, host scoping, env-var skipping)
206
+ - Both tools (config guard + arg forwarding)
207
+ - Config-schema validation (secret-header rejection, strict mode, required keys)
208
+
209
+ Run with:
210
+
211
+ ```sh
212
+ npm test
213
+ ```
@@ -0,0 +1,11 @@
1
+ import { z } from 'zod';
2
+ export declare const McpToolConfigSchema: z.ZodObject<{
3
+ allowedHosts: z.ZodArray<z.ZodString>;
4
+ allowInsecureHttp: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
5
+ allowPrivateHosts: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
6
+ defaultHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
7
+ headerEnv: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
8
+ hostHeaderEnv: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>>;
9
+ }, z.core.$strict>;
10
+ export type McpToolConfig = z.infer<typeof McpToolConfigSchema>;
11
+ //# sourceMappingURL=mcp-tool-config.schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-tool-config.schema.d.ts","sourceRoot":"","sources":["../../src/config/mcp-tool-config.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAuBxB,eAAO,MAAM,mBAAmB;;;;;;;kBAiC5B,CAAC;AAEL,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ const RESERVED_SECRET_HEADER_NAMES = new Set([
3
+ 'authorization',
4
+ 'proxy-authorization',
5
+ 'cookie',
6
+ 'set-cookie',
7
+ 'x-api-key',
8
+ ]);
9
+ function rejectSecretsInDefaults(defaults, ctx) {
10
+ if (!defaults)
11
+ return;
12
+ for (const name of Object.keys(defaults)) {
13
+ if (RESERVED_SECRET_HEADER_NAMES.has(name.toLowerCase())) {
14
+ ctx.addIssue({
15
+ code: 'custom',
16
+ path: ['defaultHeaders', name],
17
+ message: `Header "${name}" likely carries a secret; use headerEnv or hostHeaderEnv instead of defaultHeaders.`,
18
+ });
19
+ }
20
+ }
21
+ }
22
+ export const McpToolConfigSchema = z
23
+ .object({
24
+ allowedHosts: z
25
+ .array(z.string().min(1))
26
+ .min(1)
27
+ .describe('Hostnames allowed for `serverUrl`. Use `*.example.com` to allow any subdomain of example.com (also matches example.com).'),
28
+ allowInsecureHttp: z.boolean().optional().default(false),
29
+ allowPrivateHosts: z.boolean().optional().default(false),
30
+ defaultHeaders: z.record(z.string(), z.string()).optional(),
31
+ headerEnv: z.record(z.string(), z.string()).optional(),
32
+ hostHeaderEnv: z.record(z.string(), z.record(z.string(), z.string())).optional(),
33
+ })
34
+ .strict()
35
+ .superRefine((cfg, ctx) => {
36
+ rejectSecretsInDefaults(cfg.defaultHeaders, ctx);
37
+ });
38
+ //# sourceMappingURL=mcp-tool-config.schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-tool-config.schema.js","sourceRoot":"","sources":["../../src/config/mcp-tool-config.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,4BAA4B,GAAwB,IAAI,GAAG,CAAC;IAChE,eAAe;IACf,qBAAqB;IACrB,QAAQ;IACR,YAAY;IACZ,WAAW;CACZ,CAAC,CAAC;AAEH,SAAS,uBAAuB,CAAC,QAA4C,EAAE,GAAoB;IACjG,IAAI,CAAC,QAAQ;QAAE,OAAO;IACtB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzC,IAAI,4BAA4B,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YACzD,GAAG,CAAC,QAAQ,CAAC;gBACX,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,CAAC,gBAAgB,EAAE,IAAI,CAAC;gBAC9B,OAAO,EAAE,WAAW,IAAI,sFAAsF;aAC/G,CAAC,CAAC;QACL,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC;KACjC,MAAM,CAAC;IACN,YAAY,EAAE,CAAC;SACZ,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;SACxB,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,0HAA0H,CAC3H;IAEH,iBAAiB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAOxD,iBAAiB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAGxD,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAG3D,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAMtD,aAAa,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE;CACjF,CAAC;KACD,MAAM,EAAE;KACR,WAAW,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IACxB,uBAAuB,CAAC,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC"}
@@ -0,0 +1,14 @@
1
+ export declare abstract class McpError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class McpUrlSecurityError extends McpError {
5
+ }
6
+ export declare class McpAuthError extends McpError {
7
+ }
8
+ export declare class McpTimeoutError extends McpError {
9
+ }
10
+ export declare class McpProtocolError extends McpError {
11
+ }
12
+ export declare class McpTransportError extends McpError {
13
+ }
14
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,8BAAsB,QAAS,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,mBAAoB,SAAQ,QAAQ;CAAG;AAEpD,qBAAa,YAAa,SAAQ,QAAQ;CAAG;AAC7C,qBAAa,eAAgB,SAAQ,QAAQ;CAAG;AAChD,qBAAa,gBAAiB,SAAQ,QAAQ;CAAG;AAEjD,qBAAa,iBAAkB,SAAQ,QAAQ;CAAG"}
package/dist/errors.js ADDED
@@ -0,0 +1,17 @@
1
+ export class McpError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = new.target.name;
5
+ }
6
+ }
7
+ export class McpUrlSecurityError extends McpError {
8
+ }
9
+ export class McpAuthError extends McpError {
10
+ }
11
+ export class McpTimeoutError extends McpError {
12
+ }
13
+ export class McpProtocolError extends McpError {
14
+ }
15
+ export class McpTransportError extends McpError {
16
+ }
17
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAgB,QAAS,SAAQ,KAAK;IAC1C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,QAAQ;CAAG;AAEpD,MAAM,OAAO,YAAa,SAAQ,QAAQ;CAAG;AAC7C,MAAM,OAAO,eAAgB,SAAQ,QAAQ;CAAG;AAChD,MAAM,OAAO,gBAAiB,SAAQ,QAAQ;CAAG;AAEjD,MAAM,OAAO,iBAAkB,SAAQ,QAAQ;CAAG"}
@@ -0,0 +1,8 @@
1
+ export * from './config/mcp-tool-config.schema.js';
2
+ export * from './errors.js';
3
+ export * from './mcp.module.js';
4
+ export * from './services/index.js';
5
+ export * from './tools/index.js';
6
+ export * from './types.js';
7
+ export * from './utils/url-security.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oCAAoC,CAAC;AACnD,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export * from './config/mcp-tool-config.schema.js';
2
+ export * from './errors.js';
3
+ export * from './mcp.module.js';
4
+ export * from './services/index.js';
5
+ export * from './tools/index.js';
6
+ export * from './types.js';
7
+ export * from './utils/url-security.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oCAAoC,CAAC;AACnD,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare class McpModule {
2
+ }
3
+ //# sourceMappingURL=mcp.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.module.d.ts","sourceRoot":"","sources":["../src/mcp.module.ts"],"names":[],"mappings":"AAiBA,qBAKa,SAAS;CAAG"}
@@ -0,0 +1,31 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Module } from '@nestjs/common';
8
+ import { LoopCoreModule } from '@loopstack/core';
9
+ import { MCP_ENV_READER, ProcessEnvReader } from './services/env-reader.js';
10
+ import { McpClientService } from './services/index.js';
11
+ import { MCP_METRICS, NoopMcpMetrics } from './services/metrics-port.js';
12
+ import { McpCallTool, McpListToolsTool } from './tools/index.js';
13
+ const envReaderProvider = {
14
+ provide: MCP_ENV_READER,
15
+ useClass: ProcessEnvReader,
16
+ };
17
+ const metricsProvider = {
18
+ provide: MCP_METRICS,
19
+ useClass: NoopMcpMetrics,
20
+ };
21
+ let McpModule = class McpModule {
22
+ };
23
+ McpModule = __decorate([
24
+ Module({
25
+ imports: [LoopCoreModule],
26
+ providers: [envReaderProvider, metricsProvider, McpClientService, McpListToolsTool, McpCallTool],
27
+ exports: [MCP_ENV_READER, MCP_METRICS, McpClientService, McpListToolsTool, McpCallTool],
28
+ })
29
+ ], McpModule);
30
+ export { McpModule };
31
+ //# sourceMappingURL=mcp.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.module.js","sourceRoot":"","sources":["../src/mcp.module.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,MAAM,EAAY,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEjE,MAAM,iBAAiB,GAAa;IAClC,OAAO,EAAE,cAAc;IACvB,QAAQ,EAAE,gBAAgB;CAC3B,CAAC;AAEF,MAAM,eAAe,GAAa;IAChC,OAAO,EAAE,WAAW;IACpB,QAAQ,EAAE,cAAc;CACzB,CAAC;AAOK,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,SAAS;IALrB,MAAM,CAAC;QACN,OAAO,EAAE,CAAC,cAAc,CAAC;QACzB,SAAS,EAAE,CAAC,iBAAiB,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,CAAC;QAChG,OAAO,EAAE,CAAC,cAAc,EAAE,WAAW,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,CAAC;KACxF,CAAC;GACW,SAAS,CAAG"}
@@ -0,0 +1,8 @@
1
+ export interface EnvReader {
2
+ get(key: string): string | undefined;
3
+ }
4
+ export declare class ProcessEnvReader implements EnvReader {
5
+ get(key: string): string | undefined;
6
+ }
7
+ export declare const MCP_ENV_READER: unique symbol;
8
+ //# sourceMappingURL=env-reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-reader.d.ts","sourceRoot":"","sources":["../../src/services/env-reader.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACtC;AAED,qBAAa,gBAAiB,YAAW,SAAS;IAChD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAKrC;AAED,eAAO,MAAM,cAAc,eAA2B,CAAC"}
@@ -0,0 +1,10 @@
1
+ export class ProcessEnvReader {
2
+ get(key) {
3
+ const v = process.env[key];
4
+ if (v === undefined || v === '')
5
+ return undefined;
6
+ return v;
7
+ }
8
+ }
9
+ export const MCP_ENV_READER = Symbol('MCP_ENV_READER');
10
+ //# sourceMappingURL=env-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-reader.js","sourceRoot":"","sources":["../../src/services/env-reader.ts"],"names":[],"mappings":"AAIA,MAAM,OAAO,gBAAgB;IAC3B,GAAG,CAAC,GAAW;QACb,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE;YAAE,OAAO,SAAS,CAAC;QAClD,OAAO,CAAC,CAAC;IACX,CAAC;CACF;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from './env-reader.js';
2
+ export * from './mcp-client.service.js';
3
+ export * from './mcp-sdk.types.js';
4
+ export * from './metrics-port.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from './env-reader.js';
2
+ export * from './mcp-client.service.js';
3
+ export * from './mcp-sdk.types.js';
4
+ export * from './metrics-port.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,39 @@
1
+ import type { McpToolConfig } from '../config/mcp-tool-config.schema.js';
2
+ import type { McpTransportKind } from '../types.js';
3
+ import { EnvReader } from './env-reader.js';
4
+ import { McpMetricsPort } from './metrics-port.js';
5
+ export type { McpTransportKind };
6
+ export interface McpClientCallOptions {
7
+ timeoutMs?: number;
8
+ skipDnsResolution?: boolean;
9
+ transport?: McpTransportKind;
10
+ }
11
+ export type McpCallToolResult = {
12
+ kind: 'legacyToolResult';
13
+ toolResult: unknown;
14
+ meta?: unknown;
15
+ } | {
16
+ kind: 'callToolResult';
17
+ content: unknown;
18
+ structuredContent?: unknown;
19
+ isError?: unknown;
20
+ meta?: unknown;
21
+ };
22
+ export declare class McpClientService {
23
+ private readonly env;
24
+ private readonly metrics;
25
+ private readonly logger;
26
+ constructor(env?: EnvReader, metrics?: McpMetricsPort);
27
+ listTools(rawUrl: string, config: McpToolConfig, options?: McpClientCallOptions): Promise<{
28
+ tools: unknown;
29
+ }>;
30
+ callTool(rawUrl: string, config: McpToolConfig, toolName: string, toolArgs: Record<string, unknown>, options?: McpClientCallOptions): Promise<McpCallToolResult>;
31
+ mergeHeaders(config: McpToolConfig, url: URL): Record<string, string>;
32
+ private applyEnvMap;
33
+ private connect;
34
+ private dispose;
35
+ private mapSdkError;
36
+ private recordConnect;
37
+ private recordCall;
38
+ }
39
+ //# sourceMappingURL=mcp-client.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-client.service.d.ts","sourceRoot":"","sources":["../../src/services/mcp-client.service.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EAAE,SAAS,EAAoC,MAAM,iBAAiB,CAAC;AAE9E,OAAO,EAKL,cAAc,EAEf,MAAM,mBAAmB,CAAC;AAa3B,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GACjE;IACE,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEN,qBACa,gBAAgB;IAIW,OAAO,CAAC,QAAQ,CAAC,GAAG;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJ3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;gBAGL,GAAG,GAAE,SAAkC,EAC1C,OAAO,GAAE,cAAqC;IAG5F,SAAS,CACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IA6BxB,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,iBAAiB,CAAC;IAyD7B,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAwBrE,OAAO,CAAC,WAAW;YAOL,OAAO;YA4EP,OAAO;IAUrB,OAAO,CAAC,WAAW;IA0BnB,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,UAAU;CAWnB"}