toolhome 0.5.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.
Files changed (214) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +295 -0
  3. package/README.zh.md +281 -0
  4. package/dist/server/app.d.ts +16 -0
  5. package/dist/server/app.d.ts.map +1 -0
  6. package/dist/server/app.js +166 -0
  7. package/dist/server/app.js.map +1 -0
  8. package/dist/server/cli/main.d.ts +3 -0
  9. package/dist/server/cli/main.d.ts.map +1 -0
  10. package/dist/server/cli/main.js +587 -0
  11. package/dist/server/cli/main.js.map +1 -0
  12. package/dist/server/cli-plane/allow-list.d.ts +18 -0
  13. package/dist/server/cli-plane/allow-list.d.ts.map +1 -0
  14. package/dist/server/cli-plane/allow-list.js +28 -0
  15. package/dist/server/cli-plane/allow-list.js.map +1 -0
  16. package/dist/server/cli-plane/cli-service.d.ts +41 -0
  17. package/dist/server/cli-plane/cli-service.d.ts.map +1 -0
  18. package/dist/server/cli-plane/cli-service.js +282 -0
  19. package/dist/server/cli-plane/cli-service.js.map +1 -0
  20. package/dist/server/cli-plane/frames.d.ts +26 -0
  21. package/dist/server/cli-plane/frames.d.ts.map +1 -0
  22. package/dist/server/cli-plane/frames.js +12 -0
  23. package/dist/server/cli-plane/frames.js.map +1 -0
  24. package/dist/server/cli-plane/models.d.ts +174 -0
  25. package/dist/server/cli-plane/models.d.ts.map +1 -0
  26. package/dist/server/cli-plane/models.js +169 -0
  27. package/dist/server/cli-plane/models.js.map +1 -0
  28. package/dist/server/cli-plane/routes.d.ts +32 -0
  29. package/dist/server/cli-plane/routes.d.ts.map +1 -0
  30. package/dist/server/cli-plane/routes.js +94 -0
  31. package/dist/server/cli-plane/routes.js.map +1 -0
  32. package/dist/server/cli-plane/runner.d.ts +41 -0
  33. package/dist/server/cli-plane/runner.d.ts.map +1 -0
  34. package/dist/server/cli-plane/runner.js +163 -0
  35. package/dist/server/cli-plane/runner.js.map +1 -0
  36. package/dist/server/cli-plane/status.d.ts +12 -0
  37. package/dist/server/cli-plane/status.d.ts.map +1 -0
  38. package/dist/server/cli-plane/status.js +24 -0
  39. package/dist/server/cli-plane/status.js.map +1 -0
  40. package/dist/server/config/harness-import.d.ts +44 -0
  41. package/dist/server/config/harness-import.d.ts.map +1 -0
  42. package/dist/server/config/harness-import.js +166 -0
  43. package/dist/server/config/harness-import.js.map +1 -0
  44. package/dist/server/config.d.ts +20 -0
  45. package/dist/server/config.d.ts.map +1 -0
  46. package/dist/server/config.js +80 -0
  47. package/dist/server/config.js.map +1 -0
  48. package/dist/server/control/client.d.ts +24 -0
  49. package/dist/server/control/client.d.ts.map +1 -0
  50. package/dist/server/control/client.js +115 -0
  51. package/dist/server/control/client.js.map +1 -0
  52. package/dist/server/control/control-api.d.ts +39 -0
  53. package/dist/server/control/control-api.d.ts.map +1 -0
  54. package/dist/server/control/control-api.js +291 -0
  55. package/dist/server/control/control-api.js.map +1 -0
  56. package/dist/server/control/control-service.d.ts +713 -0
  57. package/dist/server/control/control-service.d.ts.map +1 -0
  58. package/dist/server/control/control-service.js +693 -0
  59. package/dist/server/control/control-service.js.map +1 -0
  60. package/dist/server/control/openapi.d.ts +2 -0
  61. package/dist/server/control/openapi.d.ts.map +1 -0
  62. package/dist/server/control/openapi.js +370 -0
  63. package/dist/server/control/openapi.js.map +1 -0
  64. package/dist/server/data-plane/data-plane.d.ts +20 -0
  65. package/dist/server/data-plane/data-plane.d.ts.map +1 -0
  66. package/dist/server/data-plane/data-plane.js +335 -0
  67. package/dist/server/data-plane/data-plane.js.map +1 -0
  68. package/dist/server/data-plane/gateway-server.d.ts +13 -0
  69. package/dist/server/data-plane/gateway-server.d.ts.map +1 -0
  70. package/dist/server/data-plane/gateway-server.js +796 -0
  71. package/dist/server/data-plane/gateway-server.js.map +1 -0
  72. package/dist/server/data-plane/legacy-session.d.ts +18 -0
  73. package/dist/server/data-plane/legacy-session.d.ts.map +1 -0
  74. package/dist/server/data-plane/legacy-session.js +137 -0
  75. package/dist/server/data-plane/legacy-session.js.map +1 -0
  76. package/dist/server/data-plane/projection.d.ts +18 -0
  77. package/dist/server/data-plane/projection.d.ts.map +1 -0
  78. package/dist/server/data-plane/projection.js +44 -0
  79. package/dist/server/data-plane/projection.js.map +1 -0
  80. package/dist/server/data-plane/registry.d.ts +31 -0
  81. package/dist/server/data-plane/registry.d.ts.map +1 -0
  82. package/dist/server/data-plane/registry.js +129 -0
  83. package/dist/server/data-plane/registry.js.map +1 -0
  84. package/dist/server/data-plane/task-extension.d.ts +7 -0
  85. package/dist/server/data-plane/task-extension.d.ts.map +1 -0
  86. package/dist/server/data-plane/task-extension.js +33 -0
  87. package/dist/server/data-plane/task-extension.js.map +1 -0
  88. package/dist/server/data-plane/virtualization.d.ts +35 -0
  89. package/dist/server/data-plane/virtualization.d.ts.map +1 -0
  90. package/dist/server/data-plane/virtualization.js +228 -0
  91. package/dist/server/data-plane/virtualization.js.map +1 -0
  92. package/dist/server/domain/errors.d.ts +9 -0
  93. package/dist/server/domain/errors.d.ts.map +1 -0
  94. package/dist/server/domain/errors.js +19 -0
  95. package/dist/server/domain/errors.js.map +1 -0
  96. package/dist/server/domain/models.d.ts +720 -0
  97. package/dist/server/domain/models.d.ts.map +1 -0
  98. package/dist/server/domain/models.js +327 -0
  99. package/dist/server/domain/models.js.map +1 -0
  100. package/dist/server/main.d.ts +2 -0
  101. package/dist/server/main.d.ts.map +1 -0
  102. package/dist/server/main.js +37 -0
  103. package/dist/server/main.js.map +1 -0
  104. package/dist/server/manage/management-mcp.d.ts +27 -0
  105. package/dist/server/manage/management-mcp.d.ts.map +1 -0
  106. package/dist/server/manage/management-mcp.js +431 -0
  107. package/dist/server/manage/management-mcp.js.map +1 -0
  108. package/dist/server/market/catalog.d.ts +111 -0
  109. package/dist/server/market/catalog.d.ts.map +1 -0
  110. package/dist/server/market/catalog.js +784 -0
  111. package/dist/server/market/catalog.js.map +1 -0
  112. package/dist/server/market/installers.d.ts +64 -0
  113. package/dist/server/market/installers.d.ts.map +1 -0
  114. package/dist/server/market/installers.js +181 -0
  115. package/dist/server/market/installers.js.map +1 -0
  116. package/dist/server/market/market-service.d.ts +146 -0
  117. package/dist/server/market/market-service.d.ts.map +1 -0
  118. package/dist/server/market/market-service.js +930 -0
  119. package/dist/server/market/market-service.js.map +1 -0
  120. package/dist/server/observability/call-recorder.d.ts +22 -0
  121. package/dist/server/observability/call-recorder.d.ts.map +1 -0
  122. package/dist/server/observability/call-recorder.js +99 -0
  123. package/dist/server/observability/call-recorder.js.map +1 -0
  124. package/dist/server/observability/logger.d.ts +16 -0
  125. package/dist/server/observability/logger.d.ts.map +1 -0
  126. package/dist/server/observability/logger.js +25 -0
  127. package/dist/server/observability/logger.js.map +1 -0
  128. package/dist/server/security/api-keys.d.ts +15 -0
  129. package/dist/server/security/api-keys.d.ts.map +1 -0
  130. package/dist/server/security/api-keys.js +35 -0
  131. package/dist/server/security/api-keys.js.map +1 -0
  132. package/dist/server/security/auth-service.d.ts +26 -0
  133. package/dist/server/security/auth-service.d.ts.map +1 -0
  134. package/dist/server/security/auth-service.js +76 -0
  135. package/dist/server/security/auth-service.js.map +1 -0
  136. package/dist/server/security/control-session.d.ts +9 -0
  137. package/dist/server/security/control-session.d.ts.map +1 -0
  138. package/dist/server/security/control-session.js +51 -0
  139. package/dist/server/security/control-session.js.map +1 -0
  140. package/dist/server/security/credential-materializer.d.ts +20 -0
  141. package/dist/server/security/credential-materializer.d.ts.map +1 -0
  142. package/dist/server/security/credential-materializer.js +82 -0
  143. package/dist/server/security/credential-materializer.js.map +1 -0
  144. package/dist/server/security/cursor-codec.d.ts +13 -0
  145. package/dist/server/security/cursor-codec.d.ts.map +1 -0
  146. package/dist/server/security/cursor-codec.js +43 -0
  147. package/dist/server/security/cursor-codec.js.map +1 -0
  148. package/dist/server/security/oauth-routes.d.ts +21 -0
  149. package/dist/server/security/oauth-routes.d.ts.map +1 -0
  150. package/dist/server/security/oauth-routes.js +100 -0
  151. package/dist/server/security/oauth-routes.js.map +1 -0
  152. package/dist/server/security/oauth-server.d.ts +16 -0
  153. package/dist/server/security/oauth-server.d.ts.map +1 -0
  154. package/dist/server/security/oauth-server.js +544 -0
  155. package/dist/server/security/oauth-server.js.map +1 -0
  156. package/dist/server/security/secret-box.d.ts +7 -0
  157. package/dist/server/security/secret-box.d.ts.map +1 -0
  158. package/dist/server/security/secret-box.js +27 -0
  159. package/dist/server/security/secret-box.js.map +1 -0
  160. package/dist/server/security/secure-action.d.ts +21 -0
  161. package/dist/server/security/secure-action.d.ts.map +1 -0
  162. package/dist/server/security/secure-action.js +69 -0
  163. package/dist/server/security/secure-action.js.map +1 -0
  164. package/dist/server/storage/sqlite-store.d.ts +70 -0
  165. package/dist/server/storage/sqlite-store.d.ts.map +1 -0
  166. package/dist/server/storage/sqlite-store.js +1522 -0
  167. package/dist/server/storage/sqlite-store.js.map +1 -0
  168. package/dist/server/storage/store.d.ts +82 -0
  169. package/dist/server/storage/store.d.ts.map +1 -0
  170. package/dist/server/storage/store.js +2 -0
  171. package/dist/server/storage/store.js.map +1 -0
  172. package/dist/server/upstream/adapter.d.ts +52 -0
  173. package/dist/server/upstream/adapter.d.ts.map +1 -0
  174. package/dist/server/upstream/adapter.js +807 -0
  175. package/dist/server/upstream/adapter.js.map +1 -0
  176. package/dist/server/upstream/credential-resolver.d.ts +14 -0
  177. package/dist/server/upstream/credential-resolver.d.ts.map +1 -0
  178. package/dist/server/upstream/credential-resolver.js +56 -0
  179. package/dist/server/upstream/credential-resolver.js.map +1 -0
  180. package/dist/server/upstream/extension-transport.d.ts +17 -0
  181. package/dist/server/upstream/extension-transport.d.ts.map +1 -0
  182. package/dist/server/upstream/extension-transport.js +207 -0
  183. package/dist/server/upstream/extension-transport.js.map +1 -0
  184. package/dist/server/upstream/manager.d.ts +23 -0
  185. package/dist/server/upstream/manager.d.ts.map +1 -0
  186. package/dist/server/upstream/manager.js +324 -0
  187. package/dist/server/upstream/manager.js.map +1 -0
  188. package/dist/server/upstream/oauth-provider.d.ts +38 -0
  189. package/dist/server/upstream/oauth-provider.d.ts.map +1 -0
  190. package/dist/server/upstream/oauth-provider.js +225 -0
  191. package/dist/server/upstream/oauth-provider.js.map +1 -0
  192. package/dist/server/upstream/oauth-refresh.d.ts +65 -0
  193. package/dist/server/upstream/oauth-refresh.d.ts.map +1 -0
  194. package/dist/server/upstream/oauth-refresh.js +240 -0
  195. package/dist/server/upstream/oauth-refresh.js.map +1 -0
  196. package/dist/server/upstream/oauth-routes.d.ts +17 -0
  197. package/dist/server/upstream/oauth-routes.d.ts.map +1 -0
  198. package/dist/server/upstream/oauth-routes.js +87 -0
  199. package/dist/server/upstream/oauth-routes.js.map +1 -0
  200. package/dist/server/upstream/oauth-service.d.ts +143 -0
  201. package/dist/server/upstream/oauth-service.d.ts.map +1 -0
  202. package/dist/server/upstream/oauth-service.js +217 -0
  203. package/dist/server/upstream/oauth-service.js.map +1 -0
  204. package/dist/server/upstream/stable-json.d.ts +3 -0
  205. package/dist/server/upstream/stable-json.d.ts.map +1 -0
  206. package/dist/server/upstream/stable-json.js +17 -0
  207. package/dist/server/upstream/stable-json.js.map +1 -0
  208. package/package.json +69 -0
  209. package/skills/toolhome/SKILL.md +238 -0
  210. package/skills/toolhome/references/cli-reference.md +242 -0
  211. package/skills/toolhome/references/deployment.md +113 -0
  212. package/skills/toolhome/references/market-guide.md +128 -0
  213. package/skills/toolhome/references/oauth-guide.md +96 -0
  214. package/skills/toolhome/references/troubleshooting.md +133 -0
@@ -0,0 +1,217 @@
1
+ import { assertSecureTokenEndpoint, auth, } from '@modelcontextprotocol/client';
2
+ import { z } from 'zod';
3
+ import { AppError } from '../domain/errors.js';
4
+ import { StoredOAuthProvider } from './oauth-provider.js';
5
+ const beginInputSchema = z.object({
6
+ serverId: z.uuid().optional(),
7
+ force: z.boolean().default(false),
8
+ });
9
+ export class UpstreamOAuthService {
10
+ #store;
11
+ #publicUrl;
12
+ #upstreams;
13
+ #logger;
14
+ #urlClientId;
15
+ constructor(store, publicUrl, upstreams, logger, urlClientId = true) {
16
+ this.#store = store;
17
+ this.#publicUrl = publicUrl;
18
+ this.#upstreams = upstreams;
19
+ this.#logger = logger;
20
+ this.#urlClientId = urlClientId;
21
+ }
22
+ #provider(credentialId, urlClientId = this.#urlClientId) {
23
+ return new StoredOAuthProvider(this.#store, credentialId, this.#publicUrl, {
24
+ urlClientId,
25
+ });
26
+ }
27
+ metadata(credentialId) {
28
+ return this.#provider(credentialId).clientMetadata;
29
+ }
30
+ async begin(credentialId, value = {}) {
31
+ const input = beginInputSchema.parse(value);
32
+ const server = this.#serverFor(credentialId, input.serverId);
33
+ const provider = this.#provider(credentialId, server.settings.urlClientId ?? this.#urlClientId);
34
+ const payload = this.#oauthPayload(credentialId);
35
+ if (input.force) {
36
+ // A forced flow starts over: discard any previously registered client so
37
+ // the next authorization re-registers under the current configuration
38
+ // (e.g. DCR instead of URL-based client metadata).
39
+ provider.invalidateCredentials('client');
40
+ }
41
+ const result = await auth(provider, {
42
+ serverUrl: server.transport.url,
43
+ ...(payload.scope === undefined ? {} : { scope: payload.scope }),
44
+ ...(input.force ? { forceReauthorization: true } : {}),
45
+ });
46
+ if (result === 'AUTHORIZED') {
47
+ const connection = await this.#reload(server);
48
+ return {
49
+ status: 'authorized',
50
+ credential: this.#store.getCredential(credentialId),
51
+ serverId: server.id,
52
+ connection,
53
+ };
54
+ }
55
+ const authorizationUrl = provider.authorizationUrl();
56
+ if (!authorizationUrl) {
57
+ throw new AppError('oauth_redirect_missing', 'Authorization server did not return a redirect', 502);
58
+ }
59
+ await this.#upstreams.invalidate(server.id);
60
+ this.#store.appendEvent({
61
+ level: 'info',
62
+ type: 'credential.authorization_started',
63
+ serverId: server.id,
64
+ message: `Started OAuth authorization for ${server.slug}`,
65
+ detail: { credentialId },
66
+ });
67
+ return {
68
+ status: 'authorization-required',
69
+ credential: this.#store.getCredential(credentialId),
70
+ serverId: server.id,
71
+ authorizationUrl,
72
+ callbackUrl: provider.redirectUrl.toString(),
73
+ };
74
+ }
75
+ async callback(credentialId, input) {
76
+ const server = this.#serverFor(credentialId);
77
+ const provider = this.#provider(credentialId, server.settings.urlClientId ?? this.#urlClientId);
78
+ provider.validateCallbackState(input.state);
79
+ const payload = this.#oauthPayload(credentialId);
80
+ const result = await auth(provider, {
81
+ serverUrl: server.transport.url,
82
+ authorizationCode: input.code,
83
+ ...(input.iss === undefined ? {} : { iss: input.iss }),
84
+ ...(payload.scope === undefined ? {} : { scope: payload.scope }),
85
+ });
86
+ if (result !== 'AUTHORIZED') {
87
+ throw new AppError('oauth_exchange_incomplete', 'OAuth authorization did not complete', 502);
88
+ }
89
+ const connection = await this.#reload(server);
90
+ this.#store.appendEvent({
91
+ level: 'info',
92
+ type: 'credential.authorized',
93
+ serverId: server.id,
94
+ message: `Authorized ${server.slug}`,
95
+ detail: { credentialId },
96
+ });
97
+ return {
98
+ status: 'authorized',
99
+ server,
100
+ credential: this.#store.getCredential(credentialId),
101
+ connection,
102
+ };
103
+ }
104
+ async revoke(credentialId) {
105
+ const server = this.#serverFor(credentialId);
106
+ const provider = this.#provider(credentialId, server.settings.urlClientId ?? this.#urlClientId);
107
+ const payload = this.#oauthPayload(credentialId);
108
+ const metadata = payload.discoveryState?.authorizationServerMetadata;
109
+ const endpointValue = metadata ? Reflect.get(metadata, 'revocation_endpoint') : undefined;
110
+ const endpoint = typeof endpointValue === 'string' ? endpointValue : undefined;
111
+ const clientInformation = provider.clientInformation();
112
+ const attempts = [];
113
+ if (endpoint) {
114
+ assertSecureTokenEndpoint(endpoint);
115
+ if (payload.refreshToken) {
116
+ attempts.push(await this.#revokeToken(endpoint, payload.refreshToken, 'refresh_token', clientInformation));
117
+ }
118
+ if (payload.accessToken) {
119
+ attempts.push(await this.#revokeToken(endpoint, payload.accessToken, 'access_token', clientInformation));
120
+ }
121
+ }
122
+ provider.invalidateCredentials('tokens');
123
+ provider.invalidateCredentials('verifier');
124
+ await this.#upstreams.invalidate(server.id);
125
+ this.#store.appendEvent({
126
+ level: attempts.some((attempt) => !attempt.ok) ? 'warn' : 'info',
127
+ type: 'credential.revoked',
128
+ serverId: server.id,
129
+ message: `Revoked OAuth credential for ${server.slug}`,
130
+ detail: { credentialId, remoteRevocationSupported: endpoint !== undefined, attempts },
131
+ });
132
+ return {
133
+ revoked: true,
134
+ remoteRevocationSupported: endpoint !== undefined,
135
+ remoteRevoked: attempts.length > 0 && attempts.every((attempt) => attempt.ok),
136
+ attempts,
137
+ credential: this.#store.getCredential(credentialId),
138
+ };
139
+ }
140
+ async #revokeToken(endpoint, token, tokenType, clientInformation) {
141
+ const body = new URLSearchParams({ token, token_type_hint: tokenType });
142
+ const headers = new Headers({ 'content-type': 'application/x-www-form-urlencoded' });
143
+ if (clientInformation?.client_secret) {
144
+ const user = formEncode(clientInformation.client_id);
145
+ const password = formEncode(clientInformation.client_secret);
146
+ headers.set('authorization', `Basic ${Buffer.from(`${user}:${password}`).toString('base64')}`);
147
+ }
148
+ else if (clientInformation) {
149
+ body.set('client_id', clientInformation.client_id);
150
+ }
151
+ try {
152
+ const response = await fetch(endpoint, {
153
+ method: 'POST',
154
+ headers,
155
+ body,
156
+ redirect: 'error',
157
+ signal: AbortSignal.timeout(10_000),
158
+ });
159
+ return { tokenType, ok: response.ok, status: response.status };
160
+ }
161
+ catch (error) {
162
+ this.#logger.warn('Remote OAuth token revocation failed', {
163
+ error: error instanceof Error ? error.message : String(error),
164
+ });
165
+ return { tokenType, ok: false };
166
+ }
167
+ }
168
+ #serverFor(credentialId, serverId) {
169
+ const candidates = this.#store
170
+ .listServers()
171
+ .filter((server) => server.credentialId === credentialId);
172
+ const server = serverId
173
+ ? candidates.find((candidate) => candidate.id === serverId)
174
+ : candidates.length === 1
175
+ ? candidates[0]
176
+ : undefined;
177
+ if (!server) {
178
+ throw new AppError('oauth_server_required', candidates.length === 0
179
+ ? 'Attach the OAuth credential to a remote MCP server first'
180
+ : 'OAuth credential must be attached to exactly one remote MCP server', 400);
181
+ }
182
+ if (server.transport.type !== 'streamable-http') {
183
+ throw new AppError('oauth_remote_only', 'OAuth is only available for remote MCP servers', 400);
184
+ }
185
+ return { ...server, transport: server.transport };
186
+ }
187
+ #oauthPayload(credentialId) {
188
+ const payload = this.#store.getCredentialPayload(credentialId);
189
+ if (!payload || payload.type !== 'oauth') {
190
+ throw new AppError('oauth_credential_invalid', 'Credential is not OAuth', 400);
191
+ }
192
+ return payload;
193
+ }
194
+ async #reload(server) {
195
+ await this.#upstreams.remove(server.id);
196
+ if (!server.enabled)
197
+ return { refreshed: false, reason: 'server-disabled' };
198
+ try {
199
+ await this.#upstreams.refresh(server.id);
200
+ return { refreshed: true };
201
+ }
202
+ catch (error) {
203
+ this.#logger.warn('OAuth completed but the upstream MCP refresh failed', {
204
+ serverId: server.id,
205
+ error: error instanceof Error ? error.message : String(error),
206
+ });
207
+ return {
208
+ refreshed: false,
209
+ reason: error instanceof Error ? error.message : String(error),
210
+ };
211
+ }
212
+ }
213
+ }
214
+ function formEncode(value) {
215
+ return new URLSearchParams({ value }).toString().slice('value='.length);
216
+ }
217
+ //# sourceMappingURL=oauth-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-service.js","sourceRoot":"","sources":["../../../src/upstream/oauth-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare function stableJson(value: unknown): string;
2
+ export declare function fingerprint(value: unknown): string;
3
+ //# sourceMappingURL=stable-json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stable-json.d.ts","sourceRoot":"","sources":["../../../src/upstream/stable-json.ts"],"names":[],"mappings":"AAYA,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEjD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAElD"}
@@ -0,0 +1,17 @@
1
+ import { createHash } from 'node:crypto';
2
+ function normalize(value) {
3
+ if (Array.isArray(value))
4
+ return value.map(normalize);
5
+ if (value === null || typeof value !== 'object')
6
+ return value;
7
+ return Object.fromEntries(Object.entries(value)
8
+ .sort(([left], [right]) => left.localeCompare(right))
9
+ .map(([key, item]) => [key, normalize(item)]));
10
+ }
11
+ export function stableJson(value) {
12
+ return JSON.stringify(normalize(value));
13
+ }
14
+ export function fingerprint(value) {
15
+ return createHash('sha256').update(stableJson(value)).digest('hex');
16
+ }
17
+ //# sourceMappingURL=stable-json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stable-json.js","sourceRoot":"","sources":["../../../src/upstream/stable-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACtD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC9D,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;SAClB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;SACpD,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAChD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACtE,CAAC"}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "toolhome",
3
+ "version": "0.5.0",
4
+ "description": "Self-hosted home for remote-native and home-hosted MCP servers and CLIs.",
5
+ "type": "module",
6
+ "bin": {
7
+ "toolhome": "./dist/server/cli/main.js",
8
+ "mcp-home": "./dist/server/cli/main.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "skills/toolhome"
13
+ ],
14
+ "keywords": [
15
+ "mcp",
16
+ "model-context-protocol",
17
+ "gateway",
18
+ "proxy",
19
+ "oauth",
20
+ "cli",
21
+ "self-hosted"
22
+ ],
23
+ "license": "MIT",
24
+ "engines": {
25
+ "node": ">=24.0.0"
26
+ },
27
+ "allowScripts": {
28
+ "esbuild@0.28.2": true,
29
+ "fsevents": false
30
+ },
31
+ "scripts": {
32
+ "build": "npm run build:server && npm run build:web",
33
+ "build:server": "tsc -p tsconfig.build.json",
34
+ "build:web": "npm --prefix web run build",
35
+ "dev:web": "npm --prefix web run dev",
36
+ "prepublishOnly": "npm run build:server",
37
+ "check": "tsc -p tsconfig.json --noEmit",
38
+ "dev": "tsx watch src/main.ts",
39
+ "start": "node dist/server/main.js",
40
+ "cli": "tsx src/cli/main.ts",
41
+ "format": "prettier --write .",
42
+ "format:check": "prettier --check .",
43
+ "test": "vitest run --config vitest.config.ts",
44
+ "test:unit": "vitest run --config vitest.config.ts tests/unit",
45
+ "test:integration": "vitest run --config vitest.config.ts tests/integration",
46
+ "test:real": "tsx scripts/real-environment.ts",
47
+ "test:all": "npm run format:check && npm run build && npm run test && npm run test:real",
48
+ "smoke:market": "tsx scripts/smoke-market.ts"
49
+ },
50
+ "dependencies": {
51
+ "@hono/node-server": "2.1.0",
52
+ "@modelcontextprotocol/client": "2.0.0",
53
+ "@modelcontextprotocol/core": "2.0.0",
54
+ "@modelcontextprotocol/hono": "2.0.0",
55
+ "@modelcontextprotocol/server": "2.0.0",
56
+ "commander": "15.0.0",
57
+ "hono": "4.13.1",
58
+ "jose": "6.2.8",
59
+ "zod": "4.4.3"
60
+ },
61
+ "devDependencies": {
62
+ "@types/node": "26.2.0",
63
+ "prettier": "3.9.6",
64
+ "tsx": "4.23.11",
65
+ "typescript": "7.0.2",
66
+ "vite": "8.2.1",
67
+ "vitest": "4.1.10"
68
+ }
69
+ }
@@ -0,0 +1,238 @@
1
+ ---
2
+ name: toolhome
3
+ description: >
4
+ Deploy and manage a self-hosted ToolHome instance: an MCP and Hosted CLI
5
+ control plane that aggregates upstream capabilities and platform CLIs. Use
6
+ when the user wants to set up ToolHome, manage MCP servers or hosted CLIs,
7
+ reuse encrypted credentials, authorize OAuth upstreams, install from the
8
+ Market catalog, configure harnesses (Claude Code, Cursor, Codex, Grok), or
9
+ troubleshoot ToolHome. Covers CLI, web console, Docker deployment, and CI/CD.
10
+ ---
11
+
12
+ # ToolHome
13
+
14
+ ToolHome is a single-user, self-hosted control plane for MCP servers and Hosted platform CLIs. Manage upstream MCP servers, CLIs such as Azure `az`, GitHub `gh`, and Tailscale, and encrypted credentials in one place.
15
+
16
+ ## When to Use This Skill
17
+
18
+ - User wants to deploy or manage the MCP or Hosted CLI plane
19
+ - User wants to aggregate multiple MCP servers behind one URL
20
+ - User wants to run platform CLIs such as Azure `az`, GitHub `gh`, Tailscale, Cloudflare `wrangler`, Vercel, Lark `lark-cli`, Firecrawl, or Aliyun `aliyun` remotely
21
+ - User needs to authorize OAuth for MCP upstreams or reuse access tokens in a CLI
22
+ - User wants to install MCP servers or hosted CLIs from the Market catalog
23
+ - User wants to connect Claude Code, Cursor, Codex, or Grok to a self-hosted MCP gateway
24
+ - User is troubleshooting ToolHome (status, OAuth, connectivity)
25
+
26
+ ## Installation
27
+
28
+ ToolHome has three components. Install what you need:
29
+
30
+ ### 1. Deploy the server (Docker)
31
+
32
+ ```bash
33
+ docker run -d \
34
+ --name toolhome \
35
+ -p 3344:3344 \
36
+ -v toolhome-data:/data \
37
+ -e TOOLHOME_MASTER_KEY="$(openssl rand -base64 48)" \
38
+ -e TOOLHOME_BOOTSTRAP_CONTROL_KEY="tch_ctl_$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')" \
39
+ -e TOOLHOME_PUBLIC_URL="https://tool.cyncyn.xyz" \
40
+ ghcr.io/crayonlu/toolhome:latest
41
+ ```
42
+
43
+ Or with Docker Compose (see `references/deployment.md`). After startup, open the web console at `TOOLHOME_PUBLIC_URL` and sign in with the bootstrap Control Key.
44
+
45
+ ### 2. Install the CLI (npm)
46
+
47
+ ```bash
48
+ npm install -g toolhome
49
+ toolhome auth login --url https://tool.cyncyn.xyz --control-key "$TOOLHOME_CONTROL_KEY"
50
+ ```
51
+
52
+ The CLI manages MCP servers and hosted CLIs as equal planes, plus shared credentials, OAuth, Market, and diagnostics. It requires Node.js 24 or later. The npm package also includes this skill under `skills/toolhome`.
53
+
54
+ ### 3. Install this skill (for AI agents)
55
+
56
+ ```bash
57
+ npx skills add crayonlu/toolhome -g -y
58
+ ```
59
+
60
+ Teaches the agent all CLI commands, OAuth flows, Market installation, and troubleshooting.
61
+
62
+ ## CLI Quick Reference
63
+
64
+ ```bash
65
+ toolhome status # overview
66
+ toolhome doctor # health check
67
+ toolhome server list # list servers
68
+ toolhome server add ./server.json # add a server
69
+ toolhome cli list # list hosted CLIs
70
+ toolhome cli add ./cli.json # register a hosted CLI
71
+ toolhome cli status gh-cli # probe a hosted CLI
72
+ toolhome cli exec gh-cli -- --version # run remotely, stream output
73
+ toolhome credential list # list credentials
74
+ toolhome credential authorize <name> # OAuth authorization (opens browser, waits)
75
+ toolhome access-key create laptop # create an MCP Access Key for harnesses
76
+ toolhome endpoint aggregate # show the aggregate endpoint URL
77
+ toolhome market list # browse the Market catalog
78
+ toolhome market install resend # supply the secret at the one-time browser URL
79
+ toolhome calls list --limit 10 # recent tool calls
80
+ toolhome calls stats # call statistics
81
+ ```
82
+
83
+ ## Common Workflows
84
+
85
+ ### Deploy ToolHome
86
+
87
+ 1. Generate two random keys (master + bootstrap control, each 32+ chars)
88
+ 2. Start the Docker container with the keys and public URL
89
+ 3. Open the web console, sign in with the bootstrap key
90
+ 4. Create a new Control Key, revoke bootstrap
91
+ 5. Run `toolhome doctor` to verify health
92
+
93
+ ### Add an Upstream Server
94
+
95
+ 1. Create a credential:
96
+ ```bash
97
+ echo '{"name":"firecrawl","payload":{"type":"bearer","token":"fc-xxx"}}' | toolhome credential add -
98
+ ```
99
+ 2. Get the credential ID from `toolhome credential list`
100
+ 3. Create a server:
101
+ ```bash
102
+ echo '{"slug":"firecrawl","name":"Firecrawl","kind":"remote","transport":{"type":"streamable-http","url":"https://mcp.firecrawl.dev/v2/mcp"},"credentialId":"<id>","enabled":true}' | toolhome server add -
103
+ ```
104
+ 4. Verify: `toolhome doctor`
105
+
106
+ ### Install and Run a Hosted CLI
107
+
108
+ ```bash
109
+ toolhome market install gh-cli
110
+ toolhome cli status gh-cli
111
+ toolhome cli exec gh-cli -- --version
112
+ toolhome cli exec gh-cli --stdin $'\n' -- auth login --hostname github.com --git-protocol https --web
113
+ toolhome cli exec gh-cli -- auth status
114
+ toolhome cli exec gh-cli -- repo view crayonlu/toolhome
115
+ ```
116
+
117
+ Give the user the device code and authorization URL for browser confirmation. Run the platform CLI only on the ToolHome server; the client needs only `toolhome`. Put platform arguments after `--`. Registry commands take the record ID; `exec` and `status` take its slug. Use `toolhome cli get <id>` to inspect allowed commands before execution. `host` runs installed binaries and `docker` runs sibling containers with explicit state volumes. Read `references/market-guide.md` for supported products and deployment prerequisites, and `references/cli-reference.md` for registry, stdin, timeout, and streaming options.
118
+
119
+ ### Authorize OAuth Upstream
120
+
121
+ ```bash
122
+ toolhome credential authorize cloudflare
123
+ ```
124
+
125
+ This resolves the credential by name, opens the browser, and waits until authorization succeeds, fails, or times out (default 600s). For force re-authorization:
126
+
127
+ ```bash
128
+ toolhome credential authorize cloudflare --force
129
+ ```
130
+
131
+ If OAuth fails with "Invalid client" or "Incompatible auth server", switch the registration method per-server:
132
+
133
+ - URL-based (default): works for most providers
134
+ - DCR: needed for Cloudflare, Notion, Linear (set `urlClientId: false` in server settings)
135
+
136
+ See `references/oauth-guide.md` for per-provider compatibility.
137
+
138
+ ### Install from Market
139
+
140
+ ```bash
141
+ toolhome market list # browse MCP and CLI entries
142
+ toolhome market install resend # home-stdio (npm); browser secret
143
+ toolhome market install context7 # remote (bearer); browser secret
144
+ toolhome market install deepwiki # remote (no auth)
145
+ toolhome market install fetch # uvx (Python, no config)
146
+ toolhome market install markitdown # Docker-backed MCP
147
+ toolhome market install gh-cli # Hosted GitHub CLI (no token; device-flow login after)
148
+ toolhome market uninstall resend # remove
149
+ ```
150
+
151
+ Market installs are async with progress: the CLI shows installer steps, the web console shows a live log. Packaged artifacts have version pins and installs write a persistent record (source, version, recipe revision). Remote services and the existing `/bin/sh` host-shell entry have no downloadable version pin. If required secrets are omitted, the CLI prints a one-time browser action URL. Use that flow for secrets; `--set` also accepts values, so reserve it for non-secret configuration.
152
+
153
+ **Docker entries** (e.g. `markitdown`) run the image as a sibling container via `docker run --rm -i <image>`: the install pulls the image, or builds it from the entry's inline Dockerfile when not pullable. The gateway container must mount the host docker socket and its runtime user must be in the host docker group (compose `group_add`, default GID 999, override with `DOCKER_GROUP_ID`). Only needed when a package cannot run inside the Alpine gateway image (e.g. `markitdown` — its `onnxruntime` dependency ships no musl wheels).
154
+
155
+ ### Give an AI Agent Safe Management Access
156
+
157
+ Create an **agent-scoped control key** (web console → Settings → Control Keys, or API):
158
+
159
+ ```bash
160
+ echo '{"name":"agent-key","scope":"agent"}' | toolhome api POST /api/v1/control-keys --body -
161
+ ```
162
+
163
+ Agent keys can read state and run safe operations (enable/disable/refresh/restart, market install, tool visibility) but are denied credentials, control/access keys, secret exports, and server deletion (HTTP 403). Existing keys keep full admin scope.
164
+
165
+ Point an agent's management MCP at:
166
+
167
+ ```json
168
+ {
169
+ "url": "https://tool.cyncyn.xyz/manage/mcp",
170
+ "headers": { "Authorization": "Bearer tch_ctl_agent..." }
171
+ }
172
+ ```
173
+
174
+ The management surface exposes `home_status`, `server_list`, `server_get`, `market_search`, `tool_list`, `calls_query`, and idempotent writes `server_set_enabled`, `server_refresh`, `server_restart`, `market_install`, `tool_set_visibility`. All writes are audited in the call log. The management endpoint never appears inside the `/mcp` aggregate.
175
+
176
+ ### Connect a Harness
177
+
178
+ Create an MCP Access Key:
179
+
180
+ ```bash
181
+ toolhome access-key create laptop
182
+ # Returns: tch_mcp_xxx (shown once, copy it)
183
+ ```
184
+
185
+ Configure the harness (aggregate endpoint):
186
+
187
+ ```json
188
+ {
189
+ "url": "https://tool.cyncyn.xyz/mcp",
190
+ "headers": { "Authorization": "Bearer tch_mcp_xxx" }
191
+ }
192
+ ```
193
+
194
+ Or per-server (independent endpoint, original tool names):
195
+
196
+ ```json
197
+ {
198
+ "url": "https://tool.cyncyn.xyz/mcp/firecrawl",
199
+ "headers": { "Authorization": "Bearer tch_mcp_xxx" }
200
+ }
201
+ ```
202
+
203
+ Aggregate tool names are `{server_slug}.{tool_name}`. Per-server preserves original names.
204
+
205
+ ### Diagnose Issues
206
+
207
+ ```bash
208
+ toolhome doctor # check all servers
209
+ toolhome server status <id> # detailed runtime state + last error
210
+ toolhome server logs <id> # recent log entries
211
+ toolhome cli status <slug> # hosted CLI probe
212
+ toolhome events --limit 100 # recent events, including cli.exec
213
+ ```
214
+
215
+ ## Configuration
216
+
217
+ | Env | Description | Default |
218
+ | --------------------------------- | -------------------------------------------------- | ------------------------------ |
219
+ | `TOOLHOME_PUBLIC_URL` | External HTTPS origin | required |
220
+ | `TOOLHOME_MASTER_KEY` | Secret encryption key (32+ chars) | required |
221
+ | `TOOLHOME_BOOTSTRAP_CONTROL_KEY` | First-boot Control Key | required on first boot |
222
+ | `TOOLHOME_HOST` / `TOOLHOME_PORT` | Listen address | `127.0.0.1:3344` |
223
+ | `TOOLHOME_DATA_DIR` | SQLite + market data | `/data` |
224
+ | `TOOLHOME_MARKET_DIR` | Market npm install dir | `<dataDir>/market` |
225
+ | `TOOLHOME_WEB_DIR` | Web console static files | disabled (set in Docker image) |
226
+ | `TOOLHOME_OAUTH_URL_CLIENT_ID` | Global OAuth client registration | `true` (URL-based) |
227
+ | `TOOLHOME_UV_INDEX_URL` | PyPI mirror for uvx Market installs | unset (pypi.org) |
228
+ | `TOOLHOME_CALLS_RETENTION_DAYS` | Tool call record retention in days (metadata only) | `30` |
229
+
230
+ ## Deep Dives
231
+
232
+ For detailed information, read the reference files:
233
+
234
+ - `references/cli-reference.md` — Full CLI command reference with all flags
235
+ - `references/oauth-guide.md` — OAuth registration methods, per-provider compatibility, troubleshooting
236
+ - `references/market-guide.md` — Market catalog entries, installation details
237
+ - `references/deployment.md` — Docker Compose, CI/CD, reverse proxy, data persistence
238
+ - `references/troubleshooting.md` — Common issues and solutions