|
3 | 3 | * SPDX-License-Identifier: MPL-2.0 |
4 | 4 | */ |
5 | 5 |
|
6 | | -import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; |
7 | | -import { loadTokenCatalog } from "./store/index.js"; |
8 | | -import { toJsonResourceResponse } from "../utils.js"; |
| 6 | +import getTokensResource from "./get-tokens.js"; |
| 7 | +import getTokenByKeyResource from "./get-token-by-key.js"; |
9 | 8 |
|
10 | 9 | import type { McpResource } from "../types.js"; |
11 | | -import type { TokenCatalogStore } from "./store/index.js"; |
12 | | -import type { JsonObject } from "../../types.js"; |
13 | | -import type { TokenRecord, TokenSummary } from "./store/lookup.js"; |
14 | | - |
15 | | -export const TOKENS_URI = "hds://tokens"; |
16 | | -export const TOKEN_BY_KEY_URI_TEMPLATE = `${TOKENS_URI}/{tokenKey}`; |
17 | | - |
18 | | -const getTokenByKeyUri = (tokenKey: string): string => { |
19 | | - return `${TOKENS_URI}/${encodeURIComponent(tokenKey)}`; |
20 | | -}; |
21 | | - |
22 | | -const decodeTokenKey = (tokenKey: string): string => { |
23 | | - try { |
24 | | - return decodeURIComponent(tokenKey); |
25 | | - } catch { |
26 | | - return tokenKey; |
27 | | - } |
28 | | -}; |
29 | | - |
30 | | -const toSerializableTokenSummary = (token: TokenSummary): JsonObject => { |
31 | | - return { |
32 | | - key: token.key, |
33 | | - name: token.name, |
34 | | - type: token.type, |
35 | | - value: token.value, |
36 | | - cssVar: token.cssVar, |
37 | | - category: token.category, |
38 | | - path: token.path, |
39 | | - }; |
40 | | -}; |
41 | | - |
42 | | -const toSerializableTokenRecord = (token: TokenRecord): JsonObject => { |
43 | | - return { |
44 | | - ...toSerializableTokenSummary(token), |
45 | | - ...(token.original === undefined ? {} : { original: token.original }), |
46 | | - }; |
47 | | -}; |
48 | | - |
49 | | -export const readTokensResource = (store: TokenCatalogStore) => { |
50 | | - const payload = { |
51 | | - totalTokenCount: store.getMeta().totalTokenCount, |
52 | | - tokens: store |
53 | | - .listTokens() |
54 | | - .map((token) => toSerializableTokenSummary(token)), |
55 | | - }; |
56 | | - |
57 | | - return toJsonResourceResponse(TOKENS_URI, payload); |
58 | | -}; |
59 | | - |
60 | | -export const readTokenByKeyResource = ( |
61 | | - store: TokenCatalogStore, |
62 | | - tokenKey: string, |
63 | | -) => { |
64 | | - const token = store.getTokenByKey(tokenKey); |
65 | | - |
66 | | - if (token === null) { |
67 | | - return toJsonResourceResponse(getTokenByKeyUri(tokenKey), { |
68 | | - found: false, |
69 | | - requestedTokenKey: tokenKey, |
70 | | - message: "Token not found for provided tokenKey.", |
71 | | - }); |
72 | | - } |
73 | | - |
74 | | - return toJsonResourceResponse(getTokenByKeyUri(tokenKey), { |
75 | | - found: true, |
76 | | - requestedTokenKey: tokenKey, |
77 | | - token: toSerializableTokenRecord(token), |
78 | | - }); |
79 | | -}; |
80 | | - |
81 | | -let tokenStore: TokenCatalogStore | null = null; |
82 | | - |
83 | | -const getOrLoadTokenStore = (): TokenCatalogStore => { |
84 | | - if (tokenStore === null) { |
85 | | - tokenStore = loadTokenCatalog(); |
86 | | - } |
87 | | - |
88 | | - return tokenStore; |
89 | | -}; |
90 | | - |
91 | | -const completeTokenKey = (value: string): string[] => { |
92 | | - const query = value.trim().toLowerCase(); |
93 | | - const matches: string[] = []; |
94 | | - |
95 | | - for (const token of getOrLoadTokenStore().listTokens()) { |
96 | | - const aliases = [token.key, token.name, token.path.join(".")]; |
97 | | - const isMatch = |
98 | | - query.length === 0 || |
99 | | - aliases.some((alias) => alias.toLowerCase().includes(query)); |
100 | | - |
101 | | - if (isMatch) { |
102 | | - matches.push(token.key); |
103 | | - } |
104 | | - |
105 | | - if (matches.length >= 100) { |
106 | | - break; |
107 | | - } |
108 | | - } |
109 | | - |
110 | | - return matches; |
111 | | -}; |
112 | | - |
113 | | -const getTokensResource: McpResource = { |
114 | | - name: "get_hds_tokens", |
115 | | - uri: TOKENS_URI, |
116 | | - config: { |
117 | | - title: "HDS token catalog index", |
118 | | - description: "Canonical list of tokens with summary metadata", |
119 | | - mimeType: "application/json", |
120 | | - }, |
121 | | - readCallback: async () => { |
122 | | - return readTokensResource(getOrLoadTokenStore()); |
123 | | - }, |
124 | | -}; |
125 | | - |
126 | | -const getTokenByKeyResource: McpResource = { |
127 | | - name: "get_hds_token", |
128 | | - template: new ResourceTemplate(TOKEN_BY_KEY_URI_TEMPLATE, { |
129 | | - list: undefined, |
130 | | - complete: { |
131 | | - tokenKey: completeTokenKey, |
132 | | - }, |
133 | | - }), |
134 | | - config: { |
135 | | - title: "HDS token detail", |
136 | | - description: "Detailed token record for a specific token key", |
137 | | - mimeType: "application/json", |
138 | | - }, |
139 | | - readCallback: async ( |
140 | | - uri: URL, |
141 | | - variables: Record<string, string | string[]>, |
142 | | - ) => { |
143 | | - const tokenKey = variables.tokenKey; |
144 | | - |
145 | | - if (typeof tokenKey !== "string" || tokenKey.trim().length === 0) { |
146 | | - return toJsonResourceResponse(uri.toString(), { |
147 | | - found: false, |
148 | | - message: "Missing tokenKey variable.", |
149 | | - }); |
150 | | - } |
151 | | - |
152 | | - return readTokenByKeyResource( |
153 | | - getOrLoadTokenStore(), |
154 | | - decodeTokenKey(tokenKey), |
155 | | - ); |
156 | | - }, |
157 | | -}; |
158 | 10 |
|
159 | 11 | const TOKENS_RESOURCES: McpResource[] = [ |
| 12 | + // TOKENS |
160 | 13 | getTokensResource, |
161 | 14 | getTokenByKeyResource, |
162 | 15 | ]; |
|
0 commit comments