mcpico 0.1.2 → 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.
package/README.md CHANGED
@@ -28,6 +28,8 @@ Group related tools under a single entry point. The model sees 9 groups instead
28
28
  - **Dual listen transport** — MCPico itself listens via stdio or HTTP/SSE (configurable port)
29
29
  - **Configurable timeouts** — Per-server connection timeout with sensible default (30s)
30
30
  - **Resource & prompt passthrough** — Namespaced to avoid collisions across servers
31
+ - **Authentication** — Bearer, custom header, and OAuth2 client_credentials with automatic token refresh
32
+ - **Listen endpoint auth** — Protect the SSE endpoint with bearer token validation
31
33
 
32
34
  ## Usage
33
35
 
@@ -178,12 +180,111 @@ Groups from different servers are merged if they share a prefix. Otherwise each
178
180
  | `type` | `"sse"` | yes | Transport type |
179
181
  | `url` | `string` | yes | Full URL to MCP Streamable HTTP endpoint |
180
182
 
183
+ ## Authentication
184
+
185
+ MCPico supports two layers of authentication:
186
+
187
+ ### Layer 1: Protecting the listen endpoint
188
+
189
+ When MCPico exposes an SSE endpoint, you can require a bearer token from clients:
190
+
191
+ ```json
192
+ {
193
+ "servers": [...],
194
+ "listen": {
195
+ "type": "sse",
196
+ "port": 3000,
197
+ "auth": {
198
+ "type": "bearer",
199
+ "token": "${MCPICO_API_KEY}"
200
+ }
201
+ }
202
+ }
203
+ ```
204
+
205
+ Clients must include `Authorization: Bearer <token>` in requests. Invalid or missing tokens receive a 401 response.
206
+
207
+ ### Layer 2: Authenticating to upstream servers
208
+
209
+ Upstream servers can require authentication. MCPico supports three methods:
210
+
211
+ **Bearer token** — standard `Authorization: Bearer <token>` header:
212
+
213
+ ```json
214
+ {
215
+ "servers": [
216
+ {
217
+ "name": "internal-api",
218
+ "transport": {
219
+ "type": "sse",
220
+ "url": "https://api.internal/mcp"
221
+ },
222
+ "auth": {
223
+ "type": "bearer",
224
+ "token": "${INTERNAL_KEY}"
225
+ }
226
+ }
227
+ ]
228
+ }
229
+ ```
230
+
231
+ **Custom header** — arbitrary headers (e.g. `X-API-Key`):
232
+
233
+ ```json
234
+ {
235
+ "auth": {
236
+ "type": "header",
237
+ "name": "X-API-Key",
238
+ "value": "${WIDGET_KEY}"
239
+ }
240
+ }
241
+ ```
242
+
243
+ **OAuth 2.0 client credentials** — machine-to-machine authentication with automatic token refresh:
244
+
245
+ ```json
246
+ {
247
+ "auth": {
248
+ "type": "oauth",
249
+ "grant_type": "client_credentials",
250
+ "client_id": "${PROVIDER_CLIENT_ID}",
251
+ "client_secret": "${PROVIDER_CLIENT_SECRET}",
252
+ "token_url": "https://auth.example.com/oauth/token",
253
+ "scopes": ["read", "write"]
254
+ }
255
+ }
256
+ ```
257
+
258
+ MCPico handles the full OAuth flow:
259
+ - Fetches initial access token on startup
260
+ - Caches tokens in `~/.mcplico/credentials.json`
261
+ - Automatically refreshes before expiry
262
+ - Retries on 401 with fresh tokens
263
+
264
+ All auth fields support `${ENV_VAR}` interpolation — never hardcode secrets.
265
+
266
+ ### Auth config reference
267
+
268
+ | Field | Type | Required | Description |
269
+ |-------|------|----------|-------------|
270
+ | `auth.type` | `"bearer"` \| `"header"` \| `"oauth"` | yes | Auth method |
271
+ | `auth.token` | `string` | for `bearer` | Bearer token value |
272
+ | `auth.name` | `string` | for `header` | Header name |
273
+ | `auth.value` | `string` | for `header` | Header value |
274
+ | `auth.grant_type` | `"client_credentials"` | for `oauth` | OAuth grant type |
275
+ | `auth.client_id` | `string` | for `oauth` | OAuth client ID |
276
+ | `auth.client_secret` | `string` | for `oauth` | OAuth client secret |
277
+ | `auth.token_url` | `string` | for `oauth` | Token endpoint URL |
278
+ | `auth.scopes` | `string[]` | no | OAuth scopes to request |
279
+ | `auth.authorization_server_url` | `string` | no | Auth server URL (if different from token_url issuer) |
280
+
281
+ ## Development
181
282
  ## Development
182
283
 
183
284
  ```bash
184
285
  npm install
185
286
  npm run build # TypeScript compilation
186
- npm test # Run tests (105 tests, vitest)
287
+ npm test # Run tests (138 tests, vitest)
187
288
  npm run dev # Run directly with tsx
188
289
  ```
189
290
 
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Authentication types for MCPico.
3
+ *
4
+ * Two layers:
5
+ * Layer 1 — Auth for the listen endpoint (protecting MCPico from clients)
6
+ * Layer 2 — Auth passthrough to upstream MCP servers
7
+ */
8
+ export interface BearerAuth {
9
+ type: "bearer";
10
+ /** Bearer token value. Supports ${ENV_VAR} interpolation. */
11
+ token: string;
12
+ }
13
+ export interface HeaderAuth {
14
+ type: "header";
15
+ /** Header name (e.g. "X-API-Key", "X-Auth-Token") */
16
+ name: string;
17
+ /** Header value. Supports ${ENV_VAR} interpolation. */
18
+ value: string;
19
+ }
20
+ export interface OAuthClientCredentials {
21
+ type: "oauth";
22
+ /** OAuth 2.0 grant type */
23
+ grant_type: "client_credentials";
24
+ /** Client ID for the OAuth provider */
25
+ client_id: string;
26
+ /** Client secret for the OAuth provider */
27
+ client_secret: string;
28
+ /** Token endpoint URL */
29
+ token_url: string;
30
+ /** Optional scopes to request */
31
+ scopes?: string[];
32
+ /** Optional authorization server metadata URL (if different from token_url issuer) */
33
+ authorization_server_url?: string;
34
+ }
35
+ export type UpstreamAuth = BearerAuth | HeaderAuth | OAuthClientCredentials;
36
+ export interface ListenBearerAuth {
37
+ type: "bearer";
38
+ /** Bearer token that clients must provide. Supports ${ENV_VAR} interpolation. */
39
+ token: string;
40
+ }
41
+ export type ListenAuth = ListenBearerAuth;
42
+ export interface ResolvedBearerAuth {
43
+ type: "bearer";
44
+ token: string;
45
+ }
46
+ export interface ResolvedHeaderAuth {
47
+ type: "header";
48
+ name: string;
49
+ value: string;
50
+ }
51
+ export type ResolvedUpstreamAuth = ResolvedBearerAuth | ResolvedHeaderAuth | (OAuthClientCredentials & {
52
+ type: "oauth";
53
+ });
54
+ export type ResolvedListenAuth = ResolvedBearerAuth;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Authentication types for MCPico.
3
+ *
4
+ * Two layers:
5
+ * Layer 1 — Auth for the listen endpoint (protecting MCPico from clients)
6
+ * Layer 2 — Auth passthrough to upstream MCP servers
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=auth-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-types.js","sourceRoot":"","sources":["../src/auth-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
package/dist/auth.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Auth utilities: env var resolution, header generation, listen token validation.
3
+ */
4
+ import type { IncomingMessage, ServerResponse } from "node:http";
5
+ import type { UpstreamAuth, ListenAuth, ResolvedUpstreamAuth, ResolvedListenAuth } from "./auth-types.js";
6
+ /**
7
+ * Replace ${VAR} patterns with process.env values.
8
+ * Throws if a referenced env var is not set.
9
+ */
10
+ export declare function resolveEnvVars(value: string): string;
11
+ /**
12
+ * Resolve an UpstreamAuth config to a ResolvedUpstreamAuth with env vars interpolated.
13
+ */
14
+ export declare function resolveUpstreamAuth(auth: UpstreamAuth): ResolvedUpstreamAuth;
15
+ /**
16
+ * Resolve a ListenAuth config to ResolvedListenAuth.
17
+ */
18
+ export declare function resolveListenAuth(auth: ListenAuth): ResolvedListenAuth;
19
+ /**
20
+ * Generate HTTP headers from a resolved upstream auth config.
21
+ * For bearer/header types, returns headers to attach to requests.
22
+ * For OAuth, returns empty — the SDK authProvider handles it.
23
+ */
24
+ export declare function upstreamAuthHeaders(auth: ResolvedUpstreamAuth): Record<string, string>;
25
+ /**
26
+ * Extract bearer token from an Authorization header.
27
+ * Returns undefined if no Bearer token found.
28
+ */
29
+ export declare function extractBearerToken(req: IncomingMessage): string | undefined;
30
+ /**
31
+ * Validate a bearer token against the configured token.
32
+ * Returns true if token is valid, false otherwise.
33
+ */
34
+ export declare function validateBearerToken(providedToken: string | undefined, expectedToken: string): boolean;
35
+ /**
36
+ * Send a 401 Unauthorized response with a WWW-Authenticate header.
37
+ */
38
+ export declare function sendUnauthorized(res: ServerResponse): void;
package/dist/auth.js ADDED
@@ -0,0 +1,122 @@
1
+ // ── Env var interpolation ──
2
+ /**
3
+ * Replace ${VAR} patterns with process.env values.
4
+ * Throws if a referenced env var is not set.
5
+ */
6
+ export function resolveEnvVars(value) {
7
+ return value.replace(/\$\{(\w+)\}/g, (_match, varName) => {
8
+ const envValue = process.env[varName];
9
+ if (envValue === undefined) {
10
+ throw new Error(`Environment variable "${varName}" is not set. ` +
11
+ `Referenced in auth config. Set it or remove the auth block.`);
12
+ }
13
+ return envValue;
14
+ });
15
+ }
16
+ // ── Auth resolution ──
17
+ /**
18
+ * Resolve an UpstreamAuth config to a ResolvedUpstreamAuth with env vars interpolated.
19
+ */
20
+ export function resolveUpstreamAuth(auth) {
21
+ switch (auth.type) {
22
+ case "bearer":
23
+ return {
24
+ type: "bearer",
25
+ token: resolveEnvVars(auth.token),
26
+ };
27
+ case "header":
28
+ return {
29
+ type: "header",
30
+ name: resolveEnvVars(auth.name),
31
+ value: resolveEnvVars(auth.value),
32
+ };
33
+ case "oauth":
34
+ // OAuth credentials are resolved at request time by the provider
35
+ return {
36
+ type: "oauth",
37
+ grant_type: auth.grant_type,
38
+ client_id: resolveEnvVars(auth.client_id),
39
+ client_secret: resolveEnvVars(auth.client_secret),
40
+ token_url: resolveEnvVars(auth.token_url),
41
+ scopes: auth.scopes,
42
+ authorization_server_url: auth.authorization_server_url
43
+ ? resolveEnvVars(auth.authorization_server_url)
44
+ : undefined,
45
+ };
46
+ }
47
+ }
48
+ /**
49
+ * Resolve a ListenAuth config to ResolvedListenAuth.
50
+ */
51
+ export function resolveListenAuth(auth) {
52
+ return {
53
+ type: "bearer",
54
+ token: resolveEnvVars(auth.token),
55
+ };
56
+ }
57
+ // ── Header generation for upstream requests ──
58
+ /**
59
+ * Generate HTTP headers from a resolved upstream auth config.
60
+ * For bearer/header types, returns headers to attach to requests.
61
+ * For OAuth, returns empty — the SDK authProvider handles it.
62
+ */
63
+ export function upstreamAuthHeaders(auth) {
64
+ if (auth.type === "bearer") {
65
+ return { Authorization: `Bearer ${auth.token}` };
66
+ }
67
+ if (auth.type === "header") {
68
+ return { [auth.name]: auth.value };
69
+ }
70
+ // OAuth is handled by the MCP SDK's authProvider — no static headers
71
+ return {};
72
+ }
73
+ // ── Listen endpoint auth middleware ──
74
+ /**
75
+ * Extract bearer token from an Authorization header.
76
+ * Returns undefined if no Bearer token found.
77
+ */
78
+ export function extractBearerToken(req) {
79
+ const auth = req.headers.authorization;
80
+ if (!auth)
81
+ return undefined;
82
+ const match = auth.match(/^Bearer\s+(.+)$/i);
83
+ return match ? match[1] : undefined;
84
+ }
85
+ /**
86
+ * Validate a bearer token against the configured token.
87
+ * Returns true if token is valid, false otherwise.
88
+ */
89
+ export function validateBearerToken(providedToken, expectedToken) {
90
+ if (!providedToken)
91
+ return false;
92
+ // Constant-time comparison to prevent timing attacks
93
+ return timingSafeEqual(providedToken, expectedToken);
94
+ }
95
+ /**
96
+ * Send a 401 Unauthorized response with a WWW-Authenticate header.
97
+ */
98
+ export function sendUnauthorized(res) {
99
+ res.writeHead(401, {
100
+ "Content-Type": "application/json",
101
+ "WWW-Authenticate": 'Bearer realm="mcplico"',
102
+ });
103
+ res.end(JSON.stringify({ error: "Unauthorized", message: "Bearer token required" }));
104
+ }
105
+ // ── Constant-time string comparison ──
106
+ function timingSafeEqual(a, b) {
107
+ if (a.length !== b.length) {
108
+ // Still compare to avoid leaking length via timing
109
+ const maxLen = Math.max(a.length, b.length);
110
+ let result = 0;
111
+ for (let i = 0; i < maxLen; i++) {
112
+ result |= a.charCodeAt(i % a.length) ^ b.charCodeAt(i % b.length);
113
+ }
114
+ return false;
115
+ }
116
+ let result = 0;
117
+ for (let i = 0; i < a.length; i++) {
118
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
119
+ }
120
+ return result === 0;
121
+ }
122
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
package/dist/config.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Transport configuration for connecting to an upstream MCP server.
3
3
  */
4
+ import type { UpstreamAuth, ListenAuth } from "./auth-types.js";
4
5
  export type TransportConfig = {
5
6
  type: "stdio";
6
7
  command: string;
@@ -22,6 +23,8 @@ export interface ServerConfig {
22
23
  transport: TransportConfig;
23
24
  /** Connection timeout in milliseconds (default: 30000) */
24
25
  connectTimeoutMs?: number;
26
+ /** Authentication config for this upstream server */
27
+ auth?: UpstreamAuth;
25
28
  }
26
29
  /**
27
30
  * Tool grouping override — map group names to explicit tool lists.
@@ -38,6 +41,7 @@ export type ListenConfig = {
38
41
  type: "sse";
39
42
  port: number;
40
43
  host?: string;
44
+ auth?: ListenAuth;
41
45
  };
42
46
  /**
43
47
  * Full MCPico configuration.
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAyDA;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAoB;IACvD,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpD,OAAO,mDAAmD,CAAC;IAC7D,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACtB,OAAO,WAAW,MAAM,CAAC,IAAI,wBAAwB,CAAC;IACxD,CAAC;IACD,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;YAC9B,OAAO,WAAW,MAAM,CAAC,IAAI,uCAAuC,CAAC;QACvE,CAAC;IACH,CAAC;SAAM,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC3C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;YAC1B,OAAO,WAAW,MAAM,CAAC,IAAI,iCAAiC,CAAC;QACjE,CAAC;QACD,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,WAAW,MAAM,CAAC,IAAI,+CAA+C,MAAM,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC;QACtG,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,WAAW,MAAM,CAAC,IAAI,8BAA+B,MAAM,CAAC,SAAoC,CAAC,IAAI,0BAA0B,CAAC;IACzI,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AA8DA;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAoB;IACvD,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpD,OAAO,mDAAmD,CAAC;IAC7D,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACtB,OAAO,WAAW,MAAM,CAAC,IAAI,wBAAwB,CAAC;IACxD,CAAC;IACD,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;YAC9B,OAAO,WAAW,MAAM,CAAC,IAAI,uCAAuC,CAAC;QACvE,CAAC;IACH,CAAC;SAAM,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC3C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;YAC1B,OAAO,WAAW,MAAM,CAAC,IAAI,iCAAiC,CAAC;QACjE,CAAC;QACD,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,WAAW,MAAM,CAAC,IAAI,+CAA+C,MAAM,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC;QACtG,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,WAAW,MAAM,CAAC,IAAI,8BAA+B,MAAM,CAAC,SAAoC,CAAC,IAAI,0BAA0B,CAAC;IACzI,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  import type { TransportConfig } from "./config.js";
3
+ import type { ResolvedUpstreamAuth } from "./auth-types.js";
3
4
  /**
4
5
  * Tool metadata from an upstream MCP server.
5
6
  */
@@ -45,7 +46,7 @@ export interface DiscoveredServer {
45
46
  * Supports stdio and streamable HTTP transports.
46
47
  * Default connection timeout is 30 seconds.
47
48
  */
48
- export declare function discoverServer(name: string, transportConfig: TransportConfig, connectTimeoutMs?: number): Promise<DiscoveredServer>;
49
+ export declare function discoverServer(name: string, transportConfig: TransportConfig, connectTimeoutMs?: number, auth?: ResolvedUpstreamAuth): Promise<DiscoveredServer>;
49
50
  /**
50
51
  * Disconnect from an upstream server.
51
52
  */
@@ -1,13 +1,15 @@
1
1
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
3
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4
+ import { upstreamAuthHeaders } from "./auth.js";
5
+ import { createClientCredentialsProvider } from "./oauth-provider.js";
4
6
  /**
5
7
  * Connect to an upstream MCP server and discover its tools.
6
8
  *
7
9
  * Supports stdio and streamable HTTP transports.
8
10
  * Default connection timeout is 30 seconds.
9
11
  */
10
- export async function discoverServer(name, transportConfig, connectTimeoutMs = 30_000) {
12
+ export async function discoverServer(name, transportConfig, connectTimeoutMs = 30_000, auth) {
11
13
  let transport;
12
14
  if (transportConfig.type === "stdio") {
13
15
  transport = new StdioClientTransport({
@@ -18,12 +20,22 @@ export async function discoverServer(name, transportConfig, connectTimeoutMs = 3
18
20
  });
19
21
  }
20
22
  else if (transportConfig.type === "sse") {
21
- transport = new StreamableHTTPClientTransport(new URL(transportConfig.url));
23
+ const url = new URL(transportConfig.url);
24
+ const opts = {};
25
+ if (auth) {
26
+ if (auth.type === "oauth") {
27
+ opts.authProvider = createClientCredentialsProvider(auth.client_id, auth.client_secret, transportConfig.url);
28
+ }
29
+ else {
30
+ opts.requestInit = { headers: upstreamAuthHeaders(auth) };
31
+ }
32
+ }
33
+ transport = new StreamableHTTPClientTransport(url, opts);
22
34
  }
23
35
  else {
24
36
  throw new Error(`Unsupported transport type: ${transportConfig.type}`);
25
37
  }
26
- const client = new Client({ name: `MCPico-${name}`, version: "0.1.0" }, { capabilities: {} });
38
+ const client = new Client({ name: `MCPico-${name}`, version: "0.2.0" }, { capabilities: {} });
27
39
  // Connect with timeout
28
40
  await withTimeout(client.connect(transport), connectTimeoutMs, `Connection to "${name}" timed out after ${connectTimeoutMs}ms`);
29
41
  // Discover tools
@@ -1 +1 @@
1
- {"version":3,"file":"discoverer.js","sourceRoot":"","sources":["../src/discoverer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"discoverer.js","sourceRoot":"","sources":["../src/discoverer.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ async function main() {
20
20
  i++;
21
21
  }
22
22
  else if (args[i] === "--version" || args[i] === "-v") {
23
- console.log("MCPico v0.1.0");
23
+ console.log("MCPico v0.2.0");
24
24
  process.exit(0);
25
25
  }
26
26
  else if (args[i] === "--help" || args[i] === "-h") {
@@ -0,0 +1,19 @@
1
+ /**
2
+ * OAuth client_credentials provider for MCPico.
3
+ *
4
+ * Implements the MCP SDK's OAuthClientProvider interface so that
5
+ * StreamableHTTPClientTransport handles auth discovery, token exchange,
6
+ * and refresh automatically.
7
+ */
8
+ import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
9
+ /**
10
+ * Create an OAuthClientProvider for client_credentials grant.
11
+ *
12
+ * Uses a fixed server URL for token storage key and provides
13
+ * client metadata from config. The MCP SDK handles:
14
+ * - RFC 9728 resource metadata discovery
15
+ * - RFC 8414 authorization server metadata discovery
16
+ * - Token exchange
17
+ * - Token refresh via refreshAuthorization()
18
+ */
19
+ export declare function createClientCredentialsProvider(clientId: string, clientSecret: string, serverUrl: string): OAuthClientProvider;
@@ -0,0 +1,79 @@
1
+ import { loadTokens, saveTokens, clearTokens } from "./token-store.js";
2
+ /**
3
+ * Create an OAuthClientProvider for client_credentials grant.
4
+ *
5
+ * Uses a fixed server URL for token storage key and provides
6
+ * client metadata from config. The MCP SDK handles:
7
+ * - RFC 9728 resource metadata discovery
8
+ * - RFC 8414 authorization server metadata discovery
9
+ * - Token exchange
10
+ * - Token refresh via refreshAuthorization()
11
+ */
12
+ export function createClientCredentialsProvider(clientId, clientSecret, serverUrl) {
13
+ return new ClientCredentialsProvider(clientId, clientSecret, serverUrl);
14
+ }
15
+ class ClientCredentialsProvider {
16
+ clientId;
17
+ clientSecret;
18
+ serverUrl;
19
+ _tokens;
20
+ constructor(clientId, clientSecret, serverUrl) {
21
+ this.clientId = clientId;
22
+ this.clientSecret = clientSecret;
23
+ this.serverUrl = serverUrl;
24
+ // Load any cached tokens on creation
25
+ this._tokens = loadTokens(serverUrl);
26
+ }
27
+ get redirectUrl() {
28
+ // client_credentials is non-interactive — no redirect URL
29
+ return undefined;
30
+ }
31
+ get clientMetadata() {
32
+ return {
33
+ redirect_uris: ["http://localhost:0/mcplico-callback"],
34
+ grant_types: ["client_credentials"],
35
+ client_name: "MCPico",
36
+ };
37
+ }
38
+ // Client information for token exchange
39
+ clientInformation() {
40
+ return {
41
+ client_id: this.clientId,
42
+ client_secret: this.clientSecret,
43
+ client_secret_expires_at: 0, // Never expires
44
+ };
45
+ }
46
+ // Return cached tokens (or undefined for initial auth)
47
+ tokens() {
48
+ return this._tokens;
49
+ }
50
+ // Persist tokens after successful auth/refresh
51
+ saveTokens(tokens) {
52
+ this._tokens = tokens;
53
+ saveTokens(this.serverUrl, tokens);
54
+ }
55
+ // No redirect needed for client_credentials
56
+ redirectToAuthorization(_authorizationUrl) {
57
+ // Non-interactive — no redirect
58
+ }
59
+ // PKCE not needed for client_credentials
60
+ saveCodeVerifier(_codeVerifier) {
61
+ // No-op for client_credentials
62
+ }
63
+ codeVerifier() {
64
+ return "";
65
+ }
66
+ // Use client_credentials grant
67
+ prepareTokenRequest(_scope) {
68
+ const params = new URLSearchParams({
69
+ grant_type: "client_credentials",
70
+ });
71
+ return params;
72
+ }
73
+ // Clear tokens on invalidation
74
+ invalidateCredentials(_scope) {
75
+ this._tokens = undefined;
76
+ clearTokens(this.serverUrl);
77
+ }
78
+ }
79
+ //# sourceMappingURL=oauth-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-provider.js","sourceRoot":"","sources":["../src/oauth-provider.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEvE;;;;;;;;;GASG;AACH,MAAM,UAAU,+BAA+B,CAC7C,QAAgB,EAChB,YAAoB,EACpB,SAAiB;IAEjB,OAAO,IAAI,yBAAyB,CAAC,QAAQ,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,yBAAyB;IAInB;IACA;IACA;IALF,OAAO,CAA0B;IAEzC,YACU,QAAgB,EAChB,YAAoB,EACpB,SAAiB;QAFjB,aAAQ,GAAR,QAAQ,CAAQ;QAChB,iBAAY,GAAZ,YAAY,CAAQ;QACpB,cAAS,GAAT,SAAS,CAAQ;QAEzB,qCAAqC;QACrC,IAAI,CAAC,OAAO,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,WAAW;QACb,0DAA0D;QAC1D,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,cAAc;QAChB,OAAO;YACL,aAAa,EAAE,CAAC,qCAAqC,CAAC;YACtD,WAAW,EAAE,CAAC,oBAAoB,CAAC;YACnC,WAAW,EAAE,QAAQ;SACtB,CAAC;IACJ,CAAC;IAED,wCAAwC;IACxC,iBAAiB;QACf,OAAO;YACL,SAAS,EAAE,IAAI,CAAC,QAAQ;YACxB,aAAa,EAAE,IAAI,CAAC,YAAY;YAChC,wBAAwB,EAAE,CAAC,EAAE,gBAAgB;SAC9C,CAAC;IACJ,CAAC;IAED,uDAAuD;IACvD,MAAM;QACJ,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,+CAA+C;IAC/C,UAAU,CAAC,MAAmB;QAC5B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACrC,CAAC;IAED,4CAA4C;IAC5C,uBAAuB,CAAC,iBAAsB;QAC5C,gCAAgC;IAClC,CAAC;IAED,yCAAyC;IACzC,gBAAgB,CAAC,aAAqB;QACpC,+BAA+B;IACjC,CAAC;IAED,YAAY;QACV,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,+BAA+B;IAC/B,mBAAmB,CACjB,MAAe;QAEf,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;YACjC,UAAU,EAAE,oBAAoB;SACjC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,+BAA+B;IAC/B,qBAAqB,CAAC,MAA8D;QAClF,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QACzB,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC9B,CAAC;CACF"}
package/dist/server.js CHANGED
@@ -9,6 +9,7 @@ import { groupTools } from "./grouper.js";
9
9
  import { parseCommand } from "./parser.js";
10
10
  import { generateHelpText } from "./help.js";
11
11
  import { forwardToolCall } from "./proxy.js";
12
+ import { resolveUpstreamAuth, resolveListenAuth, extractBearerToken, validateBearerToken, sendUnauthorized } from "./auth.js";
12
13
  /** Helper: create a simple text content result */
13
14
  export function textResult(text) {
14
15
  return {
@@ -128,7 +129,10 @@ export async function startServer(config) {
128
129
  ? serverConfig.transport.url
129
130
  : serverConfig.transport.command;
130
131
  console.error(` Connecting to "${serverConfig.name}" (${serverConfig.transport.type}: ${transportLabel})...`);
131
- const discovered = await discoverServer(serverConfig.name, serverConfig.transport, serverConfig.connectTimeoutMs);
132
+ const resolvedAuth = serverConfig.auth
133
+ ? resolveUpstreamAuth(serverConfig.auth)
134
+ : undefined;
135
+ const discovered = await discoverServer(serverConfig.name, serverConfig.transport, serverConfig.connectTimeoutMs, resolvedAuth);
132
136
  servers.push(discovered);
133
137
  const groups = groupTools(serverConfig.name, discovered.tools, separator, config.groups);
134
138
  console.error(` → ${discovered.tools.length} tools → ${groups.length} groups: ${groups.map((g) => g.groupName).join(", ")}`);
@@ -145,7 +149,7 @@ export async function startServer(config) {
145
149
  // Build lookup: groupName → ToolGroup (merged across servers)
146
150
  const mergedGroups = mergeGroups(allGroups);
147
151
  // Create the MCPico server
148
- const server = new McpServer({ name: "MCPico", version: "0.1.0" }, {
152
+ const server = new McpServer({ name: "MCPico", version: "0.2.0" }, {
149
153
  capabilities: { tools: {}, resources: {}, prompts: {} },
150
154
  instructions: "MCPico bundles upstream MCP tools into hierarchical groups. " +
151
155
  "Use 'help' on any group to discover available subcommands. " +
@@ -231,8 +235,21 @@ export async function startServer(config) {
231
235
  // Connect to client via configured transport
232
236
  const listenConfig = config.listen || { type: "stdio" };
233
237
  if (listenConfig.type === "sse") {
238
+ // Resolve listen auth
239
+ let listenAuth;
240
+ if (listenConfig.auth) {
241
+ listenAuth = resolveListenAuth(listenConfig.auth);
242
+ }
234
243
  const transport = new StreamableHTTPServerTransport();
235
244
  const httpServer = createServer(async (req, res) => {
245
+ // Auth check
246
+ if (listenAuth) {
247
+ const providedToken = extractBearerToken(req);
248
+ if (!validateBearerToken(providedToken, listenAuth.token)) {
249
+ sendUnauthorized(res);
250
+ return;
251
+ }
252
+ }
236
253
  // Collect body for handleRequest
237
254
  const chunks = [];
238
255
  for await (const chunk of req) {