@notjustyou/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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Not Just You
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,66 @@
1
+ # @notjustyou/mcp
2
+
3
+ Read-only MCP server for Not Just You AI service status lookup.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @notjustyou/mcp
9
+ ```
10
+
11
+ You can also run it from a workspace checkout:
12
+
13
+ ```bash
14
+ pnpm --filter @notjustyou/mcp build
15
+ NOTJUSTYOU_BASE_URL=http://localhost:3000 node packages/notjustyou-mcp/dist/index.js
16
+ ```
17
+
18
+ ## MCP Client Configuration
19
+
20
+ ```json
21
+ {
22
+ "mcpServers": {
23
+ "notjustyou": {
24
+ "command": "notjustyou-mcp",
25
+ "env": {
26
+ "NOTJUSTYOU_BASE_URL": "https://notjustyou.dev"
27
+ }
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ For local development, set `NOTJUSTYOU_BASE_URL` to your local app URL:
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "notjustyou-local": {
39
+ "command": "node",
40
+ "args": ["packages/notjustyou-mcp/dist/index.js"],
41
+ "env": {
42
+ "NOTJUSTYOU_BASE_URL": "http://localhost:3000"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## Tools
50
+
51
+ - `list_surfaces`
52
+ - `get_surface_status`
53
+ - `get_recent_signals`
54
+ - `explain_privacy`
55
+
56
+ All tools are read-only. This package does not expose a signal submission tool and does not require collector credentials.
57
+
58
+ ## Privacy Boundary
59
+
60
+ The MCP server reads public status summaries only:
61
+
62
+ - `/api/summary`
63
+ - `/api/signals/summary`
64
+ - `/api/official`
65
+
66
+ It does not collect prompt text, request or response bodies, headers, API keys, cookies, source files, diffs, clipboard content, exact IP addresses, account emails, or machine/user names.
package/dist/api.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { InstalledSignalSummaryResponse, StatusData } from "./types.js";
2
+ export declare function fetchStatusData(baseUrl: string, options?: {
3
+ serviceId?: string;
4
+ signalWindowMinutes?: number;
5
+ }): Promise<StatusData>;
6
+ export declare function fetchInstalledSignalSummary(baseUrl: string, options: {
7
+ serviceId: string;
8
+ windowMinutes?: number;
9
+ }): Promise<InstalledSignalSummaryResponse>;
10
+ export declare function normalizeBaseUrl(baseUrl: string): string;
package/dist/api.js ADDED
@@ -0,0 +1,46 @@
1
+ export async function fetchStatusData(baseUrl, options = {}) {
2
+ const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
3
+ const signalsUrl = new URL(`${normalizedBaseUrl}/api/signals/summary`);
4
+ if (options.serviceId) {
5
+ signalsUrl.searchParams.set("serviceId", options.serviceId);
6
+ }
7
+ if (options.signalWindowMinutes) {
8
+ signalsUrl.searchParams.set("windowMinutes", String(options.signalWindowMinutes));
9
+ }
10
+ const [communityResult, installedSignalsResult, officialResult] = await Promise.allSettled([
11
+ fetchJson(`${normalizedBaseUrl}/api/summary`),
12
+ fetchJson(signalsUrl.toString()),
13
+ fetchJson(`${normalizedBaseUrl}/api/official`),
14
+ ]);
15
+ if (communityResult.status === "rejected") {
16
+ throw communityResult.reason;
17
+ }
18
+ return {
19
+ community: communityResult.value,
20
+ installedSignals: installedSignalsResult.status === "fulfilled"
21
+ ? installedSignalsResult.value
22
+ : null,
23
+ official: officialResult.status === "fulfilled" ? officialResult.value : null,
24
+ };
25
+ }
26
+ export async function fetchInstalledSignalSummary(baseUrl, options) {
27
+ const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
28
+ const signalsUrl = new URL(`${normalizedBaseUrl}/api/signals/summary`);
29
+ signalsUrl.searchParams.set("serviceId", options.serviceId);
30
+ if (options.windowMinutes) {
31
+ signalsUrl.searchParams.set("windowMinutes", String(options.windowMinutes));
32
+ }
33
+ return fetchJson(signalsUrl.toString());
34
+ }
35
+ export function normalizeBaseUrl(baseUrl) {
36
+ return baseUrl.replace(/\/+$/, "");
37
+ }
38
+ async function fetchJson(url) {
39
+ const response = await fetch(url, {
40
+ cache: "no-store",
41
+ });
42
+ if (!response.ok) {
43
+ throw new Error(`Request failed: ${url} (${response.status})`);
44
+ }
45
+ return (await response.json());
46
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export declare function main(): Promise<void>;
3
+ export declare function isDirectRun(metaUrl: string, argvPath?: string): boolean;
package/dist/index.js ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ import { realpathSync } from "node:fs";
3
+ import { createInterface } from "node:readline/promises";
4
+ import { fileURLToPath } from "node:url";
5
+ import { handleJsonRpcMessage, parseJsonRpcLine, serializeJsonRpcMessage, } from "./protocol.js";
6
+ export async function main() {
7
+ const lines = createInterface({
8
+ input: process.stdin,
9
+ crlfDelay: Infinity,
10
+ });
11
+ for await (const line of lines) {
12
+ if (!line.trim())
13
+ continue;
14
+ const response = await handleLine(line);
15
+ if (response) {
16
+ process.stdout.write(serializeJsonRpcMessage(response));
17
+ }
18
+ }
19
+ }
20
+ async function handleLine(line) {
21
+ try {
22
+ return await handleJsonRpcMessage(parseJsonRpcLine(line));
23
+ }
24
+ catch (error) {
25
+ return {
26
+ jsonrpc: "2.0",
27
+ id: null,
28
+ error: {
29
+ code: -32700,
30
+ message: error instanceof Error ? error.message : String(error),
31
+ },
32
+ };
33
+ }
34
+ }
35
+ export function isDirectRun(metaUrl, argvPath = process.argv[1]) {
36
+ if (!argvPath)
37
+ return false;
38
+ try {
39
+ return realpathSync(argvPath) === realpathSync(fileURLToPath(metaUrl));
40
+ }
41
+ catch {
42
+ return false;
43
+ }
44
+ }
45
+ if (isDirectRun(import.meta.url)) {
46
+ main().catch((error) => {
47
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
48
+ process.exitCode = 1;
49
+ });
50
+ }
@@ -0,0 +1,16 @@
1
+ type JsonRpcId = string | number | null;
2
+ export declare function handleJsonRpcMessage(message: unknown): Promise<{
3
+ jsonrpc: string;
4
+ id: JsonRpcId;
5
+ error: {
6
+ code: number;
7
+ message: string;
8
+ };
9
+ } | {
10
+ jsonrpc: string;
11
+ id: JsonRpcId;
12
+ result: unknown;
13
+ } | null>;
14
+ export declare function parseJsonRpcLine(line: string): unknown;
15
+ export declare function serializeJsonRpcMessage(message: unknown): string;
16
+ export {};
@@ -0,0 +1,93 @@
1
+ import { callTool, toolErrorResult, ToolExecutionError, TOOLS } from "./tools.js";
2
+ const PROTOCOL_VERSION = "2025-06-18";
3
+ export async function handleJsonRpcMessage(message) {
4
+ if (!isJsonRpcRequest(message) || !message.method) {
5
+ return errorResponse(null, -32600, "Invalid Request");
6
+ }
7
+ if (message.id === undefined) {
8
+ return null;
9
+ }
10
+ try {
11
+ if (message.method === "initialize") {
12
+ return successResponse(message.id, {
13
+ protocolVersion: PROTOCOL_VERSION,
14
+ capabilities: {
15
+ tools: {
16
+ listChanged: false,
17
+ },
18
+ },
19
+ serverInfo: {
20
+ name: "notjustyou-mcp",
21
+ title: "Not Just You MCP",
22
+ version: "0.1.0",
23
+ },
24
+ instructions: "Read-only Not Just You status lookup. This server does not submit signals.",
25
+ });
26
+ }
27
+ if (message.method === "ping") {
28
+ return successResponse(message.id, {});
29
+ }
30
+ if (message.method === "tools/list") {
31
+ return successResponse(message.id, {
32
+ tools: TOOLS,
33
+ });
34
+ }
35
+ if (message.method === "tools/call") {
36
+ const params = readParamsObject(message.params);
37
+ const name = readToolName(params.name);
38
+ const result = await callTool(name, params.arguments).catch((error) => {
39
+ if (error instanceof ToolExecutionError) {
40
+ return toolErrorResult(error);
41
+ }
42
+ throw error;
43
+ });
44
+ return successResponse(message.id, result);
45
+ }
46
+ return errorResponse(message.id, -32601, `Method not found: ${message.method}`);
47
+ }
48
+ catch (error) {
49
+ return errorResponse(message.id, -32602, error instanceof Error ? error.message : String(error));
50
+ }
51
+ }
52
+ export function parseJsonRpcLine(line) {
53
+ return JSON.parse(line);
54
+ }
55
+ export function serializeJsonRpcMessage(message) {
56
+ return `${JSON.stringify(message)}\n`;
57
+ }
58
+ function successResponse(id, result) {
59
+ return {
60
+ jsonrpc: "2.0",
61
+ id,
62
+ result,
63
+ };
64
+ }
65
+ function errorResponse(id, code, message) {
66
+ return {
67
+ jsonrpc: "2.0",
68
+ id,
69
+ error: {
70
+ code,
71
+ message,
72
+ },
73
+ };
74
+ }
75
+ function isJsonRpcRequest(value) {
76
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
77
+ return false;
78
+ }
79
+ const candidate = value;
80
+ return candidate.jsonrpc === "2.0";
81
+ }
82
+ function readParamsObject(value) {
83
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
84
+ throw new Error("params must be an object.");
85
+ }
86
+ return value;
87
+ }
88
+ function readToolName(value) {
89
+ if (typeof value !== "string" || value.length === 0) {
90
+ throw new Error("Tool name must be a non-empty string.");
91
+ }
92
+ return value;
93
+ }
@@ -0,0 +1,102 @@
1
+ export declare const DEFAULT_BASE_URL = "https://notjustyou.dev";
2
+ export interface McpTool {
3
+ name: string;
4
+ title: string;
5
+ description: string;
6
+ inputSchema: JsonSchema;
7
+ annotations: {
8
+ readOnlyHint: true;
9
+ };
10
+ }
11
+ export interface ToolResult {
12
+ content: Array<{
13
+ type: "text";
14
+ text: string;
15
+ }>;
16
+ structuredContent?: Record<string, unknown>;
17
+ isError?: boolean;
18
+ }
19
+ export declare class ToolExecutionError extends Error {
20
+ }
21
+ type JsonSchema = {
22
+ type: "object";
23
+ properties: Record<string, unknown>;
24
+ required?: string[];
25
+ additionalProperties: false;
26
+ };
27
+ export declare const TOOLS: readonly [{
28
+ readonly name: "list_surfaces";
29
+ readonly title: "List Not Just You Surfaces";
30
+ readonly description: "List AI service surfaces with public community, installed-signal, and official status summaries.";
31
+ readonly inputSchema: {
32
+ readonly type: "object";
33
+ readonly properties: {
34
+ readonly provider: {
35
+ readonly type: "string";
36
+ readonly description: "Optional provider id such as openai, anthropic, google, or cursor.";
37
+ };
38
+ };
39
+ readonly additionalProperties: false;
40
+ };
41
+ readonly annotations: {
42
+ readonly readOnlyHint: true;
43
+ };
44
+ }, {
45
+ readonly name: "get_surface_status";
46
+ readonly title: "Get Surface Status";
47
+ readonly description: "Get public Not Just You status for one service surface by serviceId.";
48
+ readonly inputSchema: {
49
+ readonly type: "object";
50
+ readonly properties: {
51
+ readonly serviceId: {
52
+ readonly type: "string";
53
+ readonly description: "Service id, for example openai-api.";
54
+ };
55
+ };
56
+ readonly required: ["serviceId"];
57
+ readonly additionalProperties: false;
58
+ };
59
+ readonly annotations: {
60
+ readonly readOnlyHint: true;
61
+ };
62
+ }, {
63
+ readonly name: "get_recent_signals";
64
+ readonly title: "Get Recent Signals";
65
+ readonly description: "Get recent installed-client signal aggregates for one service surface.";
66
+ readonly inputSchema: {
67
+ readonly type: "object";
68
+ readonly properties: {
69
+ readonly serviceId: {
70
+ readonly type: "string";
71
+ readonly description: "Service id, for example openai-api.";
72
+ };
73
+ readonly windowMinutes: {
74
+ readonly type: "integer";
75
+ readonly minimum: 1;
76
+ readonly maximum: 60;
77
+ readonly description: "Signal summary window in minutes. Defaults to the server default.";
78
+ };
79
+ };
80
+ readonly required: ["serviceId"];
81
+ readonly additionalProperties: false;
82
+ };
83
+ readonly annotations: {
84
+ readonly readOnlyHint: true;
85
+ };
86
+ }, {
87
+ readonly name: "explain_privacy";
88
+ readonly title: "Explain Privacy Boundary";
89
+ readonly description: "Explain the public privacy boundary for Not Just You local tools.";
90
+ readonly inputSchema: {
91
+ readonly type: "object";
92
+ readonly properties: {};
93
+ readonly additionalProperties: false;
94
+ };
95
+ readonly annotations: {
96
+ readonly readOnlyHint: true;
97
+ };
98
+ }];
99
+ export declare function getBaseUrl(): string;
100
+ export declare function callTool(name: string, argumentsValue: unknown, baseUrl?: string): Promise<ToolResult>;
101
+ export declare function toolErrorResult(error: unknown): ToolResult;
102
+ export {};
package/dist/tools.js ADDED
@@ -0,0 +1,305 @@
1
+ import { fetchInstalledSignalSummary, fetchStatusData } from "./api.js";
2
+ export const DEFAULT_BASE_URL = "https://notjustyou.dev";
3
+ export class ToolExecutionError extends Error {
4
+ }
5
+ export const TOOLS = [
6
+ {
7
+ name: "list_surfaces",
8
+ title: "List Not Just You Surfaces",
9
+ description: "List AI service surfaces with public community, installed-signal, and official status summaries.",
10
+ inputSchema: {
11
+ type: "object",
12
+ properties: {
13
+ provider: {
14
+ type: "string",
15
+ description: "Optional provider id such as openai, anthropic, google, or cursor.",
16
+ },
17
+ },
18
+ additionalProperties: false,
19
+ },
20
+ annotations: {
21
+ readOnlyHint: true,
22
+ },
23
+ },
24
+ {
25
+ name: "get_surface_status",
26
+ title: "Get Surface Status",
27
+ description: "Get public Not Just You status for one service surface by serviceId.",
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {
31
+ serviceId: {
32
+ type: "string",
33
+ description: "Service id, for example openai-api.",
34
+ },
35
+ },
36
+ required: ["serviceId"],
37
+ additionalProperties: false,
38
+ },
39
+ annotations: {
40
+ readOnlyHint: true,
41
+ },
42
+ },
43
+ {
44
+ name: "get_recent_signals",
45
+ title: "Get Recent Signals",
46
+ description: "Get recent installed-client signal aggregates for one service surface.",
47
+ inputSchema: {
48
+ type: "object",
49
+ properties: {
50
+ serviceId: {
51
+ type: "string",
52
+ description: "Service id, for example openai-api.",
53
+ },
54
+ windowMinutes: {
55
+ type: "integer",
56
+ minimum: 1,
57
+ maximum: 60,
58
+ description: "Signal summary window in minutes. Defaults to the server default.",
59
+ },
60
+ },
61
+ required: ["serviceId"],
62
+ additionalProperties: false,
63
+ },
64
+ annotations: {
65
+ readOnlyHint: true,
66
+ },
67
+ },
68
+ {
69
+ name: "explain_privacy",
70
+ title: "Explain Privacy Boundary",
71
+ description: "Explain the public privacy boundary for Not Just You local tools.",
72
+ inputSchema: {
73
+ type: "object",
74
+ properties: {},
75
+ additionalProperties: false,
76
+ },
77
+ annotations: {
78
+ readOnlyHint: true,
79
+ },
80
+ },
81
+ ];
82
+ export function getBaseUrl() {
83
+ return process.env.NOTJUSTYOU_BASE_URL ?? DEFAULT_BASE_URL;
84
+ }
85
+ export async function callTool(name, argumentsValue, baseUrl = getBaseUrl()) {
86
+ if (name === "list_surfaces") {
87
+ const args = readObject(argumentsValue);
88
+ rejectUnknownFields(args, ["provider"]);
89
+ const provider = readOptionalString(args.provider, "provider");
90
+ const data = await readStatusData(baseUrl);
91
+ const surfaces = listSurfaceSummaries(data, provider);
92
+ return jsonToolResult({
93
+ surfaces,
94
+ sources: sourceAvailability(data),
95
+ });
96
+ }
97
+ if (name === "get_surface_status") {
98
+ const args = readObject(argumentsValue);
99
+ rejectUnknownFields(args, ["serviceId"]);
100
+ const serviceId = readRequiredString(args.serviceId, "serviceId");
101
+ const data = await readStatusData(baseUrl);
102
+ const status = getSurfaceSummary(data, serviceId);
103
+ return jsonToolResult({
104
+ serviceId,
105
+ found: Boolean(status.community || status.installedSignals || status.official),
106
+ ...status,
107
+ sources: sourceAvailability(data),
108
+ });
109
+ }
110
+ if (name === "get_recent_signals") {
111
+ const args = readObject(argumentsValue);
112
+ rejectUnknownFields(args, ["serviceId", "windowMinutes"]);
113
+ const serviceId = readRequiredString(args.serviceId, "serviceId");
114
+ const windowMinutes = readOptionalWindowMinutes(args.windowMinutes);
115
+ const summary = await readInstalledSignalSummary(baseUrl, {
116
+ serviceId,
117
+ windowMinutes,
118
+ });
119
+ const installedSignals = summary.services.find((service) => service.serviceId === serviceId);
120
+ return jsonToolResult({
121
+ serviceId,
122
+ windowMinutes: summary.windowMinutes,
123
+ installedSignalsAvailable: true,
124
+ installedSignals: installedSignals ? formatInstalled(installedSignals) : null,
125
+ });
126
+ }
127
+ if (name === "explain_privacy") {
128
+ rejectUnknownFields(readObject(argumentsValue), []);
129
+ return jsonToolResult({
130
+ readOnly: true,
131
+ sendsSignals: false,
132
+ requiresCollectorToken: false,
133
+ readsPublicEndpoints: [
134
+ "/api/summary",
135
+ "/api/signals/summary",
136
+ "/api/official",
137
+ ],
138
+ doesNotCollect: [
139
+ "prompt text",
140
+ "request or response bodies",
141
+ "headers",
142
+ "API keys",
143
+ "cookies",
144
+ "source files",
145
+ "diffs",
146
+ "clipboard content",
147
+ "exact IP addresses",
148
+ "account emails",
149
+ "machine or user names",
150
+ ],
151
+ });
152
+ }
153
+ throw new Error(`Unknown tool: ${name}`);
154
+ }
155
+ export function toolErrorResult(error) {
156
+ return {
157
+ content: [
158
+ {
159
+ type: "text",
160
+ text: error instanceof Error ? error.message : String(error),
161
+ },
162
+ ],
163
+ isError: true,
164
+ };
165
+ }
166
+ async function readStatusData(baseUrl, options = {}) {
167
+ try {
168
+ return await fetchStatusData(baseUrl, options);
169
+ }
170
+ catch (error) {
171
+ throw new ToolExecutionError(`Failed to read Not Just You status: ${errorMessage(error)}`);
172
+ }
173
+ }
174
+ async function readInstalledSignalSummary(baseUrl, options) {
175
+ try {
176
+ return await fetchInstalledSignalSummary(baseUrl, options);
177
+ }
178
+ catch (error) {
179
+ throw new ToolExecutionError(`Failed to read installed signal summary: ${errorMessage(error)}`);
180
+ }
181
+ }
182
+ function listSurfaceSummaries(data, provider) {
183
+ return getServiceIds(data)
184
+ .filter((serviceId) => !provider || getProviderId(serviceId) === provider)
185
+ .map((serviceId) => ({
186
+ serviceId,
187
+ providerId: getProviderId(serviceId),
188
+ ...getSurfaceSummary(data, serviceId),
189
+ }));
190
+ }
191
+ function getSurfaceSummary(data, serviceId) {
192
+ const community = data.community.services.find((service) => service.serviceId === serviceId);
193
+ const installedSignals = data.installedSignals?.services.find((service) => service.serviceId === serviceId);
194
+ const official = data.official?.services.find((service) => service.serviceId === serviceId);
195
+ return {
196
+ community: community ? formatCommunity(community) : null,
197
+ installedSignals: installedSignals ? formatInstalled(installedSignals) : null,
198
+ official: official ? formatOfficial(official) : null,
199
+ };
200
+ }
201
+ function getServiceIds(data) {
202
+ return [
203
+ ...new Set([
204
+ ...data.community.services.map((service) => service.serviceId),
205
+ ...(data.installedSignals?.services.map((service) => service.serviceId) ?? []),
206
+ ...(data.official?.services.map((service) => service.serviceId) ?? []),
207
+ ]),
208
+ ];
209
+ }
210
+ function getProviderId(serviceId) {
211
+ return serviceId.split("-")[0] ?? "unknown";
212
+ }
213
+ function formatCommunity(service) {
214
+ return {
215
+ total: service.total,
216
+ counts: service.counts,
217
+ state: service.communityState,
218
+ };
219
+ }
220
+ function formatInstalled(service) {
221
+ return {
222
+ total: service.total,
223
+ uniqueInstallationsApprox: service.uniqueInstallationsApprox,
224
+ countsBySource: numericRecord(service.countsBySource),
225
+ countsBySymptom: numericRecord(service.countsBySymptom),
226
+ lastSignal: service.lastSignal
227
+ ? {
228
+ symptom: service.lastSignal.symptom,
229
+ source: service.lastSignal.source,
230
+ observedAt: service.lastSignal.observedAt,
231
+ }
232
+ : null,
233
+ };
234
+ }
235
+ function formatOfficial(service) {
236
+ return {
237
+ overall: service.overall,
238
+ source: service.source,
239
+ updatedAt: service.updatedAt,
240
+ };
241
+ }
242
+ function sourceAvailability(data) {
243
+ return {
244
+ community: true,
245
+ installedSignals: Boolean(data.installedSignals),
246
+ official: Boolean(data.official),
247
+ };
248
+ }
249
+ function jsonToolResult(value) {
250
+ return {
251
+ content: [
252
+ {
253
+ type: "text",
254
+ text: JSON.stringify(value, null, 2),
255
+ },
256
+ ],
257
+ structuredContent: value,
258
+ isError: false,
259
+ };
260
+ }
261
+ function readObject(value) {
262
+ if (value === undefined || value === null)
263
+ return {};
264
+ if (typeof value !== "object" || Array.isArray(value)) {
265
+ throw new Error("Tool arguments must be an object.");
266
+ }
267
+ return value;
268
+ }
269
+ function rejectUnknownFields(value, allowedFields) {
270
+ const unknownField = Object.keys(value).find((key) => !allowedFields.includes(key));
271
+ if (unknownField) {
272
+ throw new Error(`Unknown argument: ${unknownField}`);
273
+ }
274
+ }
275
+ function readRequiredString(value, name) {
276
+ if (typeof value !== "string" || value.length === 0) {
277
+ throw new Error(`${name} must be a non-empty string.`);
278
+ }
279
+ return value;
280
+ }
281
+ function readOptionalString(value, name) {
282
+ if (value === undefined)
283
+ return undefined;
284
+ if (typeof value !== "string" || value.length === 0) {
285
+ throw new Error(`${name} must be a non-empty string when provided.`);
286
+ }
287
+ return value;
288
+ }
289
+ function readOptionalWindowMinutes(value) {
290
+ if (value === undefined)
291
+ return undefined;
292
+ if (!Number.isInteger(value) || Number(value) < 1 || Number(value) > 60) {
293
+ throw new Error("windowMinutes must be an integer from 1 to 60 when provided.");
294
+ }
295
+ return Number(value);
296
+ }
297
+ function errorMessage(error) {
298
+ return error instanceof Error ? error.message : String(error);
299
+ }
300
+ function numericRecord(value) {
301
+ return Object.fromEntries(Object.entries(value).filter((entry) => {
302
+ const [, count] = entry;
303
+ return typeof count === "number";
304
+ }));
305
+ }
@@ -0,0 +1,47 @@
1
+ export interface CommunityServiceSummary {
2
+ serviceId: string;
3
+ total: number;
4
+ counts: {
5
+ slow: number;
6
+ error: number;
7
+ down: number;
8
+ };
9
+ communityState: string;
10
+ }
11
+ export interface CommunitySummaryResponse {
12
+ windowMinutes: number;
13
+ updatedAt: string;
14
+ services: CommunityServiceSummary[];
15
+ }
16
+ export interface InstalledSignalServiceSummary {
17
+ serviceId: string;
18
+ total: number;
19
+ uniqueInstallationsApprox: number;
20
+ countsBySource: Record<string, number>;
21
+ countsBySymptom: Record<string, number>;
22
+ lastSignal: {
23
+ symptom: string;
24
+ source: string;
25
+ observedAt: string;
26
+ } | null;
27
+ }
28
+ export interface InstalledSignalSummaryResponse {
29
+ windowMinutes: number;
30
+ updatedAt: string;
31
+ services: InstalledSignalServiceSummary[];
32
+ }
33
+ export interface OfficialServiceStatus {
34
+ serviceId: string;
35
+ overall: string;
36
+ source: "official" | "not_connected";
37
+ updatedAt: string;
38
+ }
39
+ export interface OfficialSummaryResponse {
40
+ updatedAt: string;
41
+ services: OfficialServiceStatus[];
42
+ }
43
+ export interface StatusData {
44
+ community: CommunitySummaryResponse;
45
+ installedSignals: InstalledSignalSummaryResponse | null;
46
+ official: OfficialSummaryResponse | null;
47
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@notjustyou/mcp",
3
+ "version": "0.1.0",
4
+ "description": "Read-only MCP server for Not Just You AI service status lookup.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/dobbylee/notjustyou.git",
9
+ "directory": "packages/notjustyou-mcp"
10
+ },
11
+ "homepage": "https://github.com/dobbylee/notjustyou#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/dobbylee/notjustyou/issues"
14
+ },
15
+ "keywords": [
16
+ "ai-status",
17
+ "mcp",
18
+ "status",
19
+ "notjustyou"
20
+ ],
21
+ "type": "module",
22
+ "files": [
23
+ "dist",
24
+ "LICENSE",
25
+ "README.md"
26
+ ],
27
+ "main": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js"
33
+ }
34
+ },
35
+ "bin": {
36
+ "notjustyou-mcp": "dist/index.js"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc -p tsconfig.json",
40
+ "prepack": "pnpm run build",
41
+ "pack:dry-run": "pnpm pack --dry-run"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "engines": {
47
+ "node": ">=20.9.0"
48
+ }
49
+ }