mcpose 2.0.2 → 3.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 (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +494 -37
  3. package/dist/backendClient.d.ts +5 -0
  4. package/dist/backendClient.d.ts.map +1 -1
  5. package/dist/backendClient.js +17 -9
  6. package/dist/backendClient.js.map +1 -1
  7. package/dist/core.d.ts +293 -28
  8. package/dist/core.d.ts.map +1 -1
  9. package/dist/core.js +969 -175
  10. package/dist/core.js.map +1 -1
  11. package/dist/delegation.d.ts +89 -0
  12. package/dist/delegation.d.ts.map +1 -0
  13. package/dist/delegation.js +175 -0
  14. package/dist/delegation.js.map +1 -0
  15. package/dist/eventStore.d.ts +12 -0
  16. package/dist/eventStore.d.ts.map +1 -1
  17. package/dist/eventStore.js +43 -5
  18. package/dist/eventStore.js.map +1 -1
  19. package/dist/hiddenTools.d.ts +42 -0
  20. package/dist/hiddenTools.d.ts.map +1 -0
  21. package/dist/hiddenTools.js +53 -0
  22. package/dist/hiddenTools.js.map +1 -0
  23. package/dist/index.d.ts +15 -6
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +7 -3
  26. package/dist/index.js.map +1 -1
  27. package/dist/mesh.d.ts +82 -0
  28. package/dist/mesh.d.ts.map +1 -0
  29. package/dist/mesh.js +141 -0
  30. package/dist/mesh.js.map +1 -0
  31. package/dist/middleware.d.ts +13 -0
  32. package/dist/middleware.d.ts.map +1 -1
  33. package/dist/middleware.js +27 -3
  34. package/dist/middleware.js.map +1 -1
  35. package/dist/proxyContext.d.ts +65 -5
  36. package/dist/proxyContext.d.ts.map +1 -1
  37. package/dist/proxyContext.js +39 -4
  38. package/dist/proxyContext.js.map +1 -1
  39. package/dist/rejection.d.ts +1 -1
  40. package/dist/rejection.d.ts.map +1 -1
  41. package/dist/rejection.js +1 -1
  42. package/dist/rejection.js.map +1 -1
  43. package/dist/sanitizeCatalog.d.ts +37 -0
  44. package/dist/sanitizeCatalog.d.ts.map +1 -0
  45. package/dist/sanitizeCatalog.js +86 -0
  46. package/dist/sanitizeCatalog.js.map +1 -0
  47. package/dist/sessionRegistry.d.ts +79 -0
  48. package/dist/sessionRegistry.d.ts.map +1 -0
  49. package/dist/sessionRegistry.js +83 -0
  50. package/dist/sessionRegistry.js.map +1 -0
  51. package/dist/telemetry.d.ts +29 -1
  52. package/dist/telemetry.d.ts.map +1 -1
  53. package/dist/testing.d.ts +18 -2
  54. package/dist/testing.d.ts.map +1 -1
  55. package/dist/testing.js +30 -8
  56. package/dist/testing.js.map +1 -1
  57. package/dist/version.d.ts +7 -0
  58. package/dist/version.d.ts.map +1 -0
  59. package/dist/version.js +7 -0
  60. package/dist/version.js.map +1 -0
  61. package/package.json +30 -14
  62. package/src/__tests__/_helpers.ts +64 -0
  63. package/src/__tests__/backendClient.test.ts +198 -0
  64. package/src/__tests__/core.delegation.test.ts +964 -0
  65. package/src/__tests__/core.edge.test.ts +314 -0
  66. package/src/__tests__/core.hiddenTools.predicate.test.ts +236 -0
  67. package/src/__tests__/core.localTools.test.ts +314 -0
  68. package/src/__tests__/core.mesh.test.ts +1182 -0
  69. package/src/__tests__/core.stripRequestMeta.test.ts +226 -0
  70. package/src/__tests__/core.stripResultMeta.test.ts +217 -0
  71. package/src/__tests__/core.test.ts +1019 -0
  72. package/src/__tests__/eventStore.test.ts +141 -0
  73. package/src/__tests__/mapToolResult.test.ts +101 -0
  74. package/src/__tests__/middleware.edge.test.ts +126 -0
  75. package/src/__tests__/middleware.test.ts +233 -0
  76. package/src/__tests__/proxyContext.test.ts +140 -0
  77. package/src/__tests__/sanitizeCatalog.test.ts +359 -0
  78. package/src/__tests__/startHttpProxy.lifecycle.test.ts +638 -0
  79. package/src/__tests__/startHttpProxy.registry.test.ts +409 -0
  80. package/src/__tests__/startHttpProxy.replay.test.ts +116 -0
  81. package/src/__tests__/startHttpProxy.security.test.ts +288 -0
  82. package/src/__tests__/startHttpProxy.test.ts +985 -0
  83. package/src/__tests__/testing.test.ts +250 -0
  84. package/src/__tests__/version.test.ts +12 -0
  85. package/src/backendClient.ts +83 -0
  86. package/src/core.ts +2042 -0
  87. package/src/delegation.ts +224 -0
  88. package/src/eventStore.ts +106 -0
  89. package/src/hiddenTools.ts +84 -0
  90. package/src/identity.ts +15 -0
  91. package/src/index.ts +63 -0
  92. package/src/mesh.ts +204 -0
  93. package/src/middleware.ts +101 -0
  94. package/src/proxyContext.ts +108 -0
  95. package/src/rejection.ts +37 -0
  96. package/src/sanitizeCatalog.ts +120 -0
  97. package/src/sessionRegistry.ts +142 -0
  98. package/src/telemetry.ts +46 -0
  99. package/src/testing.ts +174 -0
  100. package/src/version.ts +6 -0
@@ -0,0 +1,198 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth';
3
+
4
+ vi.mock('@modelcontextprotocol/sdk/client/streamableHttp.js', () => ({
5
+ StreamableHTTPClientTransport: vi.fn(),
6
+ }));
7
+
8
+ vi.mock('@modelcontextprotocol/sdk/client/stdio.js', () => ({
9
+ StdioClientTransport: vi.fn(),
10
+ }));
11
+
12
+ // A class, not an arrow function: createBackendClient calls `new Client(...)`,
13
+ // and only `function`/`class` implementations are constructible.
14
+ vi.mock('@modelcontextprotocol/sdk/client/index.js', () => ({
15
+ Client: class {
16
+ connect = vi.fn().mockResolvedValue(undefined);
17
+ },
18
+ }));
19
+
20
+ import { createBackendClient } from '../backendClient.js';
21
+
22
+ describe('createBackendClient() URL scheme validation', () => {
23
+ beforeEach(() => {
24
+ vi.clearAllMocks();
25
+ });
26
+
27
+ it('throws for file: URLs', async () => {
28
+ await expect(
29
+ createBackendClient({ url: 'file:///etc/passwd' }),
30
+ ).rejects.toThrow('unsupported URL protocol "file:"');
31
+ });
32
+
33
+ it('throws for ftp: URLs', async () => {
34
+ await expect(
35
+ createBackendClient({ url: 'ftp://example.com/mcp' }),
36
+ ).rejects.toThrow('unsupported URL protocol "ftp:"');
37
+ });
38
+
39
+ it('throws for javascript: URLs', async () => {
40
+ await expect(
41
+ createBackendClient({ url: 'javascript:alert(1)' }),
42
+ ).rejects.toThrow('unsupported URL protocol "javascript:"');
43
+ });
44
+
45
+ it('passes for http: URLs', async () => {
46
+ const { StreamableHTTPClientTransport } =
47
+ await import('@modelcontextprotocol/sdk/client/streamableHttp.js');
48
+ await expect(
49
+ createBackendClient({ url: 'http://localhost:3000/mcp' }),
50
+ ).resolves.toBeDefined();
51
+ const lastUrl = vi.mocked(StreamableHTTPClientTransport).mock.lastCall?.[0];
52
+ expect(lastUrl).toBeInstanceOf(URL);
53
+ expect((lastUrl as URL).protocol).toBe('http:');
54
+ });
55
+
56
+ it('passes for https: URLs', async () => {
57
+ const { StreamableHTTPClientTransport } =
58
+ await import('@modelcontextprotocol/sdk/client/streamableHttp.js');
59
+ await expect(
60
+ createBackendClient({ url: 'https://example.com/mcp' }),
61
+ ).resolves.toBeDefined();
62
+ const lastUrl = vi.mocked(StreamableHTTPClientTransport).mock.lastCall?.[0];
63
+ expect(lastUrl).toBeInstanceOf(URL);
64
+ expect((lastUrl as URL).protocol).toBe('https:');
65
+ });
66
+
67
+ it('throws when neither command nor url is provided', async () => {
68
+ await expect(createBackendClient({})).rejects.toThrow(
69
+ 'either command or url must be provided',
70
+ );
71
+ });
72
+
73
+ it('throws when both command and url are empty strings (both falsy)', async () => {
74
+ await expect(createBackendClient({ command: '', url: '' })).rejects.toThrow(
75
+ 'either command or url must be provided',
76
+ );
77
+ });
78
+
79
+ it('prefers url over command when both are provided', async () => {
80
+ const { StreamableHTTPClientTransport } =
81
+ await import('@modelcontextprotocol/sdk/client/streamableHttp.js');
82
+ await expect(
83
+ createBackendClient({
84
+ command: '/definitely/not/an/executable',
85
+ args: ['--bogus'],
86
+ url: 'https://example.com/mcp',
87
+ }),
88
+ ).resolves.toBeDefined();
89
+ const lastUrl = vi.mocked(StreamableHTTPClientTransport).mock.lastCall?.[0];
90
+ expect(lastUrl).toBeInstanceOf(URL);
91
+ expect((lastUrl as URL).protocol).toBe('https:');
92
+ });
93
+
94
+ it('normalizes uppercase URL schemes via WHATWG URL parsing', async () => {
95
+ await expect(
96
+ createBackendClient({ url: 'HTTPS://example.com/mcp' }),
97
+ ).resolves.toBeDefined();
98
+ });
99
+
100
+ it('accepts URLs with userinfo, query, and fragment on https', async () => {
101
+ await expect(
102
+ createBackendClient({
103
+ url: 'https://user:pass@example.com/mcp?foo=bar#frag',
104
+ }),
105
+ ).resolves.toBeDefined();
106
+ });
107
+
108
+ it('throws on a malformed URL string', async () => {
109
+ await expect(createBackendClient({ url: 'not a url' })).rejects.toThrow();
110
+ });
111
+ });
112
+
113
+ describe('createBackendClient() HTTP transport options', () => {
114
+ // A recognizable stand-in for an OAuthClientProvider. createBackendClient only
115
+ // stores the reference, so we assert identity rather than driving a real flow.
116
+ const stubAuthProvider = {
117
+ redirectUrl: 'http://localhost:3000/callback',
118
+ } as unknown as OAuthClientProvider;
119
+
120
+ beforeEach(() => {
121
+ vi.clearAllMocks();
122
+ });
123
+
124
+ /** Resolves to the options object passed to the last StreamableHTTPClientTransport. */
125
+ const lastTransportOpts = async () => {
126
+ const { StreamableHTTPClientTransport } =
127
+ await import('@modelcontextprotocol/sdk/client/streamableHttp.js');
128
+ return vi.mocked(StreamableHTTPClientTransport).mock.lastCall?.[1] as
129
+ | {
130
+ requestInit?: { headers?: Record<string, string> };
131
+ authProvider?: OAuthClientProvider;
132
+ }
133
+ | undefined;
134
+ };
135
+
136
+ it('forwards custom headers to the HTTP transport via requestInit', async () => {
137
+ const headers = { Authorization: 'Bearer abc', 'X-Trace-Id': '123' };
138
+ await createBackendClient({ url: 'https://example.com/mcp', headers });
139
+
140
+ expect((await lastTransportOpts())?.requestInit?.headers).toEqual(headers);
141
+ });
142
+
143
+ it('forwards an OAuth authProvider to the HTTP transport', async () => {
144
+ await createBackendClient({
145
+ url: 'https://example.com/mcp',
146
+ authProvider: stubAuthProvider,
147
+ });
148
+
149
+ expect((await lastTransportOpts())?.authProvider).toBe(stubAuthProvider);
150
+ });
151
+
152
+ it('forwards headers and authProvider together', async () => {
153
+ const headers = { Authorization: 'Bearer t' };
154
+ await createBackendClient({
155
+ url: 'https://example.com/mcp',
156
+ headers,
157
+ authProvider: stubAuthProvider,
158
+ });
159
+ const opts = await lastTransportOpts();
160
+
161
+ expect(opts?.requestInit?.headers).toEqual(headers);
162
+ expect(opts?.authProvider).toBe(stubAuthProvider);
163
+ });
164
+
165
+ it('omits requestInit and authProvider when neither is configured', async () => {
166
+ await createBackendClient({ url: 'https://example.com/mcp' });
167
+ const opts = await lastTransportOpts();
168
+
169
+ expect(opts?.requestInit).toBeUndefined();
170
+ expect(opts?.authProvider).toBeUndefined();
171
+ });
172
+ });
173
+
174
+ describe('createBackendClient() stdio transport', () => {
175
+ beforeEach(() => {
176
+ vi.clearAllMocks();
177
+ });
178
+
179
+ it('spawns the command with the configured args', async () => {
180
+ const { StdioClientTransport } =
181
+ await import('@modelcontextprotocol/sdk/client/stdio.js');
182
+ await createBackendClient({ command: 'node', args: ['server.js'] });
183
+ expect(vi.mocked(StdioClientTransport).mock.lastCall?.[0]).toEqual({
184
+ command: 'node',
185
+ args: ['server.js'],
186
+ });
187
+ });
188
+
189
+ it('defaults args to an empty array', async () => {
190
+ const { StdioClientTransport } =
191
+ await import('@modelcontextprotocol/sdk/client/stdio.js');
192
+ await createBackendClient({ command: 'node' });
193
+ expect(vi.mocked(StdioClientTransport).mock.lastCall?.[0]).toEqual({
194
+ command: 'node',
195
+ args: [],
196
+ });
197
+ });
198
+ });