psn-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 Timothy Peterson
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,81 @@
1
+ # psn-mcp
2
+
3
+ An [MCP](https://modelcontextprotocol.io) (Model Context Protocol) server for interfacing with the PlayStation Network. Lets MCP clients like Claude look up PSN profiles, friends, online presence, trophies, and play history.
4
+
5
+ The PSN API layer is written from scratch in TypeScript on Node's built-in `fetch` — no PSN client dependencies. It authenticates the same way the official PlayStation mobile app does and talks directly to Sony's `m.np.playstation.com` API.
6
+
7
+ ## Requirements
8
+
9
+ - Node.js 18+
10
+ - A PlayStation Network account and its **NPSSO token** (see below)
11
+
12
+ ## Getting your NPSSO token
13
+
14
+ 1. Sign in at [playstation.com](https://www.playstation.com).
15
+ 2. In the same browser, open <https://ca.account.sony.com/api/v1/ssocookie>.
16
+ 3. Copy the 64-character `npsso` value from the JSON response.
17
+
18
+ The server exchanges the NPSSO for an OAuth access token on first use and refreshes it automatically. NPSSO tokens expire after about two months; when tools start failing with an auth error, fetch a fresh one.
19
+
20
+ > **Note:** this uses your personal account session. What you can see (other users' friends, presence, play history) is governed by normal PSN privacy settings.
21
+
22
+ ## Usage with an MCP client
23
+
24
+ No installation needed — run it with `npx`. Add to your client's MCP configuration (e.g. `claude_desktop_config.json`):
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "psn": {
30
+ "command": "npx",
31
+ "args": ["-y", "psn-mcp"],
32
+ "env": {
33
+ "PSN_NPSSO": "<your npsso token>"
34
+ }
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ Or for Claude Code:
41
+
42
+ ```sh
43
+ claude mcp add psn --env PSN_NPSSO=<your npsso token> -- npx -y psn-mcp
44
+ ```
45
+
46
+ ## Tools
47
+
48
+ | Tool | Description |
49
+ | --- | --- |
50
+ | `psn_get_profile` | Profile for a user: online id, about-me, avatars, PS Plus status |
51
+ | `psn_search_players` | Search PSN players by name; returns online ids and account ids |
52
+ | `psn_get_friends` | A user's friends list, resolved to profiles |
53
+ | `psn_get_presence` | Online status, current platform, and the game being played |
54
+ | `psn_get_trophy_summary` | Trophy level, tier, and total trophy counts |
55
+ | `psn_get_trophy_titles` | Games with trophy progress, most recently played first |
56
+ | `psn_get_title_trophies` | Full trophy list defined for a game (names, types, groups) |
57
+ | `psn_get_earned_trophies` | Which trophies a user earned in a game, with timestamps and rarity |
58
+ | `psn_get_played_games` | Played PS4/PS5 games with play counts and durations |
59
+
60
+ Every user-scoped tool accepts `"me"` (the authenticated account), a PSN online id (username), or a numeric account id — online ids are resolved automatically.
61
+
62
+ For PS5 titles pass `npServiceName: "trophy2"`; for PS4 and earlier use `"trophy"`. `psn_get_trophy_titles` reports the right value per game.
63
+
64
+ ## Architecture
65
+
66
+ ```
67
+ src/
68
+ index.ts Entry point: stdio MCP server wiring
69
+ tools.ts MCP tool definitions (zod schemas -> PSN API calls)
70
+ psn/
71
+ auth.ts NPSSO -> OAuth code -> access token exchange, auto-refresh
72
+ http.ts Authenticated JSON client for m.np.playstation.com
73
+ api.ts Typed endpoint wrappers (profiles, trophies, games, search)
74
+ types.ts PSN API response types
75
+ ```
76
+
77
+ Authentication is lazy: the server starts and lists tools without credentials; the token exchange happens on the first tool call. Access tokens are refreshed ahead of expiry, falling back to a full NPSSO re-exchange if the refresh token has expired.
78
+
79
+ ## Disclaimer
80
+
81
+ This project uses undocumented PSN endpoints and is not affiliated with or endorsed by Sony Interactive Entertainment. Use at your own risk.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { TokenManager } from "./psn/auth.js";
5
+ import { PsnHttpClient } from "./psn/http.js";
6
+ import { PsnApi } from "./psn/api.js";
7
+ import { registerTools } from "./tools.js";
8
+ async function main() {
9
+ const npsso = process.env.PSN_NPSSO ?? "";
10
+ if (!npsso) {
11
+ // stdout is reserved for the MCP protocol; diagnostics go to stderr.
12
+ console.error("warning: PSN_NPSSO is not set - tools will fail until it is configured.\n" +
13
+ "Sign in at https://www.playstation.com, then copy the token from " +
14
+ "https://ca.account.sony.com/api/v1/ssocookie");
15
+ }
16
+ // Auth is lazy: the token exchange happens on the first tool call, so the
17
+ // server starts (and lists tools) even before credentials are configured.
18
+ const psn = new PsnApi(new PsnHttpClient(new TokenManager(npsso || "unset")));
19
+ const server = new McpServer({ name: "psn-mcp", version: "0.1.0" });
20
+ registerTools(server, psn);
21
+ await server.connect(new StdioServerTransport());
22
+ console.error("psn-mcp server running on stdio");
23
+ }
24
+ main().catch((error) => {
25
+ console.error("fatal:", error);
26
+ process.exit(1);
27
+ });
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,KAAK,UAAU,IAAI;IACjB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC;IAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,qEAAqE;QACrE,OAAO,CAAC,KAAK,CACX,2EAA2E;YACzE,mEAAmE;YACnE,8CAA8C,CACjD,CAAC;IACJ,CAAC;IAED,0EAA0E;IAC1E,0EAA0E;IAC1E,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC,IAAI,aAAa,CAAC,IAAI,YAAY,CAAC,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IAE9E,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACpE,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE3B,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;AACnD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC/B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { PsnHttpClient } from "./http.js";
2
+ import type { BasicPresence, FriendsResponse, PlayedGamesResponse, TrophiesResponse, TrophySummary, TrophyTitlesResponse, UniversalSearchResponse, UserProfile } from "./types.js";
3
+ export type NpServiceName = "trophy" | "trophy2";
4
+ /**
5
+ * Typed wrappers around the PSN mobile API endpoints.
6
+ *
7
+ * User-scoped endpoints accept the literal string "me" (the authenticated
8
+ * account) or a numeric account id. Use {@link resolveAccountId} to turn an
9
+ * online id (PSN username) into an account id first.
10
+ */
11
+ export declare class PsnApi {
12
+ private readonly http;
13
+ constructor(http: PsnHttpClient);
14
+ getProfile(accountId: string): Promise<UserProfile>;
15
+ searchPlayers(searchTerm: string, limit?: number): Promise<UniversalSearchResponse>;
16
+ /**
17
+ * Resolves a user reference to an account id. Accepts "me", a numeric
18
+ * account id (passed through), or an online id which is looked up via
19
+ * universal search and matched exactly (case-insensitively).
20
+ */
21
+ resolveAccountId(user: string): Promise<string>;
22
+ getFriends(accountId: string, limit?: number, offset?: number): Promise<FriendsResponse>;
23
+ getBasicPresence(accountId: string): Promise<{
24
+ basicPresence: BasicPresence;
25
+ }>;
26
+ getBasicPresences(accountIds: string[]): Promise<{
27
+ basicPresences: BasicPresence[];
28
+ }>;
29
+ getTrophySummary(accountId: string): Promise<TrophySummary>;
30
+ getTrophyTitles(accountId: string, limit?: number, offset?: number): Promise<TrophyTitlesResponse>;
31
+ /** Trophy definitions for a title (names, descriptions, types). */
32
+ getTitleTrophies(npCommunicationId: string, npServiceName: NpServiceName, trophyGroupId?: string, limit?: number, offset?: number): Promise<TrophiesResponse>;
33
+ /** A user's earned/unearned status for each trophy in a title. */
34
+ getEarnedTrophies(accountId: string, npCommunicationId: string, npServiceName: NpServiceName, trophyGroupId?: string, limit?: number, offset?: number): Promise<TrophiesResponse>;
35
+ getPlayedGames(accountId: string, limit?: number, offset?: number): Promise<PlayedGamesResponse>;
36
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Typed wrappers around the PSN mobile API endpoints.
3
+ *
4
+ * User-scoped endpoints accept the literal string "me" (the authenticated
5
+ * account) or a numeric account id. Use {@link resolveAccountId} to turn an
6
+ * online id (PSN username) into an account id first.
7
+ */
8
+ export class PsnApi {
9
+ http;
10
+ constructor(http) {
11
+ this.http = http;
12
+ }
13
+ // ---- Users -------------------------------------------------------------
14
+ getProfile(accountId) {
15
+ return this.http.request(`/userProfile/v1/internal/users/${encodeURIComponent(accountId)}/profiles`);
16
+ }
17
+ searchPlayers(searchTerm, limit = 20) {
18
+ return this.http.request("/search/v1/universalSearch", {
19
+ method: "POST",
20
+ body: {
21
+ searchTerm,
22
+ domainRequests: [
23
+ {
24
+ domain: "SocialAllAccounts",
25
+ pagination: { cursor: "", pageSize: limit },
26
+ },
27
+ ],
28
+ },
29
+ });
30
+ }
31
+ /**
32
+ * Resolves a user reference to an account id. Accepts "me", a numeric
33
+ * account id (passed through), or an online id which is looked up via
34
+ * universal search and matched exactly (case-insensitively).
35
+ */
36
+ async resolveAccountId(user) {
37
+ const trimmed = user.trim();
38
+ if (trimmed === "me" || /^\d+$/.test(trimmed))
39
+ return trimmed;
40
+ const search = await this.searchPlayers(trimmed, 20);
41
+ const results = search.domainResponses[0]?.results ?? [];
42
+ const match = results.find((r) => r.socialMetadata?.onlineId?.toLowerCase() === trimmed.toLowerCase());
43
+ if (!match?.socialMetadata?.accountId) {
44
+ throw new Error(`No PSN user found with online id "${trimmed}". ` +
45
+ "Try psn_search_players to find the exact online id.");
46
+ }
47
+ return match.socialMetadata.accountId;
48
+ }
49
+ getFriends(accountId, limit = 100, offset = 0) {
50
+ return this.http.request(`/userProfile/v1/internal/users/${encodeURIComponent(accountId)}/friends`, { query: { limit, offset } });
51
+ }
52
+ getBasicPresence(accountId) {
53
+ return this.http.request(`/userProfile/v1/internal/users/${encodeURIComponent(accountId)}/basicPresences`, { query: { type: "primary" } });
54
+ }
55
+ getBasicPresences(accountIds) {
56
+ return this.http.request("/userProfile/v1/internal/users/basicPresences", { query: { type: "primary", accountIds: accountIds.join(",") } });
57
+ }
58
+ // ---- Trophies ----------------------------------------------------------
59
+ getTrophySummary(accountId) {
60
+ return this.http.request(`/trophy/v1/users/${encodeURIComponent(accountId)}/trophySummary`);
61
+ }
62
+ getTrophyTitles(accountId, limit = 100, offset = 0) {
63
+ return this.http.request(`/trophy/v1/users/${encodeURIComponent(accountId)}/trophyTitles`, { query: { limit, offset } });
64
+ }
65
+ /** Trophy definitions for a title (names, descriptions, types). */
66
+ getTitleTrophies(npCommunicationId, npServiceName, trophyGroupId = "all", limit = 200, offset = 0) {
67
+ return this.http.request(`/trophy/v1/npCommunicationIds/${encodeURIComponent(npCommunicationId)}` +
68
+ `/trophyGroups/${encodeURIComponent(trophyGroupId)}/trophies`, { query: { npServiceName, limit, offset } });
69
+ }
70
+ /** A user's earned/unearned status for each trophy in a title. */
71
+ getEarnedTrophies(accountId, npCommunicationId, npServiceName, trophyGroupId = "all", limit = 200, offset = 0) {
72
+ return this.http.request(`/trophy/v1/users/${encodeURIComponent(accountId)}` +
73
+ `/npCommunicationIds/${encodeURIComponent(npCommunicationId)}` +
74
+ `/trophyGroups/${encodeURIComponent(trophyGroupId)}/trophies`, { query: { npServiceName, limit, offset } });
75
+ }
76
+ // ---- Game library ------------------------------------------------------
77
+ getPlayedGames(accountId, limit = 50, offset = 0) {
78
+ return this.http.request(`/gamelist/v2/users/${encodeURIComponent(accountId)}/titles`, {
79
+ query: {
80
+ categories: "ps4_game,ps5_native_game,pspc_game",
81
+ limit,
82
+ offset,
83
+ },
84
+ });
85
+ }
86
+ }
87
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/psn/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * PlayStation Network authentication.
3
+ *
4
+ * PSN has no public developer API. Access works the same way the official
5
+ * PlayStation mobile app authenticates: an NPSSO token (obtained by the user
6
+ * from their browser session) is exchanged for an OAuth authorization code,
7
+ * which is then exchanged for a bearer access token + refresh token.
8
+ *
9
+ * The client id/secret below are the public constants baked into the
10
+ * official PlayStation Android app and are required for the token exchange.
11
+ */
12
+ export interface TokenSet {
13
+ accessToken: string;
14
+ /** Epoch ms after which the access token is no longer valid. */
15
+ expiresAt: number;
16
+ refreshToken: string;
17
+ /** Epoch ms after which the refresh token is no longer valid. */
18
+ refreshExpiresAt: number;
19
+ }
20
+ export declare class PsnAuthError extends Error {
21
+ constructor(message: string);
22
+ }
23
+ /** Full NPSSO -> authorization code -> access token exchange. */
24
+ export declare function authenticateWithNpsso(npsso: string): Promise<TokenSet>;
25
+ export declare function refreshTokens(refreshToken: string): Promise<TokenSet>;
26
+ /**
27
+ * Manages the token lifecycle: authenticates lazily on first use, refreshes
28
+ * ahead of expiry, and falls back to a fresh NPSSO exchange if the refresh
29
+ * token itself has expired.
30
+ */
31
+ export declare class TokenManager {
32
+ private readonly npsso;
33
+ private tokens;
34
+ private pending;
35
+ constructor(npsso: string);
36
+ /** Returns a valid access token, authenticating or refreshing as needed. */
37
+ getAccessToken(): Promise<string>;
38
+ private renew;
39
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * PlayStation Network authentication.
3
+ *
4
+ * PSN has no public developer API. Access works the same way the official
5
+ * PlayStation mobile app authenticates: an NPSSO token (obtained by the user
6
+ * from their browser session) is exchanged for an OAuth authorization code,
7
+ * which is then exchanged for a bearer access token + refresh token.
8
+ *
9
+ * The client id/secret below are the public constants baked into the
10
+ * official PlayStation Android app and are required for the token exchange.
11
+ */
12
+ const AUTH_BASE = "https://ca.account.sony.com/api/authz/v3/oauth";
13
+ const CLIENT_ID = "09515159-7237-4370-9b40-3806e67c0891";
14
+ const CLIENT_SECRET = "ucPjka5tntB2KqsP";
15
+ const REDIRECT_URI = "com.scee.psxandroid.scecompcall://redirect";
16
+ const SCOPE = "psn:mobile.v2.core psn:clientapp";
17
+ export class PsnAuthError extends Error {
18
+ constructor(message) {
19
+ super(message);
20
+ this.name = "PsnAuthError";
21
+ }
22
+ }
23
+ /** Exchange an NPSSO token for an OAuth authorization code. */
24
+ async function exchangeNpssoForCode(npsso) {
25
+ const params = new URLSearchParams({
26
+ access_type: "offline",
27
+ client_id: CLIENT_ID,
28
+ redirect_uri: REDIRECT_URI,
29
+ response_type: "code",
30
+ scope: SCOPE,
31
+ });
32
+ const res = await fetch(`${AUTH_BASE}/authorize?${params}`, {
33
+ method: "GET",
34
+ headers: { Cookie: `npsso=${npsso}` },
35
+ redirect: "manual",
36
+ });
37
+ const location = res.headers.get("location");
38
+ if (!location || !location.includes("code=")) {
39
+ throw new PsnAuthError("Failed to obtain an authorization code from the NPSSO token. " +
40
+ "The token is most likely expired or invalid - fetch a fresh one from " +
41
+ "https://ca.account.sony.com/api/v1/ssocookie while signed in to PlayStation.");
42
+ }
43
+ const code = new URL(location).searchParams.get("code");
44
+ if (!code) {
45
+ throw new PsnAuthError("Authorization redirect did not contain a code parameter.");
46
+ }
47
+ return code;
48
+ }
49
+ async function requestToken(body) {
50
+ const res = await fetch(`${AUTH_BASE}/token`, {
51
+ method: "POST",
52
+ headers: {
53
+ Authorization: `Basic ${Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64")}`,
54
+ "Content-Type": "application/x-www-form-urlencoded",
55
+ },
56
+ body,
57
+ });
58
+ if (!res.ok) {
59
+ const text = await res.text().catch(() => "");
60
+ throw new PsnAuthError(`Token request failed with HTTP ${res.status}: ${text}`);
61
+ }
62
+ const data = (await res.json());
63
+ const now = Date.now();
64
+ return {
65
+ accessToken: data.access_token,
66
+ expiresAt: now + data.expires_in * 1000,
67
+ refreshToken: data.refresh_token,
68
+ refreshExpiresAt: now + data.refresh_token_expires_in * 1000,
69
+ };
70
+ }
71
+ /** Full NPSSO -> authorization code -> access token exchange. */
72
+ export async function authenticateWithNpsso(npsso) {
73
+ const code = await exchangeNpssoForCode(npsso);
74
+ return requestToken(new URLSearchParams({
75
+ code,
76
+ redirect_uri: REDIRECT_URI,
77
+ grant_type: "authorization_code",
78
+ token_format: "jwt",
79
+ }));
80
+ }
81
+ export async function refreshTokens(refreshToken) {
82
+ return requestToken(new URLSearchParams({
83
+ refresh_token: refreshToken,
84
+ grant_type: "refresh_token",
85
+ scope: SCOPE,
86
+ token_format: "jwt",
87
+ }));
88
+ }
89
+ /**
90
+ * Manages the token lifecycle: authenticates lazily on first use, refreshes
91
+ * ahead of expiry, and falls back to a fresh NPSSO exchange if the refresh
92
+ * token itself has expired.
93
+ */
94
+ export class TokenManager {
95
+ npsso;
96
+ tokens = null;
97
+ pending = null;
98
+ constructor(npsso) {
99
+ this.npsso = npsso;
100
+ if (!npsso) {
101
+ throw new PsnAuthError("No NPSSO token configured. Set the PSN_NPSSO environment variable. " +
102
+ "Sign in at https://www.playstation.com, then visit " +
103
+ "https://ca.account.sony.com/api/v1/ssocookie to copy your token.");
104
+ }
105
+ }
106
+ /** Returns a valid access token, authenticating or refreshing as needed. */
107
+ async getAccessToken() {
108
+ // 60s of headroom so a token can't expire mid-request.
109
+ const margin = 60_000;
110
+ if (this.tokens && this.tokens.expiresAt - margin > Date.now()) {
111
+ return this.tokens.accessToken;
112
+ }
113
+ if (!this.pending) {
114
+ this.pending = this.renew().finally(() => {
115
+ this.pending = null;
116
+ });
117
+ }
118
+ this.tokens = await this.pending;
119
+ return this.tokens.accessToken;
120
+ }
121
+ async renew() {
122
+ const margin = 60_000;
123
+ if (this.tokens && this.tokens.refreshExpiresAt - margin > Date.now()) {
124
+ try {
125
+ return await refreshTokens(this.tokens.refreshToken);
126
+ }
127
+ catch {
128
+ // Refresh failed (revoked, etc.) - fall through to a full re-auth.
129
+ }
130
+ }
131
+ return authenticateWithNpsso(this.npsso);
132
+ }
133
+ }
134
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/psn/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { TokenManager } from "./auth.js";
2
+ export declare class PsnApiError extends Error {
3
+ readonly status: number;
4
+ readonly body?: unknown | undefined;
5
+ constructor(message: string, status: number, body?: unknown | undefined);
6
+ }
7
+ export interface RequestOptions {
8
+ method?: "GET" | "POST";
9
+ /** Query string parameters; undefined values are dropped. */
10
+ query?: Record<string, string | number | boolean | undefined>;
11
+ /** JSON body for POST requests. */
12
+ body?: unknown;
13
+ /** Extra headers merged over the defaults. */
14
+ headers?: Record<string, string>;
15
+ }
16
+ /** Authenticated JSON client for the m.np.playstation.com API. */
17
+ export declare class PsnHttpClient {
18
+ private readonly tokens;
19
+ constructor(tokens: TokenManager);
20
+ request<T>(path: string, options?: RequestOptions): Promise<T>;
21
+ }
@@ -0,0 +1,58 @@
1
+ const API_BASE = "https://m.np.playstation.com/api";
2
+ export class PsnApiError extends Error {
3
+ status;
4
+ body;
5
+ constructor(message, status, body) {
6
+ super(message);
7
+ this.status = status;
8
+ this.body = body;
9
+ this.name = "PsnApiError";
10
+ }
11
+ }
12
+ /** Authenticated JSON client for the m.np.playstation.com API. */
13
+ export class PsnHttpClient {
14
+ tokens;
15
+ constructor(tokens) {
16
+ this.tokens = tokens;
17
+ }
18
+ async request(path, options = {}) {
19
+ const accessToken = await this.tokens.getAccessToken();
20
+ const url = new URL(`${API_BASE}${path}`);
21
+ for (const [key, value] of Object.entries(options.query ?? {})) {
22
+ if (value !== undefined)
23
+ url.searchParams.set(key, String(value));
24
+ }
25
+ const res = await fetch(url, {
26
+ method: options.method ?? "GET",
27
+ headers: {
28
+ Authorization: `Bearer ${accessToken}`,
29
+ "Accept-Language": "en-US",
30
+ ...(options.body !== undefined ? { "Content-Type": "application/json" } : {}),
31
+ ...options.headers,
32
+ },
33
+ body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
34
+ });
35
+ if (!res.ok) {
36
+ let body;
37
+ let message = `PSN API request to ${path} failed with HTTP ${res.status}`;
38
+ try {
39
+ body = await res.json();
40
+ const err = body.error;
41
+ if (err?.message)
42
+ message += `: ${err.message}`;
43
+ }
44
+ catch {
45
+ // Non-JSON error body; keep the generic message.
46
+ }
47
+ if (res.status === 403) {
48
+ message +=
49
+ ". The target profile may be private, or your account may not be allowed to view it.";
50
+ }
51
+ throw new PsnApiError(message, res.status, body);
52
+ }
53
+ // A few endpoints return 204 / empty bodies.
54
+ const text = await res.text();
55
+ return (text ? JSON.parse(text) : {});
56
+ }
57
+ }
58
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/psn/http.ts"],"names":[],"mappings":"AAEA,MAAM,QAAQ,GAAG,kCAAkC,CAAC;AAEpD,MAAM,OAAO,WAAY,SAAQ,KAAK;IAGlB;IACA;IAHlB,YACE,OAAe,EACC,MAAc,EACd,IAAc;QAE9B,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAU;QAG9B,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAYD,kEAAkE;AAClE,MAAM,OAAO,aAAa;IACK;IAA7B,YAA6B,MAAoB;QAApB,WAAM,GAAN,MAAM,CAAc;IAAG,CAAC;IAErD,KAAK,CAAC,OAAO,CAAI,IAAY,EAAE,UAA0B,EAAE;QACzD,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;QAEvD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,QAAQ,GAAG,IAAI,EAAE,CAAC,CAAC;QAC1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;YAC/D,IAAI,KAAK,KAAK,SAAS;gBAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;YAC/B,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,WAAW,EAAE;gBACtC,iBAAiB,EAAE,OAAO;gBAC1B,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC7E,GAAG,OAAO,CAAC,OAAO;aACnB;YACD,IAAI,EAAE,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;SAC5E,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,IAAa,CAAC;YAClB,IAAI,OAAO,GAAG,sBAAsB,IAAI,qBAAqB,GAAG,CAAC,MAAM,EAAE,CAAC;YAC1E,IAAI,CAAC;gBACH,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;gBACxB,MAAM,GAAG,GAAI,IAAyC,CAAC,KAAK,CAAC;gBAC7D,IAAI,GAAG,EAAE,OAAO;oBAAE,OAAO,IAAI,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC;YAClD,CAAC;YAAC,MAAM,CAAC;gBACP,iDAAiD;YACnD,CAAC;YACD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACvB,OAAO;oBACL,qFAAqF,CAAC;YAC1F,CAAC;YACD,MAAM,IAAI,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACnD,CAAC;QAED,6CAA6C;QAC7C,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAM,CAAC;IAC7C,CAAC;CACF"}
@@ -0,0 +1,127 @@
1
+ /** Shapes returned by the PSN API. Fields are optional where PSN omits them. */
2
+ export interface UserProfile {
3
+ onlineId: string;
4
+ accountId?: string;
5
+ aboutMe?: string;
6
+ avatars?: Array<{
7
+ size: string;
8
+ url: string;
9
+ }>;
10
+ languages?: string[];
11
+ isPlus?: boolean;
12
+ isOfficiallyVerified?: boolean;
13
+ isMe?: boolean;
14
+ }
15
+ export interface UniversalSearchResult {
16
+ domain: string;
17
+ domainResponse: {
18
+ totalResultCount: number;
19
+ results?: Array<{
20
+ id: string;
21
+ socialMetadata?: {
22
+ accountId: string;
23
+ onlineId: string;
24
+ verifiedUserName?: string;
25
+ avatarUrl?: string;
26
+ isPsPlus?: boolean;
27
+ relationshipState?: string;
28
+ };
29
+ }>;
30
+ };
31
+ }
32
+ export interface UniversalSearchResponse {
33
+ domainResponses: UniversalSearchResult["domainResponse"][];
34
+ }
35
+ export interface FriendsResponse {
36
+ friends: string[];
37
+ totalItemCount: number;
38
+ nextOffset?: number;
39
+ previousOffset?: number;
40
+ }
41
+ export interface BasicPresence {
42
+ accountId?: string;
43
+ availability?: string;
44
+ lastAvailableDate?: string;
45
+ primaryPlatformInfo?: {
46
+ onlineStatus?: string;
47
+ platform?: string;
48
+ lastOnlineDate?: string;
49
+ };
50
+ gameTitleInfoList?: Array<{
51
+ npTitleId: string;
52
+ titleName: string;
53
+ format?: string;
54
+ launchPlatform?: string;
55
+ conceptIconUrl?: string;
56
+ }>;
57
+ }
58
+ export interface TrophySummary {
59
+ accountId: string;
60
+ trophyLevel: number;
61
+ progress: number;
62
+ tier: number;
63
+ earnedTrophies: TrophyCounts;
64
+ }
65
+ export interface TrophyCounts {
66
+ bronze: number;
67
+ silver: number;
68
+ gold: number;
69
+ platinum: number;
70
+ }
71
+ export interface TrophyTitle {
72
+ npServiceName: "trophy" | "trophy2";
73
+ npCommunicationId: string;
74
+ trophyTitleName: string;
75
+ trophyTitleDetail?: string;
76
+ trophyTitleIconUrl?: string;
77
+ trophyTitlePlatform: string;
78
+ hasTrophyGroups: boolean;
79
+ definedTrophies: TrophyCounts;
80
+ progress: number;
81
+ earnedTrophies: TrophyCounts;
82
+ hiddenFlag?: boolean;
83
+ lastUpdatedDateTime: string;
84
+ }
85
+ export interface TrophyTitlesResponse {
86
+ trophyTitles: TrophyTitle[];
87
+ totalItemCount: number;
88
+ nextOffset?: number;
89
+ }
90
+ export interface Trophy {
91
+ trophyId: number;
92
+ trophyHidden: boolean;
93
+ trophyType: "bronze" | "silver" | "gold" | "platinum";
94
+ trophyName?: string;
95
+ trophyDetail?: string;
96
+ trophyIconUrl?: string;
97
+ trophyGroupId?: string;
98
+ earned?: boolean;
99
+ earnedDateTime?: string;
100
+ trophyEarnedRate?: string;
101
+ trophyRare?: number;
102
+ progress?: string;
103
+ progressRate?: number;
104
+ }
105
+ export interface TrophiesResponse {
106
+ trophySetVersion: string;
107
+ hasTrophyGroups: boolean;
108
+ trophies: Trophy[];
109
+ totalItemCount: number;
110
+ nextOffset?: number;
111
+ }
112
+ export interface PlayedGame {
113
+ titleId: string;
114
+ name: string;
115
+ localizedName?: string;
116
+ imageUrl?: string;
117
+ category: string;
118
+ playCount?: number;
119
+ firstPlayedDateTime?: string;
120
+ lastPlayedDateTime?: string;
121
+ playDuration?: string;
122
+ }
123
+ export interface PlayedGamesResponse {
124
+ titles: PlayedGame[];
125
+ totalItemCount: number;
126
+ nextOffset?: number;
127
+ }
@@ -0,0 +1,3 @@
1
+ /** Shapes returned by the PSN API. Fields are optional where PSN omits them. */
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/psn/types.ts"],"names":[],"mappings":"AAAA,gFAAgF"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { PsnApi } from "./psn/api.js";
3
+ export declare function registerTools(server: McpServer, psn: PsnApi): void;
package/dist/tools.js ADDED
@@ -0,0 +1,151 @@
1
+ import { z } from "zod";
2
+ const userParam = z
3
+ .string()
4
+ .describe('PSN user: "me" for the authenticated account, a PSN online id (username), or a numeric account id.');
5
+ const npServiceNameParam = z
6
+ .enum(["trophy", "trophy2"])
7
+ .describe('Trophy service: "trophy2" for PS5/PS VR2 titles, "trophy" for PS4 and earlier. ' +
8
+ "Use the npServiceName reported by psn_get_trophy_titles.");
9
+ function jsonResult(data) {
10
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
11
+ }
12
+ function errorResult(error) {
13
+ const message = error instanceof Error ? error.message : String(error);
14
+ return { content: [{ type: "text", text: message }], isError: true };
15
+ }
16
+ /** Wraps a handler so PSN/auth failures surface as MCP tool errors, not crashes. */
17
+ function handle(fn) {
18
+ return async (args) => {
19
+ try {
20
+ return jsonResult(await fn(args));
21
+ }
22
+ catch (error) {
23
+ return errorResult(error);
24
+ }
25
+ };
26
+ }
27
+ export function registerTools(server, psn) {
28
+ server.registerTool("psn_get_profile", {
29
+ title: "Get PSN profile",
30
+ description: "Get a PlayStation Network user's profile: online id, about-me, avatars, " +
31
+ "languages, and PS Plus / verification status.",
32
+ inputSchema: { user: userParam },
33
+ }, handle(async ({ user }) => psn.getProfile(await psn.resolveAccountId(user))));
34
+ server.registerTool("psn_search_players", {
35
+ title: "Search PSN players",
36
+ description: "Search PlayStation Network for players by name. Returns matching online ids, " +
37
+ "account ids, and avatars. Useful for finding a user's account id.",
38
+ inputSchema: {
39
+ query: z.string().describe("Name or online id to search for."),
40
+ limit: z.number().int().min(1).max(50).default(20).describe("Max results."),
41
+ },
42
+ }, handle(async ({ query, limit }) => {
43
+ const res = await psn.searchPlayers(query, limit);
44
+ const results = res.domainResponses[0]?.results ?? [];
45
+ return results
46
+ .map((r) => r.socialMetadata)
47
+ .filter(Boolean)
48
+ .map((m) => ({
49
+ onlineId: m.onlineId,
50
+ accountId: m.accountId,
51
+ verifiedUserName: m.verifiedUserName || undefined,
52
+ avatarUrl: m.avatarUrl,
53
+ isPsPlus: m.isPsPlus,
54
+ }));
55
+ }));
56
+ server.registerTool("psn_get_friends", {
57
+ title: "Get PSN friends",
58
+ description: "List a user's PSN friends with their profiles. Friends lists other than your " +
59
+ "own are only visible if that user's privacy settings allow it.",
60
+ inputSchema: {
61
+ user: userParam.default("me"),
62
+ limit: z.number().int().min(1).max(500).default(50).describe("Max friends to return."),
63
+ offset: z.number().int().min(0).default(0).describe("Pagination offset."),
64
+ },
65
+ }, handle(async ({ user, limit, offset }) => {
66
+ const accountId = await psn.resolveAccountId(user);
67
+ const friends = await psn.getFriends(accountId, limit, offset);
68
+ // The friends endpoint returns bare account ids; resolve them to profiles.
69
+ const profiles = await Promise.all(friends.friends.map(async (id) => {
70
+ try {
71
+ const profile = await psn.getProfile(id);
72
+ return { accountId: id, onlineId: profile.onlineId, isPlus: profile.isPlus };
73
+ }
74
+ catch {
75
+ return { accountId: id };
76
+ }
77
+ }));
78
+ return {
79
+ totalItemCount: friends.totalItemCount,
80
+ nextOffset: friends.nextOffset,
81
+ friends: profiles,
82
+ };
83
+ }));
84
+ server.registerTool("psn_get_presence", {
85
+ title: "Get PSN presence",
86
+ description: "Get a user's current online status: whether they are online, on which " +
87
+ "platform (PS5/PS4), what game they are playing, and when they were last online.",
88
+ inputSchema: { user: userParam.default("me") },
89
+ }, handle(async ({ user }) => psn.getBasicPresence(await psn.resolveAccountId(user))));
90
+ server.registerTool("psn_get_trophy_summary", {
91
+ title: "Get trophy summary",
92
+ description: "Get a user's overall trophy summary: trophy level, tier, progress to next " +
93
+ "level, and total bronze/silver/gold/platinum counts.",
94
+ inputSchema: { user: userParam.default("me") },
95
+ }, handle(async ({ user }) => psn.getTrophySummary(await psn.resolveAccountId(user))));
96
+ server.registerTool("psn_get_trophy_titles", {
97
+ title: "Get trophy titles",
98
+ description: "List the games a user has earned trophies in, ordered by most recently " +
99
+ "played, with per-game progress and earned counts. Returns each game's " +
100
+ "npCommunicationId and npServiceName for use with the trophy detail tools.",
101
+ inputSchema: {
102
+ user: userParam.default("me"),
103
+ limit: z.number().int().min(1).max(800).default(50).describe("Max titles to return."),
104
+ offset: z.number().int().min(0).default(0).describe("Pagination offset."),
105
+ },
106
+ }, handle(async ({ user, limit, offset }) => psn.getTrophyTitles(await psn.resolveAccountId(user), limit, offset)));
107
+ server.registerTool("psn_get_title_trophies", {
108
+ title: "Get a game's trophy list",
109
+ description: "Get the full trophy list defined for a game (names, descriptions, types, " +
110
+ "groups). Get npCommunicationId and npServiceName from psn_get_trophy_titles.",
111
+ inputSchema: {
112
+ npCommunicationId: z
113
+ .string()
114
+ .describe('Trophy set id, e.g. "NPWR20188_00".'),
115
+ npServiceName: npServiceNameParam,
116
+ trophyGroupId: z
117
+ .string()
118
+ .default("all")
119
+ .describe('Trophy group: "all", "default" (base game), or "001", "002"... for DLC.'),
120
+ limit: z.number().int().min(1).max(500).default(200),
121
+ offset: z.number().int().min(0).default(0),
122
+ },
123
+ }, handle(({ npCommunicationId, npServiceName, trophyGroupId, limit, offset }) => psn.getTitleTrophies(npCommunicationId, npServiceName, trophyGroupId, limit, offset)));
124
+ server.registerTool("psn_get_earned_trophies", {
125
+ title: "Get a user's earned trophies for a game",
126
+ description: "Get which trophies a user has earned in a specific game, including earned " +
127
+ "timestamps and global rarity. Combine with psn_get_title_trophies for trophy " +
128
+ "names and descriptions (this endpoint returns ids and earned state).",
129
+ inputSchema: {
130
+ user: userParam.default("me"),
131
+ npCommunicationId: z
132
+ .string()
133
+ .describe('Trophy set id, e.g. "NPWR20188_00".'),
134
+ npServiceName: npServiceNameParam,
135
+ trophyGroupId: z.string().default("all"),
136
+ limit: z.number().int().min(1).max(500).default(200),
137
+ offset: z.number().int().min(0).default(0),
138
+ },
139
+ }, handle(async ({ user, npCommunicationId, npServiceName, trophyGroupId, limit, offset }) => psn.getEarnedTrophies(await psn.resolveAccountId(user), npCommunicationId, npServiceName, trophyGroupId, limit, offset)));
140
+ server.registerTool("psn_get_played_games", {
141
+ title: "Get played games",
142
+ description: "List the PS4/PS5 games a user has played, with play counts, first/last " +
143
+ "played dates, and total play duration. Subject to the user's privacy settings.",
144
+ inputSchema: {
145
+ user: userParam.default("me"),
146
+ limit: z.number().int().min(1).max(200).default(50).describe("Max games to return."),
147
+ offset: z.number().int().min(0).default(0).describe("Pagination offset."),
148
+ },
149
+ }, handle(async ({ user, limit, offset }) => psn.getPlayedGames(await psn.resolveAccountId(user), limit, offset)));
150
+ }
151
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,SAAS,GAAG,CAAC;KAChB,MAAM,EAAE;KACR,QAAQ,CACP,oGAAoG,CACrG,CAAC;AAEJ,MAAM,kBAAkB,GAAG,CAAC;KACzB,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;KAC3B,QAAQ,CACP,iFAAiF;IAC/E,0DAA0D,CAC7D,CAAC;AAEJ,SAAS,UAAU,CAAC,IAAa;IAC/B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACvF,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAChF,CAAC;AAED,oFAAoF;AACpF,SAAS,MAAM,CAAI,EAAiC;IAClD,OAAO,KAAK,EAAE,IAAO,EAAE,EAAE;QACvB,IAAI,CAAC;YACH,OAAO,UAAU,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAiB,EAAE,GAAW;IAC1D,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;QACE,KAAK,EAAE,iBAAiB;QACxB,WAAW,EACT,0EAA0E;YAC1E,+CAA+C;QACjD,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;KACjC,EACD,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAC7E,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EACT,+EAA+E;YAC/E,mEAAmE;QACrE,WAAW,EAAE;YACX,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;YAC9D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC;SAC5E;KACF,EACD,MAAM,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;QAChC,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAClD,MAAM,OAAO,GAAG,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,EAAE,CAAC;QACtD,OAAO,OAAO;aACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC;aAC5B,MAAM,CAAC,OAAO,CAAC;aACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACX,QAAQ,EAAE,CAAE,CAAC,QAAQ;YACrB,SAAS,EAAE,CAAE,CAAC,SAAS;YACvB,gBAAgB,EAAE,CAAE,CAAC,gBAAgB,IAAI,SAAS;YAClD,SAAS,EAAE,CAAE,CAAC,SAAS;YACvB,QAAQ,EAAE,CAAE,CAAC,QAAQ;SACtB,CAAC,CAAC,CAAC;IACR,CAAC,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;QACE,KAAK,EAAE,iBAAiB;QACxB,WAAW,EACT,+EAA+E;YAC/E,gEAAgE;QAClE,WAAW,EAAE;YACX,IAAI,EAAE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;YAC7B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,wBAAwB,CAAC;YACtF,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,oBAAoB,CAAC;SAC1E;KACF,EACD,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE;QACvC,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QAC/D,2EAA2E;QAC3E,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YAC/B,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;gBACzC,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;YAC/E,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;YAC3B,CAAC;QACH,CAAC,CAAC,CACH,CAAC;QACF,OAAO;YACL,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,OAAO,EAAE,QAAQ;SAClB,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,kBAAkB,EAClB;QACE,KAAK,EAAE,kBAAkB;QACzB,WAAW,EACT,wEAAwE;YACxE,iFAAiF;QACnF,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;KAC/C,EACD,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CACnF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,wBAAwB,EACxB;QACE,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EACT,4EAA4E;YAC5E,sDAAsD;QACxD,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;KAC/C,EACD,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CACnF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,uBAAuB,EACvB;QACE,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EACT,yEAAyE;YACzE,wEAAwE;YACxE,2EAA2E;QAC7E,WAAW,EAAE;YACX,IAAI,EAAE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;YAC7B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,uBAAuB,CAAC;YACrF,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,oBAAoB,CAAC;SAC1E;KACF,EACD,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CACvC,GAAG,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CACrE,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,wBAAwB,EACxB;QACE,KAAK,EAAE,0BAA0B;QACjC,WAAW,EACT,2EAA2E;YAC3E,8EAA8E;QAChF,WAAW,EAAE;YACX,iBAAiB,EAAE,CAAC;iBACjB,MAAM,EAAE;iBACR,QAAQ,CAAC,qCAAqC,CAAC;YAClD,aAAa,EAAE,kBAAkB;YACjC,aAAa,EAAE,CAAC;iBACb,MAAM,EAAE;iBACR,OAAO,CAAC,KAAK,CAAC;iBACd,QAAQ,CAAC,yEAAyE,CAAC;YACtF,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;YACpD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;SAC3C;KACF,EACD,MAAM,CAAC,CAAC,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAC5E,GAAG,CAAC,gBAAgB,CAAC,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,CAAC,CACrF,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,yBAAyB,EACzB;QACE,KAAK,EAAE,yCAAyC;QAChD,WAAW,EACT,4EAA4E;YAC5E,+EAA+E;YAC/E,sEAAsE;QACxE,WAAW,EAAE;YACX,IAAI,EAAE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;YAC7B,iBAAiB,EAAE,CAAC;iBACjB,MAAM,EAAE;iBACR,QAAQ,CAAC,qCAAqC,CAAC;YAClD,aAAa,EAAE,kBAAkB;YACjC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;YACxC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;YACpD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;SAC3C;KACF,EACD,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CACxF,GAAG,CAAC,iBAAiB,CACnB,MAAM,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAChC,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,KAAK,EACL,MAAM,CACP,CACF,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,KAAK,EAAE,kBAAkB;QACzB,WAAW,EACT,yEAAyE;YACzE,gFAAgF;QAClF,WAAW,EAAE;YACX,IAAI,EAAE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;YAC7B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,sBAAsB,CAAC;YACpF,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,oBAAoB,CAAC;SAC1E;KACF,EACD,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CACvC,GAAG,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CACpE,CACF,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "psn-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for interfacing with the PlayStation Network",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "psn-mcp": "dist/index.js"
9
+ },
10
+ "main": "dist/index.js",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18.0.0"
16
+ },
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "prepublishOnly": "tsc",
20
+ "dev": "tsx src/index.ts",
21
+ "start": "node dist/index.js",
22
+ "typecheck": "tsc --noEmit"
23
+ },
24
+ "keywords": [
25
+ "mcp",
26
+ "model-context-protocol",
27
+ "playstation",
28
+ "psn",
29
+ "trophies"
30
+ ],
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.12.0",
33
+ "zod": "^3.24.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^22.0.0",
37
+ "tsx": "^4.19.0",
38
+ "typescript": "^5.7.0"
39
+ }
40
+ }