@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/LICENSE +201 -0
- package/NOTICE +8 -0
- package/README.md +124 -0
- package/dist/client.d.ts +129 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +161 -0
- package/dist/client.js.map +1 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +31 -0
- package/dist/server.js.map +1 -0
- package/dist/stdio.d.ts +17 -0
- package/dist/stdio.d.ts.map +1 -0
- package/dist/stdio.js +53 -0
- package/dist/stdio.js.map +1 -0
- package/dist/tools.d.ts +30 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +246 -0
- package/dist/tools.js.map +1 -0
- package/dist/worker.d.ts +39 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +247 -0
- package/dist/worker.js.map +1 -0
- package/package.json +64 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the MCP server. Transport-free on purpose: `stdio.ts` connects a
|
|
3
|
+
* stdio transport to it, and the hosted deployment will connect an HTTP one to
|
|
4
|
+
* the same object, so neither entry point can drift on which tools exist or
|
|
5
|
+
* how they behave.
|
|
6
|
+
*/
|
|
7
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
import { registerTools } from "./tools.js";
|
|
9
|
+
/**
|
|
10
|
+
* The protocol identifier, not the brand. Clients namespace tool names with it
|
|
11
|
+
* (`mcp__rifts__create_survey`), so it stays a bare slug: a dot in here would
|
|
12
|
+
* ride along into every tool name. `title` below is where "rifts.to" belongs.
|
|
13
|
+
*/
|
|
14
|
+
export const SERVER_NAME = "rifts";
|
|
15
|
+
/** Kept in step with package.json by hand; nothing imports JSON at runtime. */
|
|
16
|
+
export const SERVER_VERSION = "0.1.0";
|
|
17
|
+
export function createServer(client) {
|
|
18
|
+
const server = new McpServer({
|
|
19
|
+
name: SERVER_NAME,
|
|
20
|
+
version: SERVER_VERSION,
|
|
21
|
+
title: "rifts.to MCP server",
|
|
22
|
+
}, {
|
|
23
|
+
// Shown by clients that surface it, and the only place this server can
|
|
24
|
+
// explain the account/subscription requirement before a tool call fails.
|
|
25
|
+
instructions: "Create and read live audience surveys on rifts.to. create_survey returns a public link to share with respondents and an admin link that shows results in real time. Every call uses the account that owns the configured RIFTS_TOKEN, and requires that account to have an active rifts.to subscription.",
|
|
26
|
+
});
|
|
27
|
+
registerTools(server, client);
|
|
28
|
+
return server;
|
|
29
|
+
}
|
|
30
|
+
export { RiftsClient, RiftsApiError, DEFAULT_BASE_URL } from "./client.js";
|
|
31
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC;AAEnC,+EAA+E;AAC/E,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AAEtC,MAAM,UAAU,YAAY,CAAC,MAAmB;IAC9C,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B;QACE,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,cAAc;QACvB,KAAK,EAAE,qBAAqB;KAC7B,EACD;QACE,uEAAuE;QACvE,yEAAyE;QACzE,YAAY,EACV,0SAA0S;KAC7S,CACF,CAAC;IAEF,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE9B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/stdio.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The `rifts-mcp` executable: MCP over stdio, credentials from the
|
|
4
|
+
* environment.
|
|
5
|
+
*
|
|
6
|
+
* Taking the token from `RIFTS_TOKEN` rather than running an OAuth flow is
|
|
7
|
+
* what the MCP specification asks of a stdio server, not a shortcut — a
|
|
8
|
+
* process with no browser and no redirect URI has nowhere to run one.
|
|
9
|
+
*
|
|
10
|
+
* **stdout belongs to the protocol.** A single stray byte written to it — a
|
|
11
|
+
* banner, a `console.log`, a stray `process.stdout.write` from a dependency —
|
|
12
|
+
* lands in the middle of the JSON-RPC stream and the client fails with an
|
|
13
|
+
* opaque parse error that points at nothing. Everything diagnostic goes to
|
|
14
|
+
* stderr, which clients collect as logs.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=stdio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;GAaG"}
|
package/dist/stdio.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The `rifts-mcp` executable: MCP over stdio, credentials from the
|
|
4
|
+
* environment.
|
|
5
|
+
*
|
|
6
|
+
* Taking the token from `RIFTS_TOKEN` rather than running an OAuth flow is
|
|
7
|
+
* what the MCP specification asks of a stdio server, not a shortcut — a
|
|
8
|
+
* process with no browser and no redirect URI has nowhere to run one.
|
|
9
|
+
*
|
|
10
|
+
* **stdout belongs to the protocol.** A single stray byte written to it — a
|
|
11
|
+
* banner, a `console.log`, a stray `process.stdout.write` from a dependency —
|
|
12
|
+
* lands in the middle of the JSON-RPC stream and the client fails with an
|
|
13
|
+
* opaque parse error that points at nothing. Everything diagnostic goes to
|
|
14
|
+
* stderr, which clients collect as logs.
|
|
15
|
+
*/
|
|
16
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
17
|
+
import { DEFAULT_BASE_URL, RiftsClient } from "./client.js";
|
|
18
|
+
import { createServer } from "./server.js";
|
|
19
|
+
// Belt and braces for the rule above: anything that reaches for console.log,
|
|
20
|
+
// here or in a dependency, is redirected to stderr instead of corrupting the
|
|
21
|
+
// stream. Cheaper than diagnosing it later from a client-side parse error.
|
|
22
|
+
console.log = console.error;
|
|
23
|
+
async function main() {
|
|
24
|
+
const token = process.env.RIFTS_TOKEN?.trim();
|
|
25
|
+
if (!token) {
|
|
26
|
+
console.error([
|
|
27
|
+
"rifts-mcp: RIFTS_TOKEN is not set.",
|
|
28
|
+
"",
|
|
29
|
+
"Create a personal access token at https://rifts.to/en/account (API tokens),",
|
|
30
|
+
"then pass it to this server in the environment, e.g.:",
|
|
31
|
+
"",
|
|
32
|
+
' "env": { "RIFTS_TOKEN": "rifts_pat_..." }',
|
|
33
|
+
].join("\n"));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
const baseUrl = process.env.RIFTS_API_URL?.trim() || DEFAULT_BASE_URL;
|
|
37
|
+
const server = createServer(new RiftsClient({ baseUrl, token }));
|
|
38
|
+
// The token is never logged, here or anywhere else — the base URL is the
|
|
39
|
+
// only part of the configuration that is safe to echo.
|
|
40
|
+
console.error(`rifts-mcp ready (api: ${baseUrl})`);
|
|
41
|
+
await server.connect(new StdioServerTransport());
|
|
42
|
+
// A client disconnecting closes stdin; exit rather than linger as an orphan.
|
|
43
|
+
const shutdown = () => {
|
|
44
|
+
void server.close().finally(() => process.exit(0));
|
|
45
|
+
};
|
|
46
|
+
process.on("SIGINT", shutdown);
|
|
47
|
+
process.on("SIGTERM", shutdown);
|
|
48
|
+
}
|
|
49
|
+
main().catch((error) => {
|
|
50
|
+
console.error(`rifts-mcp: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,6EAA6E;AAC7E,6EAA6E;AAC7E,2EAA2E;AAC3E,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC;AAE5B,KAAK,UAAU,IAAI;IACjB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;IAC9C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CACX;YACE,oCAAoC;YACpC,EAAE;YACF,6EAA6E;YAC7E,uDAAuD;YACvD,EAAE;YACF,6CAA6C;SAC9C,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,gBAAgB,CAAC;IAEtE,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,WAAW,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAEjE,yEAAyE;IACzE,uDAAuD;IACvD,OAAO,CAAC,KAAK,CAAC,yBAAyB,OAAO,GAAG,CAAC,CAAC;IAEnD,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IAEjD,6EAA6E;IAC7E,MAAM,QAAQ,GAAG,GAAG,EAAE;QACpB,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,cAAc,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACtF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four tools, and the text a model actually reads.
|
|
3
|
+
*
|
|
4
|
+
* **Descriptions say what a tool does, never how Claude should behave.** The
|
|
5
|
+
* Connectors Directory rejects descriptions that instruct the model rather
|
|
6
|
+
* than describe the tool, and the rule is a good one independent of review:
|
|
7
|
+
* a description is read in every conversation this server is connected to,
|
|
8
|
+
* so behavioural instructions in it are a standing side effect nobody asked
|
|
9
|
+
* for. Stating that an admin link is a credential is a fact about the return
|
|
10
|
+
* value. Telling Claude to ask permission before returning one is not.
|
|
11
|
+
* Naming when a tool applies ("use this to poll a room") is still a
|
|
12
|
+
* description of the tool's purpose and is fine.
|
|
13
|
+
*
|
|
14
|
+
* Two further rules shape everything here:
|
|
15
|
+
*
|
|
16
|
+
* 1. The input schemas must not accept a question shape rifts.to's own
|
|
17
|
+
* `validateQuestions` would reject, or the model gets a 400 it cannot fix
|
|
18
|
+
* from the error text. They may be *narrower* (empty question text is
|
|
19
|
+
* accepted by the server and refused here), never wider.
|
|
20
|
+
* 2. Every result carries readable prose in `content` and the raw JSON in
|
|
21
|
+
* `structuredContent`. The prose is what the model reasons over, so a
|
|
22
|
+
* dump of response rows is the wrong thing to hand it for a 200-response
|
|
23
|
+
* survey — `get_survey_results` summarises per question and lets
|
|
24
|
+
* `structuredContent` carry the rows for anything that wants them.
|
|
25
|
+
*/
|
|
26
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
27
|
+
import type { RiftsClient } from "./client.js";
|
|
28
|
+
/** Registers every tool on `server`. Split out so server.ts stays a wiring file. */
|
|
29
|
+
export declare function registerTools(server: McpServer, client: RiftsClient): void;
|
|
30
|
+
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,OAAO,KAAK,EAGV,WAAW,EAGZ,MAAM,aAAa,CAAC;AAqCrB,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CA6H1E"}
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four tools, and the text a model actually reads.
|
|
3
|
+
*
|
|
4
|
+
* **Descriptions say what a tool does, never how Claude should behave.** The
|
|
5
|
+
* Connectors Directory rejects descriptions that instruct the model rather
|
|
6
|
+
* than describe the tool, and the rule is a good one independent of review:
|
|
7
|
+
* a description is read in every conversation this server is connected to,
|
|
8
|
+
* so behavioural instructions in it are a standing side effect nobody asked
|
|
9
|
+
* for. Stating that an admin link is a credential is a fact about the return
|
|
10
|
+
* value. Telling Claude to ask permission before returning one is not.
|
|
11
|
+
* Naming when a tool applies ("use this to poll a room") is still a
|
|
12
|
+
* description of the tool's purpose and is fine.
|
|
13
|
+
*
|
|
14
|
+
* Two further rules shape everything here:
|
|
15
|
+
*
|
|
16
|
+
* 1. The input schemas must not accept a question shape rifts.to's own
|
|
17
|
+
* `validateQuestions` would reject, or the model gets a 400 it cannot fix
|
|
18
|
+
* from the error text. They may be *narrower* (empty question text is
|
|
19
|
+
* accepted by the server and refused here), never wider.
|
|
20
|
+
* 2. Every result carries readable prose in `content` and the raw JSON in
|
|
21
|
+
* `structuredContent`. The prose is what the model reasons over, so a
|
|
22
|
+
* dump of response rows is the wrong thing to hand it for a 200-response
|
|
23
|
+
* survey — `get_survey_results` summarises per question and lets
|
|
24
|
+
* `structuredContent` carry the rows for anything that wants them.
|
|
25
|
+
*/
|
|
26
|
+
import { z } from "zod";
|
|
27
|
+
/**
|
|
28
|
+
* Rating questions are stored with a `scale`, but `SurveyForm.tsx` renders a
|
|
29
|
+
* hardcoded 1–10 grid and never reads it. Exposing a caller-chosen range would
|
|
30
|
+
* therefore promise respondents a UI that does not exist, so the tool takes no
|
|
31
|
+
* scale and always sends the one the web builder sends.
|
|
32
|
+
*/
|
|
33
|
+
const RATING_SCALE = { min: 1, max: 10 };
|
|
34
|
+
/** Matches isValidCustomSlug in src/lib/slugs.ts. */
|
|
35
|
+
const CUSTOM_SLUG = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
36
|
+
const questionSchema = z
|
|
37
|
+
.discriminatedUnion("type", [
|
|
38
|
+
z.object({
|
|
39
|
+
type: z.literal("multiple_choice"),
|
|
40
|
+
text: z.string().min(1).describe("The question, as the respondent reads it."),
|
|
41
|
+
options: z
|
|
42
|
+
.array(z.string().min(1))
|
|
43
|
+
.min(1)
|
|
44
|
+
.describe("The choices. Respondents pick exactly one."),
|
|
45
|
+
}),
|
|
46
|
+
z.object({
|
|
47
|
+
type: z.literal("free_text"),
|
|
48
|
+
text: z.string().min(1).describe("The question, as the respondent reads it."),
|
|
49
|
+
}),
|
|
50
|
+
z.object({
|
|
51
|
+
type: z.literal("rating"),
|
|
52
|
+
text: z
|
|
53
|
+
.string()
|
|
54
|
+
.min(1)
|
|
55
|
+
.describe("The question, as the respondent reads it. Answered on a 1-10 scale."),
|
|
56
|
+
}),
|
|
57
|
+
])
|
|
58
|
+
.describe("A single question. Questions are answered in the order given.");
|
|
59
|
+
/** Registers every tool on `server`. Split out so server.ts stays a wiring file. */
|
|
60
|
+
export function registerTools(server, client) {
|
|
61
|
+
server.registerTool("create_survey", {
|
|
62
|
+
title: "Create a survey",
|
|
63
|
+
description: "Create a live audience survey on rifts.to and get back the two links that run it: a public URL to share with the audience (anyone with the link can answer, no account or sign-in needed) and an admin URL that shows the results updating in real time. Questions can be multiple choice, free text, or a 1-10 rating, and are answered in the order given. Use this whenever someone wants to poll a room, run a quick vote, or collect open-ended feedback. The survey is open for responses immediately.",
|
|
64
|
+
inputSchema: {
|
|
65
|
+
title: z
|
|
66
|
+
.string()
|
|
67
|
+
.min(1)
|
|
68
|
+
.max(200)
|
|
69
|
+
.describe("The survey title. Respondents see it above the questions."),
|
|
70
|
+
questions: z.array(questionSchema).min(1).describe("At least one question."),
|
|
71
|
+
slug: z
|
|
72
|
+
.string()
|
|
73
|
+
.min(3)
|
|
74
|
+
.max(64)
|
|
75
|
+
.regex(CUSTOM_SLUG, "lowercase letters, digits and single hyphens only")
|
|
76
|
+
.optional()
|
|
77
|
+
.describe("Optional custom link, e.g. 'standup-mood' for rifts.to/en/s/standup-mood. Omit it and rifts.to picks a readable three-word one. Fails if the name is already taken."),
|
|
78
|
+
},
|
|
79
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
80
|
+
}, async ({ title, questions, slug }) => run(async () => {
|
|
81
|
+
const survey = await client.createSurvey({
|
|
82
|
+
title,
|
|
83
|
+
questions: questions.map(toQuestionInput),
|
|
84
|
+
...(slug ? { slug } : {}),
|
|
85
|
+
});
|
|
86
|
+
return result([
|
|
87
|
+
`Created "${survey.title}".`,
|
|
88
|
+
``,
|
|
89
|
+
`Share this link with the audience: ${survey.url}`,
|
|
90
|
+
`Watch the results here: ${survey.admin_url}`,
|
|
91
|
+
``,
|
|
92
|
+
`Survey id: ${survey.id} (use it with get_survey_results and close_survey).`,
|
|
93
|
+
].join("\n"), survey);
|
|
94
|
+
}));
|
|
95
|
+
server.registerTool("list_surveys", {
|
|
96
|
+
title: "List your surveys",
|
|
97
|
+
description: "List the surveys on the authenticated rifts.to account, newest first, with the id, title, open/closed status, response count and public link for each. Use it to find the id of a survey before reading its results or closing it. Archived surveys are never listed. Admin links are left out unless include_admin is set, so a routine listing does not fill the conversation with credentials nobody asked for.",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
include_admin: z
|
|
100
|
+
.boolean()
|
|
101
|
+
.optional()
|
|
102
|
+
.describe("Include each survey's admin link. An admin link is a credential: anyone holding it can read every response and close the survey. Omitted by default."),
|
|
103
|
+
include_closed: z
|
|
104
|
+
.boolean()
|
|
105
|
+
.optional()
|
|
106
|
+
.describe("Include surveys that are already closed. Defaults to true."),
|
|
107
|
+
},
|
|
108
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
109
|
+
}, async ({ include_admin, include_closed }) => run(async () => {
|
|
110
|
+
const surveys = await client.listSurveys({
|
|
111
|
+
...(include_admin === undefined ? {} : { includeAdmin: include_admin }),
|
|
112
|
+
...(include_closed === undefined ? {} : { includeClosed: include_closed }),
|
|
113
|
+
});
|
|
114
|
+
return result(formatList(surveys), { surveys: surveys });
|
|
115
|
+
}));
|
|
116
|
+
server.registerTool("get_survey_results", {
|
|
117
|
+
title: "Read survey results",
|
|
118
|
+
description: "Read the answers to one of your surveys. Returns the questions with a summary of how they were answered: the tally per option for multiple choice, the average for ratings, and every free-text answer in full, plus the total response count and the survey's current status. Responses carry no respondent identity: rifts.to never records who answered, so results cannot be attributed to a person.",
|
|
119
|
+
inputSchema: {
|
|
120
|
+
id: z
|
|
121
|
+
.string()
|
|
122
|
+
.min(1)
|
|
123
|
+
.describe("The survey id, e.g. 'fuzzy-sleepy-tornado', the last path segment of the public link. Get it from create_survey or list_surveys."),
|
|
124
|
+
},
|
|
125
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
126
|
+
}, async ({ id }) => run(async () => {
|
|
127
|
+
const results = await client.getSurveyResults(id);
|
|
128
|
+
return result(formatResults(results), results);
|
|
129
|
+
}));
|
|
130
|
+
server.registerTool("close_survey", {
|
|
131
|
+
title: "Close a survey",
|
|
132
|
+
description: "Stop a survey from accepting new responses. The public link keeps working and shows the survey as closed, and the results stay readable with get_survey_results. Closing is not reversible through this server: reopening can only be done from the rifts.to admin dashboard.",
|
|
133
|
+
inputSchema: {
|
|
134
|
+
id: z
|
|
135
|
+
.string()
|
|
136
|
+
.min(1)
|
|
137
|
+
.describe("The survey id, e.g. 'fuzzy-sleepy-tornado'."),
|
|
138
|
+
},
|
|
139
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
140
|
+
}, async ({ id }) => run(async () => {
|
|
141
|
+
const closed = await client.closeSurvey(id);
|
|
142
|
+
return result(`Closed "${closed.id}". It no longer accepts responses; the results are still readable with get_survey_results.`, closed);
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* `scale` is added here rather than in the tool schema — see RATING_SCALE.
|
|
147
|
+
*/
|
|
148
|
+
function toQuestionInput(question) {
|
|
149
|
+
return question.type === "rating" ? { ...question, scale: { ...RATING_SCALE } } : question;
|
|
150
|
+
}
|
|
151
|
+
function result(text, structuredContent) {
|
|
152
|
+
// No `outputSchema` on any of these tools, deliberately: the SDK validates
|
|
153
|
+
// results against one when it is declared, which would turn an additive
|
|
154
|
+
// field on a versioned API into a tool failure for every caller running an
|
|
155
|
+
// older copy of this package. The text is the contract for the model.
|
|
156
|
+
return { content: [{ type: "text", text }], structuredContent };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The SDK already converts a thrown error into an `isError` result, but doing
|
|
160
|
+
* it here keeps the wording ours and independent of the SDK's internals.
|
|
161
|
+
*/
|
|
162
|
+
async function run(handler) {
|
|
163
|
+
try {
|
|
164
|
+
return await handler();
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return {
|
|
168
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
169
|
+
isError: true,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function formatList(surveys) {
|
|
174
|
+
if (surveys.length === 0) {
|
|
175
|
+
return "No surveys on this account yet. create_survey makes one.";
|
|
176
|
+
}
|
|
177
|
+
const lines = surveys.map((s) => {
|
|
178
|
+
const parts = [
|
|
179
|
+
`- ${s.title} [${s.id}]: ${s.status}, ${plural(s.response_count, "response")}`,
|
|
180
|
+
` ${s.url}`,
|
|
181
|
+
];
|
|
182
|
+
if (s.admin_url)
|
|
183
|
+
parts.push(` admin: ${s.admin_url}`);
|
|
184
|
+
return parts.join("\n");
|
|
185
|
+
});
|
|
186
|
+
return `${plural(surveys.length, "survey")}:\n${lines.join("\n")}`;
|
|
187
|
+
}
|
|
188
|
+
function formatResults(results) {
|
|
189
|
+
const header = [
|
|
190
|
+
`"${results.title}" [${results.id}]: ${results.status}, ${plural(results.response_count, "response")}`,
|
|
191
|
+
results.url,
|
|
192
|
+
];
|
|
193
|
+
if (results.response_count === 0) {
|
|
194
|
+
return [...header, "", "Nobody has answered yet."].join("\n");
|
|
195
|
+
}
|
|
196
|
+
const blocks = results.questions.map((question) => {
|
|
197
|
+
// A response that skipped this question has no key for it at all, so an
|
|
198
|
+
// unanswered question is an absence rather than an empty string.
|
|
199
|
+
const answers = results.responses
|
|
200
|
+
.map((r) => r.answers[String(question.index)])
|
|
201
|
+
.filter((a) => a !== undefined && a !== null && a !== "");
|
|
202
|
+
return [`Q${question.index + 1}. ${question.text}`, ...summarise(question, answers)].join("\n");
|
|
203
|
+
});
|
|
204
|
+
return [header.join("\n"), ...blocks].join("\n\n");
|
|
205
|
+
}
|
|
206
|
+
function summarise(question, answers) {
|
|
207
|
+
if (answers.length === 0)
|
|
208
|
+
return [" (no answers)"];
|
|
209
|
+
switch (question.type) {
|
|
210
|
+
case "multiple_choice": {
|
|
211
|
+
// Seeded from the declared options so a choice nobody picked still shows
|
|
212
|
+
// as 0 rather than vanishing, and counted by string value because that
|
|
213
|
+
// is how an answer is stored — an option renamed after the fact stops
|
|
214
|
+
// matching, which is exactly why the admin UI forbids renaming one.
|
|
215
|
+
const counts = new Map(question.options.map((o) => [o, 0]));
|
|
216
|
+
let unrecognised = 0;
|
|
217
|
+
for (const answer of answers) {
|
|
218
|
+
const value = String(answer);
|
|
219
|
+
const current = counts.get(value);
|
|
220
|
+
if (current === undefined)
|
|
221
|
+
unrecognised++;
|
|
222
|
+
else
|
|
223
|
+
counts.set(value, current + 1);
|
|
224
|
+
}
|
|
225
|
+
const lines = [...counts].map(([option, count]) => ` ${option}: ${count} (${percent(count, answers.length)})`);
|
|
226
|
+
if (unrecognised > 0) {
|
|
227
|
+
lines.push(` (${unrecognised} answer(s) no longer match any listed option)`);
|
|
228
|
+
}
|
|
229
|
+
return lines;
|
|
230
|
+
}
|
|
231
|
+
case "rating": {
|
|
232
|
+
const values = answers.map(Number).filter((n) => !Number.isNaN(n));
|
|
233
|
+
if (values.length === 0)
|
|
234
|
+
return [" (no numeric answers)"];
|
|
235
|
+
const average = values.reduce((a, b) => a + b, 0) / values.length;
|
|
236
|
+
return [
|
|
237
|
+
` average ${average.toFixed(1)} out of ${question.scale?.max ?? RATING_SCALE.max}, from ${plural(values.length, "rating")}`,
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
case "free_text":
|
|
241
|
+
return answers.map((a) => ` - ${String(a)}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const percent = (count, total) => total === 0 ? "0%" : `${Math.round((count / total) * 100)}%`;
|
|
245
|
+
const plural = (n, noun) => `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
246
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
export interface Env {
|
|
32
|
+
/** Overridable so a preview deployment can be pointed at a preview API. */
|
|
33
|
+
RIFTS_API_URL?: string;
|
|
34
|
+
}
|
|
35
|
+
declare const _default: {
|
|
36
|
+
fetch(request: Request, env: Env): Promise<Response>;
|
|
37
|
+
};
|
|
38
|
+
export default _default;
|
|
39
|
+
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH,MAAM,WAAW,GAAG;IAClB,2EAA2E;IAC3E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;;mBAmCsB,OAAO,OAAO,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC;;AAD5D,wBA2BE"}
|