@chrischall/mcp-connector 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 chrischall
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # @chrischall/mcp-connector
2
+
3
+ Reusable, **auth-agnostic** Cloudflare Worker harness that turns any MCP server's
4
+ tool registrars into a hosted **remote connector** for claude.ai — OAuth login +
5
+ streamable-HTTP/SSE via the `agents` SDK `McpAgent` and
6
+ `@cloudflare/workers-oauth-provider`.
7
+
8
+ It is the shared harness behind the `*-mcp` connector fleet (ofw, untappd, …). Each
9
+ MCP keeps its own Worker, hostname, KV, and Durable Objects; this package supplies
10
+ the identical OAuth + transport plumbing so none of it is copy-pasted per repo.
11
+
12
+ > **AI-maintained.** This package is developed and maintained by Claude.
13
+
14
+ ## Install
15
+
16
+ ```sh
17
+ npm i @chrischall/mcp-connector
18
+ ```
19
+
20
+ Peer dependencies (provide these in the consuming Worker so a single copy is
21
+ bundled): `@modelcontextprotocol/sdk`, `agents`, `@cloudflare/workers-oauth-provider`.
22
+
23
+ ## Usage
24
+
25
+ In your Worker entry point (`src/worker.ts`):
26
+
27
+ ```ts
28
+ import { createConnector, type ConnectorAuth } from '@chrischall/mcp-connector';
29
+ import { MyClient } from './client.js';
30
+ import { registerFooTools } from './tools/foo.js';
31
+
32
+ interface Props { apiKey: string; [k: string]: unknown }
33
+
34
+ const myAuth: ConnectorAuth<Props> = {
35
+ service: 'MyService',
36
+ accent: '#3366ff',
37
+ privacyNote: 'Your key is stored encrypted and used only to call MyService on your behalf.',
38
+ fields: [{ name: 'apiKey', label: 'MyService API key', type: 'password' }],
39
+ async login(fields, env) {
40
+ // verify the credentials (throw on bad creds → shown on the login page)
41
+ await MyClient.verify(fields.apiKey);
42
+ return { apiKey: fields.apiKey };
43
+ },
44
+ };
45
+
46
+ const { Agent, handler } = createConnector<Props, MyClient>({
47
+ name: 'my-mcp',
48
+ version: '1.0.0',
49
+ auth: myAuth,
50
+ buildClient: (props, env) => new MyClient({ apiKey: props.apiKey }),
51
+ tools: [registerFooTools],
52
+ });
53
+
54
+ export { Agent as MyMcpAgent };
55
+ export default handler;
56
+ ```
57
+
58
+ `createConnector` mounts `/mcp` (streamable HTTP), `/sse`, `/authorize`, `/token`,
59
+ and `/register` (dynamic client registration), and renders a self-contained,
60
+ theme-aware login page from the `ConnectorAuth` descriptor.
61
+
62
+ ## API
63
+
64
+ - `createConnector<Props, Client>(opts): { Agent, handler }` — `opts`:
65
+ `{ name, version, auth, buildClient(props, env), tools: Array<(server, client) => void> }`.
66
+ Bind `Agent` to a Durable Object namespace (`MCP_OBJECT`) in `wrangler.jsonc` and
67
+ `export default handler`.
68
+ - `ConnectorAuth<Props>` — `{ service, fields: LoginField[], login(fields, env): Promise<Props>, privacyNote?, accent? }`.
69
+ - `LoginField` — `{ name, label, type?: 'text' | 'password' }`.
70
+
71
+ ## License
72
+
73
+ MIT
@@ -0,0 +1,10 @@
1
+ import { McpAgent } from 'agents/mcp';
2
+ import { OAuthProvider } from '@cloudflare/workers-oauth-provider';
3
+ import type { ConnectorOptions } from './types.js';
4
+ export * from './types.js';
5
+ export { renderLoginPage } from './login-page.js';
6
+ export { handleAuthorize, parseLoginForm } from './login.js';
7
+ export declare function createConnector<Props extends Record<string, unknown>, Client>(opts: ConnectorOptions<Props, Client>): {
8
+ Agent: typeof McpAgent;
9
+ handler: OAuthProvider;
10
+ };
package/dist/index.js ADDED
@@ -0,0 +1,39 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { McpAgent } from 'agents/mcp';
3
+ import { OAuthProvider } from '@cloudflare/workers-oauth-provider';
4
+ import { handleAuthorize } from './login.js';
5
+ export * from './types.js';
6
+ export { renderLoginPage } from './login-page.js';
7
+ export { handleAuthorize, parseLoginForm } from './login.js';
8
+ export function createConnector(opts) {
9
+ class ConnectorAgent extends McpAgent {
10
+ server = new McpServer({ name: opts.name, version: opts.version });
11
+ async init() {
12
+ const client = opts.buildClient(this.props, this.env);
13
+ for (const register of opts.tools)
14
+ register(this.server, client);
15
+ }
16
+ }
17
+ const defaultHandler = {
18
+ fetch: (request, env, _ctx) => {
19
+ const url = new URL(request.url);
20
+ if (url.pathname === '/authorize')
21
+ return handleAuthorize(request, env, opts.auth);
22
+ return new Response('Not found', { status: 404 });
23
+ },
24
+ };
25
+ const handler = new OAuthProvider({
26
+ apiHandlers: {
27
+ '/mcp': ConnectorAgent.serve('/mcp'),
28
+ '/sse': ConnectorAgent.serveSSE('/sse'),
29
+ },
30
+ defaultHandler: defaultHandler,
31
+ authorizeEndpoint: '/authorize',
32
+ tokenEndpoint: '/token',
33
+ clientRegistrationEndpoint: '/register',
34
+ });
35
+ // `ConnectorAgent` fixes Props to this call's type parameter, but `typeof McpAgent`
36
+ // is universally quantified over Props in its constructor signature — a concrete
37
+ // subclass can never satisfy that generically, so we cast at this fuzzy boundary.
38
+ return { Agent: ConnectorAgent, handler };
39
+ }
@@ -0,0 +1,13 @@
1
+ import type { ConnectorAuth } from './types.js';
2
+ export interface RenderLoginPageOptions {
3
+ error?: string;
4
+ oauthReq?: unknown;
5
+ }
6
+ /**
7
+ * Renders the login form for a connector's own service credentials.
8
+ * Auth-agnostic: knows nothing about any particular service beyond the field
9
+ * list, the service name, an optional accent, and an optional privacy note.
10
+ * Fully self-contained (inline CSS, no external assets) so it renders under a
11
+ * strict Worker CSP; theme-aware, accessible, and responsive.
12
+ */
13
+ export declare function renderLoginPage<Props>(auth: ConnectorAuth<Props>, options?: RenderLoginPageOptions): string;
@@ -0,0 +1,195 @@
1
+ function escapeHtml(value) {
2
+ return value
3
+ .replace(/&/g, '&amp;')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;')
7
+ .replace(/'/g, '&#39;');
8
+ }
9
+ /** A safe CSS color: only accept a #rgb/#rrggbb hex, else fall back. */
10
+ function safeAccent(value, fallback) {
11
+ return value && /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(value) ? value : fallback;
12
+ }
13
+ /** Readable text color on top of a hex background (dark ink on light accents, white on dark). */
14
+ function inkOn(hex) {
15
+ let h = hex.slice(1);
16
+ if (h.length === 3)
17
+ h = h.split('').map((c) => c + c).join('');
18
+ const r = parseInt(h.slice(0, 2), 16) / 255;
19
+ const g = parseInt(h.slice(2, 4), 16) / 255;
20
+ const b = parseInt(h.slice(4, 6), 16) / 255;
21
+ const lin = (c) => (c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
22
+ const L = 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
23
+ // 0.179 is the WCAG contrast crossover: the luminance above which black
24
+ // text (#141414) contrasts better than white against the background,
25
+ // rather than the naive 0.5 midpoint — so mid-range accents (e.g. a
26
+ // saturated blue at L≈0.3) correctly get dark ink instead of white.
27
+ return L > 0.179 ? '#141414' : '#ffffff';
28
+ }
29
+ /** Best-guess autocomplete so password managers cooperate. */
30
+ function autocompleteFor(name, isPassword) {
31
+ if (isPassword)
32
+ return 'current-password';
33
+ if (/user|email|login|account/i.test(name))
34
+ return 'username';
35
+ return 'off';
36
+ }
37
+ /**
38
+ * Renders the login form for a connector's own service credentials.
39
+ * Auth-agnostic: knows nothing about any particular service beyond the field
40
+ * list, the service name, an optional accent, and an optional privacy note.
41
+ * Fully self-contained (inline CSS, no external assets) so it renders under a
42
+ * strict Worker CSP; theme-aware, accessible, and responsive.
43
+ */
44
+ export function renderLoginPage(auth, options = {}) {
45
+ const { error, oauthReq } = options;
46
+ const encodedOauthReq = oauthReq !== undefined ? btoa(JSON.stringify(oauthReq)) : '';
47
+ const service = escapeHtml(auth.service);
48
+ const accent = safeAccent(auth.accent, '#4f46e5');
49
+ const accentInk = inkOn(accent);
50
+ const fieldsHtml = auth.fields
51
+ .map((field, i) => {
52
+ const isPassword = field.type === 'password';
53
+ const type = isPassword ? 'password' : 'text';
54
+ const name = escapeHtml(field.name);
55
+ const extras = isPassword ? '' : ' autocapitalize="none" autocorrect="off" spellcheck="false"';
56
+ return ` <div class="field">
57
+ <label for="f-${name}">${escapeHtml(field.label)}</label>
58
+ <input id="f-${name}" name="${name}" type="${type}" required autocomplete="${autocompleteFor(field.name, isPassword)}"${i === 0 ? ' autofocus' : ''}${extras} />
59
+ </div>`;
60
+ })
61
+ .join('\n');
62
+ const errorHtml = error
63
+ ? `<div class="error" role="alert"><svg viewBox="0 0 20 20" aria-hidden="true" width="16" height="16"><path fill="currentColor" d="M10 1.7 1 18h18L10 1.7Zm0 5.6a.9.9 0 0 1 .9.9v3.6a.9.9 0 0 1-1.8 0V8.2a.9.9 0 0 1 .9-.9Zm0 7.3a1 1 0 1 1 0 2 1 1 0 0 1 0-2Z"/></svg><span>${escapeHtml(error)}</span></div>`
64
+ : '';
65
+ const privacyHtml = auth.privacyNote
66
+ ? `<p class="privacy"><svg viewBox="0 0 20 20" aria-hidden="true" width="13" height="13"><path fill="currentColor" d="M10 1a4 4 0 0 0-4 4v2H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-1V5a4 4 0 0 0-4-4Zm-2 6V5a2 2 0 1 1 4 0v2H8Z"/></svg><span>${escapeHtml(auth.privacyNote)}</span></p>`
67
+ : '';
68
+ return `<!doctype html>
69
+ <html lang="en">
70
+ <head>
71
+ <meta charset="utf-8" />
72
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
73
+ <meta name="color-scheme" content="light dark" />
74
+ <title>Connect to ${service}</title>
75
+ <style>
76
+ :root {
77
+ --accent: ${accent};
78
+ --accent-ink: ${accentInk};
79
+ --bg: #f4f3f0;
80
+ --card: #ffffff;
81
+ --ink: #16150f;
82
+ --muted: #6d6a60;
83
+ --border: #e5e2da;
84
+ --input-bg: #fbfaf7;
85
+ --err-bg: #fef3f2;
86
+ --err-ink: #b42318;
87
+ --err-border: #fecdc9;
88
+ --ring: color-mix(in srgb, var(--accent) 35%, transparent);
89
+ --shadow: 0 1px 2px rgba(20,18,10,.04), 0 12px 32px -12px rgba(20,18,10,.18);
90
+ }
91
+ @media (prefers-color-scheme: dark) {
92
+ :root {
93
+ --bg: #0e0e11;
94
+ --card: #17171c;
95
+ --ink: #f3f2ee;
96
+ --muted: #9c9aa4;
97
+ --border: #292930;
98
+ --input-bg: #1e1e24;
99
+ --err-bg: #2a1614;
100
+ --err-ink: #f9a29b;
101
+ --err-border: #55302c;
102
+ --shadow: 0 1px 2px rgba(0,0,0,.3), 0 16px 40px -12px rgba(0,0,0,.6);
103
+ }
104
+ }
105
+ * { box-sizing: border-box; }
106
+ html, body { height: 100%; }
107
+ body {
108
+ margin: 0;
109
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
110
+ color: var(--ink);
111
+ background:
112
+ radial-gradient(120% 80% at 50% -10%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 60%),
113
+ var(--bg);
114
+ display: grid;
115
+ place-items: center;
116
+ padding: 24px;
117
+ -webkit-font-smoothing: antialiased;
118
+ }
119
+ .card {
120
+ width: 100%;
121
+ max-width: 400px;
122
+ background: var(--card);
123
+ border: 1px solid var(--border);
124
+ border-radius: 16px;
125
+ box-shadow: var(--shadow);
126
+ overflow: hidden;
127
+ animation: rise .35s cubic-bezier(.2,.7,.2,1) both;
128
+ }
129
+ .card::before { content: ""; display: block; height: 3px; background: var(--accent); }
130
+ .inner { padding: 34px 32px 28px; }
131
+ .eyebrow {
132
+ display: inline-flex; align-items: center; gap: 6px;
133
+ font-size: 11px; font-weight: 600; letter-spacing: .09em; text-transform: uppercase;
134
+ color: var(--muted); margin: 0 0 14px;
135
+ }
136
+ .eyebrow svg { color: var(--accent); }
137
+ h1 { margin: 0; font-size: 25px; font-weight: 700; letter-spacing: -.02em; line-height: 1.15; }
138
+ .sub { margin: 8px 0 0; color: var(--muted); font-size: 14.5px; line-height: 1.5; }
139
+ form { margin: 24px 0 0; }
140
+ .field { margin: 0 0 15px; }
141
+ label { display: block; font-size: 13px; font-weight: 550; margin: 0 0 7px; color: var(--ink); }
142
+ input {
143
+ width: 100%; height: 44px; padding: 0 13px;
144
+ font-size: 15px; color: var(--ink);
145
+ background: var(--input-bg);
146
+ border: 1px solid var(--border); border-radius: 10px;
147
+ outline: none; transition: border-color .12s, box-shadow .12s;
148
+ }
149
+ input:focus-visible { border-color: var(--accent); box-shadow: 0 0 0 3.5px var(--ring); }
150
+ input::placeholder { color: var(--muted); }
151
+ button {
152
+ width: 100%; height: 46px; margin-top: 6px;
153
+ font-size: 15px; font-weight: 600; font-family: inherit;
154
+ color: var(--accent-ink); background: var(--accent);
155
+ border: 0; border-radius: 10px; cursor: pointer;
156
+ transition: filter .12s, transform .04s;
157
+ }
158
+ button:hover { filter: brightness(1.06) saturate(1.05); }
159
+ button:active { transform: translateY(1px); }
160
+ button:focus-visible { outline: none; box-shadow: 0 0 0 3.5px var(--ring); }
161
+ .error {
162
+ display: flex; gap: 9px; align-items: flex-start;
163
+ margin: 0 0 20px; padding: 11px 13px;
164
+ font-size: 13.5px; line-height: 1.45;
165
+ color: var(--err-ink); background: var(--err-bg);
166
+ border: 1px solid var(--err-border); border-radius: 10px;
167
+ }
168
+ .error svg { flex: none; margin-top: 1px; }
169
+ .privacy {
170
+ display: flex; gap: 8px; align-items: flex-start;
171
+ margin: 20px 0 0; color: var(--muted); font-size: 12.5px; line-height: 1.5;
172
+ }
173
+ .privacy svg { flex: none; margin-top: 2px; color: var(--muted); }
174
+ @keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
175
+ @media (prefers-reduced-motion: reduce) { .card { animation: none; } * { transition: none !important; } }
176
+ </style>
177
+ </head>
178
+ <body>
179
+ <main class="card">
180
+ <div class="inner">
181
+ <p class="eyebrow"><svg viewBox="0 0 20 20" aria-hidden="true" width="12" height="12"><path fill="currentColor" d="M10 1a4 4 0 0 0-4 4v2H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-1V5a4 4 0 0 0-4-4Zm-2 6V5a2 2 0 1 1 4 0v2H8Z"/></svg>Secure sign-in</p>
182
+ <h1>Connect to ${service}</h1>
183
+ <p class="sub">Sign in with your ${service} account to authorize access.</p>
184
+ ${errorHtml}
185
+ <form method="post">
186
+ ${fieldsHtml}
187
+ <input type="hidden" name="oauthReq" value="${escapeHtml(encodedOauthReq)}" />
188
+ <button type="submit">Authorize ${service}</button>
189
+ </form>
190
+ ${privacyHtml}
191
+ </div>
192
+ </main>
193
+ </body>
194
+ </html>`;
195
+ }
@@ -0,0 +1,19 @@
1
+ import type { ConnectorAuth } from './types.js';
2
+ export interface ParsedLoginForm {
3
+ /** Raw form field values, keyed by input name (excludes the hidden oauthReq carrier). */
4
+ values: Record<string, string>;
5
+ /** The decoded OAuth authorization request carried in the hidden `oauthReq` field. */
6
+ oauthReq: unknown;
7
+ }
8
+ /**
9
+ * Parses the login form's POST body: extracts plain field values and decodes
10
+ * the hidden `oauthReq` field (base64-encoded JSON) that round-trips the
11
+ * pending OAuth authorization request across the login form submission.
12
+ */
13
+ export declare function parseLoginForm(request: Request): Promise<ParsedLoginForm>;
14
+ /**
15
+ * Default handler for the `/authorize` route: GET renders the service's login
16
+ * form; POST verifies the submitted credentials via `auth.login` and, on
17
+ * success, completes the OAuth authorization with the resulting props.
18
+ */
19
+ export declare function handleAuthorize<Props>(request: Request, env: any, auth: ConnectorAuth<Props>): Promise<Response>;
package/dist/login.js ADDED
@@ -0,0 +1,58 @@
1
+ import { renderLoginPage } from './login-page.js';
2
+ /**
3
+ * Parses the login form's POST body: extracts plain field values and decodes
4
+ * the hidden `oauthReq` field (base64-encoded JSON) that round-trips the
5
+ * pending OAuth authorization request across the login form submission.
6
+ */
7
+ export async function parseLoginForm(request) {
8
+ const formData = await request.formData();
9
+ const values = {};
10
+ for (const [key, value] of formData.entries()) {
11
+ if (typeof value === 'string') {
12
+ values[key] = value;
13
+ }
14
+ }
15
+ const { oauthReq: encodedOauthReq, ...rest } = values;
16
+ const oauthReq = encodedOauthReq ? JSON.parse(atob(encodedOauthReq)) : undefined;
17
+ return { values: rest, oauthReq };
18
+ }
19
+ function messageOf(e) {
20
+ if (e instanceof Error)
21
+ return e.message;
22
+ return String(e);
23
+ }
24
+ /**
25
+ * Default handler for the `/authorize` route: GET renders the service's login
26
+ * form; POST verifies the submitted credentials via `auth.login` and, on
27
+ * success, completes the OAuth authorization with the resulting props.
28
+ */
29
+ export async function handleAuthorize(request, env, auth) {
30
+ if (request.method === 'GET') {
31
+ const oauthReqInfo = await env.OAUTH_PROVIDER.parseAuthRequest(request);
32
+ return new Response(renderLoginPage(auth, { oauthReq: oauthReqInfo }), {
33
+ headers: { 'content-type': 'text/html' },
34
+ });
35
+ }
36
+ const { values, oauthReq: oauthReqInfo } = await parseLoginForm(request);
37
+ const fields = {};
38
+ for (const field of auth.fields) {
39
+ fields[field.name] = values[field.name] ?? '';
40
+ }
41
+ try {
42
+ const props = await auth.login(fields, env);
43
+ const { redirectTo } = await env.OAUTH_PROVIDER.completeAuthorization({
44
+ request: oauthReqInfo,
45
+ userId: fields[auth.fields[0].name],
46
+ scope: [],
47
+ metadata: {},
48
+ props,
49
+ });
50
+ return Response.redirect(redirectTo, 302);
51
+ }
52
+ catch (e) {
53
+ return new Response(renderLoginPage(auth, { error: messageOf(e), oauthReq: oauthReqInfo }), {
54
+ status: 200,
55
+ headers: { 'content-type': 'text/html' },
56
+ });
57
+ }
58
+ }
@@ -0,0 +1,23 @@
1
+ export interface LoginField {
2
+ name: string;
3
+ label: string;
4
+ type?: 'text' | 'password';
5
+ }
6
+ export interface ConnectorAuth<Props> {
7
+ /** Login-page branding, e.g. "Untappd". */
8
+ service: string;
9
+ fields: LoginField[];
10
+ /** Verifies credentials and returns the OAuth props to store. Throws on bad creds. */
11
+ login(fields: Record<string, string>, env: any): Promise<Props>;
12
+ /** One-line note shown under the form. */
13
+ privacyNote?: string;
14
+ /** Brand accent as a hex color (e.g. "#FFC000") for the login page's button, focus ring, and tint. Optional — a neutral is used if absent. */
15
+ accent?: string;
16
+ }
17
+ export interface ConnectorOptions<Props extends Record<string, unknown>, Client> {
18
+ name: string;
19
+ version: string;
20
+ auth: ConnectorAuth<Props>;
21
+ buildClient(props: Props, env: any): Client;
22
+ tools: Array<(server: any, client: Client) => void>;
23
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@chrischall/mcp-connector",
3
+ "version": "0.1.0",
4
+ "description": "Reusable Cloudflare remote-connector harness for MCP servers (auth-agnostic OAuth + streamable-HTTP McpAgent).",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/chrischall/mcp-connector.git"
20
+ },
21
+ "homepage": "https://github.com/chrischall/mcp-connector#readme",
22
+ "bugs": {
23
+ "url": "https://github.com/chrischall/mcp-connector/issues"
24
+ },
25
+ "keywords": [
26
+ "mcp",
27
+ "model-context-protocol",
28
+ "cloudflare",
29
+ "workers",
30
+ "oauth",
31
+ "connector",
32
+ "remote-mcp"
33
+ ],
34
+ "author": "chrischall",
35
+ "license": "MIT",
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc -p tsconfig.json",
41
+ "prepare": "tsc -p tsconfig.json",
42
+ "typecheck": "tsc --noEmit",
43
+ "test": "vitest run"
44
+ },
45
+ "peerDependencies": {
46
+ "@cloudflare/workers-oauth-provider": "^0.0.11",
47
+ "@modelcontextprotocol/sdk": "^1.27.0",
48
+ "agents": "^0.17.3"
49
+ },
50
+ "devDependencies": {
51
+ "@cloudflare/workers-oauth-provider": "^0.0.11",
52
+ "@cloudflare/workers-types": "^5.20260708.1",
53
+ "@modelcontextprotocol/sdk": "^1.27.0",
54
+ "agents": "^0.17.3",
55
+ "typescript": "^5.9.3",
56
+ "vitest": "^3.2.7"
57
+ }
58
+ }