@rifts_to/mcp 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/dist/worker.js ADDED
@@ -0,0 +1,247 @@
1
+ /**
2
+ * MCP over Streamable HTTP, on Cloudflare Workers. What `mcp.rifts.to` runs.
3
+ *
4
+ * **Transport choice.** The SDK ships two Streamable HTTP server transports.
5
+ * `StreamableHTTPServerTransport` is written against Node's `IncomingMessage`
6
+ * and `ServerResponse`, so it needs `nodejs_compat` and a shim layer that would
7
+ * be load-bearing but never exercised by a test — a build that type-checks and
8
+ * then fails on the first real request. Its own implementation delegates to
9
+ * `WebStandardStreamableHTTPServerTransport`, which is written against
10
+ * `Request`/`Response`/`ReadableStream` and is documented for Workers, so this
11
+ * file uses that one directly and hand-rolls nothing. Writing a bespoke
12
+ * fetch-shaped transport was the other option and was rejected for the same
13
+ * reason: the SDK's version already handles Accept negotiation, protocol
14
+ * version checks and batched messages, and a reimplementation would drift from
15
+ * the spec the moment the spec moved.
16
+ *
17
+ * **Stateless, one server object per request.** Every caller presents a
18
+ * different bearer token, and a Worker isolate is shared between callers, so a
19
+ * long-lived `McpServer` would have to hold a token from some earlier request.
20
+ * Building the server and transport per request makes that impossible by
21
+ * construction. It costs a session: there is no `Mcp-Session-Id`, no `GET /mcp`
22
+ * notification stream, and no resumability. All four tools are plain
23
+ * request/response, so nothing here has a use for any of that.
24
+ *
25
+ * **No secrets, no bindings.** The Worker never sees a credential of its own.
26
+ * It reads the caller's bearer token, hands it to `/api/v1`, and turns the
27
+ * API's 401 back into a `WWW-Authenticate` challenge. Token validation happens
28
+ * in exactly one place, and compromising this Worker yields nothing that was
29
+ * not already presented to it.
30
+ */
31
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
32
+ import { DEFAULT_BASE_URL, RiftsClient } from "./client.js";
33
+ import { createServer } from "./server.js";
34
+ const MCP_PATH = "/mcp";
35
+ /**
36
+ * RFC 9728 fixes this path for a resource identified by a bare origin. The
37
+ * `resource` this document advertises is the origin with no path, which is what
38
+ * makes that the right URL — and it is also the audience value `/api/v1`
39
+ * accepts for tokens minted for this server, so the two must not drift.
40
+ */
41
+ const RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
42
+ /** rifts.to is the authorization server: it is where accounts and D1 live. */
43
+ const AUTHORIZATION_SERVER = "https://rifts.to";
44
+ const SCOPES = ["surveys:read", "surveys:write"];
45
+ /**
46
+ * A wildcard origin is safe here only because this Worker has no ambient
47
+ * authority: no cookies, no bindings, and every request is authorized by a
48
+ * bearer token the caller had to already hold. A browser page that reaches this
49
+ * endpoint gains nothing it could not get with `curl`.
50
+ */
51
+ const CORS_HEADERS = {
52
+ "Access-Control-Allow-Origin": "*",
53
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
54
+ "Access-Control-Allow-Headers": "Authorization, Content-Type, Mcp-Session-Id, MCP-Protocol-Version, Last-Event-ID",
55
+ // Without this the browser hides both headers from the page: the session id a
56
+ // stateful client would need, and the challenge that starts the OAuth flow.
57
+ "Access-Control-Expose-Headers": "Mcp-Session-Id, WWW-Authenticate",
58
+ "Access-Control-Max-Age": "86400",
59
+ };
60
+ export default {
61
+ async fetch(request, env) {
62
+ const url = new URL(request.url);
63
+ if (request.method === "OPTIONS") {
64
+ return new Response(null, { status: 204, headers: CORS_HEADERS });
65
+ }
66
+ if (url.pathname === RESOURCE_METADATA_PATH) {
67
+ if (request.method !== "GET" && request.method !== "HEAD") {
68
+ return methodNotAllowed("GET, HEAD, OPTIONS");
69
+ }
70
+ return resourceMetadata(url, env);
71
+ }
72
+ if (url.pathname === MCP_PATH) {
73
+ if (request.method !== "POST") {
74
+ // 405 rather than 404: a client probing `GET /mcp` for the optional
75
+ // server-initiated stream must learn that this server does not offer
76
+ // one, not that it typed the URL wrong.
77
+ return methodNotAllowed("POST, OPTIONS");
78
+ }
79
+ return handleMcp(request, env, url);
80
+ }
81
+ return json({ error: "not found" }, 404);
82
+ },
83
+ };
84
+ /**
85
+ * RFC 9728 metadata. Everything is derived rather than baked in, so one build
86
+ * is correct on workers.dev and on mcp.rifts.to without a config change that
87
+ * somebody has to remember.
88
+ *
89
+ * `authorization_servers` follows `RIFTS_API_URL` rather than being pinned to
90
+ * production, because the rifts.to deployment this server talks to *is* its
91
+ * authorization server. Pinning them apart is how a preview build ends up
92
+ * telling clients to go and authorize somewhere that does not know about it.
93
+ */
94
+ function resourceMetadata(url, env) {
95
+ const authorizationServer = env.RIFTS_API_URL?.trim() || AUTHORIZATION_SERVER;
96
+ return json({
97
+ resource: url.origin,
98
+ authorization_servers: [authorizationServer.replace(/\/$/, "")],
99
+ scopes_supported: SCOPES,
100
+ // OAuth 2.1 and the MCP spec both forbid a token in a query string, and
101
+ // this server reads only the header. Saying so is not decoration: it is
102
+ // the machine-readable half of that rule.
103
+ bearer_methods_supported: ["header"],
104
+ }, 200, {
105
+ // Metadata that rarely changes, but a stale copy sends a client to the
106
+ // wrong authorization server, so keep the window short.
107
+ "Cache-Control": "public, max-age=3600",
108
+ });
109
+ }
110
+ async function handleMcp(request, env, url) {
111
+ const token = bearerToken(request.headers.get("Authorization"));
112
+ if (!token)
113
+ return unauthorized(url, "no bearer token was presented");
114
+ const baseUrl = env.RIFTS_API_URL?.trim() || DEFAULT_BASE_URL;
115
+ // Validate before serving anything.
116
+ //
117
+ // The reactive check below only trips when a tool actually calls the API, and
118
+ // `initialize` and `tools/list` never do. Without this, a request carrying a
119
+ // revoked or expired token got a cheerful 200 listing four tools, so a client
120
+ // sat there looking connected and only discovered the problem on the first
121
+ // tool call, as an error message rather than as the 401 that would have made
122
+ // it refresh. For an OAuth client the 401 *is* the signal, so it has to come
123
+ // before the work, not after.
124
+ //
125
+ // The cost is one edge-to-edge request per call. These are conversational
126
+ // volumes, not /api/respond volumes, and correctness here is worth more than
127
+ // the round trip.
128
+ const probe = await fetch(`${baseUrl}/api/v1/me`, {
129
+ headers: { Authorization: `Bearer ${token}` },
130
+ });
131
+ if (probe.status === 401) {
132
+ return unauthorized(url, "the rifts.to API rejected the token", "invalid_token");
133
+ }
134
+ if (probe.status === 403) {
135
+ // Entitlement, not authentication. A challenge would send the client round
136
+ // the OAuth loop again to arrive at the same place, so say what is wrong
137
+ // instead: this needs a subscription, not a fresh token.
138
+ return json({
139
+ error: "insufficient_scope",
140
+ error_description: "this rifts.to account does not have an active subscription",
141
+ }, 403);
142
+ }
143
+ // The API is the only thing that can tell a live token from a dead one, so
144
+ // "was this token rejected" is observed at the fetch layer rather than
145
+ // guessed at. A tool call that 401s is not a tool failure the model should
146
+ // apologise for; it is an expired credential, and the client can only know to
147
+ // re-authenticate if it gets the challenge as an HTTP status.
148
+ let tokenRejected = false;
149
+ const client = new RiftsClient({
150
+ baseUrl,
151
+ token,
152
+ fetchImpl: async (input, init) => {
153
+ const response = await fetch(input, init);
154
+ if (response.status === 401)
155
+ tokenRejected = true;
156
+ return response;
157
+ },
158
+ });
159
+ const server = createServer(client);
160
+ const transport = new WebStandardStreamableHTTPServerTransport({
161
+ // Stateless: no session to resume and nowhere to keep one. See the header.
162
+ sessionIdGenerator: undefined,
163
+ // Plain JSON rather than an SSE frame per response. Nothing here streams,
164
+ // and a resolved Response (as opposed to a stream the isolate must stay
165
+ // alive to feed) is also what lets the 401 check below run before anything
166
+ // has been sent.
167
+ enableJsonResponse: true,
168
+ });
169
+ try {
170
+ await server.connect(transport);
171
+ const response = await transport.handleRequest(request);
172
+ if (tokenRejected) {
173
+ return unauthorized(url, "the rifts.to API rejected the token", "invalid_token");
174
+ }
175
+ return withCors(response);
176
+ }
177
+ catch (error) {
178
+ // A throw out of the transport is this server's bug, not the caller's, and
179
+ // it still has to come back as JSON-RPC or the client reports a transport
180
+ // error with nothing in it.
181
+ return json({
182
+ jsonrpc: "2.0",
183
+ error: { code: -32603, message: message(error) },
184
+ id: null,
185
+ }, 500);
186
+ }
187
+ finally {
188
+ // In JSON mode the body is fully materialised before handleRequest
189
+ // resolves, so closing here cannot truncate a response in flight.
190
+ await server.close().catch(() => { });
191
+ }
192
+ }
193
+ /**
194
+ * The `resource_metadata` parameter is the whole point of this response: it is
195
+ * what tells a client where to look up the authorization server, and a client
196
+ * that cannot parse it simply never offers to sign in. RFC 9728 quotes the
197
+ * value, so it is quoted here.
198
+ *
199
+ * `error` is omitted when nothing was presented at all, per RFC 6750: an
200
+ * `invalid_token` code on a request that carried no token tells a client its
201
+ * stored credential was rejected, which would send it to refresh a token it
202
+ * never sent.
203
+ */
204
+ function unauthorized(url, detail, code) {
205
+ const params = [
206
+ `realm="rifts.to"`,
207
+ ...(code ? [`error="${code}"`] : []),
208
+ `error_description="${detail}"`,
209
+ `resource_metadata="${url.origin}${RESOURCE_METADATA_PATH}"`,
210
+ ];
211
+ return json({ error: code ?? "unauthorized", error_description: detail }, 401, {
212
+ "WWW-Authenticate": `Bearer ${params.join(", ")}`,
213
+ });
214
+ }
215
+ /**
216
+ * Only the `Bearer` scheme, and the scheme name compared case-insensitively
217
+ * because RFC 7235 says it is. Anything else is treated as no token at all, so
218
+ * a client sending Basic credentials gets the challenge rather than a 500.
219
+ */
220
+ function bearerToken(header) {
221
+ if (!header)
222
+ return undefined;
223
+ const match = /^Bearer[ ]+(.+)$/i.exec(header.trim());
224
+ return match?.[1]?.trim() || undefined;
225
+ }
226
+ function methodNotAllowed(allow) {
227
+ return json({ error: "method not allowed" }, 405, { Allow: allow });
228
+ }
229
+ function json(body, status, headers = {}) {
230
+ return new Response(JSON.stringify(body), {
231
+ status,
232
+ headers: { "Content-Type": "application/json", ...CORS_HEADERS, ...headers },
233
+ });
234
+ }
235
+ /** The transport builds its own Response, so CORS has to be added afterwards. */
236
+ function withCors(response) {
237
+ const headers = new Headers(response.headers);
238
+ for (const [name, value] of Object.entries(CORS_HEADERS))
239
+ headers.set(name, value);
240
+ return new Response(response.body, {
241
+ status: response.status,
242
+ statusText: response.statusText,
243
+ headers,
244
+ });
245
+ }
246
+ const message = (error) => (error instanceof Error ? error.message : String(error));
247
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@rifts_to/mcp",
3
+ "version": "0.1.0",
4
+ "description": "rifts.to MCP server. Create live audience surveys and read their results from an AI client.",
5
+ "license": "Apache-2.0",
6
+ "author": "RIFTS TO, LLC",
7
+ "homepage": "https://rifts.to",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/riftsto/mcp.git"
11
+ },
12
+ "type": "module",
13
+ "bin": {
14
+ "rifts-mcp": "dist/stdio.js"
15
+ },
16
+ "main": "dist/server.js",
17
+ "types": "dist/server.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/server.d.ts",
21
+ "default": "./dist/server.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "LICENSE",
27
+ "NOTICE",
28
+ "README.md"
29
+ ],
30
+ "engines": {
31
+ "node": ">=20"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "keywords": [
37
+ "mcp",
38
+ "modelcontextprotocol",
39
+ "rifts.to",
40
+ "survey",
41
+ "poll"
42
+ ],
43
+ "scripts": {
44
+ "build": "tsc",
45
+ "test": "vitest run",
46
+ "typecheck": "tsc -p tsconfig.test.json",
47
+ "dev": "wrangler dev",
48
+ "deploy": "wrangler deploy",
49
+ "prepublishOnly": "npm run build"
50
+ },
51
+ "dependencies": {
52
+ "@modelcontextprotocol/sdk": "^1.30.0",
53
+ "zod": "^4.1.13"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^20.19.9",
57
+ "typescript": "^5.9.3",
58
+ "vitest": "^4.1.10",
59
+ "wrangler": "^4.120.0"
60
+ },
61
+ "bugs": {
62
+ "url": "https://github.com/riftsto/mcp/issues"
63
+ }
64
+ }