@tymio/mcp-server 1.0.0 → 1.0.1

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.
@@ -0,0 +1,89 @@
1
+ import http from "node:http";
2
+ /**
3
+ * Listens on the host/port of `redirectUrl` and resolves with the authorization `code`
4
+ * once the browser hits the redirect URI. Resolves after the socket is accepting connections.
5
+ */
6
+ export async function startOAuthCallbackServer(redirectUrl, provider) {
7
+ const pathname = redirectUrl.pathname || "/";
8
+ let resolveCode;
9
+ let rejectWait;
10
+ const waitForCode = new Promise((resolve, reject) => {
11
+ resolveCode = resolve;
12
+ rejectWait = reject;
13
+ });
14
+ const server = http.createServer(async (req, res) => {
15
+ try {
16
+ if (!req.url) {
17
+ res.writeHead(400);
18
+ res.end("Bad request");
19
+ return;
20
+ }
21
+ const u = new URL(req.url, `http://${req.headers.host ?? "127.0.0.1"}`);
22
+ if (u.pathname !== pathname) {
23
+ res.writeHead(404);
24
+ res.end("Not found");
25
+ return;
26
+ }
27
+ const code = u.searchParams.get("code");
28
+ const state = u.searchParams.get("state");
29
+ const errParam = u.searchParams.get("error");
30
+ if (errParam) {
31
+ const desc = u.searchParams.get("error_description") ?? errParam;
32
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
33
+ res.end(`<p>Authorization failed: ${escapeHtml(desc)}</p>`);
34
+ rejectWait(new Error(desc));
35
+ return;
36
+ }
37
+ if (!code || !state) {
38
+ res.writeHead(400);
39
+ res.end("Missing code or state");
40
+ rejectWait(new Error("Missing authorization code"));
41
+ return;
42
+ }
43
+ const expected = await provider.state();
44
+ if (state !== expected) {
45
+ res.writeHead(400);
46
+ res.end("Invalid state");
47
+ rejectWait(new Error("OAuth state mismatch"));
48
+ return;
49
+ }
50
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
51
+ res.end("<p>Signed in to Tymio. You can close this tab and return to the terminal.</p>");
52
+ resolveCode(code);
53
+ }
54
+ catch (e) {
55
+ res.writeHead(500);
56
+ res.end("Internal error");
57
+ rejectWait(e instanceof Error ? e : new Error(String(e)));
58
+ }
59
+ });
60
+ const host = redirectUrl.hostname;
61
+ const port = Number(redirectUrl.port) || (redirectUrl.protocol === "https:" ? 443 : 80);
62
+ const listening = new Promise((resolve, reject) => {
63
+ server.once("error", reject);
64
+ server.listen(port, host, () => {
65
+ process.stderr.write(`Listening for OAuth callback on ${redirectUrl.origin}${pathname}\n`);
66
+ server.off("error", reject);
67
+ resolve();
68
+ });
69
+ });
70
+ try {
71
+ await listening;
72
+ }
73
+ catch (err) {
74
+ server.close();
75
+ throw err instanceof Error ? err : new Error(String(err));
76
+ }
77
+ server.on("error", (err) => {
78
+ rejectWait(err instanceof Error ? err : new Error(String(err)));
79
+ });
80
+ return {
81
+ waitForCode,
82
+ close: () => {
83
+ server.close();
84
+ }
85
+ };
86
+ }
87
+ function escapeHtml(s) {
88
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
89
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * One-line stderr hint when starting stdio (does not touch stdout — MCP JSON-RPC stays clean).
3
+ * Suppress with TYMIO_MCP_QUIET=1 or non-TTY stderr.
4
+ */
5
+ export declare function writeStdioStartupHint(mode: "oauth" | "api-key"): void;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * One-line stderr hint when starting stdio (does not touch stdout — MCP JSON-RPC stays clean).
3
+ * Suppress with TYMIO_MCP_QUIET=1 or non-TTY stderr.
4
+ */
5
+ export function writeStdioStartupHint(mode) {
6
+ if (process.env.TYMIO_MCP_QUIET)
7
+ return;
8
+ if (!process.stderr.isTTY)
9
+ return;
10
+ if (mode === "oauth") {
11
+ process.stderr.write("[tymio-mcp] OAuth proxy to Tymio MCP. No MCP key in Tymio Settings — use login/OAuth. First run: `tymio-mcp login`. Guide: `tymio-mcp instructions` | `tymio-mcp help`\n");
12
+ }
13
+ else {
14
+ process.stderr.write("[tymio-mcp] API-key REST bridge. Set DRD_API_BASE_URL + DRD_API_KEY. Agent guide: `tymio-mcp instructions`\n");
15
+ }
16
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tymio/mcp-server",
3
- "version": "1.0.0",
4
- "description": "Tymio hub MCP server (stdio) exposes REST APIs as MCP tools via API key",
3
+ "version": "1.0.1",
4
+ "description": "Tymio MCP CLI: OAuth stdio proxy to hosted MCP, or API-key REST tool bridge",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
@@ -9,13 +9,16 @@
9
9
  },
10
10
  "files": [
11
11
  "dist",
12
- "README.md"
12
+ "README.md",
13
+ "TYMIO_MCP_CLI_AGENT_GUIDANCE.md"
13
14
  ],
14
15
  "scripts": {
15
16
  "clean": "rm -rf dist",
16
17
  "build": "tsc -p tsconfig.json",
17
18
  "start": "node dist/index.js",
18
19
  "dev": "tsx src/index.ts",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
19
22
  "prepublishOnly": "npm run clean && npm run build"
20
23
  },
21
24
  "dependencies": {
@@ -25,9 +28,18 @@
25
28
  "devDependencies": {
26
29
  "@types/node": "^22.13.14",
27
30
  "tsx": "^4.19.3",
28
- "typescript": "^5.8.2"
31
+ "typescript": "^5.8.2",
32
+ "vitest": "^4.0.18"
29
33
  },
30
34
  "engines": {
31
35
  "node": ">=20.0.0"
32
- }
36
+ },
37
+ "keywords": [
38
+ "mcp",
39
+ "tymio",
40
+ "model-context-protocol",
41
+ "cursor",
42
+ "oauth",
43
+ "stdio"
44
+ ]
33
45
  }